* RBAC: Fix resolver issue on wildcard resulting in wrong status code for endpoints (#54208)
* RBAC: Test evaluation before attaching mutator
* RBAC: Return error if no resolver is found for scope
* RBAC: Sync changes to evaluation in mock
* RBAC: Check for resolver not found error and just fail the evaluation in that case
(cherry picked from commit 552d3fec8d)
141 lines
4.8 KiB
Go
141 lines
4.8 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/localcache"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
)
|
|
|
|
const (
|
|
ttl = 30 * time.Second
|
|
cleanInterval = 2 * time.Minute
|
|
)
|
|
|
|
func NewScopeResolvers() ScopeResolvers {
|
|
return ScopeResolvers{
|
|
keywordResolvers: map[string]ScopeKeywordResolver{
|
|
"users:self": userSelfResolver,
|
|
},
|
|
attributeResolvers: map[string]ScopeAttributeResolver{},
|
|
cache: localcache.New(ttl, cleanInterval),
|
|
log: log.New("accesscontrol.resolver"),
|
|
}
|
|
}
|
|
|
|
type ScopeResolvers struct {
|
|
log log.Logger
|
|
cache *localcache.CacheService
|
|
keywordResolvers map[string]ScopeKeywordResolver
|
|
attributeResolvers map[string]ScopeAttributeResolver
|
|
}
|
|
|
|
func (s *ScopeResolvers) GetScopeAttributeMutator(orgID int64) ScopeAttributeMutator {
|
|
return func(ctx context.Context, scope string) ([]string, error) {
|
|
key := getScopeCacheKey(orgID, scope)
|
|
// Check cache before computing the scope
|
|
if cachedScope, ok := s.cache.Get(key); ok {
|
|
scopes := cachedScope.([]string)
|
|
s.log.Debug("used cache to resolve scope", "scope", scope, "resolved_scopes", scopes)
|
|
return scopes, nil
|
|
}
|
|
|
|
prefix := ScopePrefix(scope)
|
|
if resolver, ok := s.attributeResolvers[prefix]; ok {
|
|
scopes, err := resolver.Resolve(ctx, orgID, scope)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("could not resolve %v: %w", scope, err)
|
|
}
|
|
// Cache result
|
|
s.cache.Set(key, scopes, ttl)
|
|
s.log.Debug("resolved scope", "scope", scope, "resolved_scopes", scopes)
|
|
return scopes, nil
|
|
}
|
|
return nil, ErrResolverNotFound
|
|
}
|
|
}
|
|
|
|
func (s *ScopeResolvers) GetScopeKeywordMutator(user *models.SignedInUser) ScopeKeywordMutator {
|
|
return func(ctx context.Context, scope string) (string, error) {
|
|
if resolver, ok := s.keywordResolvers[scope]; ok {
|
|
scopes, err := resolver.Resolve(ctx, user)
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not resolve %v: %w", scope, err)
|
|
}
|
|
s.log.Debug("resolved scope", "scope", scope, "resolved_scopes", scopes)
|
|
return scopes, nil
|
|
}
|
|
// By default, the scope remains unchanged
|
|
return scope, nil
|
|
}
|
|
}
|
|
|
|
func (s *ScopeResolvers) AddScopeKeywordResolver(keyword string, resolver ScopeKeywordResolver) {
|
|
s.log.Debug("adding scope keyword resolver for '%v'", keyword)
|
|
s.keywordResolvers[keyword] = resolver
|
|
}
|
|
|
|
func (s *ScopeResolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver) {
|
|
s.log.Debug("adding scope attribute resolver for '%v'", prefix)
|
|
s.attributeResolvers[prefix] = resolver
|
|
}
|
|
|
|
// ScopeAttributeResolver is used to resolve attributes in scopes to one or more scopes that are
|
|
// evaluated by logical or. E.g. "dashboards:id:1" -> "dashboards:uid:test-dashboard" or "folder:uid:test-folder"
|
|
type ScopeAttributeResolver interface {
|
|
Resolve(ctx context.Context, orgID int64, scope string) ([]string, error)
|
|
}
|
|
|
|
// ScopeAttributeResolverFunc is an adapter to allow functions to implement ScopeAttributeResolver interface
|
|
type ScopeAttributeResolverFunc func(ctx context.Context, orgID int64, scope string) ([]string, error)
|
|
|
|
func (f ScopeAttributeResolverFunc) Resolve(ctx context.Context, orgID int64, scope string) ([]string, error) {
|
|
return f(ctx, orgID, scope)
|
|
}
|
|
|
|
type ScopeAttributeMutator func(context.Context, string) ([]string, error)
|
|
|
|
// ScopeKeywordResolver is used to resolve keywords in scopes e.g. "users:self" -> "user:id:1".
|
|
// These type of resolvers is used when fetching stored permissions
|
|
type ScopeKeywordResolver interface {
|
|
Resolve(ctx context.Context, user *models.SignedInUser) (string, error)
|
|
}
|
|
|
|
// ScopeKeywordResolverFunc is an adapter to allow functions to implement ScopeKeywordResolver interface
|
|
type ScopeKeywordResolverFunc func(ctx context.Context, user *models.SignedInUser) (string, error)
|
|
|
|
func (f ScopeKeywordResolverFunc) Resolve(ctx context.Context, user *models.SignedInUser) (string, error) {
|
|
return f(ctx, user)
|
|
}
|
|
|
|
type ScopeKeywordMutator func(context.Context, string) (string, error)
|
|
|
|
// getScopeCacheKey creates an identifier to fetch and store resolution of scopes in the cache
|
|
func getScopeCacheKey(orgID int64, scope string) string {
|
|
return fmt.Sprintf("%s-%v", scope, orgID)
|
|
}
|
|
|
|
//ScopeInjector inject request params into the templated scopes. e.g. "settings:" + eval.Parameters(":id")
|
|
func ScopeInjector(params ScopeParams) ScopeAttributeMutator {
|
|
return func(_ context.Context, scope string) ([]string, error) {
|
|
tmpl, err := template.New("scope").Parse(scope)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var buf bytes.Buffer
|
|
if err = tmpl.Execute(&buf, params); err != nil {
|
|
return nil, err
|
|
}
|
|
return []string{buf.String()}, nil
|
|
}
|
|
}
|
|
|
|
var userSelfResolver = ScopeKeywordResolverFunc(func(ctx context.Context, user *models.SignedInUser) (string, error) {
|
|
return Scope("users", "id", fmt.Sprintf("%v", user.UserId)), nil
|
|
})
|