mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 19:30:36 -06:00
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 []string{scope}, nil
|
|
}
|
|
}
|
|
|
|
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
|
|
})
|