mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 01:53:33 -06:00
* Add actions and scopes * add resource service for dashboard and folder * Add dashboard guardian with fgac permission evaluation * Add CanDelete function to guardian interface * Add CanDelete property to folder and dashboard dto and set values * change to correct function name * Add accesscontrol to folder endpoints * add access control to dashboard endpoints * check access for nav links * Add fixed roles for dashboard and folders * use correct package * add hack to override guardian Constructor if accesscontrol is enabled * Add services * Add function to handle api backward compatability * Add permissionServices to HttpServer * Set permission when new dashboard is created * Add default permission when creating new dashboard * Set default permission when creating folder and dashboard * Add access control filter for dashboard search * Add to accept list * Add accesscontrol to dashboardimport * Disable access control in tests * Add check to see if user is allow to create a dashboard * Use SetPermissions * Use function to set several permissions at once * remove permissions for folder and dashboard on delete * update required permission * set permission for provisioning * Add CanCreate to dashboard guardian and set correct permisisons for provisioning * Dont set admin on folder / dashboard creation * Add dashboard and folder permission migrations * Add tests for CanCreate * Add roles and update descriptions * Solve uid to id for dashboard and folder permissions * Add folder and dashboard actions to permission filter * Handle viewer_can_edit flag * set folder and dashboard permissions services * Add dashboard permissions when importing a new dashboard * Set access control permissions on provisioning * Pass feature flags and only set permissions if access control is enabled * only add default permissions for folders and dashboards without folders * Batch create permissions in migrations * Remove `dashboards:edit` action * Remove unused function from interface * Update pkg/services/guardian/accesscontrol_guardian_test.go Co-authored-by: Gabriel MABILLE <gamab@users.noreply.github.com> Co-authored-by: Ieva <ieva.vasiljeva@grafana.com>
239 lines
8.5 KiB
Go
239 lines
8.5 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/grafana/grafana/pkg/api/dtos"
|
|
"github.com/grafana/grafana/pkg/api/response"
|
|
"github.com/grafana/grafana/pkg/api/routing"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestFoldersAPIEndpoint(t *testing.T) {
|
|
folderService := &dashboards.FakeFolderService{}
|
|
defer folderService.AssertExpectations(t)
|
|
|
|
t.Run("Given a correct request for creating a folder", func(t *testing.T) {
|
|
cmd := models.CreateFolderCommand{
|
|
Uid: "uid",
|
|
Title: "Folder",
|
|
}
|
|
|
|
folderResult := &models.Folder{Id: 1, Uid: "uid", Title: "Folder"}
|
|
folderService.On("CreateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(folderResult, nil).Once()
|
|
|
|
createFolderScenario(t, "When calling POST on", "/api/folders", "/api/folders", folderService, cmd,
|
|
func(sc *scenarioContext) {
|
|
callCreateFolder(sc)
|
|
|
|
folder := dtos.Folder{}
|
|
err := json.NewDecoder(sc.resp.Body).Decode(&folder)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), folder.Id)
|
|
assert.Equal(t, "uid", folder.Uid)
|
|
assert.Equal(t, "Folder", folder.Title)
|
|
})
|
|
})
|
|
|
|
t.Run("Given incorrect requests for creating a folder", func(t *testing.T) {
|
|
testCases := []struct {
|
|
Error error
|
|
ExpectedStatusCode int
|
|
}{
|
|
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409},
|
|
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
|
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409},
|
|
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
|
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
|
|
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
|
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
|
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
|
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
|
|
}
|
|
|
|
cmd := models.CreateFolderCommand{
|
|
Uid: "uid",
|
|
Title: "Folder",
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
folderService.On("CreateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, tc.Error).Once()
|
|
|
|
createFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling POST on", tc.Error.Error()),
|
|
"/api/folders", "/api/folders", folderService, cmd, func(sc *scenarioContext) {
|
|
callCreateFolder(sc)
|
|
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for error %s", tc.Error)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Given a correct request for updating a folder", func(t *testing.T) {
|
|
cmd := models.UpdateFolderCommand{
|
|
Title: "Folder upd",
|
|
}
|
|
|
|
folderService.On("UpdateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
|
|
cmd := args.Get(4).(*models.UpdateFolderCommand)
|
|
cmd.Result = &models.Folder{Id: 1, Uid: "uid", Title: "Folder upd"}
|
|
}).Return(nil).Once()
|
|
|
|
updateFolderScenario(t, "When calling PUT on", "/api/folders/uid", "/api/folders/:uid", folderService, cmd,
|
|
func(sc *scenarioContext) {
|
|
callUpdateFolder(sc)
|
|
|
|
folder := dtos.Folder{}
|
|
err := json.NewDecoder(sc.resp.Body).Decode(&folder)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, int64(1), folder.Id)
|
|
assert.Equal(t, "uid", folder.Uid)
|
|
assert.Equal(t, "Folder upd", folder.Title)
|
|
})
|
|
})
|
|
|
|
t.Run("Given incorrect requests for updating a folder", func(t *testing.T) {
|
|
testCases := []struct {
|
|
Error error
|
|
ExpectedStatusCode int
|
|
}{
|
|
{Error: models.ErrFolderWithSameUIDExists, ExpectedStatusCode: 409},
|
|
{Error: models.ErrFolderTitleEmpty, ExpectedStatusCode: 400},
|
|
{Error: models.ErrFolderSameNameExists, ExpectedStatusCode: 409},
|
|
{Error: models.ErrDashboardInvalidUid, ExpectedStatusCode: 400},
|
|
{Error: models.ErrDashboardUidTooLong, ExpectedStatusCode: 400},
|
|
{Error: models.ErrFolderAccessDenied, ExpectedStatusCode: 403},
|
|
{Error: models.ErrFolderNotFound, ExpectedStatusCode: 404},
|
|
{Error: models.ErrFolderVersionMismatch, ExpectedStatusCode: 412},
|
|
{Error: models.ErrFolderFailedGenerateUniqueUid, ExpectedStatusCode: 500},
|
|
}
|
|
|
|
cmd := models.UpdateFolderCommand{
|
|
Title: "Folder upd",
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
folderService.On("UpdateFolder", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(tc.Error).Once()
|
|
updateFolderScenario(t, fmt.Sprintf("Expect '%s' error when calling PUT on", tc.Error.Error()),
|
|
"/api/folders/uid", "/api/folders/:uid", folderService, cmd, func(sc *scenarioContext) {
|
|
callUpdateFolder(sc)
|
|
assert.Equalf(t, tc.ExpectedStatusCode, sc.resp.Code, "Wrong status code for %s", tc.Error)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func callCreateFolder(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
|
|
}
|
|
|
|
func createFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
|
|
cmd models.CreateFolderCommand, fn scenarioFunc) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
t.Cleanup(bus.ClearBusHandlers)
|
|
|
|
hs := HTTPServer{
|
|
Bus: bus.GetBus(),
|
|
Cfg: setting.NewCfg(),
|
|
folderService: folderService,
|
|
Features: featuremgmt.WithFeatures(),
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
c.Req.Body = mockRequestBody(cmd)
|
|
c.Req.Header.Add("Content-Type", "application/json")
|
|
sc.context = c
|
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
|
|
|
return hs.CreateFolder(c)
|
|
})
|
|
|
|
sc.m.Post(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
func callUpdateFolder(sc *scenarioContext) {
|
|
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
|
|
}
|
|
|
|
func updateFolderScenario(t *testing.T, desc string, url string, routePattern string, folderService dashboards.FolderService,
|
|
cmd models.UpdateFolderCommand, fn scenarioFunc) {
|
|
t.Run(fmt.Sprintf("%s %s", desc, url), func(t *testing.T) {
|
|
defer bus.ClearBusHandlers()
|
|
|
|
hs := HTTPServer{
|
|
Cfg: setting.NewCfg(),
|
|
folderService: folderService,
|
|
}
|
|
|
|
sc := setupScenarioContext(t, url)
|
|
sc.defaultHandler = routing.Wrap(func(c *models.ReqContext) response.Response {
|
|
c.Req.Body = mockRequestBody(cmd)
|
|
c.Req.Header.Add("Content-Type", "application/json")
|
|
sc.context = c
|
|
sc.context.SignedInUser = &models.SignedInUser{OrgId: testOrgID, UserId: testUserID}
|
|
|
|
return hs.UpdateFolder(c)
|
|
})
|
|
|
|
sc.m.Put(routePattern, sc.defaultHandler)
|
|
|
|
fn(sc)
|
|
})
|
|
}
|
|
|
|
type fakeFolderService struct {
|
|
dashboards.FolderService
|
|
|
|
GetFoldersResult []*models.Folder
|
|
GetFoldersError error
|
|
GetFolderByUIDResult *models.Folder
|
|
GetFolderByUIDError error
|
|
GetFolderByIDResult *models.Folder
|
|
GetFolderByIDError error
|
|
CreateFolderResult *models.Folder
|
|
CreateFolderError error
|
|
UpdateFolderResult *models.Folder
|
|
UpdateFolderError error
|
|
DeleteFolderResult *models.Folder
|
|
DeleteFolderError error
|
|
DeletedFolderUids []string
|
|
}
|
|
|
|
func (s *fakeFolderService) GetFolders(ctx context.Context, user *models.SignedInUser, orgID int64, limit int64, page int64) ([]*models.Folder, error) {
|
|
return s.GetFoldersResult, s.GetFoldersError
|
|
}
|
|
|
|
func (s *fakeFolderService) GetFolderByID(ctx context.Context, user *models.SignedInUser, id int64, orgID int64) (*models.Folder, error) {
|
|
return s.GetFolderByIDResult, s.GetFolderByIDError
|
|
}
|
|
|
|
func (s *fakeFolderService) GetFolderByUID(ctx context.Context, user *models.SignedInUser, orgID int64, uid string) (*models.Folder, error) {
|
|
return s.GetFolderByUIDResult, s.GetFolderByUIDError
|
|
}
|
|
|
|
func (s *fakeFolderService) CreateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, title, uid string) (*models.Folder, error) {
|
|
return s.CreateFolderResult, s.CreateFolderError
|
|
}
|
|
|
|
func (s *fakeFolderService) UpdateFolder(ctx context.Context, user *models.SignedInUser, orgID int64, existingUid string, cmd *models.UpdateFolderCommand) error {
|
|
cmd.Result = s.UpdateFolderResult
|
|
return s.UpdateFolderError
|
|
}
|
|
|
|
func (s *fakeFolderService) DeleteFolder(ctx context.Context, user *models.SignedInUser, orgID int64, uid string, forceDeleteRules bool) (*models.Folder, error) {
|
|
s.DeletedFolderUids = append(s.DeletedFolderUids, uid)
|
|
return s.DeleteFolderResult, s.DeleteFolderError
|
|
}
|