Commit f1e1da39 by Daniel Lee

WIP: get Dashboard Permissions

The guardian class checks if the user is allowed to get the
permissions for a dashboard.
parent 074ef7ce
...@@ -247,6 +247,10 @@ func (hs *HttpServer) registerRoutes() { ...@@ -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
......
...@@ -79,6 +79,8 @@ func GetDashboard(c *middleware.Context) { ...@@ -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,
}, },
} }
......
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)
}
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)
})
})
})
})
}
...@@ -56,6 +56,10 @@ func TestDataSourcesProxy(t *testing.T) { ...@@ -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) { ...@@ -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)
} }
......
...@@ -21,6 +21,8 @@ type DashboardMeta struct { ...@@ -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 {
......
...@@ -17,16 +17,32 @@ const ( ...@@ -17,16 +17,32 @@ const (
// Dashboard ACL model // Dashboard ACL model
type DashboardAcl struct { type DashboardAcl struct {
Id int64 Id int64 `json:"id"`
OrgId int64 OrgId int64 `json:"-"`
DashboardId int64 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 Created time.Time `json:"created"`
Updated time.Time Updated time.Time `json:"updated"`
UserId int64 UserId int64 `json:"userId"`
UserGroupId int64 UserLogin string `json:"userLogin"`
Permissions PermissionType UserEmail string `json:"userEmail"`
UserGroupId int64 `json:"userGroupId"`
UserGroup string `json:"userGroup"`
Permissions PermissionType `json:"permissions"`
} }
// //
...@@ -54,5 +70,5 @@ type RemoveDashboardPermissionCommand struct { ...@@ -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
} }
...@@ -21,6 +21,24 @@ func RemoveRestrictedDashboards(dashList []int64, orgId int64, userId int64) ([] ...@@ -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)
......
...@@ -16,13 +16,14 @@ func init() { ...@@ -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 ...@@ -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=?)" 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) _, 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 { ...@@ -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) rawSQL := `SELECT
query.Result = make([]*m.DashboardAcl, 0) da.*,
return sess.Find(&query.Result) 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
} }
...@@ -11,14 +11,15 @@ import ( ...@@ -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) { ...@@ -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}}
......
...@@ -28,7 +28,7 @@ where ( ...@@ -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
......
...@@ -19,7 +19,7 @@ func TestGuardianDataAccess(t *testing.T) { ...@@ -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) { ...@@ -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
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment