diff --git a/pkg/registry/apis/dashboard/search.go b/pkg/registry/apis/dashboard/search.go index 3060972a32d..fe97d4cabb1 100644 --- a/pkg/registry/apis/dashboard/search.go +++ b/pkg/registry/apis/dashboard/search.go @@ -333,10 +333,14 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) { return } - // hijacks the "name" query param to only search for shared dashboard UIDs - if len(dashboardUIDs) > 0 { - names = append(names, dashboardUIDs...) + if len(dashboardUIDs) == 0 { + s.write(w, dashboardv0alpha1.SearchResults{ + Hits: []dashboardv0alpha1.DashboardHit{}, + }) + return } + // hijacks the "name" query param to only search for shared dashboard UIDs + names = append(names, dashboardUIDs...) } else if folder != "" { if folder == rootFolder { folder = "" // root folder is empty in the search index diff --git a/pkg/registry/apis/dashboard/search_test.go b/pkg/registry/apis/dashboard/search_test.go index 3d8967359c9..de2658c28c1 100644 --- a/pkg/registry/apis/dashboard/search_test.go +++ b/pkg/registry/apis/dashboard/search_test.go @@ -357,7 +357,131 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { searchHandler.DoSearch(rr, req) - assert.Equal(t, mockClient.CallCount, 1) + assert.Equal(t, mockClient.CallCount, 0) + }) + + t.Run("should return empty result without searching if user does not have shared dashboards", func(t *testing.T) { + mockClient := &MockClient{} + + features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) + searchHandler := SearchHandler{ + log: log.New("test", "test"), + client: mockClient, + tracer: tracing.NewNoopTracerService(), + features: features, + } + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/search?folder=sharedwithme", nil) + req.Header.Add("content-type", "application/json") + // "Permissions" prop in "SignedInUser" is where we store the uid of dashboards shared with the user + // doesn't exist here, which represents a user without any shared dashboards + req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test"})) + + searchHandler.DoSearch(rr, req) + + assert.Equal(t, mockClient.CallCount, 0) + + resp := rr.Result() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatal(err) + } + }() + + p := &v0alpha1.SearchResults{} + err := json.NewDecoder(resp.Body).Decode(p) + require.NoError(t, err) + assert.Equal(t, 0, len(p.Hits)) + }) + + t.Run("should return empty result if user has access to folder of all shared dashboards", func(t *testing.T) { + // dashboardSearchRequest + mockResponse1 := &resource.ResourceSearchResponse{ + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + { + Name: "folder", + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "dashboardinroot", + Resource: "dashboard", + }, + Cells: [][]byte{[]byte("")}, // root folder doesn't have uid + }, + { + Key: &resource.ResourceKey{ + Name: "dashboardinpublicfolder", + Resource: "dashboard", + }, + Cells: [][]byte{ + []byte("publicfolder"), // folder uid + }, + }, + }, + }, + } + + // folderSearchRequest + mockResponse2 := &resource.ResourceSearchResponse{ + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + { + Name: "folder", + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "publicfolder", + Resource: "folder", + }, + Cells: [][]byte{ + []byte(""), // root folder uid + }, + }, + }, + }, + } + + mockClient := &MockClient{ + MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2}, + } + + features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) + searchHandler := SearchHandler{ + log: log.New("test", "test"), + client: mockClient, + tracer: tracing.NewNoopTracerService(), + features: features, + } + rr := httptest.NewRecorder() + req := httptest.NewRequest("GET", "/search?folder=sharedwithme", nil) + req.Header.Add("content-type", "application/json") + allPermissions := make(map[int64]map[string][]string) + permissions := make(map[string][]string) + permissions[dashboards.ActionDashboardsRead] = []string{"dashboards:uid:dashboardinroot", "dashboards:uid:dashboardinpublicfolder"} + 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})) + + searchHandler.DoSearch(rr, req) + + assert.Equal(t, mockClient.CallCount, 2) + + resp := rr.Result() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatal(err) + } + }() + + p := &v0alpha1.SearchResults{} + err := json.NewDecoder(resp.Body).Decode(p) + require.NoError(t, err) + assert.Equal(t, 0, len(p.Hits)) }) t.Run("should return the dashboards shared with the user", func(t *testing.T) { @@ -421,8 +545,29 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { }, } + mockResponse3 := &resource.ResourceSearchResponse{ + Results: &resource.ResourceTable{ + Columns: []*resource.ResourceTableColumnDefinition{ + { + Name: "folder", + }, + }, + Rows: []*resource.ResourceTableRow{ + { + Key: &resource.ResourceKey{ + Name: "dashboardinprivatefolder", + Resource: "dashboard", + }, + Cells: [][]byte{ + []byte("privatefolder"), // folder uid + }, + }, + }, + }, + } + mockClient := &MockClient{ - MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2}, + MockResponses: []*resource.ResourceSearchResponse{mockResponse1, mockResponse2, mockResponse3}, } features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearchPermissionFiltering) @@ -439,6 +584,7 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { permissions := make(map[string][]string) permissions[dashboards.ActionDashboardsRead] = []string{"dashboards:uid:dashboardinroot", "dashboards:uid:dashboardinprivatefolder", "dashboards:uid:dashboardinpublicfolder"} 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})) searchHandler.DoSearch(rr, req) @@ -455,6 +601,18 @@ func TestSearchHandlerSharedDashboards(t *testing.T) { // permission to read thirdCall := mockClient.MockCalls[2] assert.Equal(t, thirdCall.Options.Fields[0].Values, []string{"dashboardinprivatefolder"}) + + resp := rr.Result() + defer func() { + if err := resp.Body.Close(); err != nil { + t.Fatal(err) + } + }() + + p := &v0alpha1.SearchResults{} + err := json.NewDecoder(resp.Body).Decode(p) + require.NoError(t, err) + assert.Equal(t, len(mockResponse3.Results.Rows), len(p.Hits)) }) }