package lib // ============================================================================ // System Role Definitions // ============================================================================ // Predefined roles that can be applied to grantees. Each role defines // a set of operations (r/w/d/m) at root level and optionally at specific // categories. // ============================================================================ // RoleGrant defines a single permission grant within a role type RoleGrant struct { Category int // 0 = root (all categories), >0 = specific category Ops string // "r", "rw", "rwd", "rwdm" } // RoleTemplate defines a predefined role with its grants type RoleTemplate struct { Name string // Role identifier (e.g., "Family", "Doctor") Description string // Human-readable description Grants []RoleGrant // Permission grants } // SystemRoles defines all available role templates var SystemRoles = []RoleTemplate{ { Name: "Family", Description: "Full access for family members", Grants: []RoleGrant{ {Category: 0, Ops: "rwdm"}, // Full access to everything }, }, { Name: "Doctor", Description: "Read/write access for healthcare providers", Grants: []RoleGrant{ {Category: 0, Ops: "rw"}, // Read/write to everything }, }, { Name: "Caregiver", Description: "Read/write access for caregivers", Grants: []RoleGrant{ {Category: 0, Ops: "rw"}, // Read/write to everything }, }, { Name: "Trainer", Description: "Read-only with write access to exercise and nutrition", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, // Read everything {Category: CategoryExercise, Ops: "rw"}, // Write exercise {Category: CategoryNutrition, Ops: "rw"}, // Write nutrition }, }, { Name: "Friend", Description: "Read-only access", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, // Read everything }, }, { Name: "Researcher", Description: "Read-only access for research purposes", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, // Read everything }, }, } // GetSystemRoles returns all system role templates func GetSystemRoles() []RoleTemplate { return SystemRoles } // GetSystemRole returns a specific role template by name func GetSystemRole(name string) *RoleTemplate { for _, r := range SystemRoles { if r.Name == name { return &r } } return nil } // ApplyRoleTemplate creates access grants for a grantee based on a role template. // For category-specific grants, creates a root entry for that category in the // dossier to serve as the grant target. func ApplyRoleTemplate(dossierID, granteeID, roleName string) error { role := GetSystemRole(roleName) if role == nil { // Unknown role - create basic read-only grant return AccessGrantWrite(&Access{ DossierID: dossierID, GranteeID: granteeID, EntryID: "", // root Role: roleName, Ops: "r", }) } var grants []*Access for _, g := range role.Grants { grant := &Access{ DossierID: dossierID, GranteeID: granteeID, Role: roleName, Ops: g.Ops, } if g.Category == 0 { // Root grant grant.EntryID = "" } else { // Category-specific grant // Find or create category root entry catRootID, err := findOrCreateCategoryRoot(dossierID, g.Category) if err != nil { return err } grant.EntryID = catRootID } grants = append(grants, grant) } err := AccessGrantWrite(grants...) if err != nil { return err } // Invalidate cache for grantee InvalidateCacheForAccessor(granteeID) return nil } // findOrCreateCategoryRoot finds or creates a root entry for category-level grants // This is a virtual entry that serves as parent for all entries of that category func findOrCreateCategoryRoot(dossierID string, category int) (string, error) { // Look for existing category root entry (type = "category_root") entries, err := EntryList(nil, "", category, &EntryFilter{ DossierID: dossierID, Type: "category_root", Limit: 1, }) if err == nil && len(entries) > 0 { return entries[0].EntryID, nil } // Create virtual category root entry entry := &Entry{ DossierID: dossierID, Category: category, Type: "category_root", Value: CategoryName(category), } if err := EntryWrite(nil, entry); err != nil { return "", err } return entry.EntryID, nil } // RevokeRole removes all grants with the specified role for a grantee on a dossier func RevokeRole(dossierID, granteeID, roleName string) error { grants, err := AccessGrantList(&PermissionFilter{ DossierID: dossierID, GranteeID: granteeID, Role: roleName, }) if err != nil { return err } var ids []string for _, g := range grants { ids = append(ids, g.AccessID) } if len(ids) > 0 { if err := AccessGrantRemove(ids...); err != nil { return err } } // Invalidate cache for grantee InvalidateCacheForAccessor(granteeID) return nil } // GetGranteeRole returns the primary role name for a grantee on a dossier // If multiple roles exist, returns the first one found func GetGranteeRole(dossierID, granteeID string) string { grants, err := AccessGrantList(&PermissionFilter{ DossierID: dossierID, GranteeID: granteeID, }) if err != nil || len(grants) == 0 { return "" } return grants[0].Role } // GetGranteesWithAccess returns all grantees with any access to a dossier // along with their role and ops type GranteeSummary struct { GranteeID string Name string // Resolved from dossier Role string Ops string // Combined ops from all grants } func GetGranteesWithAccess(dossierID string) ([]GranteeSummary, error) { grants, err := AccessGrantList(&PermissionFilter{DossierID: dossierID}) if err != nil { return nil, err } // Group by grantee byGrantee := make(map[string]*GranteeSummary) for _, g := range grants { if g.GranteeID == "" { continue // Skip templates } if byGrantee[g.GranteeID] == nil { byGrantee[g.GranteeID] = &GranteeSummary{ GranteeID: g.GranteeID, Role: g.Role, } } // Merge ops byGrantee[g.GranteeID].Ops = mergeOps(byGrantee[g.GranteeID].Ops, g.Ops) } // Resolve names (using nil ctx for internal operation) var result []GranteeSummary for _, gs := range byGrantee { if d, err := DossierGet(nil, gs.GranteeID); err == nil { gs.Name = d.Name } result = append(result, *gs) } return result, nil }