From 97c13b77bfe569eac09da66687d080c697591c5a Mon Sep 17 00:00:00 2001 From: Daniel Lee Date: Fri, 28 Apr 2017 21:22:53 +0200 Subject: [PATCH] WIP: Add or update Dashboard ACL SQL Integration Tests for the guardian class too. --- pkg/models/dashboard_acl.go | 51 +++++++++++++++ pkg/models/dashboards.go | 9 +-- pkg/services/sqlstore/dashboard_acl.go | 71 +++++++++++++++++++++ pkg/services/sqlstore/dashboard_acl_test.go | 62 ++++++++++++++++++ pkg/services/sqlstore/datasource_test.go | 1 + pkg/services/sqlstore/guardian.go | 5 +- pkg/services/sqlstore/guardian_test.go | 50 +++++++++++++-- 7 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 pkg/models/dashboard_acl.go create mode 100644 pkg/services/sqlstore/dashboard_acl.go create mode 100644 pkg/services/sqlstore/dashboard_acl_test.go diff --git a/pkg/models/dashboard_acl.go b/pkg/models/dashboard_acl.go new file mode 100644 index 00000000000..bb065706dab --- /dev/null +++ b/pkg/models/dashboard_acl.go @@ -0,0 +1,51 @@ +package models + +import "time" + +type PermissionType int + +const ( + PERMISSION_EDIT PermissionType = 4 + PERMISSION_READ_ONLY_EDIT PermissionType = 2 + PERMISSION_VIEW PermissionType = 1 +) + +// Typed errors +// var ( +// ErrDashboardPermissionAlreadyAdded = errors.New("A permission has ") +// ) + +// Dashboard ACL model +type DashboardAcl struct { + Id int64 + OrgId int64 + DashboardId int64 + + Created time.Time + Updated time.Time + + UserId int64 + UserGroupId int64 + Permissions PermissionType +} + +// +// COMMANDS +// + +type AddOrUpdateDashboardPermissionCommand struct { + DashboardId int64 `json:"dashboardId" binding:"Required"` + OrgId int64 `json:"-"` + UserId int64 `json:"userId"` + UserGroupId int64 `json:"userGroupId"` + PermissionType PermissionType `json:"permissionType" binding:"Required"` +} + +// +// QUERIES +// + +type GetDashboardPermissionsQuery struct { + DashboardId int64 `json:"dashboardId" binding:"Required"` + Result []*DashboardAcl +} diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 2b704a69584..16fd51586bd 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -18,14 +18,6 @@ var ( ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty") ) -type PermissionType int - -const ( - PERMISSION_EDIT PermissionType = 4 - PERMISSION_READ_ONLY_EDIT PermissionType = 2 - PERMISSION_VIEW PermissionType = 1 -) - type UpdatePluginDashboardError struct { PluginId string } @@ -57,6 +49,7 @@ type Dashboard struct { CreatedBy int64 ParentId int64 IsFolder bool + HasAcl bool Title string Data *simplejson.Json diff --git a/pkg/services/sqlstore/dashboard_acl.go b/pkg/services/sqlstore/dashboard_acl.go new file mode 100644 index 00000000000..55cd4c80bfc --- /dev/null +++ b/pkg/services/sqlstore/dashboard_acl.go @@ -0,0 +1,71 @@ +package sqlstore + +import ( + "time" + + "github.com/go-xorm/xorm" + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" +) + +func init() { + bus.AddHandler("sql", AddOrUpdateDashboardPermission) + bus.AddHandler("sql", GetDashboardPermissions) +} + +func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error { + return inTransaction(func(sess *xorm.Session) error { + if res, err := sess.Query("SELECT 1 from 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.PermissionType, + } + if _, err := sess.Cols("permissions").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil { + return err + } + + return nil + } + + entity := m.DashboardAcl{ + OrgId: cmd.OrgId, + UserGroupId: cmd.UserGroupId, + UserId: cmd.UserId, + Created: time.Now(), + Updated: time.Now(), + DashboardId: cmd.DashboardId, + Permissions: cmd.PermissionType, + } + + cols := []string{"org_id", "created", "updated", "dashboard_id", "permissions"} + + if cmd.UserId != 0 { + cols = append(cols, "user_id") + } + + if cmd.UserGroupId != 0 { + cols = append(cols, "user_group_id") + } + + _, err := sess.Cols(cols...).Insert(&entity) + if err != nil { + return err + } + + dashboard := m.Dashboard{ + HasAcl: true, + } + if _, err := sess.Cols("has_acl").Where("id=? OR parent_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil { + return err + } + + return nil + }) +} + +func GetDashboardPermissions(query *m.GetDashboardPermissionsQuery) error { + sess := x.Where("dashboard_id=?", query.DashboardId) + query.Result = make([]*m.DashboardAcl, 0) + return sess.Find(&query.Result) +} diff --git a/pkg/services/sqlstore/dashboard_acl_test.go b/pkg/services/sqlstore/dashboard_acl_test.go new file mode 100644 index 00000000000..b14b464882f --- /dev/null +++ b/pkg/services/sqlstore/dashboard_acl_test.go @@ -0,0 +1,62 @@ +package sqlstore + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" + + m "github.com/grafana/grafana/pkg/models" +) + +func TestDashboardAclDataAccess(t *testing.T) { + Convey("Testing DB", t, func() { + InitTestDB(t) + Convey("Given a dashboard folder", func() { + savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp") + childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp") + + Convey("Should be able to add dashboard permission", func() { + err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{ + OrgId: 1, + UserId: 1, + DashboardId: savedFolder.Id, + PermissionType: m.PERMISSION_EDIT, + }) + So(err, ShouldBeNil) + + q1 := &m.GetDashboardPermissionsQuery{DashboardId: savedFolder.Id} + err = GetDashboardPermissions(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].UserId, ShouldEqual, 1) + + Convey("Should update hasAcl field to true for dashboard folder and its children", func() { + q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}} + err := GetDashboards(q2) + So(err, ShouldBeNil) + So(q2.Result[0].HasAcl, ShouldBeTrue) + So(q2.Result[1].HasAcl, ShouldBeTrue) + }) + + Convey("Should be able to update an existing permission", func() { + err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{ + OrgId: 1, + UserId: 1, + DashboardId: savedFolder.Id, + PermissionType: m.PERMISSION_READ_ONLY_EDIT, + }) + So(err, ShouldBeNil) + + q3 := &m.GetDashboardPermissionsQuery{DashboardId: savedFolder.Id} + err = GetDashboardPermissions(q3) + So(err, ShouldBeNil) + So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id) + So(q3.Result[0].Permissions, ShouldEqual, m.PERMISSION_READ_ONLY_EDIT) + So(q3.Result[0].UserId, ShouldEqual, 1) + + }) + }) + }) + }) +} diff --git a/pkg/services/sqlstore/datasource_test.go b/pkg/services/sqlstore/datasource_test.go index 2749a3cc426..548a70fe20e 100644 --- a/pkg/services/sqlstore/datasource_test.go +++ b/pkg/services/sqlstore/datasource_test.go @@ -15,6 +15,7 @@ func InitTestDB(t *testing.T) { x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr) //x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr) //x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr) + // x.ShowSQL() if err != nil { t.Fatalf("Failed to init in memory sqllite3 db %v", err) diff --git a/pkg/services/sqlstore/guardian.go b/pkg/services/sqlstore/guardian.go index 70a4462d0a0..6710642586b 100644 --- a/pkg/services/sqlstore/guardian.go +++ b/pkg/services/sqlstore/guardian.go @@ -30,7 +30,10 @@ where ( query.Result = make([]int64, 0) for _, dash := range res { - id, _ := strconv.ParseInt(string(dash["DashboardId"]), 10, 64) + id, err := strconv.ParseInt(string(dash["DashboardId"]), 10, 64) + if err != nil { + return err + } query.Result = append(query.Result, id) } diff --git a/pkg/services/sqlstore/guardian_test.go b/pkg/services/sqlstore/guardian_test.go index 72d34eb759c..dc60d3a76cb 100644 --- a/pkg/services/sqlstore/guardian_test.go +++ b/pkg/services/sqlstore/guardian_test.go @@ -4,29 +4,71 @@ import ( "testing" m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" . "github.com/smartystreets/goconvey/convey" ) -func TestGuardianAccess(t *testing.T) { +func TestGuardianDataAccess(t *testing.T) { Convey("Testing DB", t, func() { InitTestDB(t) Convey("Given one dashboard folder with two dashboard and one dashboard in the root folder", func() { folder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp") - // dashInFolder1 := insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp") - // dashInFolder2 := insertTestDashboard("test dash 45", 1, folder.Id, false, "prod") + // insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp") + // insertTestDashboard("test dash 45", 1, folder.Id, false, "prod") dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp") + currentUser := createUser("viewer") + Convey("and no acls are set", func() { Convey("should return all dashboards", func() { - query := &m.GetAllowedDashboardsQuery{UserId: 1, OrgId: 1, DashList: []int64{folder.Id, dashInRoot.Id}} + query := &m.GetAllowedDashboardsQuery{UserId: currentUser.Id, OrgId: 1, DashList: []int64{folder.Id, dashInRoot.Id}} err := GetAllowedDashboards(query) So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 2) So(query.Result[0], ShouldEqual, folder.Id) So(query.Result[1], ShouldEqual, dashInRoot.Id) }) }) + + Convey("and acl is set for dashboard folder", func() { + Convey("should not return folder", func() { + var otherUser int64 = 999 + updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT) + + query := &m.GetAllowedDashboardsQuery{UserId: currentUser.Id, OrgId: 1, DashList: []int64{folder.Id, dashInRoot.Id}} + err := GetAllowedDashboards(query) + So(err, ShouldBeNil) + So(len(query.Result), ShouldEqual, 1) + So(query.Result[0], ShouldEqual, dashInRoot.Id) + }) + }) }) }) } + +func createUser(name string) m.User { + setting.AutoAssignOrg = true + setting.AutoAssignOrgRole = "Viewer" + + currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: false} + err := CreateUser(¤tUserCmd) + So(err, ShouldBeNil) + + q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id} + GetUserOrgList(&q1) + So(q1.Result[0].Role, ShouldEqual, "Viewer") + + return currentUserCmd.Result +} + +func updateTestDashboardWithAcl(dashId int64, userId int64, permissionType m.PermissionType) { + err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{ + OrgId: 1, + UserId: userId, + DashboardId: dashId, + PermissionType: permissionType, + }) + So(err, ShouldBeNil) +}