diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index ba7c1b44433..3e7ad57f368 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -57,6 +57,7 @@ func GetDashboard(c *middleware.Context) Response { canEdit, _ := guardian.CanEdit() canSave, _ := guardian.CanSave() + canAdmin, _ := guardian.CanAdmin() isStarred, err := isDashboardStarredByUser(c, dash.Id) if err != nil { @@ -79,6 +80,7 @@ func GetDashboard(c *middleware.Context) Response { CanStar: c.IsSignedIn, CanSave: canSave, CanEdit: canEdit, + CanAdmin: canAdmin, Created: dash.Created, Updated: dash.Updated, UpdatedBy: updater, diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 68ca823818c..446bffcad98 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -65,6 +65,7 @@ func TestDashboardApiEndpoint(t *testing.T) { Convey("Should not be able to edit or save dashboard", func() { So(dash.Meta.CanEdit, ShouldBeFalse) So(dash.Meta.CanSave, ShouldBeFalse) + So(dash.Meta.CanAdmin, ShouldBeFalse) }) }) @@ -97,6 +98,7 @@ func TestDashboardApiEndpoint(t *testing.T) { Convey("Should be able to view but not save the dashboard", func() { So(dash.Meta.CanEdit, ShouldBeFalse) So(dash.Meta.CanSave, ShouldBeFalse) + So(dash.Meta.CanAdmin, ShouldBeFalse) }) }) @@ -130,6 +132,7 @@ func TestDashboardApiEndpoint(t *testing.T) { Convey("Should be able to edit or save dashboard", func() { So(dash.Meta.CanEdit, ShouldBeTrue) So(dash.Meta.CanSave, ShouldBeTrue) + So(dash.Meta.CanAdmin, ShouldBeFalse) }) }) @@ -299,6 +302,50 @@ func TestDashboardApiEndpoint(t *testing.T) { Convey("Should be able to get dashboard with edit rights", func() { So(dash.Meta.CanEdit, ShouldBeTrue) So(dash.Meta.CanSave, ShouldBeTrue) + So(dash.Meta.CanAdmin, ShouldBeFalse) + }) + }) + + loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) { + CallDeleteDashboard(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + + loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) { + CallGetDashboardVersion(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + + loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions", "/api/dashboards/id/:dashboardId/versions", role, func(sc *scenarioContext) { + CallGetDashboardVersions(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + + postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) { + CallPostDashboard(sc) + So(sc.resp.Code, ShouldEqual, 200) + }) + }) + + Convey("When user is an Org Viewer but has an admin permission", func() { + role := m.ROLE_VIEWER + + mockResult := []*m.DashboardAclInfoDTO{ + {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN}, + } + + bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { + query.Result = mockResult + return nil + }) + + loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) { + dash := GetDashboardShouldReturn200(sc) + + Convey("Should be able to get dashboard with edit rights", func() { + So(dash.Meta.CanEdit, ShouldBeTrue) + So(dash.Meta.CanSave, ShouldBeTrue) + So(dash.Meta.CanAdmin, ShouldBeTrue) }) }) diff --git a/pkg/api/dtos/dashboard.go b/pkg/api/dtos/dashboard.go index 8e6407d770c..0be0537527b 100644 --- a/pkg/api/dtos/dashboard.go +++ b/pkg/api/dtos/dashboard.go @@ -13,6 +13,7 @@ type DashboardMeta struct { Type string `json:"type,omitempty"` CanSave bool `json:"canSave"` CanEdit bool `json:"canEdit"` + CanAdmin bool `json:"canAdmin"` CanStar bool `json:"canStar"` Slug string `json:"slug"` Expires time.Time `json:"expires"` diff --git a/public/app/core/nav_model_srv.ts b/public/app/core/nav_model_srv.ts index cdd0ef04350..67a221bfcb4 100644 --- a/public/app/core/nav_model_srv.ts +++ b/public/app/core/nav_model_srv.ts @@ -168,11 +168,13 @@ export class NavModelSrv { clickHandler: () => dashNavCtrl.openEditView('annotations') }); - menu.push({ - title: 'Permissions...', - icon: 'fa fa-fw fa-lock', - clickHandler: () => dashNavCtrl.openEditView('permissions') - }); + if (dashboard.meta.canAdmin) { + menu.push({ + title: 'Permissions...', + icon: 'fa fa-fw fa-lock', + clickHandler: () => dashNavCtrl.openEditView('permissions') + }); + } if (!dashboard.meta.isHome) { menu.push({ diff --git a/public/app/features/dashboard/acl/acl.ts b/public/app/features/dashboard/acl/acl.ts index e9bcf660193..8316f1f30e7 100644 --- a/public/app/features/dashboard/acl/acl.ts +++ b/public/app/features/dashboard/acl/acl.ts @@ -45,9 +45,6 @@ export class AclCtrl { sortItems() { this.items = _.orderBy(this.items, ['sortRank', 'sortName'], ['desc', 'asc']); - for (let i of this.items) { - console.log(i.sortRank); - } } prepareViewModel(item: DashboardAcl): DashboardAcl { @@ -69,9 +66,6 @@ export class AclCtrl { item.nameHtml = this.$sce.trustAsHtml(`Everyone with ${item.role} Role`); item.sortName = item.role; item.sortRank = 30; - if (item.role === 'Viewer') { - item.sortRank += 2; - } if (item.role === 'Viewer') { item.sortRank += 1; } @@ -100,7 +94,7 @@ export class AclCtrl { } return this.backendSrv.post(`/api/dashboards/id/${this.dashboard.id}/acl`, { items: updated }).then(() => { - this.dismiss(); + return this.dismiss(); }); } diff --git a/public/app/features/dashboard/acl/specs/acl_specs.ts b/public/app/features/dashboard/acl/specs/acl_specs.ts index 7b26476719a..24f3e3eb61c 100644 --- a/public/app/features/dashboard/acl/specs/acl_specs.ts +++ b/public/app/features/dashboard/acl/specs/acl_specs.ts @@ -2,12 +2,16 @@ import {describe, beforeEach, it, expect, sinon, angularMocks} from 'test/lib/co import {AclCtrl} from '../acl'; describe('AclCtrl', () => { - var ctx: any = {}; - var backendSrv = { + const ctx: any = {}; + const backendSrv = { get: sinon.stub().returns(Promise.resolve([])), post: sinon.stub().returns(Promise.resolve([])) }; + const dashboardSrv = { + getCurrent: sinon.stub().returns({id: 1}) + }; + beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.controllers')); @@ -18,64 +22,59 @@ describe('AclCtrl', () => { ctx.ctrl = $controller(AclCtrl, { $scope: ctx.scope, backendSrv: backendSrv, + dashboardSrv: dashboardSrv }, { - dashboard: {id: 1} + dismiss: () => { return; } }); })); - describe('when user permission is to be added', () => { - beforeEach(done => { + describe('when permissions are added', () => { + beforeEach(() => { backendSrv.get.reset(); backendSrv.post.reset(); - ctx.ctrl.type = 'User'; - ctx.ctrl.userId = 2; - ctx.ctrl.permission = 1; - ctx.ctrl.addPermission().then(() => { + const userItem = { + id: 2, + login: 'user2', + }; + + ctx.ctrl.userPicked(userItem); + + const userGroupItem = { + id: 2, + name: 'ug1', + }; + + ctx.ctrl.groupPicked(userGroupItem); + + ctx.ctrl.newType = 'Editor'; + ctx.ctrl.typeChanged(); + + ctx.ctrl.newType = 'Viewer'; + ctx.ctrl.typeChanged(); + }); + + it('should sort the result by role, user group and user', () => { + expect(ctx.ctrl.items[0].role).to.eql('Viewer'); + expect(ctx.ctrl.items[1].role).to.eql('Editor'); + expect(ctx.ctrl.items[2].userGroupId).to.eql(2); + expect(ctx.ctrl.items[3].userId).to.eql(2); + }); + + it('should save permissions to db', (done) => { + ctx.ctrl.update().then(() => { done(); }); - }); - it('should parse the result and save to db', () => { expect(backendSrv.post.getCall(0).args[0]).to.eql('/api/dashboards/id/1/acl'); - expect(backendSrv.post.getCall(0).args[1].userId).to.eql(2); - expect(backendSrv.post.getCall(0).args[1].permissions).to.eql(1); - }); - - it('should refresh the list after saving.', () => { - expect(backendSrv.get.getCall(0).args[0]).to.eql('/api/dashboards/id/1/acl'); - }); - - it('should reset userId', () => { - expect(ctx.ctrl.userId).to.eql(null); - }); - }); - - describe('when user group permission is to be added', () => { - beforeEach(done => { - backendSrv.get.reset(); - backendSrv.post.reset(); - ctx.ctrl.type = 'User Group'; - ctx.ctrl.userGroupId = 2; - ctx.ctrl.permission = 1; - - ctx.ctrl.addPermission().then(() => { - done(); - }); - }); - - it('should parse the result and save to db', () => { - expect(backendSrv.post.getCall(0).args[0]).to.eql('/api/dashboards/id/1/acl'); - expect(backendSrv.post.getCall(0).args[1].userGroupId).to.eql(2); - expect(backendSrv.post.getCall(0).args[1].permissions).to.eql(1); - }); - - it('should refresh the list after saving.', () => { - expect(backendSrv.get.getCall(0).args[0]).to.eql('/api/dashboards/id/1/acl'); - }); - - it('should reset userGroupId', () => { - expect(ctx.ctrl.userGroupId).to.eql(null); + expect(backendSrv.post.getCall(0).args[1].items[0].role).to.eql('Viewer'); + expect(backendSrv.post.getCall(0).args[1].items[0].permission).to.eql(1); + expect(backendSrv.post.getCall(0).args[1].items[1].role).to.eql('Editor'); + expect(backendSrv.post.getCall(0).args[1].items[1].permission).to.eql(1); + expect(backendSrv.post.getCall(0).args[1].items[2].userGroupId).to.eql(2); + expect(backendSrv.post.getCall(0).args[1].items[2].permission).to.eql(1); + expect(backendSrv.post.getCall(0).args[1].items[3].userId).to.eql(2); + expect(backendSrv.post.getCall(0).args[1].items[3].permission).to.eql(1); }); }); }); diff --git a/public/app/features/dashboard/partials/settings.html b/public/app/features/dashboard/partials/settings.html index 113bf6b83f0..f28a0d51242 100644 --- a/public/app/features/dashboard/partials/settings.html +++ b/public/app/features/dashboard/partials/settings.html @@ -4,7 +4,7 @@