package role import ( "slices" "strings" ) type MissingPermissionError struct { Key string } func (e MissingPermissionError) Error() string { return ErrMissingPermission.Error() + ": " + e.Key } func (e MissingPermissionError) Is(target error) bool { return target == ErrMissingPermission } type GrantSet struct { scopes map[string]Scope } func (r Role) GrantSet() GrantSet { scopes := make(map[string]Scope, len(r.Grants)) for _, grant := range r.Grants { permission, err := resolveGrantKey(grant.Key) if err != nil || permission.OwnerOnly && !r.Locked { continue } scope := effectiveScope(permission.Kind, grant.Scope) if held, present := scopes[grant.Key]; present { scope = mostRestrictive(held, scope) } scopes[grant.Key] = scope } return GrantSet{scopes: scopes} } func effectiveScope(kind Kind, scope Scope) Scope { if scope == ScopeAll { return ScopeAll } if scope == ScopeOwn && kind == KindScoped { return ScopeOwn } return ScopeNone } func scopeRank(scope Scope) int { switch scope { case ScopeAll: return 2 case ScopeOwn: return 1 default: return 0 } } func mostRestrictive(left Scope, right Scope) Scope { if scopeRank(left) <= scopeRank(right) { return left } return right } func (g GrantSet) ScopeFor(key string, typeKey string) Scope { if typeKey == "" { return g.wildcard(key) } composite := key + typeSeparator + typeKey if _, _, err := ParseTypedKey(composite); err != nil { return ScopeNone } if scope, present := g.scopes[composite]; present { return scope } return g.wildcard(key) } func (g GrantSet) wildcard(key string) Scope { if isTypedKey(key) { return ScopeNone } scope, present := g.scopes[key] if !present { return ScopeNone } return scope } func (g GrantSet) Permits(key string, typeKey string, callerID uint32, createdBy uint32) bool { return ScopePermits(g.ScopeFor(key, typeKey), callerID, createdBy) } func ScopePermits(scope Scope, callerID uint32, createdBy uint32) bool { switch scope { case ScopeAll: return true case ScopeOwn: return callerID != 0 && callerID == createdBy default: return false } } func (g GrantSet) HoldsFor(key string, typeKey string) bool { return g.ScopeFor(key, typeKey) != ScopeNone } func (g GrantSet) Holds(key string) bool { return g.HoldsFor(key, "") } func (g GrantSet) HoldsAll(key string) bool { return g.ScopeFor(key, "") == ScopeAll } type EscalationError struct { Key string } func (e EscalationError) Error() string { return ErrEscalation.Error() + ": " + e.Key } func (e EscalationError) Is(target error) bool { return target == ErrEscalation } func (g GrantSet) Covers(other GrantSet, typeKeys []string) error { sortedTypeKeys := slices.Sorted(slices.Values(typeKeys)) for _, permission := range catalogue { if scopeRank(other.ScopeFor(permission.Key, "")) > scopeRank(g.ScopeFor(permission.Key, "")) { return EscalationError{Key: permission.Key} } if !permission.PerType() { continue } for _, typeKey := range sortedTypeKeys { if scopeRank(other.ScopeFor(permission.Key, typeKey)) > scopeRank(g.ScopeFor(permission.Key, typeKey)) { return EscalationError{Key: permission.Key + typeSeparator + typeKey} } } } return nil } func (g GrantSet) Effective() []Grant { effective := []Grant{} for _, permission := range catalogue { if scope := g.wildcard(permission.Key); scope != ScopeNone { effective = append(effective, Grant{Key: permission.Key, Scope: scope}) } if permission.PerType() { effective = append(effective, g.typedGrants(permission.Key)...) } } return effective } func (g GrantSet) typedGrants(key string) []Grant { prefix := key + typeSeparator composites := []string{} for composite := range g.scopes { if strings.HasPrefix(composite, prefix) { composites = append(composites, composite) } } slices.Sort(composites) typed := make([]Grant, 0, len(composites)) for _, composite := range composites { typed = append(typed, Grant{Key: composite, Scope: g.scopes[composite]}) } return typed } func (r Role) EffectiveGrants() []Grant { return r.GrantSet().Effective() }