Access Control: Refactor scope resolvers with support to resolve into several scopes (#48202)

* Refactor Scope resolver to support resolving into several scopes

* Change permission evaluator to match at least one of passed scopes
This commit is contained in:
Karl Persson
2022-05-02 09:29:30 +02:00
committed by GitHub
parent 9622e7457e
commit de50f39c12
18 changed files with 453 additions and 434 deletions
-114
View File
@@ -1,21 +1,11 @@
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
maxPrefixParts = 2
)
@@ -68,95 +58,6 @@ 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 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
attributeResolvers map[string]AttributeScopeResolveFunc
cache *localcache.CacheService
log log.Logger
}
func NewScopeResolver() ScopeResolver {
return 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
}
// 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)
}
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
}
}
// ScopePrefix returns the prefix associated to a given scope
// we assume prefixes are all in the form <resource>:<attribute>:<value>
// ex: "datasources:name:test" returns "datasources:name:"
@@ -169,21 +70,6 @@ func ScopePrefix(scope string) string {
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
}
}
// ScopeProvider provides methods that construct scopes
type ScopeProvider interface {
GetResourceScope(resourceID string) string