inou/lib/rbac.go

134 lines
3.4 KiB
Go

package lib
import (
"fmt"
"log"
"strings"
)
// Permission constants (bitmask)
const (
PermRead = 1 // Read access
PermWrite = 2 // Create/update
PermDelete = 4 // Delete
PermManage = 8 // Grant/revoke access to others
)
// CheckAccess checks if accessor has permission to access an entry/category/dossier.
// Returns true if access is granted, false otherwise.
func CheckAccess(accessorID, dossierID, entryID string, perm int) bool {
if accessorID == "" || accessorID == SystemAccessorID {
return true
}
if accessorID == dossierID {
return true
}
var grants []Access
if err := dbQuery(
"SELECT AccessID, DossierID, GranteeID, EntryID, Ops FROM access WHERE GranteeID = ? AND DossierID = ?",
[]any{accessorID, dossierID},
&grants,
); err != nil {
return false
}
for _, grant := range grants {
if grant.EntryID == entryID {
return (grant.Ops & perm) != 0
}
if grant.EntryID == dossierID {
return (grant.Ops & perm) != 0
}
if entryID != dossierID && entryID != "" {
var entry Entry
if err := dbLoad("entries", entryID, &entry); err == nil {
if entry.ParentID == grant.EntryID || entry.ParentID == "" && grant.EntryID == dossierID {
return (grant.Ops & perm) != 0
}
}
}
}
return false
}
// OpsToString converts ops bitmask to string representation.
func OpsToString(ops int) string {
var parts []string
if ops&PermRead != 0 {
parts = append(parts, "r")
}
if ops&PermWrite != 0 {
parts = append(parts, "w")
}
if ops&PermDelete != 0 {
parts = append(parts, "d")
}
if ops&PermManage != 0 {
parts = append(parts, "m")
}
return strings.Join(parts, "")
}
// ============================================================================
// STUBS — will be rebuilt when sharing UI is wired
// ============================================================================
func GrantAccess(dossierID, granteeID, entryID string, ops, relation int) error {
access := &Access{
AccessID: NewID(),
DossierID: dossierID,
GranteeID: granteeID,
EntryID: entryID,
Ops: ops,
Relation: relation,
CreatedAt: nowUnix(),
}
return dbSave("access", access)
}
func RevokeAccess(dossierID, granteeID, entryID string) error {
log.Printf("[STUB] RevokeAccess(dossier=%s, grantee=%s, entry=%s)", dossierID, granteeID, entryID)
return nil
}
func RevokeAllAccess(dossierID, granteeID string) error {
log.Printf("[STUB] RevokeAllAccess(dossier=%s, grantee=%s)", dossierID, granteeID)
return nil
}
func ListGrants(dossierID, granteeID string) ([]*Access, error) {
log.Printf("[STUB] ListGrants(dossier=%s, grantee=%s)", dossierID, granteeID)
return nil, nil
}
func ListGrantees(dossierID string) ([]*Access, error) {
log.Printf("[STUB] ListGrantees(dossier=%s)", dossierID)
return nil, nil
}
func CanManageDossier(accessorID, dossierID string) bool {
return CheckAccess(accessorID, dossierID, dossierID, PermManage)
}
// ============================================================================
// DEPRECATED — kept for compilation, will be removed
// ============================================================================
type AccessContext struct {
DossierID string
AccessorID string
IsSystem bool
}
var SystemContext = &AccessContext{DossierID: "system", AccessorID: "system", IsSystem: true}
var ErrAccessDenied = fmt.Errorf("access denied")
func InvalidateCacheForAccessor(accessorID string) {}
func EnsureCategoryRoot(dossierID string, category int) (string, error) {
return dossierID, nil
}