Files

196 lines
6.3 KiB
Go

package permreg
import (
"strings"
"github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/infra/log"
)
var (
// ErrInvalidScope is returned when the scope is not valid for the action
ErrInvalidScopeTplt = "invalid scope: {{.Public.Scope}}, for action: {{.Public.Action}}, expected prefixes are {{.Public.ValidScopesFormat}}"
ErrBaseInvalidScope = errutil.BadRequest("permreg.invalid-scope").MustTemplate(ErrInvalidScopeTplt, errutil.WithPublic(ErrInvalidScopeTplt))
ErrUnknownActionTplt = "unknown action: {{.Public.Action}}, was not found in the list of valid actions"
ErrBaseUnknownAction = errutil.BadRequest("permreg.unknown-action").MustTemplate(ErrUnknownActionTplt, errutil.WithPublic(ErrUnknownActionTplt))
ErrBaseUnknownKind = errutil.BadRequest("permreg.unknown-kind").MustTemplate("unknown kind: {{.Public.Kind}}")
)
func ErrInvalidScope(scope string, action string, validScopePrefixes PrefixSet) error {
if len(validScopePrefixes) == 0 {
return ErrBaseInvalidScope.Build(errutil.TemplateData{Public: map[string]any{"Scope": scope, "Action": action, "ValidScopesFormat": "[none]"}})
}
formats := generateValidScopeFormats(validScopePrefixes)
return ErrBaseInvalidScope.Build(errutil.TemplateData{Public: map[string]any{"Scope": scope, "Action": action, "ValidScopesFormat": formats}})
}
func ErrUnknownAction(action string) error {
return ErrBaseUnknownAction.Build(errutil.TemplateData{Public: map[string]any{"Action": action}})
}
func ErrUnknownKind(kind string) error {
return ErrBaseUnknownKind.Build(errutil.TemplateData{Public: map[string]any{"Kind": kind}})
}
func generateValidScopeFormats(acceptedScopePrefixes PrefixSet) []string {
if len(acceptedScopePrefixes) == 0 {
return []string{}
}
acceptedPrefixesList := make([]string, 0, 10)
acceptedPrefixesList = append(acceptedPrefixesList, "*")
for prefix := range acceptedScopePrefixes {
parts := strings.Split(prefix, ":")
// If the prefix has an attribute part add the intermediate format kind:*
if len(parts) > 2 {
acceptedPrefixesList = append(acceptedPrefixesList, parts[0]+":*")
}
// Add the most specific format kind:attribute:*
acceptedPrefixesList = append(acceptedPrefixesList, prefix+"*")
}
return acceptedPrefixesList
}
type PermissionRegistry interface {
RegisterPluginScope(scope string)
RegisterPermission(action, scope string) error
IsPermissionValid(action, scope string) error
GetScopePrefixes(action string) (PrefixSet, bool)
}
type PrefixSet map[string]bool
var _ PermissionRegistry = &permissionRegistry{}
type permissionRegistry struct {
actionScopePrefixes map[string]PrefixSet // TODO use thread safe map
kindScopePrefix map[string]string
logger log.Logger
}
func ProvidePermissionRegistry() PermissionRegistry {
return newPermissionRegistry()
}
func newPermissionRegistry() *permissionRegistry {
// defaultKindScopes maps the most specific accepted scope prefix for a given kind (folders, dashboards, etc)
defaultKindScopes := map[string]string{
"teams": "teams:id:",
"users": "users:id:",
"datasources": "datasources:uid:",
"dashboards": "dashboards:uid:",
"folders": "folders:uid:",
"annotations": "annotations:type:",
"apikeys": "apikeys:id:",
"orgs": "orgs:id:",
"plugins": "plugins:id:",
"provisioners": "provisioners:",
"reports": "reports:id:",
"permissions": "permissions:type:",
"serviceaccounts": "serviceaccounts:id:",
"settings": "settings:",
"global.users": "global.users:id:",
"roles": "roles:uid:",
"services": "services:",
"receivers": "receivers:uid:",
"secret.securevalues": "secret.securevalues:uid:",
"secret.keepers": "secret.keepers:uid:",
}
return &permissionRegistry{
actionScopePrefixes: make(map[string]PrefixSet, 200),
kindScopePrefix: defaultKindScopes,
logger: log.New("accesscontrol.permreg"),
}
}
func (pr *permissionRegistry) RegisterPluginScope(scope string) {
if scope == "" {
return
}
scopeParts := strings.Split(scope, ":")
kind := scopeParts[0]
// If the scope is already registered, return
if _, found := pr.kindScopePrefix[kind]; found {
return
}
// If the scope contains an attribute part, register the kind and attribute
if len(scopeParts) > 2 {
attr := scopeParts[1]
pr.kindScopePrefix[kind] = kind + ":" + attr + ":"
pr.logger.Debug("registered scope prefix", "kind", kind, "scope_prefix", kind+":"+attr+":")
return
}
pr.logger.Debug("registered scope prefix", "kind", kind, "scope_prefix", kind+":")
pr.kindScopePrefix[kind] = kind + ":"
}
func (pr *permissionRegistry) RegisterPermission(action, scope string) error {
if _, ok := pr.actionScopePrefixes[action]; !ok {
pr.actionScopePrefixes[action] = PrefixSet{}
}
if scope == "" {
// scopeless action
return nil
}
kind := strings.Split(scope, ":")[0]
scopePrefix, ok := pr.kindScopePrefix[kind]
if !ok {
pr.logger.Error("unknown kind: please update `kindScopePrefix` with the correct scope prefix", "kind", kind)
return ErrUnknownKind(kind)
}
// Add a new entry in case the scope is not empty
pr.actionScopePrefixes[action][scopePrefix] = true
return nil
}
func (pr *permissionRegistry) IsPermissionValid(action, scope string) error {
validScopePrefixes, ok := pr.actionScopePrefixes[action]
if !ok {
return ErrUnknownAction(action)
}
if ok && len(validScopePrefixes) == 0 {
// Expecting action without any scope
if scope != "" {
return ErrInvalidScope(scope, action, nil)
}
return nil
}
if !isScopeValid(scope, validScopePrefixes) {
return ErrInvalidScope(scope, action, validScopePrefixes)
}
return nil
}
func isScopeValid(scope string, validScopePrefixes PrefixSet) bool {
// Super wildcard scope
if scope == "*" {
return true
}
for scopePrefix := range validScopePrefixes {
// Correct scope prefix
if strings.HasPrefix(scope, scopePrefix) {
return true
}
// Scope is wildcard of the correct prefix
if strings.HasSuffix(scope, ":*") && strings.HasPrefix(scopePrefix, scope[:len(scope)-2]) {
return true
}
}
return false
}
func (pr *permissionRegistry) GetScopePrefixes(action string) (PrefixSet, bool) {
set, ok := pr.actionScopePrefixes[action]
return set, ok
}