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 { var matches []Access if err := dbQuery("SELECT * FROM access WHERE DossierID = ? AND GranteeID = ? AND EntryID = ?", []any{dossierID, granteeID, entryID}, &matches); err != nil { return err } for _, a := range matches { dbDelete("access", "AccessID", a.AccessID) } 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: SystemAccessorID, IsSystem: true} var ErrAccessDenied = fmt.Errorf("access denied") func InvalidateCacheForAccessor(accessorID string) {} func EnsureCategoryRoot(dossierID string, category int) (string, error) { return dossierID, nil }