diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 40a96816fde..e6228878625 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -15,6 +15,7 @@ import ( m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) @@ -165,6 +166,7 @@ func TestDashboardApiEndpoint(t *testing.T) { fakeDash.Id = 1 fakeDash.FolderId = 1 fakeDash.HasAcl = true + setting.ViewersCanEdit = false aclMockResp := []*m.DashboardAclInfoDTO{ { @@ -307,6 +309,35 @@ func TestDashboardApiEndpoint(t *testing.T) { }) }) + Convey("When user is an Org Viewer and viewers can edit", func() { + role := m.ROLE_VIEWER + setting.ViewersCanEdit = true + + mockResult := []*m.DashboardAclInfoDTO{ + {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW}, + } + + 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 but can save should be false", func() { + So(dash.Meta.CanEdit, ShouldBeTrue) + So(dash.Meta.CanSave, ShouldBeFalse) + 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, 403) + }) + }) + Convey("When user is an Org Viewer but has an admin permission", func() { role := m.ROLE_VIEWER diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 946ebae6b87..259d800f0a9 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -87,7 +87,7 @@ func initContextWithAnonymousUser(ctx *Context) bool { ctx.IsSignedIn = false ctx.AllowAnonymous = true - ctx.SignedInUser = &m.SignedInUser{} + ctx.SignedInUser = &m.SignedInUser{IsAnonymous: true} ctx.OrgRole = m.RoleType(setting.AnonymousOrgRole) ctx.OrgId = orgQuery.Result.Id ctx.OrgName = orgQuery.Result.Name diff --git a/pkg/models/user.go b/pkg/models/user.go index a8819711d67..d5b912e0a9c 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -162,6 +162,7 @@ type SignedInUser struct { ApiKeyId int64 OrgCount int IsGrafanaAdmin bool + IsAnonymous bool HelpFlags1 HelpFlags1 LastSeenAt time.Time } diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index a0c88d767f8..1b664c11385 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -4,6 +4,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" ) type DashboardGuardian struct { @@ -29,6 +30,10 @@ func (g *DashboardGuardian) CanSave() (bool, error) { } func (g *DashboardGuardian) CanEdit() (bool, error) { + if setting.ViewersCanEdit { + return g.HasPermission(m.PERMISSION_VIEW) + } + return g.HasPermission(m.PERMISSION_EDIT) } @@ -55,8 +60,10 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er for _, p := range acl { // user match - if p.UserId == g.user.UserId && p.Permission >= permission { - return true, nil + if !g.user.IsAnonymous { + if p.UserId == g.user.UserId && p.Permission >= permission { + return true, nil + } } // role match diff --git a/public/app/features/dashboard/settings/settings.ts b/public/app/features/dashboard/settings/settings.ts index 9acd98c934b..f8b85c81667 100644 --- a/public/app/features/dashboard/settings/settings.ts +++ b/public/app/features/dashboard/settings/settings.ts @@ -40,10 +40,10 @@ export class SettingsCtrl { this.sections.push({ title: 'Annotations', id: 'annotations', icon: 'gicon gicon-annotation' }); this.sections.push({ title: 'Variables', id: 'templating', icon: 'gicon gicon-variable' }); this.sections.push({ title: 'Links', id: 'links', icon: 'gicon gicon-link' }); + } - if (this.dashboard.id) { - this.sections.push({ title: 'Versions', id: 'versions', icon: 'fa fa-fw fa-history' }); - } + if (this.dashboard.id && this.dashboard.meta.canSave) { + this.sections.push({ title: 'Versions', id: 'versions', icon: 'fa fa-fw fa-history' }); } if (contextSrv.isEditor && !this.dashboard.editable) {