246 lines
6.4 KiB
Go
246 lines
6.4 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: "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", use empty string for system context)
|
|
entries, err := EntryList("", "", 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
|
|
}
|