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/scope.go

167 lines
5.2 KiB

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)
}
func GetResourceAllScope(resource string) string {
return Scope(resource, "*")
}
func GetResourceAllIDScope(resource string) string {
return Scope(resource, "id", "*")
}
// Scope builds scope from parts
// e.g. Scope("users", "*") return "users:*"
func Scope(parts ...string) string {
b := strings.Builder{}
for i, c := range parts {
if i != 0 {
b.WriteRune(':')
}
b.WriteString(c)
}
return b.String()
}
// Parameter returns injectable scope part, based on URL parameters.
// e.g. Scope("users", Parameter(":id")) or "users:" + Parameter(":id")
func Parameter(key string) string {
return fmt.Sprintf(`{{ index .URLParams "%s" }}`, key)
}
// Field returns an injectable scope part for selected fields from the request's context available in accesscontrol.ScopeParams.
// e.g. Scope("orgs", Parameter("OrgID")) or "orgs:" + Parameter("OrgID")
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
}
}
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
}
}