Przeglądaj źródła

Merge branch 'master' into readonly_dashboards

Marcus Efraimsson 7 lat temu
rodzic
commit
d7688241c6

+ 1 - 1
.bra.toml

@@ -1,6 +1,6 @@
 [run]
 init_cmds = [
-  ["go", "run", "build.go", "build"],
+  ["go", "run", "build.go", "build-server"],
 	["./bin/grafana-server", "cfg:app_mode=development"]
 ]
 watch_all = true

+ 1 - 0
CHANGELOG.md

@@ -17,6 +17,7 @@
 * **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165)
 * **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168)
 * **Docker**: Improved docker image (breaking changes regarding file ownership) [grafana-docker #141](https://github.com/grafana/grafana-docker/issues/141), thx [@Spindel](https://github.com/Spindel), [@ChristianKniep](https://github.com/ChristianKniep), [@brancz](https://github.com/brancz) and [@jangaraj](https://github.com/jangaraj)
+* **Folders** A folder admin cannot add user/team permissions for folder/its dashboards [#11173](https://github.com/grafana/grafana/issues/11173)
 
 ### Minor
 

+ 11 - 3
pkg/api/api.go

@@ -149,8 +149,6 @@ func (hs *HTTPServer) registerRoutes() {
 
 		// team (admin permission required)
 		apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
-			teamsRoute.Get("/:teamId", wrap(GetTeamByID))
-			teamsRoute.Get("/search", wrap(SearchTeams))
 			teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
 			teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
 			teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
@@ -159,6 +157,12 @@ func (hs *HTTPServer) registerRoutes() {
 			teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
 		}, reqOrgAdmin)
 
+		// team without requirement of user to be org admin
+		apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
+			teamsRoute.Get("/:teamId", wrap(GetTeamByID))
+			teamsRoute.Get("/search", wrap(SearchTeams))
+		})
+
 		// org information available to all users.
 		apiRoute.Group("/org", func(orgRoute RouteRegister) {
 			orgRoute.Get("/", wrap(GetOrgCurrent))
@@ -170,7 +174,6 @@ func (hs *HTTPServer) registerRoutes() {
 			orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
 			orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
 			orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
-			orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
 			orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
 			orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
 
@@ -184,6 +187,11 @@ func (hs *HTTPServer) registerRoutes() {
 			orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
 		}, reqOrgAdmin)
 
+		// current org without requirement of user to be org admin
+		apiRoute.Group("/org", func(orgRoute RouteRegister) {
+			orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
+		})
+
 		// create new org
 		apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
 

+ 5 - 0
pkg/models/dashboards.go

@@ -225,6 +225,10 @@ func GetFolderUrl(folderUid string, slug string) string {
 	return fmt.Sprintf("%s/dashboards/f/%s/%s", setting.AppSubUrl, folderUid, slug)
 }
 
+type ValidateDashboardBeforeSaveResult struct {
+	IsParentFolderChanged bool
+}
+
 //
 // COMMANDS
 //
@@ -269,6 +273,7 @@ type ValidateDashboardBeforeSaveCommand struct {
 	OrgId     int64
 	Dashboard *Dashboard
 	Overwrite bool
+	Result    *ValidateDashboardBeforeSaveResult
 }
 
 //

+ 10 - 0
pkg/services/dashboards/dashboard_service.go

@@ -103,6 +103,16 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
 		return nil, err
 	}
 
+	if validateBeforeSaveCmd.Result.IsParentFolderChanged {
+		folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User)
+		if canSave, err := folderGuardian.CanSave(); err != nil || !canSave {
+			if err != nil {
+				return nil, err
+			}
+			return nil, models.ErrDashboardUpdateAccessDenied
+		}
+	}
+
 	if validateProvisionedDashboard {
 		isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dash.Id}
 		err := bus.Dispatch(isDashboardProvisioned)

+ 4 - 0
pkg/services/dashboards/dashboard_service_test.go

@@ -53,6 +53,7 @@ func TestDashboardService(t *testing.T) {
 				})
 
 				bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+					cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 					return nil
 				})
 
@@ -97,6 +98,7 @@ func TestDashboardService(t *testing.T) {
 				})
 
 				bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+					cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 					return nil
 				})
 
@@ -140,6 +142,7 @@ func TestDashboardService(t *testing.T) {
 				})
 
 				bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+					cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 					return nil
 				})
 
@@ -176,6 +179,7 @@ func TestDashboardService(t *testing.T) {
 				})
 
 				bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+					cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 					return nil
 				})
 

+ 2 - 0
pkg/services/dashboards/folder_service_test.go

@@ -32,6 +32,7 @@ func TestFolderService(t *testing.T) {
 			})
 
 			bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+				cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 				return models.ErrDashboardUpdateAccessDenied
 			})
 
@@ -92,6 +93,7 @@ func TestFolderService(t *testing.T) {
 			})
 
 			bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
+				cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
 				return nil
 			})
 

+ 1 - 1
pkg/services/guardian/guardian.go

@@ -173,7 +173,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss
 		return true, nil
 	}
 
-	return g.checkAcl(permission, acl)
+	return g.checkAcl(permission, existingPermissions)
 }
 
 // GetAcl returns dashboard acl

+ 617 - 664
pkg/services/guardian/guardian_test.go

@@ -2,710 +2,663 @@ package guardian
 
 import (
 	"fmt"
+	"runtime"
 	"testing"
 
-	"github.com/grafana/grafana/pkg/bus"
-
 	m "github.com/grafana/grafana/pkg/models"
 	. "github.com/smartystreets/goconvey/convey"
 )
 
-func TestGuardian(t *testing.T) {
-	Convey("Guardian permission tests", t, func() {
-		orgRoleScenario("Given user has admin org role", m.ROLE_ADMIN, func(sc *scenarioContext) {
-			canAdmin, _ := sc.g.CanAdmin()
-			canEdit, _ := sc.g.CanEdit()
-			canSave, _ := sc.g.CanSave()
-			canView, _ := sc.g.CanView()
-			So(canAdmin, ShouldBeTrue)
-			So(canEdit, ShouldBeTrue)
-			So(canSave, ShouldBeTrue)
-			So(canView, ShouldBeTrue)
-
-			Convey("When trying to update permissions", func() {
-				Convey("With duplicate user permissions should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
-						{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianPermissionExists)
-				})
-
-				Convey("With duplicate team permissions should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_VIEW},
-						{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianPermissionExists)
-				})
-
-				Convey("With duplicate everyone with editor role permission should return error", func() {
-					r := m.ROLE_EDITOR
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianPermissionExists)
-				})
-
-				Convey("With duplicate everyone with viewer role permission should return error", func() {
-					r := m.ROLE_VIEWER
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianPermissionExists)
-				})
-
-				Convey("With everyone with admin role permission should return error", func() {
-					r := m.ROLE_ADMIN
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianPermissionExists)
-				})
-			})
-
-			Convey("Given default permissions", func() {
-				editor := m.ROLE_EDITOR
-				viewer := m.ROLE_VIEWER
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: -1, Role: &editor, Permission: m.PERMISSION_EDIT},
-					{OrgId: 1, DashboardId: -1, Role: &viewer, Permission: m.PERMISSION_VIEW},
-				}
-
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions without everyone with role editor can edit should be allowed", func() {
-					r := m.ROLE_VIEWER
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_VIEW},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions without everyone with role viewer can view should be allowed", func() {
-					r := m.ROLE_EDITOR
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 1, Role: &r, Permission: m.PERMISSION_EDIT},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-			})
-
-			Convey("Given parent folder has user admin permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_ADMIN},
-				}
-
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with edit user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with view user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has user edit permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
-				}
+var (
+	orgID              = int64(1)
+	defaultDashboardID = int64(-1)
+	dashboardID        = int64(1)
+	parentFolderID     = int64(2)
+	childDashboardID   = int64(3)
+	userID             = int64(1)
+	otherUserID        = int64(2)
+	teamID             = int64(1)
+	otherTeamID        = int64(2)
+	adminRole          = m.ROLE_ADMIN
+	editorRole         = m.ROLE_EDITOR
+	viewerRole         = m.ROLE_VIEWER
+)
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin user permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with edit user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with view user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has user view permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
-				}
+func TestGuardianAdmin(t *testing.T) {
+	Convey("Guardian admin org role tests", t, func() {
+		orgRoleScenario("Given user has admin org role", t, m.ROLE_ADMIN, func(sc *scenarioContext) {
+			// dashboard has default permissions
+			sc.defaultPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+
+			// dashboard has user with permission
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// dashboard has team with permission
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// dashboard has editor role with permission
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// dashboard has viewer role with permission
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// parent folder has user with permission
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// parent folder has team with permission
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// parent folder has editor role with permission
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_VIEW, FULL_ACCESS)
+
+			// parent folder has viweer role with permission
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_EDIT, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_VIEW, FULL_ACCESS)
+		})
+	})
+}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin user permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with edit user permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with view user permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, UserId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has team admin permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_ADMIN},
-				}
+func TestGuardianEditor(t *testing.T) {
+	Convey("Guardian editor org role tests", t, func() {
+		orgRoleScenario("Given user has editor org role", t, m.ROLE_EDITOR, func(sc *scenarioContext) {
+			// dashboard has user with permission
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_VIEW, CAN_VIEW)
+
+			// dashboard has team with permission
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_VIEW, CAN_VIEW)
+
+			// dashboard has editor role with permission
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// dashboard has viewer role with permission
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_ADMIN, NO_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_EDIT, NO_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_VIEW, NO_ACCESS)
+
+			// parent folder has user with permission
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has team with permission
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has editor role with permission
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has viweer role with permission
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_ADMIN, NO_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_EDIT, NO_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_VIEW, NO_ACCESS)
+		})
+	})
+}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with edit team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with view team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has team edit permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_EDIT},
-				}
+func TestGuardianViewer(t *testing.T) {
+	Convey("Guardian viewer org role tests", t, func() {
+		orgRoleScenario("Given user has viewer org role", t, m.ROLE_VIEWER, func(sc *scenarioContext) {
+			// dashboard has user with permission
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(USER, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// dashboard has team with permission
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(TEAM, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// dashboard has editor role with permission
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_ADMIN, NO_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_EDIT, NO_ACCESS)
+			sc.dashboardPermissionScenario(EDITOR, m.PERMISSION_VIEW, NO_ACCESS)
+
+			// dashboard has viewer role with permission
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.dashboardPermissionScenario(VIEWER, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has user with permission
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(USER, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has team with permission
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(TEAM, m.PERMISSION_VIEW, VIEWER_ACCESS)
+
+			// parent folder has editor role with permission
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_ADMIN, NO_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_EDIT, NO_ACCESS)
+			sc.parentFolderPermissionScenario(EDITOR, m.PERMISSION_VIEW, NO_ACCESS)
+
+			// parent folder has viweer role with permission
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_ADMIN, FULL_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_EDIT, EDITOR_ACCESS)
+			sc.parentFolderPermissionScenario(VIEWER, m.PERMISSION_VIEW, VIEWER_ACCESS)
+		})
+	})
+}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin team permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with edit team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with view team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has team view permission", func() {
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, TeamId: 1, Permission: m.PERMISSION_VIEW},
-				}
+func (sc *scenarioContext) defaultPermissionScenario(pt permissionType, permission m.PermissionType, flag permissionFlags) {
+	_, callerFile, callerLine, _ := runtime.Caller(1)
+	sc.callerFile = callerFile
+	sc.callerLine = callerLine
+	existingPermissions := []*m.DashboardAclInfoDTO{
+		toDto(newEditorRolePermission(defaultDashboardID, m.PERMISSION_EDIT)),
+		toDto(newViewerRolePermission(defaultDashboardID, m.PERMISSION_VIEW)),
+	}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with admin team permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with edit team permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_EDIT},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with view team permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, TeamId: 1, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has editor role with edit permission", func() {
-				r := m.ROLE_EDITOR
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, Role: &r, Permission: m.PERMISSION_EDIT},
-				}
+	permissionScenario("and existing permissions is the default permissions (everyone with editor role can edit, everyone with viewer role can view)", dashboardID, sc, existingPermissions, func(sc *scenarioContext) {
+		sc.expectedFlags = flag
+		sc.verifyExpectedPermissionsFlags()
+		sc.verifyDuplicatePermissionsShouldNotBeAllowed()
+		sc.verifyUpdateDashboardPermissionsShouldBeAllowed(pt)
+		sc.verifyUpdateDashboardPermissionsShouldNotBeAllowed(pt)
+	})
+}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with editor role can admin permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with editor role can edit permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_EDIT},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with editor role can view permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-
-			Convey("Given parent folder has editor role with view permission", func() {
-				r := m.ROLE_EDITOR
-				existingPermissions := []*m.DashboardAclInfoDTO{
-					{OrgId: 1, DashboardId: 2, Role: &r, Permission: m.PERMISSION_VIEW},
-				}
+func (sc *scenarioContext) dashboardPermissionScenario(pt permissionType, permission m.PermissionType, flag permissionFlags) {
+	_, callerFile, callerLine, _ := runtime.Caller(1)
+	sc.callerFile = callerFile
+	sc.callerLine = callerLine
+	var existingPermissions []*m.DashboardAclInfoDTO
+
+	switch pt {
+	case USER:
+		existingPermissions = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: dashboardID, UserId: userID, Permission: permission}}
+	case TEAM:
+		existingPermissions = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: dashboardID, TeamId: teamID, Permission: permission}}
+	case EDITOR:
+		existingPermissions = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: dashboardID, Role: &editorRole, Permission: permission}}
+	case VIEWER:
+		existingPermissions = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: dashboardID, Role: &viewerRole, Permission: permission}}
+	}
 
-				bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-					query.Result = existingPermissions
-					return nil
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with viewer role can admin permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_ADMIN},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with viewer role can edit permission should be allowed", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_EDIT},
-					}
-					ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(ok, ShouldBeTrue)
-				})
-
-				Convey("When trying to update dashboard permissions with everyone with viewer role can view permission should return error", func() {
-					p := []*m.DashboardAcl{
-						{OrgId: 1, DashboardId: 3, Role: &r, Permission: m.PERMISSION_VIEW},
-					}
-					_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-					So(err, ShouldEqual, ErrGuardianOverride)
-				})
-			})
-		})
+	permissionScenario(fmt.Sprintf("and %s has permission to %s dashboard", pt.String(), permission.String()), dashboardID, sc, existingPermissions, func(sc *scenarioContext) {
+		sc.expectedFlags = flag
+		sc.verifyExpectedPermissionsFlags()
+		sc.verifyDuplicatePermissionsShouldNotBeAllowed()
+		sc.verifyUpdateDashboardPermissionsShouldBeAllowed(pt)
+		sc.verifyUpdateDashboardPermissionsShouldNotBeAllowed(pt)
+	})
+}
 
-		orgRoleScenario("Given user has editor org role", m.ROLE_EDITOR, func(sc *scenarioContext) {
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeTrue)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeTrue)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeTrue)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeTrue)
-			})
-
-			teamWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeTrue)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			teamWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			teamWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeTrue)
-			})
-
-			Convey("When trying to update permissions should return false", func() {
-				p := []*m.DashboardAcl{
-					{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
-					{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
-				}
-				ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-				So(ok, ShouldBeFalse)
-			})
-		})
+func (sc *scenarioContext) parentFolderPermissionScenario(pt permissionType, permission m.PermissionType, flag permissionFlags) {
+	_, callerFile, callerLine, _ := runtime.Caller(1)
+	sc.callerFile = callerFile
+	sc.callerLine = callerLine
+	var folderPermissionList []*m.DashboardAclInfoDTO
+
+	switch pt {
+	case USER:
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, UserId: userID, Permission: permission}}
+	case TEAM:
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, TeamId: teamID, Permission: permission}}
+	case EDITOR:
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &editorRole, Permission: permission}}
+	case VIEWER:
+		folderPermissionList = []*m.DashboardAclInfoDTO{{OrgId: orgID, DashboardId: parentFolderID, Role: &viewerRole, Permission: permission}}
+	}
 
-		orgRoleScenario("Given user has viewer org role", m.ROLE_VIEWER, func(sc *scenarioContext) {
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_EDITOR, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeFalse)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeTrue)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			everyoneWithRoleScenario(m.ROLE_VIEWER, m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeTrue)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_ADMIN, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeTrue)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_EDIT, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeTrue)
-				So(canSave, ShouldBeTrue)
-				So(canView, ShouldBeTrue)
-			})
-
-			userWithPermissionScenario(m.PERMISSION_VIEW, sc, func(sc *scenarioContext) {
-				canAdmin, _ := sc.g.CanAdmin()
-				canEdit, _ := sc.g.CanEdit()
-				canSave, _ := sc.g.CanSave()
-				canView, _ := sc.g.CanView()
-				So(canAdmin, ShouldBeFalse)
-				So(canEdit, ShouldBeFalse)
-				So(canSave, ShouldBeFalse)
-				So(canView, ShouldBeTrue)
-			})
-
-			Convey("When trying to update permissions should return false", func() {
-				p := []*m.DashboardAcl{
-					{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW},
-					{OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN},
-				}
-				ok, _ := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
-				So(ok, ShouldBeFalse)
-			})
-		})
+	permissionScenario(fmt.Sprintf("and parent folder has %s with permission to %s", pt.String(), permission.String()), childDashboardID, sc, folderPermissionList, func(sc *scenarioContext) {
+		sc.expectedFlags = flag
+		sc.verifyExpectedPermissionsFlags()
+		sc.verifyDuplicatePermissionsShouldNotBeAllowed()
+		sc.verifyUpdateChildDashboardPermissionsShouldBeAllowed(pt, permission)
+		sc.verifyUpdateChildDashboardPermissionsShouldNotBeAllowed(pt, permission)
+		sc.verifyUpdateChildDashboardPermissionsWithOverrideShouldBeAllowed(pt, permission)
+		sc.verifyUpdateChildDashboardPermissionsWithOverrideShouldNotBeAllowed(pt, permission)
 	})
 }
 
-type scenarioContext struct {
-	g DashboardGuardian
-}
+func (sc *scenarioContext) verifyExpectedPermissionsFlags() {
+	canAdmin, _ := sc.g.CanAdmin()
+	canEdit, _ := sc.g.CanEdit()
+	canSave, _ := sc.g.CanSave()
+	canView, _ := sc.g.CanView()
 
-type scenarioFunc func(c *scenarioContext)
+	tc := fmt.Sprintf("should have permissions to %s", sc.expectedFlags.String())
+	Convey(tc, func() {
+		var actualFlag permissionFlags
 
-func orgRoleScenario(desc string, role m.RoleType, fn scenarioFunc) {
-	user := &m.SignedInUser{
-		UserId:  1,
-		OrgId:   1,
-		OrgRole: role,
-	}
-	guard := New(1, 1, user)
-	sc := &scenarioContext{
-		g: guard,
-	}
+		if canAdmin {
+			actualFlag |= CAN_ADMIN
+		}
+
+		if canEdit {
+			actualFlag |= CAN_EDIT
+		}
+
+		if canSave {
+			actualFlag |= CAN_SAVE
+		}
+
+		if canView {
+			actualFlag |= CAN_VIEW
+		}
+
+		if actualFlag.noAccess() {
+			actualFlag = NO_ACCESS
+		}
+
+		if sc.expectedFlags&actualFlag != sc.expectedFlags {
+			sc.reportFailure(tc, sc.expectedFlags.String(), actualFlag.String())
+		}
 
-	Convey(desc, func() {
-		fn(sc)
+		sc.reportSuccess()
 	})
 }
 
-func permissionScenario(desc string, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) {
-	bus.ClearBusHandlers()
+func (sc *scenarioContext) verifyDuplicatePermissionsShouldNotBeAllowed() {
+	if !sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	tc := "When updating dashboard permissions with duplicate permission for user should not be allowed"
+	Convey(tc, func() {
+		p := []*m.DashboardAcl{
+			newDefaultUserPermission(dashboardID, m.PERMISSION_VIEW),
+			newDefaultUserPermission(dashboardID, m.PERMISSION_ADMIN),
+		}
+		sc.updatePermissions = p
+		_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
 
-	bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
-		query.Result = permissions
-		return nil
+		if err != ErrGuardianPermissionExists {
+			sc.reportFailure(tc, ErrGuardianPermissionExists, err)
+		}
+		sc.reportSuccess()
 	})
 
-	teams := []*m.Team{}
+	tc = "When updating dashboard permissions with duplicate permission for team should not be allowed"
+	Convey(tc, func() {
+		p := []*m.DashboardAcl{
+			newDefaultTeamPermission(dashboardID, m.PERMISSION_VIEW),
+			newDefaultTeamPermission(dashboardID, m.PERMISSION_ADMIN),
+		}
+		sc.updatePermissions = p
+		_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
 
-	for _, p := range permissions {
-		if p.TeamId > 0 {
-			teams = append(teams, &m.Team{Id: p.TeamId})
+		if err != ErrGuardianPermissionExists {
+			sc.reportFailure(tc, ErrGuardianPermissionExists, err)
 		}
-	}
+		sc.reportSuccess()
+	})
+
+	tc = "When updating dashboard permissions with duplicate permission for editor role should not be allowed"
+	Convey(tc, func() {
+		p := []*m.DashboardAcl{
+			newEditorRolePermission(dashboardID, m.PERMISSION_VIEW),
+			newEditorRolePermission(dashboardID, m.PERMISSION_ADMIN),
+		}
+		sc.updatePermissions = p
+		_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
+
+		if err != ErrGuardianPermissionExists {
+			sc.reportFailure(tc, ErrGuardianPermissionExists, err)
+		}
+		sc.reportSuccess()
+	})
 
-	bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
-		query.Result = teams
-		return nil
+	tc = "When updating dashboard permissions with duplicate permission for viewer role should not be allowed"
+	Convey(tc, func() {
+		p := []*m.DashboardAcl{
+			newViewerRolePermission(dashboardID, m.PERMISSION_VIEW),
+			newViewerRolePermission(dashboardID, m.PERMISSION_ADMIN),
+		}
+		sc.updatePermissions = p
+		_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
+
+		if err != ErrGuardianPermissionExists {
+			sc.reportFailure(tc, ErrGuardianPermissionExists, err)
+		}
+		sc.reportSuccess()
 	})
 
-	Convey(desc, func() {
-		fn(sc)
+	tc = "When updating dashboard permissions with duplicate permission for admin role should not be allowed"
+	Convey(tc, func() {
+		p := []*m.DashboardAcl{
+			newAdminRolePermission(dashboardID, m.PERMISSION_ADMIN),
+		}
+		sc.updatePermissions = p
+		_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, p)
+
+		if err != ErrGuardianPermissionExists {
+			sc.reportFailure(tc, ErrGuardianPermissionExists, err)
+		}
+		sc.reportSuccess()
 	})
 }
 
-func userWithPermissionScenario(permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
-	p := []*m.DashboardAclInfoDTO{
-		{OrgId: 1, DashboardId: 1, UserId: 1, Permission: permission},
+func (sc *scenarioContext) verifyUpdateDashboardPermissionsShouldBeAllowed(pt permissionType) {
+	if !sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		tc := fmt.Sprintf("When updating dashboard permissions with %s permissions should be allowed", p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{}
+			switch pt {
+			case USER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(dashboardID, p),
+					newViewerRolePermission(dashboardID, p),
+					newCustomUserPermission(dashboardID, otherUserID, p),
+					newDefaultTeamPermission(dashboardID, p),
+				}
+			case TEAM:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(dashboardID, p),
+					newViewerRolePermission(dashboardID, p),
+					newDefaultUserPermission(dashboardID, p),
+					newCustomTeamPermission(dashboardID, otherTeamID, p),
+				}
+			case EDITOR, VIEWER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(dashboardID, p),
+					newViewerRolePermission(dashboardID, p),
+					newDefaultUserPermission(dashboardID, p),
+					newDefaultTeamPermission(dashboardID, p),
+				}
+			}
+
+			sc.updatePermissions = permissionList
+			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
+			if !ok {
+				sc.reportFailure(tc, false, true)
+			}
+			sc.reportSuccess()
+		})
+	}
+}
+
+func (sc *scenarioContext) verifyUpdateDashboardPermissionsShouldNotBeAllowed(pt permissionType) {
+	if sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		tc := fmt.Sprintf("When updating dashboard permissions with %s permissions should NOT be allowed", p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{
+				newEditorRolePermission(dashboardID, p),
+				newViewerRolePermission(dashboardID, p),
+			}
+			switch pt {
+			case USER:
+				permissionList = append(permissionList, []*m.DashboardAcl{
+					newCustomUserPermission(dashboardID, otherUserID, p),
+					newDefaultTeamPermission(dashboardID, p),
+				}...)
+			case TEAM:
+				permissionList = append(permissionList, []*m.DashboardAcl{
+					newDefaultUserPermission(dashboardID, p),
+					newCustomTeamPermission(dashboardID, otherTeamID, p),
+				}...)
+			}
+
+			sc.updatePermissions = permissionList
+			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
+			if ok {
+				sc.reportFailure(tc, true, false)
+			}
+			sc.reportSuccess()
+		})
+	}
+}
+
+func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsShouldBeAllowed(pt permissionType, parentFolderPermission m.PermissionType) {
+	if !sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		tc := fmt.Sprintf("When updating child dashboard permissions with %s permissions should be allowed", p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{}
+			switch pt {
+			case USER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newViewerRolePermission(childDashboardID, p),
+					newCustomUserPermission(childDashboardID, otherUserID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+			case TEAM:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newViewerRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newCustomTeamPermission(childDashboardID, otherTeamID, p),
+				}
+			case EDITOR:
+				permissionList = []*m.DashboardAcl{
+					newViewerRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+
+				// permission to update is higher than parent folder permission
+				if p > parentFolderPermission {
+					permissionList = append(permissionList, newEditorRolePermission(childDashboardID, p))
+				}
+			case VIEWER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+
+				// permission to update is higher than parent folder permission
+				if p > parentFolderPermission {
+					permissionList = append(permissionList, newViewerRolePermission(childDashboardID, p))
+				}
+			}
+
+			sc.updatePermissions = permissionList
+			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
+			if !ok {
+				sc.reportFailure(tc, false, true)
+			}
+			sc.reportSuccess()
+		})
+	}
+}
+
+func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsShouldNotBeAllowed(pt permissionType, parentFolderPermission m.PermissionType) {
+	if sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		tc := fmt.Sprintf("When updating child dashboard permissions with %s permissions should NOT be allowed", p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{}
+			switch pt {
+			case USER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newViewerRolePermission(childDashboardID, p),
+					newCustomUserPermission(childDashboardID, otherUserID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+			case TEAM:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newViewerRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newCustomTeamPermission(childDashboardID, otherTeamID, p),
+				}
+			case EDITOR:
+				permissionList = []*m.DashboardAcl{
+					newViewerRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+
+				// perminssion to update is higher than parent folder permission
+				if p > parentFolderPermission {
+					permissionList = append(permissionList, newEditorRolePermission(childDashboardID, p))
+				}
+			case VIEWER:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+					newDefaultUserPermission(childDashboardID, p),
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+
+				// perminssion to update is higher than parent folder permission
+				if p > parentFolderPermission {
+					permissionList = append(permissionList, newViewerRolePermission(childDashboardID, p))
+				}
+			}
+
+			sc.updatePermissions = permissionList
+			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
+			if ok {
+				sc.reportFailure(tc, true, false)
+			}
+			sc.reportSuccess()
+		})
 	}
-	permissionScenario(fmt.Sprintf("and user has permission to %s item", permission), sc, p, fn)
 }
 
-func teamWithPermissionScenario(permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
-	p := []*m.DashboardAclInfoDTO{
-		{OrgId: 1, DashboardId: 1, TeamId: 1, Permission: permission},
+func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShouldBeAllowed(pt permissionType, parentFolderPermission m.PermissionType) {
+	if !sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		// perminssion to update is higher tban parent folder permission
+		if p > parentFolderPermission {
+			continue
+		}
+
+		tc := fmt.Sprintf("When updating child dashboard permissions overriding parent %s permission with %s permission should NOT be allowed", pt.String(), p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{}
+			switch pt {
+			case USER:
+				permissionList = []*m.DashboardAcl{
+					newDefaultUserPermission(childDashboardID, p),
+				}
+			case TEAM:
+				permissionList = []*m.DashboardAcl{
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+			case EDITOR:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+				}
+			case VIEWER:
+				permissionList = []*m.DashboardAcl{
+					newViewerRolePermission(childDashboardID, p),
+				}
+			}
+
+			sc.updatePermissions = permissionList
+			_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != ErrGuardianOverride {
+				sc.reportFailure(tc, ErrGuardianOverride, err)
+			}
+			sc.reportSuccess()
+		})
 	}
-	permissionScenario(fmt.Sprintf("and team has permission to %s item", permission), sc, p, fn)
 }
 
-func everyoneWithRoleScenario(role m.RoleType, permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) {
-	p := []*m.DashboardAclInfoDTO{
-		{OrgId: 1, DashboardId: 1, UserId: -1, Role: &role, Permission: permission},
+func (sc *scenarioContext) verifyUpdateChildDashboardPermissionsWithOverrideShouldNotBeAllowed(pt permissionType, parentFolderPermission m.PermissionType) {
+	if !sc.expectedFlags.canAdmin() {
+		return
+	}
+
+	for _, p := range []m.PermissionType{m.PERMISSION_ADMIN, m.PERMISSION_EDIT, m.PERMISSION_VIEW} {
+		// perminssion to update is lower than/equal parent folder permission
+		if p <= parentFolderPermission {
+			continue
+		}
+
+		tc := fmt.Sprintf("When updating child dashboard permissions overriding parent %s permission with %s permission should be allowed", pt.String(), p.String())
+
+		Convey(tc, func() {
+			permissionList := []*m.DashboardAcl{}
+			switch pt {
+			case USER:
+				permissionList = []*m.DashboardAcl{
+					newDefaultUserPermission(childDashboardID, p),
+				}
+			case TEAM:
+				permissionList = []*m.DashboardAcl{
+					newDefaultTeamPermission(childDashboardID, p),
+				}
+			case EDITOR:
+				permissionList = []*m.DashboardAcl{
+					newEditorRolePermission(childDashboardID, p),
+				}
+			case VIEWER:
+				permissionList = []*m.DashboardAcl{
+					newViewerRolePermission(childDashboardID, p),
+				}
+			}
+
+			_, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+			sc.updatePermissions = permissionList
+			ok, err := sc.g.CheckPermissionBeforeUpdate(m.PERMISSION_ADMIN, permissionList)
+
+			if err != nil {
+				sc.reportFailure(tc, nil, err)
+			}
+			if !ok {
+				sc.reportFailure(tc, false, true)
+			}
+			sc.reportSuccess()
+		})
 	}
-	permissionScenario(fmt.Sprintf("and everyone with %s role can %s item", role, permission), sc, p, fn)
 }

+ 256 - 0
pkg/services/guardian/guardian_util_test.go

@@ -0,0 +1,256 @@
+package guardian
+
+import (
+	"bytes"
+	"fmt"
+	"strings"
+	"testing"
+
+	"github.com/grafana/grafana/pkg/bus"
+	m "github.com/grafana/grafana/pkg/models"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+type scenarioContext struct {
+	t                  *testing.T
+	orgRoleScenario    string
+	permissionScenario string
+	g                  DashboardGuardian
+	givenUser          *m.SignedInUser
+	givenDashboardID   int64
+	givenPermissions   []*m.DashboardAclInfoDTO
+	givenTeams         []*m.Team
+	updatePermissions  []*m.DashboardAcl
+	expectedFlags      permissionFlags
+	callerFile         string
+	callerLine         int
+}
+
+type scenarioFunc func(c *scenarioContext)
+
+func orgRoleScenario(desc string, t *testing.T, role m.RoleType, fn scenarioFunc) {
+	user := &m.SignedInUser{
+		UserId:  userID,
+		OrgId:   orgID,
+		OrgRole: role,
+	}
+	guard := New(dashboardID, orgID, user)
+	sc := &scenarioContext{
+		t:                t,
+		orgRoleScenario:  desc,
+		givenUser:        user,
+		givenDashboardID: dashboardID,
+		g:                guard,
+	}
+
+	Convey(desc, func() {
+		fn(sc)
+	})
+}
+
+func permissionScenario(desc string, dashboardID int64, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) {
+	bus.ClearBusHandlers()
+
+	bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
+		if query.OrgId != sc.givenUser.OrgId {
+			sc.reportFailure("Invalid organization id for GetDashboardAclInfoListQuery", sc.givenUser.OrgId, query.OrgId)
+		}
+		if query.DashboardId != sc.givenDashboardID {
+			sc.reportFailure("Invalid dashboard id for GetDashboardAclInfoListQuery", sc.givenDashboardID, query.DashboardId)
+		}
+
+		query.Result = permissions
+		return nil
+	})
+
+	teams := []*m.Team{}
+
+	for _, p := range permissions {
+		if p.TeamId > 0 {
+			teams = append(teams, &m.Team{Id: p.TeamId})
+		}
+	}
+
+	bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
+		if query.OrgId != sc.givenUser.OrgId {
+			sc.reportFailure("Invalid organization id for GetTeamsByUserQuery", sc.givenUser.OrgId, query.OrgId)
+		}
+		if query.UserId != sc.givenUser.UserId {
+			sc.reportFailure("Invalid user id for GetTeamsByUserQuery", sc.givenUser.UserId, query.UserId)
+		}
+
+		query.Result = teams
+		return nil
+	})
+
+	sc.permissionScenario = desc
+	sc.g = New(dashboardID, sc.givenUser.OrgId, sc.givenUser)
+	sc.givenDashboardID = dashboardID
+	sc.givenPermissions = permissions
+	sc.givenTeams = teams
+
+	Convey(desc, func() {
+		fn(sc)
+	})
+}
+
+type permissionType uint8
+
+const (
+	USER permissionType = 1 << iota
+	TEAM
+	EDITOR
+	VIEWER
+)
+
+func (p permissionType) String() string {
+	names := map[uint8]string{
+		uint8(USER):   "user",
+		uint8(TEAM):   "team",
+		uint8(EDITOR): "editor role",
+		uint8(VIEWER): "viewer role",
+	}
+	return names[uint8(p)]
+}
+
+type permissionFlags uint8
+
+const (
+	NO_ACCESS permissionFlags = 1 << iota
+	CAN_ADMIN
+	CAN_EDIT
+	CAN_SAVE
+	CAN_VIEW
+	FULL_ACCESS   = CAN_ADMIN | CAN_EDIT | CAN_SAVE | CAN_VIEW
+	EDITOR_ACCESS = CAN_EDIT | CAN_SAVE | CAN_VIEW
+	VIEWER_ACCESS = CAN_VIEW
+)
+
+func (flag permissionFlags) canAdmin() bool {
+	return flag&CAN_ADMIN != 0
+}
+
+func (flag permissionFlags) canEdit() bool {
+	return flag&CAN_EDIT != 0
+}
+
+func (flag permissionFlags) canSave() bool {
+	return flag&CAN_SAVE != 0
+}
+
+func (flag permissionFlags) canView() bool {
+	return flag&CAN_VIEW != 0
+}
+
+func (flag permissionFlags) noAccess() bool {
+	return flag&(CAN_ADMIN|CAN_EDIT|CAN_SAVE|CAN_VIEW) == 0
+}
+
+func (f permissionFlags) String() string {
+	r := []string{}
+
+	if f.canAdmin() {
+		r = append(r, "admin")
+	}
+
+	if f.canEdit() {
+		r = append(r, "edit")
+	}
+
+	if f.canSave() {
+		r = append(r, "save")
+	}
+
+	if f.canView() {
+		r = append(r, "view")
+	}
+
+	if f.noAccess() {
+		r = append(r, "<no access>")
+	}
+
+	return strings.Join(r[:], ", ")
+}
+
+func (sc *scenarioContext) reportSuccess() {
+	So(true, ShouldBeTrue)
+}
+
+func (sc *scenarioContext) reportFailure(desc string, expected interface{}, actual interface{}) {
+	var buf bytes.Buffer
+	buf.WriteString("\n")
+	buf.WriteString(sc.orgRoleScenario)
+	buf.WriteString(" ")
+	buf.WriteString(sc.permissionScenario)
+	buf.WriteString("\n  ")
+	buf.WriteString(desc)
+	buf.WriteString("\n")
+	buf.WriteString(fmt.Sprintf("Source test: %s:%d\n", sc.callerFile, sc.callerLine))
+	buf.WriteString(fmt.Sprintf("Expected: %v\n", expected))
+	buf.WriteString(fmt.Sprintf("Actual: %v\n", actual))
+	buf.WriteString("Context:")
+	buf.WriteString(fmt.Sprintf("\n  Given user: orgRole=%s, id=%d, orgId=%d", sc.givenUser.OrgRole, sc.givenUser.UserId, sc.givenUser.OrgId))
+	buf.WriteString(fmt.Sprintf("\n  Given dashboard id: %d", sc.givenDashboardID))
+
+	for i, p := range sc.givenPermissions {
+		r := "<nil>"
+		if p.Role != nil {
+			r = string(*p.Role)
+		}
+		buf.WriteString(fmt.Sprintf("\n  Given permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
+	}
+
+	for i, t := range sc.givenTeams {
+		buf.WriteString(fmt.Sprintf("\n  Given team (%d): id=%d", i, t.Id))
+	}
+
+	for i, p := range sc.updatePermissions {
+		r := "<nil>"
+		if p.Role != nil {
+			r = string(*p.Role)
+		}
+		buf.WriteString(fmt.Sprintf("\n  Update permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
+	}
+
+	sc.t.Fatalf(buf.String())
+}
+
+func newCustomUserPermission(dashboardID int64, userID int64, permission m.PermissionType) *m.DashboardAcl {
+	return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, UserId: userID, Permission: permission}
+}
+
+func newDefaultUserPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
+	return newCustomUserPermission(dashboardID, userID, permission)
+}
+
+func newCustomTeamPermission(dashboardID int64, teamID int64, permission m.PermissionType) *m.DashboardAcl {
+	return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, TeamId: teamID, Permission: permission}
+}
+
+func newDefaultTeamPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
+	return newCustomTeamPermission(dashboardID, teamID, permission)
+}
+
+func newAdminRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
+	return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &adminRole, Permission: permission}
+}
+
+func newEditorRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
+	return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &editorRole, Permission: permission}
+}
+
+func newViewerRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
+	return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &viewerRole, Permission: permission}
+}
+
+func toDto(acl *m.DashboardAcl) *m.DashboardAclInfoDTO {
+	return &m.DashboardAclInfoDTO{
+		OrgId:          acl.OrgId,
+		DashboardId:    acl.DashboardId,
+		UserId:         acl.UserId,
+		TeamId:         acl.TeamId,
+		Role:           acl.Role,
+		Permission:     acl.Permission,
+		PermissionName: acl.Permission.String(),
+	}
+}

+ 13 - 0
pkg/services/sqlstore/dashboard.go

@@ -544,6 +544,10 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 		dash.SetId(existingByUid.Id)
 		dash.SetUid(existingByUid.Uid)
 		existing = existingByUid
+
+		if !dash.IsFolder {
+			cmd.Result.IsParentFolderChanged = true
+		}
 	}
 
 	if (existing.IsFolder && !dash.IsFolder) ||
@@ -551,6 +555,10 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
 		return m.ErrDashboardTypeMismatch
 	}
 
+	if !dash.IsFolder && dash.FolderId != existing.FolderId {
+		cmd.Result.IsParentFolderChanged = true
+	}
+
 	// check for is someone else has written in between
 	if dash.Version != existing.Version {
 		if cmd.Overwrite {
@@ -586,6 +594,10 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
 			return m.ErrDashboardFolderWithSameNameAsDashboard
 		}
 
+		if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
+			cmd.Result.IsParentFolderChanged = true
+		}
+
 		if cmd.Overwrite {
 			dash.SetId(existing.Id)
 			dash.SetUid(existing.Uid)
@@ -599,6 +611,7 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
 }
 
 func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
+	cmd.Result = &m.ValidateDashboardBeforeSaveResult{}
 	return inTransaction(func(sess *DBSession) error {
 		if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
 			return err

+ 160 - 34
pkg/services/sqlstore/dashboard_service_integration_test.go

@@ -78,7 +78,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 			Convey("Given organization B", func() {
 				var otherOrgId int64 = 2
 
-				Convey("When saving a dashboard with id that are saved in organization A", func() {
+				Convey("When creating a dashboard with same id as dashboard in organization A", func() {
 					cmd := models.SaveDashboardCommand{
 						OrgId: otherOrgId,
 						Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -97,7 +97,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 				})
 
 				permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
-					Convey("When saving a dashboard with uid that are saved in organization A", func() {
+					Convey("When creating a dashboard with same uid as dashboard in organization A", func() {
 						var otherOrgId int64 = 2
 						cmd := models.SaveDashboardCommand{
 							OrgId: otherOrgId,
@@ -110,7 +110,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 						res := callSaveWithResult(cmd)
 
-						Convey("It should create dashboard in other organization", func() {
+						Convey("It should create a new dashboard in organization B", func() {
 							So(res, ShouldNotBeNil)
 
 							query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
@@ -130,7 +130,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 			permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
 
-				Convey("When trying to create a new dashboard in the General folder", func() {
+				Convey("When creating a new dashboard in the General folder", func() {
 					cmd := models.SaveDashboardCommand{
 						OrgId: testOrgId,
 						Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -142,7 +142,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 					err := callSaveWithError(cmd)
 
-					Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
+					Convey("It should create dashboard guardian for General Folder with correct arguments and result in access denied error", func() {
 						So(err, ShouldNotBeNil)
 						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
 
@@ -152,7 +152,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 					})
 				})
 
-				Convey("When trying to create a new dashboard in other folder", func() {
+				Convey("When creating a new dashboard in other folder", func() {
 					cmd := models.SaveDashboardCommand{
 						OrgId: testOrgId,
 						Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -165,7 +165,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 					err := callSaveWithError(cmd)
 
-					Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
+					Convey("It should create dashboard guardian for other folder with correct arguments and rsult in access denied error", func() {
 						So(err, ShouldNotBeNil)
 						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
 
@@ -175,7 +175,54 @@ func TestIntegratedDashboardService(t *testing.T) {
 					})
 				})
 
-				Convey("When trying to update a dashboard by existing id in the General folder", func() {
+				Convey("When creating a new dashboard by existing title in folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"title": savedDashInFolder.Title,
+						}),
+						FolderId:  savedFolder.Id,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
+
+				Convey("When creating a new dashboard by existing uid in folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"uid":   savedDashInFolder.Uid,
+							"title": "New dash",
+						}),
+						FolderId:  savedFolder.Id,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
+
+				Convey("When updating a dashboard by existing id in the General folder", func() {
 					cmd := models.SaveDashboardCommand{
 						OrgId: testOrgId,
 						Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -189,7 +236,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 					err := callSaveWithError(cmd)
 
-					Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
+					Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
 						So(err, ShouldNotBeNil)
 						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
 
@@ -199,7 +246,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 					})
 				})
 
-				Convey("When trying to update a dashboard by existing id in other folder", func() {
+				Convey("When updating a dashboard by existing id in other folder", func() {
 					cmd := models.SaveDashboardCommand{
 						OrgId: testOrgId,
 						Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -213,7 +260,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 
 					err := callSaveWithError(cmd)
 
-					Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
+					Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
 						So(err, ShouldNotBeNil)
 						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
 
@@ -222,6 +269,102 @@ func TestIntegratedDashboardService(t *testing.T) {
 						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
 					})
 				})
+
+				Convey("When moving a dashboard by existing id to other folder from General folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"id":    savedDashInGeneralFolder.Id,
+							"title": "Dash",
+						}),
+						FolderId:  otherSavedFolder.Id,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
+
+				Convey("When moving a dashboard by existing id to the General folder from other folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"id":    savedDashInFolder.Id,
+							"title": "Dash",
+						}),
+						FolderId:  0,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
+
+				Convey("When moving a dashboard by existing uid to other folder from General folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"uid":   savedDashInGeneralFolder.Uid,
+							"title": "Dash",
+						}),
+						FolderId:  otherSavedFolder.Id,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
+
+				Convey("When moving a dashboard by existing uid to the General folder from other folder", func() {
+					cmd := models.SaveDashboardCommand{
+						OrgId: testOrgId,
+						Dashboard: simplejson.NewFromAny(map[string]interface{}{
+							"uid":   savedDashInFolder.Uid,
+							"title": "Dash",
+						}),
+						FolderId:  0,
+						UserId:    10000,
+						Overwrite: true,
+					}
+
+					err := callSaveWithError(cmd)
+
+					Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
+						So(err, ShouldNotBeNil)
+						So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
+
+						So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
+						So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
+						So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
+					})
+				})
 			})
 
 			// Given user has permission to save
@@ -672,7 +815,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing folder to a dashboard using id", func() {
+					Convey("When updating existing folder to a dashboard using id", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -691,7 +834,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing dashboard to a folder using id", func() {
+					Convey("When updating existing dashboard to a folder using id", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -710,7 +853,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing folder to a dashboard using uid", func() {
+					Convey("When updating existing folder to a dashboard using uid", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -729,7 +872,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing dashboard to a folder using uid", func() {
+					Convey("When updating existing dashboard to a folder using uid", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -748,7 +891,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing folder to a dashboard using title", func() {
+					Convey("When updating existing folder to a dashboard using title", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -766,7 +909,7 @@ func TestIntegratedDashboardService(t *testing.T) {
 						})
 					})
 
-					Convey("When trying to update existing dashboard to a folder using title", func() {
+					Convey("When updating existing dashboard to a folder using title", func() {
 						cmd := models.SaveDashboardCommand{
 							OrgId: 1,
 							Dashboard: simplejson.NewFromAny(map[string]interface{}{
@@ -854,23 +997,6 @@ func callSaveWithError(cmd models.SaveDashboardCommand) error {
 	return err
 }
 
-func dashboardServiceScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
-	Convey(desc, func() {
-		origNewDashboardGuardian := guardian.New
-		guardian.MockDashboardGuardian(mock)
-
-		sc := &scenarioContext{
-			dashboardGuardianMock: mock,
-		}
-
-		defer func() {
-			guardian.New = origNewDashboardGuardian
-		}()
-
-		fn(sc)
-	})
-}
-
 func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
 	cmd := models.SaveDashboardCommand{
 		OrgId:    orgId,

+ 14 - 10
public/app/features/dashboard/folder_picker/folder_picker.ts

@@ -19,9 +19,12 @@ export class FolderPickerCtrl {
   newFolderNameTouched: boolean;
   hasValidationError: boolean;
   validationError: any;
+  isEditor: boolean;
 
   /** @ngInject */
-  constructor(private backendSrv, private validationSrv) {
+  constructor(private backendSrv, private validationSrv, private contextSrv) {
+    this.isEditor = this.contextSrv.isEditor;
+
     if (!this.labelClass) {
       this.labelClass = 'width-7';
     }
@@ -38,19 +41,20 @@ export class FolderPickerCtrl {
 
     return this.backendSrv.get('api/search', params).then(result => {
       if (
-        query === '' ||
-        query.toLowerCase() === 'g' ||
-        query.toLowerCase() === 'ge' ||
-        query.toLowerCase() === 'gen' ||
-        query.toLowerCase() === 'gene' ||
-        query.toLowerCase() === 'gener' ||
-        query.toLowerCase() === 'genera' ||
-        query.toLowerCase() === 'general'
+        this.isEditor &&
+        (query === '' ||
+          query.toLowerCase() === 'g' ||
+          query.toLowerCase() === 'ge' ||
+          query.toLowerCase() === 'gen' ||
+          query.toLowerCase() === 'gene' ||
+          query.toLowerCase() === 'gener' ||
+          query.toLowerCase() === 'genera' ||
+          query.toLowerCase() === 'general')
       ) {
         result.unshift({ title: this.rootName, id: 0 });
       }
 
-      if (this.enableCreateNew && query === '') {
+      if (this.isEditor && this.enableCreateNew && query === '') {
         result.unshift({ title: '-- New Folder --', id: -1 });
       }