mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Add function to generate wildcards from prefix (#54275)
* RBAC: Move metadata to own file * RBAC: Rename test files * RBAC: Add wildcard structure and helper function to generate wildcards from prefix * RBAC: Refactor filter to use WildcardsFromPrefix * RBAC: Refactor GetResourceMetadata to use WildcardsFromPrefix
This commit is contained in:
@@ -92,10 +92,6 @@ type User struct {
|
||||
IsExternal bool
|
||||
}
|
||||
|
||||
// Metadata contains user accesses for a given resource
|
||||
// Ex: map[string]bool{"create":true, "delete": true}
|
||||
type Metadata map[string]bool
|
||||
|
||||
// HasGlobalAccess checks user access with globally assigned permissions only
|
||||
func HasGlobalAccess(ac AccessControl, c *models.ReqContext) func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
|
||||
return func(fallback func(*models.ReqContext) bool, evaluator Evaluator) bool {
|
||||
@@ -184,66 +180,6 @@ func ValidateScope(scope string) bool {
|
||||
return !strings.ContainsAny(prefix, "*?")
|
||||
}
|
||||
|
||||
func addActionToMetadata(allMetadata map[string]Metadata, action, id string) map[string]Metadata {
|
||||
metadata, initialized := allMetadata[id]
|
||||
if !initialized {
|
||||
metadata = Metadata{action: true}
|
||||
} else {
|
||||
metadata[action] = true
|
||||
}
|
||||
allMetadata[id] = metadata
|
||||
return allMetadata
|
||||
}
|
||||
|
||||
// GetResourcesMetadata returns a map of accesscontrol metadata, listing for each resource, users available actions
|
||||
func GetResourcesMetadata(ctx context.Context, permissions map[string][]string, prefix string, resourceIDs map[string]bool) map[string]Metadata {
|
||||
rootPrefix, attributePrefix, ok := extractPrefixes(prefix)
|
||||
if !ok {
|
||||
return map[string]Metadata{}
|
||||
}
|
||||
|
||||
allScope := GetResourceAllScope(strings.TrimSuffix(rootPrefix, ":"))
|
||||
allAttributeScope := Scope(strings.TrimSuffix(attributePrefix, ":"), "*")
|
||||
|
||||
// index of the attribute in the scope
|
||||
attributeIndex := len(attributePrefix)
|
||||
|
||||
// Loop through permissions once
|
||||
result := map[string]Metadata{}
|
||||
|
||||
for action, scopes := range permissions {
|
||||
for _, scope := range scopes {
|
||||
if scope == "*" || scope == allScope || scope == allAttributeScope {
|
||||
// Add global action to all resources
|
||||
for id := range resourceIDs {
|
||||
result = addActionToMetadata(result, action, id)
|
||||
}
|
||||
} else {
|
||||
if len(scope) > attributeIndex && strings.HasPrefix(scope, attributePrefix) && resourceIDs[scope[attributeIndex:]] {
|
||||
// Add action to a specific resource
|
||||
result = addActionToMetadata(result, action, scope[attributeIndex:])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MergeMeta will merge actions matching prefix of second metadata into first
|
||||
func MergeMeta(prefix string, first Metadata, second Metadata) Metadata {
|
||||
if first == nil {
|
||||
first = Metadata{}
|
||||
}
|
||||
|
||||
for key := range second {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
first[key] = true
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
|
||||
func ManagedUserRoleName(userID int64) string {
|
||||
return fmt.Sprintf("managed:users:%d:permissions", userID)
|
||||
}
|
||||
@@ -256,16 +192,6 @@ func ManagedBuiltInRoleName(builtInRole string) string {
|
||||
return fmt.Sprintf("managed:builtins:%s:permissions", strings.ToLower(builtInRole))
|
||||
}
|
||||
|
||||
func extractPrefixes(prefix string) (string, string, bool) {
|
||||
parts := strings.Split(strings.TrimSuffix(prefix, ":"), ":")
|
||||
if len(parts) != 2 {
|
||||
return "", "", false
|
||||
}
|
||||
rootPrefix := parts[0] + ":"
|
||||
attributePrefix := rootPrefix + parts[1] + ":"
|
||||
return rootPrefix, attributePrefix, true
|
||||
}
|
||||
|
||||
func IsDisabled(cfg *setting.Cfg) bool {
|
||||
return !cfg.RBACEnabled
|
||||
}
|
||||
|
||||
@@ -88,33 +88,19 @@ func Filter(user *user.SignedInUser, sqlID, prefix string, actions ...string) (S
|
||||
func ParseScopes(prefix string, scopes []string) (ids map[interface{}]struct{}, hasWildcard bool) {
|
||||
ids = make(map[interface{}]struct{})
|
||||
|
||||
rootPrefix, attributePrefix, ok := extractPrefixes(prefix)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
parser := parseStringAttribute
|
||||
if strings.HasSuffix(prefix, ":id:") {
|
||||
parser = parseIntAttribute
|
||||
}
|
||||
|
||||
allScope := rootPrefix + "*"
|
||||
allAttributeScope := attributePrefix + "*"
|
||||
wildcards := WildcardsFromPrefix(prefix)
|
||||
|
||||
for _, scope := range scopes {
|
||||
if scope == "*" {
|
||||
if wildcards.Contains(scope) {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if strings.HasPrefix(scope, rootPrefix) {
|
||||
if scope == allScope || scope == allAttributeScope {
|
||||
return nil, true
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(scope, prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(scope, prefix) {
|
||||
if id, err := parser(scope); err == nil {
|
||||
ids[id] = struct{}{}
|
||||
}
|
||||
|
||||
63
pkg/services/accesscontrol/metadata.go
Normal file
63
pkg/services/accesscontrol/metadata.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package accesscontrol
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Metadata contains user accesses for a given resource
|
||||
// Ex: map[string]bool{"create":true, "delete": true}
|
||||
type Metadata map[string]bool
|
||||
|
||||
// GetResourcesMetadata returns a map of accesscontrol metadata, listing for each resource, users available actions
|
||||
func GetResourcesMetadata(ctx context.Context, permissions map[string][]string, prefix string, resourceIDs map[string]bool) map[string]Metadata {
|
||||
wildcards := WildcardsFromPrefix(prefix)
|
||||
|
||||
// index of the prefix in the scope
|
||||
prefixIndex := len(prefix)
|
||||
|
||||
// Loop through permissions once
|
||||
result := map[string]Metadata{}
|
||||
|
||||
for action, scopes := range permissions {
|
||||
for _, scope := range scopes {
|
||||
if wildcards.Contains(scope) {
|
||||
for id := range resourceIDs {
|
||||
result = addActionToMetadata(result, action, id)
|
||||
}
|
||||
break
|
||||
}
|
||||
if len(scope) > prefixIndex && strings.HasPrefix(scope, prefix) && resourceIDs[scope[prefixIndex:]] {
|
||||
// Add action to a specific resource
|
||||
result = addActionToMetadata(result, action, scope[prefixIndex:])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func addActionToMetadata(allMetadata map[string]Metadata, action, id string) map[string]Metadata {
|
||||
metadata, initialized := allMetadata[id]
|
||||
if !initialized {
|
||||
metadata = Metadata{action: true}
|
||||
} else {
|
||||
metadata[action] = true
|
||||
}
|
||||
allMetadata[id] = metadata
|
||||
return allMetadata
|
||||
}
|
||||
|
||||
// MergeMeta will merge actions matching prefix of second metadata into first
|
||||
func MergeMeta(prefix string, first Metadata, second Metadata) Metadata {
|
||||
if first == nil {
|
||||
first = Metadata{}
|
||||
}
|
||||
|
||||
for key := range second {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
first[key] = true
|
||||
}
|
||||
}
|
||||
return first
|
||||
}
|
||||
@@ -141,3 +141,32 @@ func (s scopeProviderImpl) GetResourceAllScope() string {
|
||||
func (s scopeProviderImpl) GetResourceAllIDScope() string {
|
||||
return GetResourceAllIDScope(s.root)
|
||||
}
|
||||
|
||||
// WildcardsFromPrefix generates valid wildcards from prefix
|
||||
// datasource:uid: => "*", "datasource:*", "datasource:uid:*"
|
||||
func WildcardsFromPrefix(prefix string) Wildcards {
|
||||
var b strings.Builder
|
||||
wildcards := Wildcards{"*"}
|
||||
parts := strings.Split(prefix, ":")
|
||||
for _, p := range parts {
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
b.WriteString(p)
|
||||
b.WriteRune(':')
|
||||
wildcards = append(wildcards, b.String()+"*")
|
||||
}
|
||||
return wildcards
|
||||
}
|
||||
|
||||
type Wildcards []string
|
||||
|
||||
// Contains check if wildcards contains scope
|
||||
func (wildcards Wildcards) Contains(scope string) bool {
|
||||
for _, w := range wildcards {
|
||||
if scope == w {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -51,3 +51,36 @@ func Test_ScopePrefix(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardsFromPrefix(t *testing.T) {
|
||||
type testCase struct {
|
||||
desc string
|
||||
prefix string
|
||||
expected Wildcards
|
||||
}
|
||||
|
||||
tests := []testCase{
|
||||
{
|
||||
desc: "should handle empty prefix",
|
||||
prefix: "",
|
||||
expected: Wildcards{"*"},
|
||||
},
|
||||
{
|
||||
desc: "should generate wildcards for prefix",
|
||||
prefix: "dashboards:uid",
|
||||
expected: Wildcards{"*", "dashboards:*", "dashboards:uid:*"},
|
||||
},
|
||||
{
|
||||
desc: "should handle trailing :",
|
||||
prefix: "dashboards:uid:",
|
||||
expected: Wildcards{"*", "dashboards:*", "dashboards:uid:*"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.desc, func(t *testing.T) {
|
||||
wildcards := WildcardsFromPrefix(tt.prefix)
|
||||
assert.Equal(t, tt.expected, wildcards)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user