diff --git a/lib/access.go b/lib/access.go index ad13324..66bfe5a 100644 --- a/lib/access.go +++ b/lib/access.go @@ -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 "" }