Authz: For list collect all folder permisions into items (#99955)

* For list collect all folder permisions into items
---------

Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com>
This commit is contained in:
Karl Persson 2025-02-03 12:14:28 +01:00 committed by GitHub
parent 4cd2ebe186
commit d16374d339
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 83 additions and 33 deletions

View File

@ -619,27 +619,28 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool,
} }
} }
folderSet := make(map[string]struct{}, len(scopeMap)) var res *authzv1.ListResponse
if strings.HasPrefix(req.Action, "folders:") {
prefix := t.prefix() res = buildFolderList(scopeMap, folderMap)
itemSet := make(map[string]struct{}, len(scopeMap)) } else {
for scope := range scopeMap { res = buildItemList(scopeMap, folderMap, t.prefix())
if strings.HasPrefix(scope, "folders:uid:") {
identifier := strings.TrimPrefix(scope, "folders:uid:")
if _, ok := folderSet[identifier]; ok {
continue
}
folderSet[identifier] = struct{}{}
getChildren(folderMap, identifier, folderSet)
} else {
identifier := strings.TrimPrefix(scope, prefix)
itemSet[identifier] = struct{}{}
}
} }
folderList := make([]string, 0, len(folderSet)) span.SetAttributes(attribute.Int("num_folders", len(res.Folders)), attribute.Int("num_items", len(res.Items)))
for folder := range folderSet { return res, nil
folderList = append(folderList, folder) }
func buildFolderList(scopes map[string]bool, tree map[string]FolderNode) *authzv1.ListResponse {
itemSet := make(map[string]struct{}, len(scopes))
for scope := range scopes {
identifier := strings.TrimPrefix(scope, "folders:uid:")
if _, ok := itemSet[identifier]; ok {
continue
}
itemSet[identifier] = struct{}{}
getChildren(tree, identifier, itemSet)
} }
itemList := make([]string, 0, len(itemSet)) itemList := make([]string, 0, len(itemSet))
@ -647,8 +648,35 @@ func (s *Service) listPermission(ctx context.Context, scopeMap map[string]bool,
itemList = append(itemList, item) itemList = append(itemList, item)
} }
span.SetAttributes(attribute.Int("num_folders", len(folderList)), attribute.Int("num_items", len(itemList))) return &authzv1.ListResponse{Items: itemList}
return &authzv1.ListResponse{Folders: folderList, Items: itemList}, nil }
func buildItemList(scopes map[string]bool, tree map[string]FolderNode, prefix string) *authzv1.ListResponse {
folderSet := make(map[string]struct{}, len(scopes))
itemSet := make(map[string]struct{}, len(scopes))
for scope := range scopes {
if identifier, ok := strings.CutPrefix(scope, "folders:uid:"); ok {
if _, ok := folderSet[identifier]; ok {
continue
}
folderSet[identifier] = struct{}{}
getChildren(tree, identifier, folderSet)
} else {
identifier := strings.TrimPrefix(scope, prefix)
itemSet[identifier] = struct{}{}
}
}
folderList := make([]string, 0, len(folderSet))
for folder := range folderSet {
folderList = append(folderList, folder)
}
itemList := make([]string, 0, len(itemSet))
for item := range itemSet {
itemList = append(itemList, item)
}
return &authzv1.ListResponse{Folders: folderList, Items: itemList}
} }
func getChildren(folderMap map[string]FolderNode, folderUID string, folderSet map[string]struct{}) { func getChildren(folderMap map[string]FolderNode, folderUID string, folderSet map[string]struct{}) {

View File

@ -452,13 +452,13 @@ func TestService_buildFolderTree(t *testing.T) {
func TestService_listPermission(t *testing.T) { func TestService_listPermission(t *testing.T) {
type testCase struct { type testCase struct {
name string name string
permissions []accesscontrol.Permission permissions []accesscontrol.Permission
folderTree map[string]FolderNode folderTree map[string]FolderNode
list ListRequest list ListRequest
expectedDashboards []string expectedItems []string
expectedFolders []string expectedFolders []string
expectedAll bool expectedAll bool
} }
testCases := []testCase{ testCases := []testCase{
@ -512,8 +512,8 @@ func TestService_listPermission(t *testing.T) {
Group: "dashboard.grafana.app", Group: "dashboard.grafana.app",
Resource: "dashboards", Resource: "dashboards",
}, },
expectedDashboards: []string{"some_dashboard"}, expectedItems: []string{"some_dashboard"},
expectedFolders: []string{"some_folder_1", "some_folder_2"}, expectedFolders: []string{"some_folder_1", "some_folder_2"},
}, },
{ {
name: "should return folders that user has inherited access to", name: "should return folders that user has inherited access to",
@ -568,8 +568,8 @@ func TestService_listPermission(t *testing.T) {
Group: "dashboard.grafana.app", Group: "dashboard.grafana.app",
Resource: "dashboards", Resource: "dashboards",
}, },
expectedDashboards: []string{"some_dashboard"}, expectedItems: []string{"some_dashboard"},
expectedFolders: []string{"some_folder_parent", "some_folder_child"}, expectedFolders: []string{"some_folder_parent", "some_folder_child"},
}, },
{ {
name: "should deduplicate folders that user has inherited as well as direct access to", name: "should deduplicate folders that user has inherited as well as direct access to",
@ -613,6 +613,28 @@ func TestService_listPermission(t *testing.T) {
Resource: "dashboards", Resource: "dashboards",
}, },
}, },
{
name: "should collect folder permissions into items",
permissions: []accesscontrol.Permission{
{
Action: "folders:read",
Scope: "folders:uid:some_folder_parent",
Kind: "folders",
Attribute: "uid",
Identifier: "some_folder_parent",
},
},
folderTree: map[string]FolderNode{
"some_folder_parent": {UID: "some_folder_parent", ChildrenUIDs: []string{"some_folder_child"}},
"some_folder_child": {UID: "some_folder_child", ParentUID: strPtr("some_folder_parent")},
},
list: ListRequest{
Action: "folders:read",
Group: "folder.grafana.app",
Resource: "folders",
},
expectedItems: []string{"some_folder_parent", "some_folder_child"},
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -626,7 +648,7 @@ func TestService_listPermission(t *testing.T) {
got, err := s.listPermission(context.Background(), getScopeMap(tc.permissions), &tc.list) got, err := s.listPermission(context.Background(), getScopeMap(tc.permissions), &tc.list)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, tc.expectedAll, got.All) assert.Equal(t, tc.expectedAll, got.All)
assert.ElementsMatch(t, tc.expectedDashboards, got.Items) assert.ElementsMatch(t, tc.expectedItems, got.Items)
assert.ElementsMatch(t, tc.expectedFolders, got.Folders) assert.ElementsMatch(t, tc.expectedFolders, got.Folders)
}) })
} }