Files
RenewCMS/internal/domain/role/roleGrantSet.go
T

182 lines
4.0 KiB
Go

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()
}