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:
Karl Persson
2022-08-26 17:10:35 +02:00
committed by GitHub
parent 6227528ea0
commit 9d2f5ef62f
7 changed files with 128 additions and 91 deletions

View File

@@ -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
}

View File

@@ -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{}{}
}

View 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
}

View File

@@ -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
}

View File

@@ -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)
})
}
}