182 lines
4.0 KiB
Go
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()
|
|
}
|