The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/services/accesscontrol/resolvers.go

140 lines
4.8 KiB

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/services/user"
)
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 []string{scope}, nil
}
}
func (s *ScopeResolvers) GetScopeKeywordMutator(user *user.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 *user.SignedInUser) (string, error)
}
// ScopeKeywordResolverFunc is an adapter to allow functions to implement ScopeKeywordResolver interface
type ScopeKeywordResolverFunc func(ctx context.Context, user *user.SignedInUser) (string, error)
func (f ScopeKeywordResolverFunc) Resolve(ctx context.Context, user *user.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 *user.SignedInUser) (string, error) {
return Scope("users", "id", fmt.Sprintf("%v", user.UserId)), nil
})