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: "Parent/Guardian", Description: "Full access to child's data", Grants: []RoleGrant{ {Category: 0, Ops: "rwdm"}, }, }, { Name: "Spouse/Partner", Description: "Full access to partner's data", Grants: []RoleGrant{ {Category: 0, Ops: "rwdm"}, }, }, { Name: "Sibling", Description: "View and add notes", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, {Category: CategoryNote, Ops: "rw"}, }, }, { Name: "Extended Family", Description: "View only", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, }, }, { Name: "Doctor", Description: "Full clinical access", Grants: []RoleGrant{ {Category: 0, Ops: "rw"}, }, }, { Name: "Specialist", Description: "Clinical data read, write imaging/labs/docs", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, {Category: CategoryImaging, Ops: "rw"}, {Category: CategoryLab, Ops: "rw"}, {Category: CategoryDocument, Ops: "rw"}, {Category: CategoryAssessment, Ops: "rw"}, }, }, { Name: "Caregiver", Description: "Daily care access", Grants: []RoleGrant{ {Category: 0, Ops: "rw"}, }, }, { Name: "Physical Therapist", Description: "Exercise, vitals, imaging", Grants: []RoleGrant{ {Category: CategoryImaging, Ops: "r"}, {Category: CategoryLab, Ops: "r"}, {Category: CategoryVital, Ops: "rw"}, {Category: CategoryExercise, Ops: "rw"}, {Category: CategoryNote, Ops: "rw"}, }, }, { Name: "Nutritionist", Description: "Diet, supplements, labs", Grants: []RoleGrant{ {Category: CategoryLab, Ops: "r"}, {Category: CategoryVital, Ops: "r"}, {Category: CategoryNutrition, Ops: "rw"}, {Category: CategorySupplement, Ops: "rw"}, }, }, { Name: "Trainer", Description: "Exercise, nutrition, vitals", Grants: []RoleGrant{ {Category: CategoryVital, Ops: "r"}, {Category: CategoryExercise, Ops: "rw"}, {Category: CategoryNutrition, Ops: "rw"}, }, }, { Name: "Therapist", Description: "Mental health, notes, assessments", Grants: []RoleGrant{ {Category: CategoryLab, Ops: "r"}, {Category: CategoryVital, Ops: "r"}, {Category: CategoryNote, Ops: "rw"}, {Category: CategorySymptom, Ops: "rw"}, {Category: CategoryAssessment, Ops: "rw"}, }, }, { Name: "Pharmacist", Description: "Medications, labs, genome", Grants: []RoleGrant{ {Category: CategoryMedication, Ops: "r"}, {Category: CategoryLab, Ops: "r"}, {Category: CategoryGenome, Ops: "r"}, }, }, { Name: "Friend", Description: "View only", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, }, }, { Name: "Researcher", Description: "View only for research", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, }, }, { Name: "Emergency", Description: "Emergency read access", Grants: []RoleGrant{ {Category: 0, Ops: "r"}, }, }, } // 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 is an alias for EnsureCategoryRoot (in access.go) func findOrCreateCategoryRoot(dossierID string, category int) (string, error) { return EnsureCategoryRoot(dossierID, category) } // 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 }