fix(unified-storage): "sharedwithme" search in legacy modes returns everything (#101054)

* fix "sharedwithme" search returning everything when user does not have any shared dashboards
This commit is contained in:
Will Assis
2025-02-20 09:53:06 -05:00
committed by GitHub
parent e36c9220f9
commit 74e621f377
2 changed files with 167 additions and 5 deletions
+7 -3
View File
@@ -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
+160 -2
View File
@@ -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))
})
}