Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 21ccecaee8 |
@@ -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{{
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user