AccessControl: Resolve attribute based scopes to id based scopes (#40742)
* AccessControl: POC scope attribute resolution Refactor based on ScopeMutators test errors and calls to cache Add comments to tests Rename logger Create keywordMutator only once * AccessControl: Add AttributeScopeResolver registration Co-authored-by: gamab <gabriel.mabille@grafana.com> * AccessControl: Add AttributeScopeResolver to datasources Co-authored-by: gamab <gabriel.mabille@grafana.com> * Test evaluation with translation * fix imports * AccessControl: Test attribute resolver * Fix trailing white space * Make ScopeResolver public for enterprise redefine * Handle wildcard Co-authored-by: Jguer <joao.guerreiro@grafana.com> Co-authored-by: jguer <joao.guerreiro@grafana.com>
This commit is contained in:
co-authored by
gamab
Jguer
parent
7a622422a9
commit
54280fc9d7
@@ -1,12 +1,23 @@
|
||||
package accesscontrol
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"strings"
|
||||
"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 GetResourceScope(resource string, resourceID string) string {
|
||||
return Scope(resource, "id", resourceID)
|
||||
}
|
||||
@@ -44,11 +55,17 @@ func Field(key string) string {
|
||||
return fmt.Sprintf(`{{ .%s }}`, key)
|
||||
}
|
||||
|
||||
// ScopeMutator alters a Scope to return a new modified Scope
|
||||
type ScopeMutator func(context.Context, string) (string, error)
|
||||
|
||||
type KeywordScopeResolveFunc func(*models.SignedInUser) (string, error)
|
||||
|
||||
// ScopeResolver contains a map of functions to resolve scope keywords such as `self` or `current` into `id` based scopes
|
||||
// ScopeResolver is used to resolve scope keywords such as `self` or `current` into `id` based scopes and scope attributes such as `name` or `uid` into `id` based scopes.
|
||||
type ScopeResolver struct {
|
||||
keywordResolvers map[string]KeywordScopeResolveFunc
|
||||
keywordResolvers map[string]KeywordScopeResolveFunc
|
||||
attributeResolvers map[string]AttributeScopeResolveFunc
|
||||
cache *localcache.CacheService
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func NewScopeResolver() ScopeResolver {
|
||||
@@ -56,21 +73,95 @@ func NewScopeResolver() ScopeResolver {
|
||||
keywordResolvers: map[string]KeywordScopeResolveFunc{
|
||||
"users:self": resolveUserSelf,
|
||||
},
|
||||
attributeResolvers: map[string]AttributeScopeResolveFunc{},
|
||||
cache: localcache.New(ttl, cleanInterval),
|
||||
log: log.New("accesscontrol.scoperesolution"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ScopeResolver) AddKeywordResolver(keyword string, fn KeywordScopeResolveFunc) {
|
||||
s.log.Debug("adding keyword resolution for '%v'", keyword)
|
||||
s.keywordResolvers[keyword] = fn
|
||||
}
|
||||
|
||||
func (s *ScopeResolver) AddAttributeResolver(prefix string, fn AttributeScopeResolveFunc) {
|
||||
s.log.Debug("adding attribute resolution for '%v'", prefix)
|
||||
s.attributeResolvers[prefix] = fn
|
||||
}
|
||||
|
||||
func resolveUserSelf(u *models.SignedInUser) (string, error) {
|
||||
return Scope("users", "id", fmt.Sprintf("%v", u.UserId)), nil
|
||||
}
|
||||
|
||||
// ResolveKeyword resolves scope with keywords such as `self` or `current` into `id` based scopes
|
||||
func (s *ScopeResolver) ResolveKeyword(user *models.SignedInUser, permission Permission) (*Permission, error) {
|
||||
if fn, ok := s.keywordResolvers[permission.Scope]; ok {
|
||||
resolvedScope, err := fn(user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not resolve %v: %v", permission.Scope, err)
|
||||
// GetResolveKeywordScopeMutator returns a function to resolve scope with keywords such as `self` or `current` into `id` based scopes
|
||||
func (s *ScopeResolver) GetResolveKeywordScopeMutator(user *models.SignedInUser) ScopeMutator {
|
||||
return func(_ context.Context, scope string) (string, error) {
|
||||
var err error
|
||||
// By default the scope remains unchanged
|
||||
resolvedScope := scope
|
||||
if fn, ok := s.keywordResolvers[scope]; ok {
|
||||
resolvedScope, err = fn(user)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not resolve %v: %w", scope, err)
|
||||
}
|
||||
s.log.Debug("resolved '%v' to '%v'", scope, resolvedScope)
|
||||
}
|
||||
permission.Scope = resolvedScope
|
||||
return resolvedScope, nil
|
||||
}
|
||||
}
|
||||
|
||||
type AttributeScopeResolveFunc func(ctx context.Context, orgID int64, initialScope string) (string, error)
|
||||
|
||||
// getCacheKey creates an identifier to fetch and store resolution of scopes in the cache
|
||||
func getCacheKey(orgID int64, scope string) string {
|
||||
return fmt.Sprintf("%s-%v", scope, orgID)
|
||||
}
|
||||
|
||||
// GetResolveAttributeScopeMutator returns a function to resolve scopes with attributes such as `name` or `uid` into `id` based scopes
|
||||
func (s *ScopeResolver) GetResolveAttributeScopeMutator(orgID int64) ScopeMutator {
|
||||
return func(ctx context.Context, scope string) (string, error) {
|
||||
// Check cache before computing the scope
|
||||
if cachedScope, ok := s.cache.Get(getCacheKey(orgID, scope)); ok {
|
||||
resolvedScope := cachedScope.(string)
|
||||
s.log.Debug("used cache to resolve '%v' to '%v'", scope, resolvedScope)
|
||||
return resolvedScope, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
// By default the scope remains unchanged
|
||||
resolvedScope := scope
|
||||
prefix := scopePrefix(scope)
|
||||
if fn, ok := s.attributeResolvers[prefix]; ok {
|
||||
resolvedScope, err = fn(ctx, orgID, scope)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not resolve %v: %w", scope, err)
|
||||
}
|
||||
// Cache result
|
||||
s.cache.Set(getCacheKey(orgID, scope), resolvedScope, ttl)
|
||||
s.log.Debug("resolved '%v' to '%v'", scope, resolvedScope)
|
||||
}
|
||||
return resolvedScope, nil
|
||||
}
|
||||
}
|
||||
|
||||
func scopePrefix(scope string) string {
|
||||
parts := strings.Split(scope, ":")
|
||||
n := len(parts) - 1
|
||||
parts[n] = ""
|
||||
return strings.Join(parts, ":")
|
||||
}
|
||||
|
||||
//Inject params into the evaluator's templated scopes. e.g. "settings:" + eval.Parameters(":id")
|
||||
func ScopeInjector(params ScopeParams) ScopeMutator {
|
||||
return func(_ context.Context, scope string) (string, error) {
|
||||
tmpl, err := template.New("scope").Parse(scope)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err = tmpl.Execute(&buf, params); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user