diff --git a/pkg/api/dashboard_acl.go b/pkg/api/dashboard_acl.go index a64360c92f7..0137c0bd8f1 100644 --- a/pkg/api/dashboard_acl.go +++ b/pkg/api/dashboard_acl.go @@ -23,7 +23,8 @@ func GetDashboardAclList(c *middleware.Context) Response { return ApiError(500, "Failed to get Dashboard ACL", err) } - return Json(200, &query.Result) + list := query.Result + return Json(200, list) } func PostDashboardAcl(c *middleware.Context, cmd m.SetDashboardAclCommand) Response { diff --git a/pkg/api/dashboard_acl_test.go b/pkg/api/dashboard_acl_test.go index f22f4a2fc69..b48916a2343 100644 --- a/pkg/api/dashboard_acl_test.go +++ b/pkg/api/dashboard_acl_test.go @@ -13,16 +13,16 @@ import ( func TestDashboardAclApiEndpoint(t *testing.T) { Convey("Given a dashboard acl", t, func() { mockResult := []*models.DashboardAcl{ - {Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT}, - {Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW}, - {Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permissions: models.PERMISSION_EDIT}, - {Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permissions: models.PERMISSION_READ_ONLY_EDIT}, + {Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_EDIT}, + {Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_VIEW}, + {Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: models.PERMISSION_EDIT}, + {Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: models.PERMISSION_READ_ONLY_EDIT}, } dtoRes := []*models.DashboardAclInfoDTO{ - {Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permissions: models.PERMISSION_EDIT}, - {Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permissions: models.PERMISSION_VIEW}, - {Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permissions: models.PERMISSION_EDIT}, - {Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permissions: models.PERMISSION_READ_ONLY_EDIT}, + {Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_EDIT}, + {Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_VIEW}, + {Id: 3, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: models.PERMISSION_EDIT}, + {Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: models.PERMISSION_READ_ONLY_EDIT}, } bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error { @@ -59,7 +59,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) { Convey("When user is editor and in the ACL", func() { loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", models.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT}) + mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_EDIT}) Convey("Should be able to access ACL", func() { sc.handlerFunc = GetDashboardAclList @@ -70,7 +70,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) { }) loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", models.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT}) + mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_EDIT}) bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error { return nil @@ -114,7 +114,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) { }) loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/user/1", "/api/dashboards/id/:dashboardsId/acl/user/:userId", models.ROLE_EDITOR, func(sc *scenarioContext) { - mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_VIEW}) + mockResult = append(mockResult, &models.DashboardAcl{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: models.PERMISSION_VIEW}) bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error { return nil }) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 02d21ebc07c..2611dbb5ae6 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -174,7 +174,7 @@ func TestDashboardApiEndpoint(t *testing.T) { aclMockResp := []*models.DashboardAcl{ { DashboardId: 1, - Permissions: models.PERMISSION_EDIT, + Permission: models.PERMISSION_EDIT, UserId: 200, }, } @@ -273,7 +273,7 @@ func TestDashboardApiEndpoint(t *testing.T) { role := models.ROLE_VIEWER mockResult := []*models.DashboardAcl{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permissions: models.PERMISSION_EDIT}, + {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT}, } bus.AddHandler("test", func(query *models.GetInheritedDashboardAclQuery) error { @@ -315,7 +315,7 @@ func TestDashboardApiEndpoint(t *testing.T) { role := models.ROLE_EDITOR mockResult := []*models.DashboardAcl{ - {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permissions: models.PERMISSION_VIEW}, + {Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW}, } bus.AddHandler("test", func(query *models.GetInheritedDashboardAclQuery) error { diff --git a/pkg/models/dashboard_acl.go b/pkg/models/dashboard_acl.go index 05a28553d40..ca2635d91ee 100644 --- a/pkg/models/dashboard_acl.go +++ b/pkg/models/dashboard_acl.go @@ -9,15 +9,15 @@ type PermissionType int const ( PERMISSION_VIEW PermissionType = 1 << iota - PERMISSION_READ_ONLY_EDIT PERMISSION_EDIT + PERMISSION_ADMIN ) func (p PermissionType) String() string { names := map[int]string{ - int(PERMISSION_VIEW): "View", - int(PERMISSION_READ_ONLY_EDIT): "Read-only Edit", - int(PERMISSION_EDIT): "Edit", + int(PERMISSION_VIEW): "View", + int(PERMISSION_EDIT): "Edit", + int(PERMISSION_ADMIN): "Admin", } return names[int(p)] } @@ -36,7 +36,7 @@ type DashboardAcl struct { UserId int64 UserGroupId int64 - Permissions PermissionType + Permission PermissionType Created time.Time Updated time.Time @@ -55,7 +55,8 @@ type DashboardAclInfoDTO struct { UserEmail string `json:"userEmail"` UserGroupId int64 `json:"userGroupId"` UserGroup string `json:"userGroup"` - Permissions PermissionType `json:"permissions"` + Role RoleType `json:"role"` + Permission PermissionType `json:"permission"` PermissionName string `json:"permissionName"` } @@ -68,7 +69,7 @@ type SetDashboardAclCommand struct { OrgId int64 `json:"-"` UserId int64 `json:"userId"` UserGroupId int64 `json:"userGroupId"` - Permissions PermissionType `json:"permissions" binding:"Required"` + Permission PermissionType `json:"permission" binding:"Required"` Result DashboardAcl `json:"-"` } diff --git a/pkg/services/guardian/guardian.go b/pkg/services/guardian/guardian.go index 087b22cdd2f..ca7150e15c4 100644 --- a/pkg/services/guardian/guardian.go +++ b/pkg/services/guardian/guardian.go @@ -29,7 +29,7 @@ func (g *DashboardGuardian) CanSave() (bool, error) { } func (g *DashboardGuardian) CanEdit() (bool, error) { - return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT, m.ROLE_READ_ONLY_EDITOR) + return g.HasPermission(m.PERMISSION_EDIT, m.ROLE_READ_ONLY_EDITOR) } func (g *DashboardGuardian) CanView() (bool, error) { @@ -57,12 +57,12 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType, fallbackR } for _, p := range acl { - if p.UserId == g.user.UserId && p.Permissions >= permission { + if p.UserId == g.user.UserId && p.Permission >= permission { return true, nil } for _, ug := range userGroups { - if ug.Id == p.UserGroupId && p.Permissions >= permission { + if ug.Id == p.UserGroupId && p.Permission >= permission { return true, nil } } diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go index aaa195d583d..202179b9099 100644 --- a/pkg/services/sqlstore/dashboard_acl.go +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -27,11 +27,13 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error { if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId); err != nil { return err } else if len(res) == 1 { + entity := m.DashboardAcl{ - Permissions: cmd.Permissions, - Updated: time.Now(), + Permission: cmd.Permission, + Updated: time.Now(), } - if _, err := sess.Cols("updated", "permissions").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil { + + if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil { return err } @@ -45,10 +47,10 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error { Created: time.Now(), Updated: time.Now(), DashboardId: cmd.DashboardId, - Permissions: cmd.Permissions, + Permission: cmd.Permission, } - cols := []string{"org_id", "created", "updated", "dashboard_id", "permissions"} + cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"} if cmd.UserId != 0 { cols = append(cols, "user_id") @@ -58,12 +60,12 @@ func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error { cols = append(cols, "user_group_id") } - entityId, err := sess.Cols(cols...).Insert(&entity) + _, err := sess.Cols(cols...).Insert(&entity) if err != nil { return err } + cmd.Result = entity - cmd.Result.Id = entityId // Update dashboard HasAcl flag dashboard := m.Dashboard{ @@ -97,7 +99,7 @@ func GetInheritedDashboardAcl(query *m.GetInheritedDashboardAclQuery) error { da.dashboard_id, da.user_id, da.user_group_id, - da.permissions, + da.permission, da.created, da.updated FROM dashboard_acl as da @@ -112,29 +114,51 @@ func GetInheritedDashboardAcl(query *m.GetInheritedDashboardAclQuery) error { } func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error { - rawSQL := `SELECT - da.id, - da.org_id, - da.dashboard_id, - da.user_id, - da.user_group_id, - da.permissions, - da.created, - da.updated, - u.login AS user_login, - u.email AS user_email, - ug.name AS user_group + rawSQL := ` + SELECT + da.id, + da.org_id, + da.dashboard_id, + da.user_id, + da.user_group_id, + da.permission, + da.role, + da.created, + da.updated, + u.login AS user_login, + u.email AS user_email, + ug.name AS user_group FROM` + dialect.Quote("dashboard_acl") + ` as da - LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id - LEFT OUTER JOIN user_group ug on ug.id = da.user_group_id - WHERE dashboard_id=?` + LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id + LEFT OUTER JOIN user_group ug on ug.id = da.user_group_id + WHERE dashboard_id = ? + + -- Also include default permission if has_acl = 0 + + UNION + SELECT + da.id, + da.org_id, + da.dashboard_id, + da.user_id, + da.user_group_id, + da.permission, + da.role, + da.created, + da.updated, + '' as user_login, + '' as user_email, + '' as user_group + FROM dashboard_acl as da, dashboard as dash + WHERE dash.id = ? AND dash.has_acl = 0 AND da.dashboard_id = -1 + ` query.Result = make([]*m.DashboardAclInfoDTO, 0) - err := x.SQL(rawSQL, query.DashboardId).Find(&query.Result) + err := x.SQL(rawSQL, query.DashboardId, query.DashboardId).Find(&query.Result) for _, p := range query.Result { - p.PermissionName = p.Permissions.String() + p.PermissionName = p.Permission.String() } return err diff --git a/pkg/services/sqlstore/dashboard_acl_test.go b/pkg/services/sqlstore/dashboard_acl_test.go index 34bab8c94e6..1b4a06a1cd8 100644 --- a/pkg/services/sqlstore/dashboard_acl_test.go +++ b/pkg/services/sqlstore/dashboard_acl_test.go @@ -20,7 +20,7 @@ func TestDashboardAclDataAccess(t *testing.T) { err := SetDashboardAcl(&m.SetDashboardAclCommand{ OrgId: 1, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_EDIT, + Permission: m.PERMISSION_EDIT, }) So(err, ShouldEqual, m.ErrDashboardAclInfoMissing) }) @@ -30,7 +30,7 @@ func TestDashboardAclDataAccess(t *testing.T) { OrgId: 1, UserId: currentUser.Id, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_EDIT, + Permission: m.PERMISSION_EDIT, }) So(err, ShouldBeNil) @@ -49,7 +49,7 @@ func TestDashboardAclDataAccess(t *testing.T) { OrgId: 1, UserId: currentUser.Id, DashboardId: childDash.Id, - Permissions: m.PERMISSION_EDIT, + Permission: m.PERMISSION_EDIT, }) So(err, ShouldBeNil) @@ -67,23 +67,29 @@ func TestDashboardAclDataAccess(t *testing.T) { }) Convey("Should be able to add dashboard permission", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + setDashAclCmd := m.SetDashboardAclCommand{ OrgId: 1, UserId: currentUser.Id, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_EDIT, - }) + Permission: m.PERMISSION_EDIT, + } + + err := SetDashboardAcl(&setDashAclCmd) So(err, ShouldBeNil) + So(setDashAclCmd.Result.Id, ShouldEqual, 3) + q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} err = GetDashboardAclInfoList(q1) So(err, ShouldBeNil) + So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id) - So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT) + So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT) So(q1.Result[0].PermissionName, ShouldEqual, "Edit") So(q1.Result[0].UserId, ShouldEqual, currentUser.Id) So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login) So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email) + So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id) Convey("Should update hasAcl field to true for dashboard folder and its children", func() { q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}} @@ -98,8 +104,9 @@ func TestDashboardAclDataAccess(t *testing.T) { OrgId: 1, UserId: 1, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_READ_ONLY_EDIT, + Permission: m.PERMISSION_ADMIN, }) + So(err, ShouldBeNil) q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} @@ -107,7 +114,7 @@ func TestDashboardAclDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(q3.Result), ShouldEqual, 1) So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id) - So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT) + So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN) So(q3.Result[0].UserId, ShouldEqual, 1) }) @@ -115,8 +122,9 @@ func TestDashboardAclDataAccess(t *testing.T) { Convey("Should be able to delete an existing permission", func() { err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{ OrgId: 1, - AclId: 1, + AclId: setDashAclCmd.Result.Id, }) + So(err, ShouldBeNil) q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} @@ -132,20 +140,35 @@ func TestDashboardAclDataAccess(t *testing.T) { So(err, ShouldBeNil) Convey("Should be able to add a user permission for a user group", func() { - err := SetDashboardAcl(&m.SetDashboardAclCommand{ + setDashAclCmd := m.SetDashboardAclCommand{ OrgId: 1, UserGroupId: group1.Result.Id, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_EDIT, - }) + Permission: m.PERMISSION_EDIT, + } + + err := SetDashboardAcl(&setDashAclCmd) So(err, ShouldBeNil) q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} err = GetDashboardAclInfoList(q1) So(err, ShouldBeNil) So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id) - So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT) + So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT) So(q1.Result[0].UserGroupId, ShouldEqual, group1.Result.Id) + + Convey("Should be able to delete an existing permission for a user group", func() { + err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{ + OrgId: 1, + AclId: setDashAclCmd.Result.Id, + }) + + So(err, ShouldBeNil) + q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} + err = GetDashboardAclInfoList(q3) + So(err, ShouldBeNil) + So(len(q3.Result), ShouldEqual, 0) + }) }) Convey("Should be able to update an existing permission for a user group", func() { @@ -153,7 +176,7 @@ func TestDashboardAclDataAccess(t *testing.T) { OrgId: 1, UserGroupId: group1.Result.Id, DashboardId: savedFolder.Id, - Permissions: m.PERMISSION_READ_ONLY_EDIT, + Permission: m.PERMISSION_ADMIN, }) So(err, ShouldBeNil) @@ -162,23 +185,10 @@ func TestDashboardAclDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(q3.Result), ShouldEqual, 1) So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id) - So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT) + So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN) So(q3.Result[0].UserGroupId, ShouldEqual, group1.Result.Id) - }) - Convey("Should be able to delete an existing permission for a user group", func() { - err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{ - OrgId: 1, - AclId: 1, - }) - So(err, ShouldBeNil) - - q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id} - err = GetDashboardAclInfoList(q3) - So(err, ShouldBeNil) - So(len(q3.Result), ShouldEqual, 0) - }) }) }) }) diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 0cf5353c04e..adccb783e31 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -384,7 +384,7 @@ func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.Permis OrgId: 1, UserId: userId, DashboardId: dashId, - Permissions: permissions, + Permission: permissions, }) So(err, ShouldBeNil) } diff --git a/pkg/services/sqlstore/migrations/dashboard_acl.go b/pkg/services/sqlstore/migrations/dashboard_acl.go index e68e580aac0..af85e48af0d 100644 --- a/pkg/services/sqlstore/migrations/dashboard_acl.go +++ b/pkg/services/sqlstore/migrations/dashboard_acl.go @@ -11,21 +11,42 @@ func addDashboardAclMigrations(mg *Migrator) { {Name: "dashboard_id", Type: DB_BigInt}, {Name: "user_id", Type: DB_BigInt, Nullable: true}, {Name: "user_group_id", Type: DB_BigInt, Nullable: true}, - {Name: "permissions", Type: DB_SmallInt, Default: "4"}, + {Name: "permission", Type: DB_SmallInt, Default: "4"}, + {Name: "role", Type: DB_Varchar, Length: 20, Nullable: true}, {Name: "created", Type: DB_DateTime, Nullable: false}, {Name: "updated", Type: DB_DateTime, Nullable: false}, }, Indices: []*Index{ - {Cols: []string{"org_id"}}, + {Cols: []string{"dashboard_id"}}, {Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex}, {Cols: []string{"dashboard_id", "user_group_id"}, Type: UniqueIndex}, }, } - mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1)) + mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1)) //------- indexes ------------------ - mg.AddMigration("add unique index dashboard_acl_org_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0])) + mg.AddMigration("add unique index dashboard_acl_dashboard_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0])) mg.AddMigration("add unique index dashboard_acl_dashboard_id_user_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[1])) mg.AddMigration("add unique index dashboard_acl_dashboard_id_group_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2])) + + const rawSQL = ` +INSERT INTO dashboard_acl + ( + org_id, + dashboard_id, + permission, + role, + created, + updated + ) + VALUES + (-1,-1, 1,'Viewer','2017-06-20','2017-06-20'), + (-1,-1, 2,'Editor','2017-06-20','2017-06-20') + ` + + mg.AddMigration("save default acl rules in dashboard_acl table", new(RawSqlMigration). + Sqlite(rawSQL). + Postgres(rawSQL). + Mysql(rawSQL)) } diff --git a/pkg/services/sqlstore/org_test.go b/pkg/services/sqlstore/org_test.go index 7af80f38320..ea3b02a3551 100644 --- a/pkg/services/sqlstore/org_test.go +++ b/pkg/services/sqlstore/org_test.go @@ -174,10 +174,10 @@ func TestAccountDataAccess(t *testing.T) { So(err, ShouldBeNil) So(len(query.Result), ShouldEqual, 3) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT}) + err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permissions: m.PERMISSION_EDIT}) + err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) Convey("When org user is deleted", func() { diff --git a/pkg/services/sqlstore/user_group_test.go b/pkg/services/sqlstore/user_group_test.go index 34e4f5e5bbf..9a8d02d9a95 100644 --- a/pkg/services/sqlstore/user_group_test.go +++ b/pkg/services/sqlstore/user_group_test.go @@ -94,7 +94,7 @@ func TestUserGroupCommandsAndQueries(t *testing.T) { So(err, ShouldBeNil) err = AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: groupId, UserId: userIds[2]}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permissions: m.PERMISSION_EDIT, UserGroupId: groupId}) + err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, UserGroupId: groupId}) err = DeleteUserGroup(&m.DeleteUserGroupCommand{Id: groupId}) So(err, ShouldBeNil) diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index 33ecea6da9a..4f99d2f550f 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -99,7 +99,7 @@ func TestUserDataAccess(t *testing.T) { err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId}) So(err, ShouldBeNil) - err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permissions: m.PERMISSION_EDIT}) + err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT}) So(err, ShouldBeNil) err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"}) diff --git a/public/app/core/components/navbar/navbar.html b/public/app/core/components/navbar/navbar.html index 085e2eef920..ea8eb21d6d6 100644 --- a/public/app/core/components/navbar/navbar.html +++ b/public/app/core/components/navbar/navbar.html @@ -8,9 +8,9 @@ - - - + + +
diff --git a/public/app/core/components/user_group_picker.ts b/public/app/core/components/user_group_picker.ts index d3b9b2f8ef4..05f2c016f40 100644 --- a/public/app/core/components/user_group_picker.ts +++ b/public/app/core/components/user_group_picker.ts @@ -4,66 +4,38 @@ import _ from 'lodash'; const template = ` + +
`; export class UserGroupPickerCtrl { - userGroupSegment: any; - userGroupId: number; - debouncedSearchUserGroups: any; + group: any; + userGroupPicked: any; + debouncedSearchGroups: any; /** @ngInject */ constructor(private backendSrv, private $scope, $sce, private uiSegmentSrv) { - this.debouncedSearchUserGroups = _.debounce(this.searchUserGroups, 500, {'leading': true, 'trailing': false}); - this.resetUserGroupSegment(); + this.debouncedSearchGroups = _.debounce(this.searchGroups, 500, {'leading': true, 'trailing': false}); + this.reset(); } - resetUserGroupSegment() { - this.userGroupId = null; - - const userGroupSegment = this.uiSegmentSrv.newSegment({ - value: 'Choose', - selectMode: true, - fake: true, - cssClass: 'gf-size-auto' - }); - - if (!this.userGroupSegment) { - this.userGroupSegment = userGroupSegment; - } else { - this.userGroupSegment.value = userGroupSegment.value; - this.userGroupSegment.html = userGroupSegment.html; - this.userGroupSegment.value = userGroupSegment.value; - } + reset() { + this.group = {text: 'Choose', value: null}; } - userGroupIdChanged(newVal) { - if (!newVal) { - this.resetUserGroupSegment(); - } - } - - searchUserGroups(query: string) { + searchGroups(query: string) { return Promise.resolve(this.backendSrv.get('/api/user-groups/search?perpage=10&page=1&query=' + query).then(result => { - return _.map(result.userGroups, ug => { return this.uiSegmentSrv.newSegment(ug.name); }); + return _.map(result.userGroups, ug => { + return {text: ug.name, value: ug}; + }); })); } - onChange() { - this.backendSrv.get('/api/user-groups/search?perpage=10&page=1&query=' + this.userGroupSegment.value) - .then(result => { - if (!result) { - return; - } - - result.userGroups.forEach(ug => { - if (ug.name === this.userGroupSegment.value) { - this.userGroupId = ug.id; - } - }); - }); + onChange(option) { + this.userGroupPicked({$group: option.value}); } } @@ -75,11 +47,11 @@ export function userGroupPicker() { bindToController: true, controllerAs: 'ctrl', scope: { - userGroupId: '=', + userGroupPicked: '&', }, link: function(scope, elem, attrs, ctrl) { - scope.$watch("ctrl.userGroupId", (newVal, oldVal) => { - ctrl.userGroupIdChanged(newVal); + scope.$on("user-group-picker-reset", () => { + ctrl.reset(); }); } }; diff --git a/public/app/core/components/user_picker.ts b/public/app/core/components/user_picker.ts index aee1e6e4348..b0310de3bae 100644 --- a/public/app/core/components/user_picker.ts +++ b/public/app/core/components/user_picker.ts @@ -7,37 +7,35 @@ const template = ` `; export class UserPickerCtrl { user: any; - userId: number; debouncedSearchUsers: any; + userPicked: any; /** @ngInject */ constructor(private backendSrv, private $scope, $sce) { - this.user = {text: 'Choose', value: null}; + this.reset(); this.debouncedSearchUsers = _.debounce(this.searchUsers, 500, {'leading': true, 'trailing': false}); } searchUsers(query: string) { return Promise.resolve(this.backendSrv.get('/api/users/search?perpage=10&page=1&query=' + query).then(result => { return _.map(result.users, user => { - return {text: user.login + ' - ' + user.email, value: user.id}; + return {text: user.login + ' - ' + user.email, value: user}; }); })); } - onChange() { - this.userId = this.user.value; + onChange(option) { + this.userPicked({$user: option.value}); } - userIdChanged() { - if (this.userId === null) { - this.user = {text: 'Choose', value: null}; - } + reset() { + this.user = {text: 'Choose', value: null}; } } @@ -56,11 +54,11 @@ export function userPicker() { bindToController: true, controllerAs: 'ctrl', scope: { - userId: '=', + userPicked: '&', }, link: function(scope, elem, attrs, ctrl) { - scope.$watch("ctrl.userId", (newVal, oldVal) => { - ctrl.userIdChanged(newVal); + scope.$on("user-picker-reset", () => { + ctrl.reset(); }); } }; diff --git a/public/app/core/directives/dash_edit_link.js b/public/app/core/directives/dash_edit_link.js index a4c1ad53b3c..d5fd80f9143 100644 --- a/public/app/core/directives/dash_edit_link.js +++ b/public/app/core/directives/dash_edit_link.js @@ -2,8 +2,9 @@ define([ 'jquery', 'angular', '../core_module', + 'lodash', ], -function ($, angular, coreModule) { +function ($, angular, coreModule, _) { 'use strict'; var editViewMap = { @@ -12,7 +13,8 @@ function ($, angular, coreModule) { 'templating': { src: 'public/app/features/templating/partials/editor.html'}, 'history': { html: ''}, 'timepicker': { src: 'public/app/features/dashboard/timepicker/dropdown.html' }, - 'import': { html: '' } + 'import': { html: '', isModal: true }, + 'permissions': { html: '', isModal: true } }; coreModule.default.directive('dashEditorView', function($compile, $location, $rootScope) { @@ -20,6 +22,7 @@ function ($, angular, coreModule) { restrict: 'A', link: function(scope, elem) { var editorScope; + var modalScope; var lastEditView; function hideEditorPane(hideToShowOtherView) { @@ -31,8 +34,7 @@ function ($, angular, coreModule) { function showEditorPane(evt, options) { if (options.editview) { - options.src = editViewMap[options.editview].src; - options.html = editViewMap[options.editview].html; + _.defaults(options, editViewMap[options.editview]); } if (lastEditView && lastEditView === options.editview) { @@ -46,6 +48,11 @@ function ($, angular, coreModule) { editorScope = options.scope ? options.scope.$new() : scope.$new(); editorScope.dismiss = function(hideToShowOtherView) { + if (modalScope) { + modalScope.dismiss(); + modalScope = null; + } + editorScope.$destroy(); lastEditView = null; editorScope = null; @@ -61,19 +68,24 @@ function ($, angular, coreModule) { var urlParams = $location.search(); if (options.editview === urlParams.editview) { delete urlParams.editview; - $location.search(urlParams); + // hack for consistently updating url + setTimeout(function() { + $rootScope.$apply(function() { + $location.search(urlParams); + }); + }); } } }; - if (options.editview === 'import') { - var modalScope = $rootScope.$new(); + if (options.isModal) { + modalScope = $rootScope.$new(); modalScope.$on("$destroy", function() { editorScope.dismiss(); }); $rootScope.appEvent('show-modal', { - templateHtml: '', + templateHtml: options.html, scope: modalScope, backdrop: 'static' }); diff --git a/public/app/core/nav_model_srv.ts b/public/app/core/nav_model_srv.ts index 058247b7fd8..cdd0ef04350 100644 --- a/public/app/core/nav_model_srv.ts +++ b/public/app/core/nav_model_srv.ts @@ -168,6 +168,12 @@ export class NavModelSrv { clickHandler: () => dashNavCtrl.openEditView('annotations') }); + menu.push({ + title: 'Permissions...', + icon: 'fa fa-fw fa-lock', + clickHandler: () => dashNavCtrl.openEditView('permissions') + }); + if (!dashboard.meta.isHome) { menu.push({ title: 'Version history', @@ -199,7 +205,7 @@ export class NavModelSrv { if (this.contextSrv.isEditor && !dashboard.meta.isFolder) { menu.push({ - title: 'Save As ...', + title: 'Save As...', icon: 'fa fa-fw fa-save', clickHandler: () => dashNavCtrl.saveDashboardAs() }); diff --git a/public/app/features/dashboard/acl/acl.html b/public/app/features/dashboard/acl/acl.html index d31b2ce6999..78c8b6d52e2 100644 --- a/public/app/features/dashboard/acl/acl.html +++ b/public/app/features/dashboard/acl/acl.html @@ -1,74 +1,116 @@ -
-
Add New Permission
-
-
-
- Type - -
-
- User - -
-
- User Group - -
-
- Permission - -
-
- -
-
-
+