Compare commits

...

1 Commits

Author SHA1 Message Date
Andrej Ocenas 21ccecaee8 Pass permission param to getDashboardsUIDsSharedWithUser 2026-01-14 12:59:48 +01:00
2 changed files with 170 additions and 10 deletions
+42 -10
View File
@@ -304,6 +304,32 @@ const rootFolder = "general"
var errEmptyResults = fmt.Errorf("empty results")
func permissionToActions(p dashboardaccess.PermissionType) (dashboardAction string, folderAction string) {
switch p {
case dashboardaccess.PERMISSION_EDIT:
return dashboards.ActionDashboardsWrite, dashboards.ActionFoldersWrite
case dashboardaccess.PERMISSION_ADMIN:
return dashboards.ActionDashboardsPermissionsWrite, dashboards.ActionFoldersPermissionsWrite
case dashboardaccess.PERMISSION_VIEW:
fallthrough
default:
return dashboards.ActionDashboardsRead, dashboards.ActionFoldersRead
}
}
func permissionFromQueryParams(queryParams url.Values) dashboardaccess.PermissionType {
switch strings.ToLower(queryParams.Get("permission")) {
case "edit":
return dashboardaccess.PERMISSION_EDIT
case "view":
return dashboardaccess.PERMISSION_VIEW
case "admin":
return dashboardaccess.PERMISSION_ADMIN
default:
return dashboardaccess.PERMISSION_VIEW
}
}
func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
ctx, span := s.tracer.Start(r.Context(), "dashboard.search")
defer span.End()
@@ -320,8 +346,10 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
return
}
requestedPermission := permissionFromQueryParams(queryParams)
searchRequest, err := convertHttpSearchRequestToResourceSearchRequest(queryParams, user, func() ([]string, error) {
return s.getDashboardsUIDsSharedWithUser(ctx, user)
return s.getDashboardsUIDsSharedWithUser(ctx, user, requestedPermission)
})
if err != nil {
if errors.Is(err, errEmptyResults) {
@@ -548,11 +576,13 @@ func asResourceKey(ns string, k string) (*resourcepb.ResourceKey, error) {
return key, nil
}
func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester) ([]string, error) {
// gets dashboards that the user was granted read access to
func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester, requestedPermission dashboardaccess.PermissionType) ([]string, error) {
// Gets dashboards/folders that the user was granted access to, but does not have access to their parent folder.
// This powers the virtual "Shared with me" folder.
dashboardAction, folderAction := permissionToActions(requestedPermission)
permissions := user.GetPermissions()
dashboardPermissions := permissions[dashboards.ActionDashboardsRead]
folderPermissions := permissions[dashboards.ActionFoldersRead]
dashboardPermissions := permissions[dashboardAction]
folderPermissions := permissions[folderAction]
dashboardUids := make([]string, 0)
sharedDashboards := make([]string, 0)
@@ -586,9 +616,10 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
}
dashboardSearchRequest := &resourcepb.ResourceSearchRequest{
Federated: []*resourcepb.ResourceKey{folderKey},
Fields: []string{"folder"},
Limit: int64(len(dashboardUids)),
Federated: []*resourcepb.ResourceKey{folderKey},
Fields: []string{"folder"},
Limit: int64(len(dashboardUids)),
Permission: int64(requestedPermission),
Options: &resourcepb.ListOptions{
Key: key,
Fields: []*resourcepb.Requirement{{
@@ -625,8 +656,9 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
}
folderSearchRequest := &resourcepb.ResourceSearchRequest{
Fields: []string{"folder"},
Limit: int64(len(allFolders)),
Fields: []string{"folder"},
Limit: int64(len(allFolders)),
Permission: int64(requestedPermission),
Options: &resourcepb.ListOptions{
Key: folderKey,
Fields: []*resourcepb.Requirement{{
+128
View File
@@ -626,6 +626,134 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, len(mockResponse3.Results.Rows), len(p.Hits))
})
t.Run("should compute shared dashboards based on requested edit permission", func(t *testing.T) {
// dashboardSearchRequest
mockResponse1 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resourcepb.ResourceKey{
Name: "dashboardinprivatefolder",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("privatefolder"), // folder uid
},
},
{
Key: &resourcepb.ResourceKey{
Name: "dashboardinpublicfolder",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("publicfolder"), // folder uid
},
},
},
},
}
// folderSearchRequest: only folders the user can EDIT should be returned here
mockResponse2 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resourcepb.ResourceKey{
Name: "publicfolder",
Resource: "folder",
},
Cells: [][]byte{
[]byte(""), // root folder uid
},
},
},
},
}
// final search should only include items in folders the user cannot edit
mockResponse3 := &resourcepb.ResourceSearchResponse{
Results: &resourcepb.ResourceTable{
Columns: []*resourcepb.ResourceTableColumnDefinition{
{
Name: "folder",
},
},
Rows: []*resourcepb.ResourceTableRow{
{
Key: &resourcepb.ResourceKey{
Name: "dashboardinprivatefolder",
Resource: "dashboard",
},
Cells: [][]byte{
[]byte("privatefolder"), // folder uid
},
},
},
},
}
mockClient := &MockClient{
MockResponses: []*resourcepb.ResourceSearchResponse{mockResponse1, mockResponse2, mockResponse3},
}
features := featuremgmt.WithFeatures()
searchHandler := SearchHandler{
log: log.New("test", "test"),
client: mockClient,
tracer: tracing.NewNoopTracerService(),
features: features,
}
rr := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/search?folder=sharedwithme&permission=edit", nil)
req.Header.Add("content-type", "application/json")
allPermissions := make(map[int64]map[string][]string)
permissions := make(map[string][]string)
permissions[dashboards.ActionDashboardsWrite] = []string{"dashboards:uid:dashboardinprivatefolder", "dashboards:uid:dashboardinpublicfolder"}
// user can view the private folder, but cannot edit it. This should still classify dashboards in it as "shared with me" for edit.
permissions[dashboards.ActionFoldersRead] = []string{"folders:uid:privatefolder"}
allPermissions[1] = permissions
req = req.WithContext(identity.WithRequester(req.Context(), &user.SignedInUser{Namespace: "test", OrgID: 1, Permissions: allPermissions}))
searchHandler.DoSearch(rr, req)
assert.Equal(t, 3, mockClient.CallCount)
firstCall := mockClient.MockCalls[0]
assert.Equal(t, int64(dashboardaccess.PERMISSION_EDIT), firstCall.Permission)
assert.Equal(t, []string{"dashboardinprivatefolder", "dashboardinpublicfolder"}, firstCall.Options.Fields[0].Values)
secondCall := mockClient.MockCalls[1]
assert.Equal(t, int64(dashboardaccess.PERMISSION_EDIT), secondCall.Permission)
assert.Equal(t, []string{"privatefolder", "publicfolder"}, secondCall.Options.Fields[0].Values)
thirdCall := mockClient.MockCalls[2]
assert.Equal(t, int64(dashboardaccess.PERMISSION_EDIT), thirdCall.Permission)
assert.Equal(t, []string{"dashboardinprivatefolder"}, thirdCall.Options.Fields[0].Values)
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))
})
}
func TestConvertHttpSearchRequestToResourceSearchRequest(t *testing.T) {