grafana/pkg/services/accesscontrol/resolvers.go

80 lines
2.6 KiB
Go

package accesscontrol
import (
"context"
"fmt"
"time"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
)
// 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)
const (
ttl = 30 * time.Second
cleanInterval = 2 * time.Minute
)
func NewResolvers(log log.Logger) Resolvers {
return Resolvers{
log: log,
cache: localcache.New(ttl, cleanInterval),
attributeResolvers: map[string]ScopeAttributeResolver{},
}
}
type Resolvers struct {
log log.Logger
cache *localcache.CacheService
attributeResolvers map[string]ScopeAttributeResolver
}
func (s *Resolvers) AddScopeAttributeResolver(prefix string, resolver ScopeAttributeResolver) {
s.log.Debug("Adding scope attribute resolver", "prefix", prefix)
s.attributeResolvers[prefix] = resolver
}
func (s *Resolvers) 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
}
}
// 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)
}