2021-08-24 04:36:28 -05:00
package accesscontrol
import (
2022-01-18 10:34:35 -06:00
"bytes"
"context"
2021-08-24 04:36:28 -05:00
"fmt"
2022-01-18 10:34:35 -06:00
"html/template"
2021-08-24 04:36:28 -05:00
"strings"
2022-01-18 10:34:35 -06:00
"time"
2021-10-20 10:11:22 -05:00
2022-01-18 10:34:35 -06:00
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
2021-10-20 10:11:22 -05:00
"github.com/grafana/grafana/pkg/models"
2021-08-24 04:36:28 -05:00
)
2022-01-18 10:34:35 -06:00
const (
ttl = 30 * time . Second
cleanInterval = 2 * time . Minute
)
2021-12-15 05:08:15 -06:00
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" , "*" )
}
2021-08-24 04:36:28 -05:00
// 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 ( )
}
2021-10-06 06:15:09 -05:00
// Parameter returns injectable scope part, based on URL parameters.
2021-08-24 04:36:28 -05:00
// e.g. Scope("users", Parameter(":id")) or "users:" + Parameter(":id")
func Parameter ( key string ) string {
2021-10-06 06:15:09 -05:00
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 )
2021-08-24 04:36:28 -05:00
}
2021-10-20 10:11:22 -05:00
2022-01-18 10:34:35 -06:00
// ScopeMutator alters a Scope to return a new modified Scope
type ScopeMutator func ( context . Context , string ) ( string , error )
2021-10-20 10:11:22 -05:00
type KeywordScopeResolveFunc func ( * models . SignedInUser ) ( string , error )
2022-01-18 10:34:35 -06:00
// 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.
2021-10-20 10:11:22 -05:00
type ScopeResolver struct {
2022-01-18 10:34:35 -06:00
keywordResolvers map [ string ] KeywordScopeResolveFunc
attributeResolvers map [ string ] AttributeScopeResolveFunc
cache * localcache . CacheService
log log . Logger
2021-10-20 10:11:22 -05:00
}
func NewScopeResolver ( ) ScopeResolver {
return ScopeResolver {
keywordResolvers : map [ string ] KeywordScopeResolveFunc {
2021-11-17 03:12:28 -06:00
"users:self" : resolveUserSelf ,
2021-10-20 10:11:22 -05:00
} ,
2022-01-18 10:34:35 -06:00
attributeResolvers : map [ string ] AttributeScopeResolveFunc { } ,
cache : localcache . New ( ttl , cleanInterval ) ,
log : log . New ( "accesscontrol.scoperesolution" ) ,
2021-10-20 10:11:22 -05:00
}
}
2022-01-18 10:34:35 -06:00
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
}
2021-10-20 10:11:22 -05:00
func resolveUserSelf ( u * models . SignedInUser ) ( string , error ) {
return Scope ( "users" , "id" , fmt . Sprintf ( "%v" , u . UserId ) ) , nil
}
2022-01-18 10:34:35 -06:00
// 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 )
2021-10-20 10:11:22 -05:00
if err != nil {
2022-01-18 10:34:35 -06:00
return "" , err
}
var buf bytes . Buffer
if err = tmpl . Execute ( & buf , params ) ; err != nil {
return "" , err
2021-10-20 10:11:22 -05:00
}
2022-01-18 10:34:35 -06:00
return buf . String ( ) , nil
2021-10-20 10:11:22 -05:00
}
}