Переглянути джерело

WIP: get Dashboard Permissions

The guardian class checks if the user is allowed to get the
permissions for a dashboard.
Daniel Lee 8 роки тому
батько
коміт
f1e1da39e3

+ 4 - 0
pkg/api/api.go

@@ -247,6 +247,10 @@ func (hs *HttpServer) registerRoutes() {
 			r.Get("/home", wrap(GetHomeDashboard))
 			r.Get("/home", wrap(GetHomeDashboard))
 			r.Get("/tags", GetDashboardTags)
 			r.Get("/tags", GetDashboardTags)
 			r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
 			r.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
+
+			r.Group("/:id/acl", func() {
+				r.Get("/", wrap(GetDashboardAcl))
+			}, reqSignedIn)
 		})
 		})
 
 
 		// Dashboard snapshots
 		// Dashboard snapshots

+ 2 - 0
pkg/api/dashboard.go

@@ -79,6 +79,8 @@ func GetDashboard(c *middleware.Context) {
 			UpdatedBy: updater,
 			UpdatedBy: updater,
 			CreatedBy: creator,
 			CreatedBy: creator,
 			Version:   dash.Version,
 			Version:   dash.Version,
+			HasAcl:    dash.HasAcl,
+			IsFolder:  dash.IsFolder,
 		},
 		},
 	}
 	}
 
 

+ 31 - 0
pkg/api/dashboard_acl.go

@@ -0,0 +1,31 @@
+package api
+
+import (
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/middleware"
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/services/guardian"
+	"github.com/grafana/grafana/pkg/util"
+)
+
+func GetDashboardAcl(c *middleware.Context) Response {
+	dashboardId := c.ParamsInt64(":id")
+
+	hasPermission, err := guardian.CanViewAcl(dashboardId, c.OrgRole, c.IsGrafanaAdmin, c.OrgId, c.UserId)
+
+	if err != nil {
+		return ApiError(500, "Failed to get Dashboard ACL", err)
+	}
+
+	if !hasPermission {
+		return Json(403, util.DynMap{"status": "Forbidden", "message": "Does not have access to this Dashboard ACL"})
+	}
+
+	query := m.GetDashboardPermissionsQuery{DashboardId: dashboardId}
+
+	if err := bus.Dispatch(&query); err != nil {
+		return ApiError(500, "Failed to get Dashboard ACL", err)
+	}
+
+	return Json(200, &query.Result)
+}

+ 57 - 0
pkg/api/dashboard_acl_test.go

@@ -0,0 +1,57 @@
+package api
+
+import (
+	"testing"
+
+	"github.com/grafana/grafana/pkg/bus"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/models"
+
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestDashboardAclApiEndpoint(t *testing.T) {
+	Convey("Given a dashboard acl", t, func() {
+		mockResult := []*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},
+		}
+		bus.AddHandler("test", func(query *models.GetDashboardPermissionsQuery) error {
+			query.Result = mockResult
+			return nil
+		})
+
+		Convey("When user is org admin", func() {
+			loggedInUserScenarioWithRole("When calling GET on", "/api/dashboard/1/acl", models.ROLE_ADMIN, func(sc *scenarioContext) {
+				Convey("Should be able to access ACL", func() {
+					sc.handlerFunc = GetDashboardAcl
+					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
+
+					So(sc.resp.Code, ShouldEqual, 200)
+
+					respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
+					So(err, ShouldBeNil)
+					So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
+					So(respJSON.GetIndex(0).Get("permissions").MustInt(), ShouldEqual, models.PERMISSION_EDIT)
+				})
+			})
+		})
+
+		Convey("When user is editor and not in the ACL", func() {
+			loggedInUserScenarioWithRole("When calling GET on", "/api/dashboard/1/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
+
+				bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
+					query.Result = []int64{1}
+					return nil
+				})
+
+				Convey("Should not be able to access ACL", func() {
+					sc.handlerFunc = GetDashboardAcl
+					sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
+
+					So(sc.resp.Code, ShouldEqual, 403)
+				})
+			})
+		})
+	})
+}

+ 5 - 1
pkg/api/datasources_test.go

@@ -56,6 +56,10 @@ func TestDataSourcesProxy(t *testing.T) {
 }
 }
 
 
 func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
 func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
+	loggedInUserScenarioWithRole(desc, url, models.ROLE_EDITOR, fn)
+}
+
+func loggedInUserScenarioWithRole(desc string, url string, role models.RoleType, fn scenarioFunc) {
 	Convey(desc+" "+url, func() {
 	Convey(desc+" "+url, func() {
 		defer bus.ClearBusHandlers()
 		defer bus.ClearBusHandlers()
 
 
@@ -77,7 +81,7 @@ func loggedInUserScenario(desc string, url string, fn scenarioFunc) {
 			sc.context = c
 			sc.context = c
 			sc.context.UserId = TestUserID
 			sc.context.UserId = TestUserID
 			sc.context.OrgId = TestOrgID
 			sc.context.OrgId = TestOrgID
-			sc.context.OrgRole = models.ROLE_EDITOR
+			sc.context.OrgRole = role
 			if sc.handlerFunc != nil {
 			if sc.handlerFunc != nil {
 				return sc.handlerFunc(sc.context)
 				return sc.handlerFunc(sc.context)
 			}
 			}

+ 2 - 0
pkg/api/dtos/dashboard.go

@@ -21,6 +21,8 @@ type DashboardMeta struct {
 	UpdatedBy  string    `json:"updatedBy"`
 	UpdatedBy  string    `json:"updatedBy"`
 	CreatedBy  string    `json:"createdBy"`
 	CreatedBy  string    `json:"createdBy"`
 	Version    int       `json:"version"`
 	Version    int       `json:"version"`
+	HasAcl     bool      `json:"hasAcl"`
+	IsFolder   bool      `json:"isFolder"`
 }
 }
 
 
 type DashboardFullWithMeta struct {
 type DashboardFullWithMeta struct {

+ 25 - 9
pkg/models/dashboard_acl.go

@@ -17,16 +17,32 @@ const (
 
 
 // Dashboard ACL model
 // Dashboard ACL model
 type DashboardAcl struct {
 type DashboardAcl struct {
-	Id          int64
-	OrgId       int64
-	DashboardId int64
+	Id          int64 `json:"id"`
+	OrgId       int64 `json:"-"`
+	DashboardId int64 `json:"dashboardId"`
+
+	Created time.Time `json:"created"`
+	Updated time.Time `json:"updated"`
+
+	UserId      int64          `json:"userId"`
+	UserGroupId int64          `json:"userGroupId"`
+	Permissions PermissionType `json:"permissions"`
+}
+
+type DashboardAclInfoDTO struct {
+	Id          int64 `json:"id"`
+	OrgId       int64 `json:"-"`
+	DashboardId int64 `json:"dashboardId"`
 
 
-	Created time.Time
-	Updated time.Time
+	Created time.Time `json:"created"`
+	Updated time.Time `json:"updated"`
 
 
-	UserId      int64
-	UserGroupId int64
-	Permissions PermissionType
+	UserId      int64          `json:"userId"`
+	UserLogin   string         `json:"userLogin"`
+	UserEmail   string         `json:"userEmail"`
+	UserGroupId int64          `json:"userGroupId"`
+	UserGroup   string         `json:"userGroup"`
+	Permissions PermissionType `json:"permissions"`
 }
 }
 
 
 //
 //
@@ -54,5 +70,5 @@ type RemoveDashboardPermissionCommand struct {
 
 
 type GetDashboardPermissionsQuery struct {
 type GetDashboardPermissionsQuery struct {
 	DashboardId int64 `json:"dashboardId" binding:"Required"`
 	DashboardId int64 `json:"dashboardId" binding:"Required"`
-	Result      []*DashboardAcl
+	Result      []*DashboardAclInfoDTO
 }
 }

+ 18 - 0
pkg/services/guardian/guardian.go

@@ -21,6 +21,24 @@ func RemoveRestrictedDashboards(dashList []int64, orgId int64, userId int64) ([]
 	return filteredList, err
 	return filteredList, err
 }
 }
 
 
+// CanViewAcl determines if a user has permission to view a dashboard's ACL
+func CanViewAcl(dashboardId int64, role m.RoleType, isGrafanaAdmin bool, orgId int64, userId int64) (bool, error) {
+	if role == m.ROLE_ADMIN || isGrafanaAdmin {
+		return true, nil
+	}
+
+	filteredList, err := getAllowedDashboards([]int64{dashboardId}, orgId, userId)
+	if err != nil {
+		return false, err
+	}
+
+	if len(filteredList) > 1 && filteredList[0] == dashboardId {
+		return true, nil
+	}
+
+	return false, nil
+}
+
 func getUser(userId int64) (*m.SignedInUser, error) {
 func getUser(userId int64) (*m.SignedInUser, error) {
 	query := m.GetSignedInUserQuery{UserId: userId}
 	query := m.GetSignedInUserQuery{UserId: userId}
 	err := bus.Dispatch(&query)
 	err := bus.Dispatch(&query)

+ 20 - 7
pkg/services/sqlstore/dashboard_acl.go

@@ -16,13 +16,14 @@ func init() {
 
 
 func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error {
 func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error {
 	return inTransaction(func(sess *xorm.Session) 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 {
+		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
 			return err
 		} else if len(res) == 1 {
 		} else if len(res) == 1 {
 			entity := m.DashboardAcl{
 			entity := m.DashboardAcl{
 				Permissions: cmd.PermissionType,
 				Permissions: cmd.PermissionType,
+				Updated:     time.Now(),
 			}
 			}
-			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 {
+			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 {
 				return err
 				return err
 			}
 			}
 
 
@@ -67,8 +68,8 @@ func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand
 
 
 func RemoveDashboardPermission(cmd *m.RemoveDashboardPermissionCommand) error {
 func RemoveDashboardPermission(cmd *m.RemoveDashboardPermissionCommand) error {
 	return inTransaction(func(sess *xorm.Session) error {
 	return inTransaction(func(sess *xorm.Session) error {
-		var rawSql = "DELETE FROM dashboard_acl WHERE dashboard_id =? and (user_group_id=? or user_id=?)"
-		_, err := sess.Exec(rawSql, cmd.DashboardId, cmd.UserGroupId, cmd.UserId)
+		var rawSQL = "DELETE FROM " + dialect.Quote("dashboard_acl") + " WHERE dashboard_id =? and (user_group_id=? or user_id=?)"
+		_, err := sess.Exec(rawSQL, cmd.DashboardId, cmd.UserGroupId, cmd.UserId)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -78,7 +79,19 @@ func RemoveDashboardPermission(cmd *m.RemoveDashboardPermissionCommand) error {
 }
 }
 
 
 func GetDashboardPermissions(query *m.GetDashboardPermissionsQuery) error {
 func GetDashboardPermissions(query *m.GetDashboardPermissionsQuery) error {
-	sess := x.Where("dashboard_id=?", query.DashboardId)
-	query.Result = make([]*m.DashboardAcl, 0)
-	return sess.Find(&query.Result)
+	rawSQL := `SELECT
+  da.*,
+  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=?`
+
+	query.Result = make([]*m.DashboardAclInfoDTO, 0)
+
+	err := x.SQL(rawSQL, query.DashboardId).Find(&query.Result)
+
+	return err
 }
 }

+ 6 - 3
pkg/services/sqlstore/dashboard_acl_test.go

@@ -11,14 +11,15 @@ import (
 func TestDashboardAclDataAccess(t *testing.T) {
 func TestDashboardAclDataAccess(t *testing.T) {
 	Convey("Testing DB", t, func() {
 	Convey("Testing DB", t, func() {
 		InitTestDB(t)
 		InitTestDB(t)
-		Convey("Given a dashboard folder", func() {
+		Convey("Given a dashboard folder and a user", func() {
+			currentUser := createUser("viewer", "Viewer", false)
 			savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
 			savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
 			childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
 			childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
 
 
 			Convey("Should be able to add dashboard permission", func() {
 			Convey("Should be able to add dashboard permission", func() {
 				err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
 				err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
 					OrgId:          1,
 					OrgId:          1,
-					UserId:         1,
+					UserId:         currentUser.Id,
 					DashboardId:    savedFolder.Id,
 					DashboardId:    savedFolder.Id,
 					PermissionType: m.PERMISSION_EDIT,
 					PermissionType: m.PERMISSION_EDIT,
 				})
 				})
@@ -29,7 +30,9 @@ func TestDashboardAclDataAccess(t *testing.T) {
 				So(err, ShouldBeNil)
 				So(err, ShouldBeNil)
 				So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
 				So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
 				So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
 				So(q1.Result[0].Permissions, ShouldEqual, m.PERMISSION_EDIT)
-				So(q1.Result[0].UserId, ShouldEqual, 1)
+				So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
+				So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
+				So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
 
 
 				Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
 				Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
 					q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}
 					q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}

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

@@ -28,7 +28,7 @@ where (
 	rawSQL = fmt.Sprintf("%v and d.id in(%v)", rawSQL, dashboardIds)
 	rawSQL = fmt.Sprintf("%v and d.id in(%v)", rawSQL, dashboardIds)
 
 
 	query.Result = make([]int64, 0)
 	query.Result = make([]int64, 0)
-	err := x.In("DashboardId", query.DashList).SQL(rawSQL, query.UserId, query.UserId, query.UserId, query.UserId, query.OrgId).Find(&query.Result)
+	err := x.SQL(rawSQL, query.UserId, query.UserId, query.UserId, query.UserId, query.OrgId).Find(&query.Result)
 
 
 	if err != nil {
 	if err != nil {
 		return err
 		return err

+ 5 - 5
pkg/services/sqlstore/guardian_test.go

@@ -19,7 +19,7 @@ func TestGuardianDataAccess(t *testing.T) {
 			insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
 			insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
 			insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
 			insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
 
 
-			currentUser := createUser("viewer")
+			currentUser := createUser("viewer", "Viewer", false)
 
 
 			Convey("and no acls are set", func() {
 			Convey("and no acls are set", func() {
 				Convey("should return all dashboards", func() {
 				Convey("should return all dashboards", func() {
@@ -61,17 +61,17 @@ func TestGuardianDataAccess(t *testing.T) {
 	})
 	})
 }
 }
 
 
-func createUser(name string) m.User {
+func createUser(name string, role string, isAdmin bool) m.User {
 	setting.AutoAssignOrg = true
 	setting.AutoAssignOrg = true
-	setting.AutoAssignOrgRole = "Viewer"
+	setting.AutoAssignOrgRole = role
 
 
-	currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: false}
+	currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
 	err := CreateUser(&currentUserCmd)
 	err := CreateUser(&currentUserCmd)
 	So(err, ShouldBeNil)
 	So(err, ShouldBeNil)
 
 
 	q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
 	q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
 	GetUserOrgList(&q1)
 	GetUserOrgList(&q1)
-	So(q1.Result[0].Role, ShouldEqual, "Viewer")
+	So(q1.Result[0].Role, ShouldEqual, role)
 
 
 	return currentUserCmd.Result
 	return currentUserCmd.Result
 }
 }