diff --git a/pkg/api/folder_acl.go b/pkg/api/folder_acl.go index 5d2c28fe769..58702dce08e 100644 --- a/pkg/api/folder_acl.go +++ b/pkg/api/folder_acl.go @@ -51,10 +51,15 @@ func UpdateFolderPermissions(c *middleware.Context, apiCmd dtos.UpdateDashboardA } guardian := guardian.New(folder.Id, c.OrgId, c.SignedInUser) - if canAdmin, err := guardian.CanAdmin(); err != nil || !canAdmin { + canAdmin, err := guardian.CanAdmin() + if err != nil { return toFolderError(err) } + if !canAdmin { + return toFolderError(m.ErrFolderAccessDenied) + } + cmd := m.UpdateDashboardAclCommand{} cmd.DashboardId = folder.Id diff --git a/pkg/api/folder_acl_test.go b/pkg/api/folder_acl_test.go new file mode 100644 index 00000000000..5ec8be9a2cb --- /dev/null +++ b/pkg/api/folder_acl_test.go @@ -0,0 +1,125 @@ +package api + +import ( + "testing" + + "github.com/grafana/grafana/pkg/api/dtos" + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/middleware" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/guardian" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestFolderAclApiEndpoint(t *testing.T) { + Convey("Folder permissions test", t, func() { + Convey("Given user has no admin permissions", func() { + origNewGuardian := guardian.New + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: false}) + + mock := &fakeFolderService{ + GetFolderByUidResult: &m.Folder{ + Id: 1, + Uid: "uid", + Title: "Folder", + }, + } + + origNewFolderService := dashboards.NewFolderService + mockFolderService(mock) + + loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", m.ROLE_EDITOR, func(sc *scenarioContext) { + callGetFolderPermissions(sc) + So(sc.resp.Code, ShouldEqual, 403) + }) + + cmd := dtos.UpdateDashboardAclCommand{ + Items: []dtos.DashboardAclUpdateItem{ + {UserId: 1000, Permission: m.PERMISSION_ADMIN}, + }, + } + + updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) { + callUpdateFolderPermissions(sc) + So(sc.resp.Code, ShouldEqual, 403) + }) + + Reset(func() { + guardian.New = origNewGuardian + dashboards.NewFolderService = origNewFolderService + }) + }) + + Convey("Given user has admin permissions and permissions to update", func() { + origNewGuardian := guardian.New + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanAdminValue: true, CheckPermissionBeforeUpdateValue: true}) + + mock := &fakeFolderService{ + GetFolderByUidResult: &m.Folder{ + Id: 1, + Uid: "uid", + Title: "Folder", + }, + } + + origNewFolderService := dashboards.NewFolderService + mockFolderService(mock) + + loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", m.ROLE_ADMIN, func(sc *scenarioContext) { + callGetFolderPermissions(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + + cmd := dtos.UpdateDashboardAclCommand{ + Items: []dtos.DashboardAclUpdateItem{ + {UserId: 1000, Permission: m.PERMISSION_ADMIN}, + }, + } + + updateFolderPermissionScenario("When calling POST on", "/api/folders/uid/permissions", "/api/folders/:uid/permissions", cmd, func(sc *scenarioContext) { + callUpdateFolderPermissions(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + + Reset(func() { + guardian.New = origNewGuardian + dashboards.NewFolderService = origNewFolderService + }) + }) + }) +} + +func callGetFolderPermissions(sc *scenarioContext) { + sc.handlerFunc = GetFolderPermissionList + sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() +} + +func callUpdateFolderPermissions(sc *scenarioContext) { + bus.AddHandler("test", func(cmd *m.UpdateDashboardAclCommand) error { + return nil + }) + + sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec() +} + +func updateFolderPermissionScenario(desc string, url string, routePattern string, cmd dtos.UpdateDashboardAclCommand, fn scenarioFunc) { + Convey(desc+" "+url, func() { + defer bus.ClearBusHandlers() + + sc := setupScenarioContext(url) + + sc.defaultHandler = wrap(func(c *middleware.Context) Response { + sc.context = c + sc.context.OrgId = TestOrgID + sc.context.UserId = TestUserID + + return UpdateFolderPermissions(c, cmd) + }) + + sc.m.Post(routePattern, sc.defaultHandler) + + fn(sc) + }) +} diff --git a/pkg/api/folder_test.go b/pkg/api/folder_test.go index bdf8e89a198..618d46c4ab5 100644 --- a/pkg/api/folder_test.go +++ b/pkg/api/folder_test.go @@ -1,5 +1,10 @@ package api +import ( + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/services/dashboards" +) + // import ( // "encoding/json" // "path/filepath" @@ -482,3 +487,50 @@ package api // fn(sc) // }) // } + +type fakeFolderService struct { + GetFoldersResult []*models.Folder + GetFoldersError error + GetFolderByUidResult *models.Folder + GetFolderByUidError error + GetFolderByIdResult *models.Folder + GetFolderByIdError error + CreateFolderError error + UpdateFolderError error + DeleteFolderResult *models.Folder + DeleteFolderError error + CreatedFolders []*models.CreateFolderCommand + UpdatedFolders []*models.UpdateFolderCommand + DeletedFolderUids []string +} + +func (s *fakeFolderService) GetFolders(limit int) ([]*models.Folder, error) { + return s.GetFoldersResult, s.GetFoldersError +} + +func (s *fakeFolderService) GetFolderById(id int64) (*models.Folder, error) { + return s.GetFolderByIdResult, s.GetFolderByIdError +} + +func (s *fakeFolderService) GetFolderByUid(uid string) (*models.Folder, error) { + return s.GetFolderByUidResult, s.GetFolderByUidError +} + +func (s *fakeFolderService) CreateFolder(cmd *models.CreateFolderCommand) error { + return s.CreateFolderError +} + +func (s *fakeFolderService) UpdateFolder(existingUid string, cmd *models.UpdateFolderCommand) error { + return s.UpdateFolderError +} + +func (s *fakeFolderService) DeleteFolder(uid string) (*models.Folder, error) { + s.DeletedFolderUids = append(s.DeletedFolderUids, uid) + return s.DeleteFolderResult, s.DeleteFolderError +} + +func mockFolderService(mock *fakeFolderService) { + dashboards.NewFolderService = func(orgId int64, user *models.SignedInUser) dashboards.FolderService { + return mock + } +}