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" } // opsFromString converts string ops ("r", "rw", "rwdm") to int bitmask func opsFromString(s string) int { ops := 0 for _, c := range s { switch c { case 'r': ops |= 1 case 'w': ops |= 2 case 'd': ops |= 4 case 'm': ops |= 8 } } return ops } // 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 Relation: 0, Ops: 1, // read only }) } var grants []*Access for _, g := range role.Grants { grant := &Access{ DossierID: dossierID, GranteeID: granteeID, Relation: 0, // default relation Ops: opsFromString(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 for a grantee on a dossier // Note: Role tracking removed from Access - this now removes ALL grants func RevokeRole(dossierID, granteeID, roleName string) error { grants, err := AccessGrantList(&PermissionFilter{ DossierID: dossierID, GranteeID: granteeID, }) 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 // Note: Role tracking removed from Access - returns empty string func GetGranteeRole(dossierID, granteeID string) string { // Role field no longer tracked in Access struct return "" } // 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: "", // Role field no longer tracked } } // Merge ops (int bitmask, convert to string for display) merged := mergeOps(opsFromString(byGrantee[g.GranteeID].Ops), g.Ops) byGrantee[g.GranteeID].Ops = OpsToString(merged) } // 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 }