Pass permission param to getDashboardsUIDsSharedWithUser
This commit is contained in:
@@ -304,6 +304,32 @@ const rootFolder = "general"
|
|||||||
|
|
||||||
var errEmptyResults = fmt.Errorf("empty results")
|
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) {
|
func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx, span := s.tracer.Start(r.Context(), "dashboard.search")
|
ctx, span := s.tracer.Start(r.Context(), "dashboard.search")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@@ -320,8 +346,10 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestedPermission := permissionFromQueryParams(queryParams)
|
||||||
|
|
||||||
searchRequest, err := convertHttpSearchRequestToResourceSearchRequest(queryParams, user, func() ([]string, error) {
|
searchRequest, err := convertHttpSearchRequestToResourceSearchRequest(queryParams, user, func() ([]string, error) {
|
||||||
return s.getDashboardsUIDsSharedWithUser(ctx, user)
|
return s.getDashboardsUIDsSharedWithUser(ctx, user, requestedPermission)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, errEmptyResults) {
|
if errors.Is(err, errEmptyResults) {
|
||||||
@@ -548,11 +576,13 @@ func asResourceKey(ns string, k string) (*resourcepb.ResourceKey, error) {
|
|||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester) ([]string, error) {
|
func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, user identity.Requester, requestedPermission dashboardaccess.PermissionType) ([]string, error) {
|
||||||
// gets dashboards that the user was granted read access to
|
// 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()
|
permissions := user.GetPermissions()
|
||||||
dashboardPermissions := permissions[dashboards.ActionDashboardsRead]
|
dashboardPermissions := permissions[dashboardAction]
|
||||||
folderPermissions := permissions[dashboards.ActionFoldersRead]
|
folderPermissions := permissions[folderAction]
|
||||||
dashboardUids := make([]string, 0)
|
dashboardUids := make([]string, 0)
|
||||||
sharedDashboards := make([]string, 0)
|
sharedDashboards := make([]string, 0)
|
||||||
|
|
||||||
@@ -586,9 +616,10 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
dashboardSearchRequest := &resourcepb.ResourceSearchRequest{
|
dashboardSearchRequest := &resourcepb.ResourceSearchRequest{
|
||||||
Federated: []*resourcepb.ResourceKey{folderKey},
|
Federated: []*resourcepb.ResourceKey{folderKey},
|
||||||
Fields: []string{"folder"},
|
Fields: []string{"folder"},
|
||||||
Limit: int64(len(dashboardUids)),
|
Limit: int64(len(dashboardUids)),
|
||||||
|
Permission: int64(requestedPermission),
|
||||||
Options: &resourcepb.ListOptions{
|
Options: &resourcepb.ListOptions{
|
||||||
Key: key,
|
Key: key,
|
||||||
Fields: []*resourcepb.Requirement{{
|
Fields: []*resourcepb.Requirement{{
|
||||||
@@ -625,8 +656,9 @@ func (s *SearchHandler) getDashboardsUIDsSharedWithUser(ctx context.Context, use
|
|||||||
}
|
}
|
||||||
|
|
||||||
folderSearchRequest := &resourcepb.ResourceSearchRequest{
|
folderSearchRequest := &resourcepb.ResourceSearchRequest{
|
||||||
Fields: []string{"folder"},
|
Fields: []string{"folder"},
|
||||||
Limit: int64(len(allFolders)),
|
Limit: int64(len(allFolders)),
|
||||||
|
Permission: int64(requestedPermission),
|
||||||
Options: &resourcepb.ListOptions{
|
Options: &resourcepb.ListOptions{
|
||||||
Key: folderKey,
|
Key: folderKey,
|
||||||
Fields: []*resourcepb.Requirement{{
|
Fields: []*resourcepb.Requirement{{
|
||||||
|
|||||||
@@ -626,6 +626,134 @@ func TestSearchHandlerSharedDashboards(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, len(mockResponse3.Results.Rows), len(p.Hits))
|
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) {
|
func TestConvertHttpSearchRequestToResourceSearchRequest(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user