From 84d034c68861240f2fa85ecfef5e46fa6128c13c Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Fri, 6 Apr 2018 14:08:23 +0200 Subject: [PATCH 1/6] api: allow authenticated users to search current org users and teams Any authenticated user of an organization should be able to - search its organization's users - search its organization's users - retrieve a single user of its organization (this how it was implemented earlier) --- pkg/api/api.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 3c7b81e472d..96b764b95b9 100644 --- a/pkg/api/api.go +++ b/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)) From f3e1557761c9fe533c559f98ebbb44f12b572dcd Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Sun, 8 Apr 2018 15:06:22 +0200 Subject: [PATCH 2/6] guardian: when updating permissions should verify existing permissions Before in CheckPermissionBeforeUpdate, access was verified for updated permissions. Now access is verified for existing permissions. Refactored guardian tests to cover more test cases for org admin, editor and viewer roles --- pkg/services/guardian/guardian.go | 2 +- pkg/services/guardian/guardian_test.go | 1295 +++++++++---------- pkg/services/guardian/guardian_util_test.go | 256 ++++ 3 files changed, 881 insertions(+), 672 deletions(-) create mode 100644 pkg/services/guardian/guardian_util_test.go diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 811b38cac86..649a6cbd661 100644 --- a/pkg/services/guardian/guardian.go +++ b/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 diff --git a/pkg/services/guardian/guardian_test.go b/pkg/services/guardian/guardian_test.go index bb7e6bd1a72..9de12c60fea 100644 --- a/pkg/services/guardian/guardian_test.go +++ b/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}, - } - - 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}, - } - - 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}, - } - - 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}, - } - - 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}, - } - - 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}, - } - - 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}, - } - - 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) - }) - }) - }) - - 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) - }) - }) - - 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) - }) +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 +) + +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) }) }) } -type scenarioContext struct { - g DashboardGuardian +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) + }) + }) } -type scenarioFunc func(c *scenarioContext) +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) -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, + // 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) + }) + }) +} + +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)), } - Convey(desc, func() { - fn(sc) + 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) }) } -func permissionScenario(desc string, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) { - bus.ClearBusHandlers() +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 - bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error { - query.Result = permissions - return nil + 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}} + } + + 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) }) +} - teams := []*m.Team{} +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 - for _, p := range permissions { - if p.TeamId > 0 { - teams = append(teams, &m.Team{Id: p.TeamId}) + 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}} + } + + 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) + }) +} + +func (sc *scenarioContext) verifyExpectedPermissionsFlags() { + canAdmin, _ := sc.g.CanAdmin() + canEdit, _ := sc.g.CanEdit() + canSave, _ := sc.g.CanSave() + canView, _ := sc.g.CanView() + + tc := fmt.Sprintf("should have permissions to %s", sc.expectedFlags.String()) + Convey(tc, func() { + var actualFlag permissionFlags + + if canAdmin { + actualFlag |= CAN_ADMIN } - } - bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error { - query.Result = teams - return nil - }) + if canEdit { + actualFlag |= CAN_EDIT + } - Convey(desc, func() { - fn(sc) + 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()) + } + + 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) verifyDuplicatePermissionsShouldNotBeAllowed() { + if !sc.expectedFlags.canAdmin() { + return } - permissionScenario(fmt.Sprintf("and user has permission to %s item", permission), sc, p, fn) + + 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) + + if err != ErrGuardianPermissionExists { + sc.reportFailure(tc, ErrGuardianPermissionExists, err) + } + sc.reportSuccess() + }) + + 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) + + 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() + }) + + 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() + }) + + 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 teamWithPermissionScenario(permission m.PermissionType, sc *scenarioContext, fn scenarioFunc) { - p := []*m.DashboardAclInfoDTO{ - {OrgId: 1, DashboardId: 1, TeamId: 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() + }) } - 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) 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() + }) + } +} + +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() + }) + } +} + +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) } diff --git a/pkg/services/guardian/guardian_util_test.go b/pkg/services/guardian/guardian_util_test.go new file mode 100644 index 00000000000..b065c4194ad --- /dev/null +++ b/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, "") + } + + 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 := "" + 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 := "" + 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(), + } +} From b4863002c98ce57d060df1a01bc1edeec92e99d0 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 11 Apr 2018 00:23:50 +0200 Subject: [PATCH 3/6] permission: fix user with org viewer save/move permissions User with org viewer role should not be able to save/move dashboards in/to general folder. --- pkg/models/dashboards.go | 5 + pkg/services/dashboards/dashboard_service.go | 10 + .../dashboards/dashboard_service_test.go | 1 + .../dashboards/folder_service_test.go | 2 + pkg/services/sqlstore/dashboard.go | 13 ++ .../dashboard_service_integration_test.go | 194 +++++++++++++++--- 6 files changed, 191 insertions(+), 34 deletions(-) diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 8cd2b01811c..33d1dd64db6 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -224,6 +224,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 // @@ -268,6 +272,7 @@ type ValidateDashboardBeforeSaveCommand struct { OrgId int64 Dashboard *Dashboard Overwrite bool + Result *ValidateDashboardBeforeSaveResult } // diff --git a/pkg/services/dashboards/dashboard_service.go b/pkg/services/dashboards/dashboard_service.go index 02a6ffc8330..1656bb74c9c 100644 --- a/pkg/services/dashboards/dashboard_service.go +++ b/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 + } + } + guard := guardian.New(dash.GetDashboardIdForSavePermissionCheck(), dto.OrgId, dto.User) if canSave, err := guard.CanSave(); err != nil || !canSave { if err != nil { diff --git a/pkg/services/dashboards/dashboard_service_test.go b/pkg/services/dashboards/dashboard_service_test.go index 965b10655b3..d2c5863d994 100644 --- a/pkg/services/dashboards/dashboard_service_test.go +++ b/pkg/services/dashboards/dashboard_service_test.go @@ -51,6 +51,7 @@ func TestDashboardService(t *testing.T) { }) bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error { + cmd.Result = &models.ValidateDashboardBeforeSaveResult{} return nil }) diff --git a/pkg/services/dashboards/folder_service_test.go b/pkg/services/dashboards/folder_service_test.go index 6c0413d1878..1e678e3b1a1 100644 --- a/pkg/services/dashboards/folder_service_test.go +++ b/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 }) diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index 8a89c3d942c..beda4b150ca 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/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 diff --git a/pkg/services/sqlstore/dashboard_service_integration_test.go b/pkg/services/sqlstore/dashboard_service_integration_test.go index d005270c33c..21ebc6505bb 100644 --- a/pkg/services/sqlstore/dashboard_service_integration_test.go +++ b/pkg/services/sqlstore/dashboard_service_integration_test.go @@ -74,7 +74,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{}{ @@ -93,7 +93,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, @@ -106,7 +106,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} @@ -126,7 +126,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{}{ @@ -138,7 +138,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) @@ -148,7 +148,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{}{ @@ -161,7 +161,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) @@ -171,7 +171,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{}{ @@ -185,7 +232,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) @@ -195,7 +242,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{}{ @@ -209,7 +256,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) @@ -218,6 +265,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 @@ -668,7 +811,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{}{ @@ -687,7 +830,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{}{ @@ -706,7 +849,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{}{ @@ -725,7 +868,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{}{ @@ -744,7 +887,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{}{ @@ -762,7 +905,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{}{ @@ -850,23 +993,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, From e73479ef3389e9530446e522d0dbdfebbfcb42ce Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 11 Apr 2018 00:26:16 +0200 Subject: [PATCH 4/6] folders: fix permissions in folder picker component Only enable creating new folders from folder picker if user has org roles admin or editor. Only render General option in folder picker if user has org roles admin or editor. --- .../dashboard/folder_picker/folder_picker.ts | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/public/app/features/dashboard/folder_picker/folder_picker.ts b/public/app/features/dashboard/folder_picker/folder_picker.ts index cbf23e3ea4b..b8ae18b14d3 100644 --- a/public/app/features/dashboard/folder_picker/folder_picker.ts +++ b/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 }); } From 4a842f96acbda620332c8664c0030c5ebefba148 Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Tue, 17 Apr 2018 13:44:42 +0200 Subject: [PATCH 5/6] changelog: adds note for #11173 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45881e602bc..d5177ca5dc5 100644 --- a/CHANGELOG.md +++ b/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 From 059719104f341ec650b339c245cbab5d79b50046 Mon Sep 17 00:00:00 2001 From: bergquist Date: Tue, 17 Apr 2018 15:02:22 +0200 Subject: [PATCH 6/6] dev: only build server with bra run --- .bra.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bra.toml b/.bra.toml index 053ae78150a..9978f416ef3 100644 --- a/.bra.toml +++ b/.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