306 lines
7.5 KiB
Go
306 lines
7.5 KiB
Go
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
|
|
}
|