From 69ccfd6bfc9541c4c912a41aafc61025fb45f234 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Mon, 12 Jan 2026 15:33:34 -0500 Subject: [PATCH] unified-storage: fix sharedwithme search not returning folders (#116089) * unified-storage: fix dashboard sharedwithme search not returning folders shared with the user --- pkg/registry/apis/dashboard/search.go | 25 ++++++++++++------ pkg/registry/apis/dashboard/search_test.go | 30 +++++++++++++++++++--- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 8572dae8295..bbc032e7382 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -552,6 +552,7 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use // gets dashboards that the user was granted read access to permissions := user.GetPermissions() dashboardPermissions := permissions[dashboards.ActionDashboardsRead] + folderPermissions := permissions[dashboards.ActionFoldersRead] dashboardUids := make([]string, 0) sharedDashboards := make([]string, 0) @@ -562,6 +563,13 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use } } } + for _, folderPermission := range folderPermissions { + if folderUid, found := strings.CutPrefix(folderPermission, dashboards.ScopeFoldersPrefix); found { + if !slices.Contains(dashboardUids, folderUid) && folderUid != foldermodel.SharedWithMeFolderUID && folderUid != foldermodel.GeneralFolderUID { + dashboardUids = append(dashboardUids, folderUid) + } + } + } if len(dashboardUids) == 0 { return sharedDashboards, nil @@ -572,9 +580,15 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use return sharedDashboards, err } + folderKey, err := asResourceKey(user.GetNamespace(), folders.RESOURCE) + if err != nil { + return sharedDashboards, err + } + dashboardSearchRequest := &resourcepb.ResourceSearchRequest{ - Fields: []string{"folder"}, - Limit: int64(len(dashboardUids)), + Federated: []*resourcepb.ResourceKey{folderKey}, + Fields: []string{"folder"}, + Limit: int64(len(dashboardUids)), Options: &resourcepb.ListOptions{ Key: key, Fields: []*resourcepb.Requirement{{ @@ -610,12 +624,6 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use } } - // only folders the user has access to will be returned here - folderKey, err := asResourceKey(user.GetNamespace(), folders.RESOURCE) - if err != nil { - return sharedDashboards, err - } - folderSearchRequest := &resourcepb.ResourceSearchRequest{ Fields: []string{"folder"}, Limit: int64(len(allFolders)), @@ -628,6 +636,7 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use }}, }, } + // only folders the user has access to will be returned here foldersResult, err := s.client.Search(ctx, folderSearchRequest) if err != nil { return sharedDashboards, err diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 3b9935f8247..c7defba3fea 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -507,6 +507,15 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { []byte("publicfolder"), // folder uid }, }, + { + Key: &resourcepb.ResourceKey{ + Name: "sharedfolder", + Resource: "folder", + }, + Cells: [][]byte{ + []byte("privatefolder"), // folder uid + }, + }, }, }, } @@ -550,6 +559,15 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { []byte("privatefolder"), // folder uid }, }, + { + Key: &resourcepb.ResourceKey{ + Name: "sharedfolder", + Resource: "folder", + }, + Cells: [][]byte{ + []byte("privatefolder"), // folder uid + }, + }, }, }, } @@ -571,6 +589,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { allPermissions := make(map[int64]map[string][]string) permissions := make(map[string][]string) permissions[dashboards.ActionDashboardsRead] = []string{"dashboards:uid:dashboardinroot", "dashboards:uid:dashboardinprivatefolder", "dashboards:uid:dashboardinpublicfolder"} + permissions[dashboards.ActionFoldersRead] = []string{"folders:uid:sharedfolder"} allPermissions[1] = permissions // "Permissions" is where we store the uid of dashboards shared with the user req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test", OrgID: 1, Permissions: allPermissions})) @@ -581,14 +600,19 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { // first call gets all dashboards user has permission for firstCall := mockClient.MockCalls[0] - assert.Equal(t, firstCall.Options.Fields[0].Values, []string{"dashboardinroot", "dashboardinprivatefolder", "dashboardinpublicfolder"}) + assert.Equal(t, firstCall.Options.Fields[0].Values, []string{"dashboardinroot", "dashboardinprivatefolder", "dashboardinpublicfolder", "sharedfolder"}) + // verify federated field is set to include folders + assert.NotNil(t, firstCall.Federated) + assert.Equal(t, 1, len(firstCall.Federated)) + assert.Equal(t, "folder.grafana.app", firstCall.Federated[0].Group) + assert.Equal(t, "folders", firstCall.Federated[0].Resource) // second call gets folders associated with the previous dashboards secondCall := mockClient.MockCalls[1] assert.Equal(t, secondCall.Options.Fields[0].Values, []string{"privatefolder", "publicfolder"}) - // lastly, search ONLY for dashboards user has permission to read that are within folders the user does NOT have + // lastly, search ONLY for dashboards and folders user has permission to read that are within folders the user does NOT have // permission to read thirdCall := mockClient.MockCalls[2] - assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder"}) + assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder", "sharedfolder"}) resp := rr.Result() defer func() {