feat: add RBAC helper functions for granting access

- EnsureCategoryEntry: creates category entry if needed
- GrantAccess: creates access grant with cache invalidation
- RevokeAccess: removes grant with cache invalidation

Category entries are automatically created when granting category-level access.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
James 2026-02-07 17:02:31 -05:00
parent f7e6c32e30
commit 8ccab9581d
1 changed files with 62 additions and 0 deletions

View File

@ -309,6 +309,36 @@ func accessGrantListRaw(f *PermissionFilter) ([]*Access, error) {
// Utility Functions // Utility Functions
// ============================================================================ // ============================================================================
// EnsureCategoryEntry creates a category entry if it doesn't exist
// Returns the entry_id of the category entry
func EnsureCategoryEntry(dossierID string, category int) (string, error) {
// Check if category entry already exists
entries, err := EntryList(SystemContext, "", category, &EntryFilter{
DossierID: dossierID,
Type: "category",
Limit: 1,
})
if err != nil {
return "", err
}
if len(entries) > 0 {
return entries[0].EntryID, nil
}
// Create category entry
entry := &Entry{
DossierID: dossierID,
Category: category,
Type: "category",
Value: CategoryName(category),
ParentID: "", // Categories are root-level
}
if err := EntrySave(SystemContext, entry); err != nil {
return "", err
}
return entry.EntryID, nil
}
// CanAccessDossier returns true if accessor can read dossier (for quick checks) // CanAccessDossier returns true if accessor can read dossier (for quick checks)
func CanAccessDossier(ctx *AccessContext, dossierID string) bool { func CanAccessDossier(ctx *AccessContext, dossierID string) bool {
return CheckAccess(ctx, dossierID, "", 'r') == nil return CheckAccess(ctx, dossierID, "", 'r') == nil
@ -319,6 +349,38 @@ func CanManageDossier(ctx *AccessContext, dossierID string) bool {
return CheckAccess(ctx, dossierID, "", 'm') == nil return CheckAccess(ctx, dossierID, "", 'm') == nil
} }
// GrantAccess creates an access grant
// If entryID is empty, grants root-level access
// If entryID is a category, ensures category entry exists first
func GrantAccess(dossierID, granteeID, entryID, ops string) error {
grant := &Access{
DossierID: dossierID,
GranteeID: granteeID,
EntryID: entryID,
Ops: ops,
CreatedAt: time.Now().Unix(),
}
err := Save("access", grant)
if err == nil {
InvalidateCacheForAccessor(granteeID)
}
return err
}
// RevokeAccess removes an access grant
func RevokeAccess(accessID string) error {
// Get the grant to know which accessor to invalidate
var grant Access
if err := Load("access", accessID, &grant); err != nil {
return err
}
err := Delete("access", "access_id", accessID)
if err == nil {
InvalidateCacheForAccessor(grant.GranteeID)
}
return err
}
// GetAccessorOps returns the operations accessor can perform on dossier/entry // GetAccessorOps returns the operations accessor can perform on dossier/entry
// Returns empty string if no access // Returns empty string if no access
func GetAccessorOps(ctx *AccessContext, dossierID, entryID string) string { func GetAccessorOps(ctx *AccessContext, dossierID, entryID string) string {