refactor: simplify RBAC - categories are entries
- Remove special cat:{id} handling from permission resolution
- Categories are now just entries with parent_id=""
- Access flows naturally through parent_id chain hierarchy
- Three levels: root (entry_id="") > categories > individual entries
- Explicit denial supported with ops=""
- Updated documentation to reflect cleaner model
Next: deprecate dossier_access table, migrate to access grants
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c1cd76559d
commit
f7e6c32e30
|
|
@ -7,17 +7,23 @@ import (
|
|||
)
|
||||
|
||||
// ============================================================================
|
||||
// RBAC Access Control - Rock-solid permission enforcement
|
||||
// RBAC Access Control - Entry-based permission system
|
||||
// ============================================================================
|
||||
//
|
||||
// Permission checks happen at the LOWEST LEVEL in v2.go functions.
|
||||
// There is NO WAY to bypass RBAC - every data access function checks permissions.
|
||||
// Three-level hierarchy:
|
||||
// 1. Root (entry_id = "") - "all" or "nothing"
|
||||
// 2. Categories - entries that are category roots (parent_id = "")
|
||||
// 3. Individual entries - access flows down via parent_id chain
|
||||
//
|
||||
// Operations:
|
||||
// r = read - view data
|
||||
// w = write - create/update data
|
||||
// d = delete - remove data
|
||||
// m = manage - grant/revoke access to others
|
||||
// "" = explicit denial (removes inherited access)
|
||||
//
|
||||
// Categories are just entries - no special handling.
|
||||
// Access to parent implies access to all children.
|
||||
//
|
||||
// ============================================================================
|
||||
|
||||
|
|
@ -196,7 +202,8 @@ func getEffectiveOps(accessorID, dossierID, entryID string) string {
|
|||
}
|
||||
|
||||
// findMatchingOps finds the most specific grant that applies to entryID
|
||||
// Priority: entry-specific > parent chain > category > root
|
||||
// Priority: entry-specific > parent chain > root
|
||||
// Categories are just entries - no special handling needed
|
||||
func findMatchingOps(grants []*Access, entryID string) string {
|
||||
// Build entry->ops map for quick lookup
|
||||
grantMap := make(map[string]string) // entry_id -> ops (empty key = root)
|
||||
|
|
@ -206,47 +213,36 @@ func findMatchingOps(grants []*Access, entryID string) string {
|
|||
grantMap[g.EntryID] = mergeOps(existing, g.Ops)
|
||||
}
|
||||
|
||||
// 1. Check entry-specific grant
|
||||
// 1. Check entry-specific grant (including category entries)
|
||||
if entryID != "" {
|
||||
if ops, ok := grantMap[entryID]; ok {
|
||||
return ops
|
||||
}
|
||||
|
||||
// 2. Walk up parent chain (using raw function to avoid RBAC recursion)
|
||||
// Also track entry category for category grant check
|
||||
var entryCategory int
|
||||
currentID := entryID
|
||||
for i := 0; i < 100; i++ { // max depth to prevent infinite loops
|
||||
entry, err := entryGetRaw(currentID)
|
||||
if err != nil || entry == nil {
|
||||
break
|
||||
}
|
||||
if i == 0 {
|
||||
entryCategory = entry.Category
|
||||
}
|
||||
if entry.ParentID == "" {
|
||||
break
|
||||
}
|
||||
// Check parent for grant
|
||||
if ops, ok := grantMap[entry.ParentID]; ok {
|
||||
return ops
|
||||
}
|
||||
currentID = entry.ParentID
|
||||
}
|
||||
|
||||
// 3. Check category grant (cat:{category_id})
|
||||
if entryCategory > 0 {
|
||||
catKey := fmt.Sprintf("cat:%d", entryCategory)
|
||||
if ops, ok := grantMap[catKey]; ok {
|
||||
return ops
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Check root grant
|
||||
// 3. Check root grant (entry_id = "" means "all")
|
||||
if ops, ok := grantMap[""]; ok {
|
||||
return ops
|
||||
}
|
||||
|
||||
// 4. No grant found
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue