dashboard acl fixes

This commit is contained in:
Torkel Ödegaard 2017-06-22 17:43:55 -04:00
parent 545b02139d
commit aa634402d9
4 changed files with 118 additions and 82 deletions

View File

@ -51,6 +51,7 @@ func GetDashboard(c *middleware.Context) Response {
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
if canView, err := guardian.CanView(); err != nil || !canView {
fmt.Printf("%v", err)
return dashboardGuardianResponse(err)
}

View File

@ -5,40 +5,40 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
m "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.DashboardAcl{
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: models.PERMISSION_VIEW},
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: models.PERMISSION_EDIT},
{Id: 3, OrgId: 1, DashboardId: 1, UserId: 4, Permission: models.PERMISSION_ADMIN},
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: models.PERMISSION_VIEW},
{Id: 5, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: models.PERMISSION_ADMIN},
mockResult := []*m.DashboardAclInfoDTO{
{Id: 1, OrgId: 1, DashboardId: 1, UserId: 2, Permission: m.PERMISSION_VIEW},
{Id: 2, OrgId: 1, DashboardId: 1, UserId: 3, Permission: m.PERMISSION_EDIT},
{Id: 3, OrgId: 1, DashboardId: 1, UserId: 4, Permission: m.PERMISSION_ADMIN},
{Id: 4, OrgId: 1, DashboardId: 1, UserGroupId: 1, Permission: m.PERMISSION_VIEW},
{Id: 5, OrgId: 1, DashboardId: 1, UserGroupId: 2, Permission: m.PERMISSION_ADMIN},
}
dtoRes := transformDashboardAclsToDTOs(mockResult)
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = dtoRes
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = mockResult
return nil
})
userGroupResp := []*models.UserGroup{}
bus.AddHandler("test", func(query *models.GetUserGroupsByUserQuery) error {
userGroupResp := []*m.UserGroup{}
bus.AddHandler("test", func(query *m.GetUserGroupsByUserQuery) error {
query.Result = userGroupResp
return nil
})
Convey("When user is org admin", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", models.ROLE_ADMIN, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", m.ROLE_ADMIN, func(sc *scenarioContext) {
Convey("Should be able to access ACL", func() {
sc.handlerFunc = GetDashboardAclList
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
@ -49,14 +49,14 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
So(err, ShouldBeNil)
So(len(respJSON.MustArray()), ShouldEqual, 5)
So(respJSON.GetIndex(0).Get("userId").MustInt(), ShouldEqual, 2)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, models.PERMISSION_VIEW)
So(respJSON.GetIndex(0).Get("permission").MustInt(), ShouldEqual, m.PERMISSION_VIEW)
})
})
})
Convey("When user is editor and has admin permission 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, Permission: models.PERMISSION_ADMIN})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
Convey("Should be able to access ACL", func() {
sc.handlerFunc = GetDashboardAclList
@ -66,10 +66,10 @@ 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, Permission: models.PERMISSION_ADMIN})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_ADMIN})
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
return nil
})
@ -82,10 +82,10 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
})
Convey("When user is a member of a user group in the ACL with admin permission", func() {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardsId/acl/:aclId", models.ROLE_EDITOR, func(sc *scenarioContext) {
userGroupResp = append(userGroupResp, &models.UserGroup{Id: 2, OrgId: 1, Name: "UG2"})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardsId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
userGroupResp = append(userGroupResp, &m.UserGroup{Id: 2, OrgId: 1, Name: "UG2"})
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
return nil
})
@ -100,8 +100,8 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
})
Convey("When user is editor and has edit permission 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, Permission: models.PERMISSION_EDIT})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
Convey("Should not be able to access ACL", func() {
sc.handlerFunc = GetDashboardAclList
@ -111,10 +111,10 @@ 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, Permission: models.PERMISSION_EDIT})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/1", "/api/dashboards/id/:dashboardId/acl/:aclId", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_EDIT})
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
return nil
})
@ -128,7 +128,7 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
})
Convey("When user is editor and not in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/1/acl", "/api/dashboards/id/:dashboardsId/acl", m.ROLE_EDITOR, func(sc *scenarioContext) {
Convey("Should not be able to access ACL", func() {
sc.handlerFunc = GetDashboardAclList
@ -138,9 +138,9 @@ 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, Permission: models.PERMISSION_VIEW})
bus.AddHandler("test3", func(cmd *models.RemoveDashboardAclCommand) error {
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/id/1/acl/user/1", "/api/dashboards/id/:dashboardsId/acl/user/:userId", m.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &m.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permission: m.PERMISSION_VIEW})
bus.AddHandler("test3", func(cmd *m.RemoveDashboardAclCommand) error {
return nil
})
@ -155,11 +155,11 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
})
}
func transformDashboardAclsToDTOs(acls []*models.DashboardAcl) []*models.DashboardAclInfoDTO {
dtos := make([]*models.DashboardAclInfoDTO, 0)
func transformDashboardAclsToDTOs(acls []*m.DashboardAclInfoDTO) []*m.DashboardAclInfoDTO {
dtos := make([]*m.DashboardAclInfoDTO, 0)
for _, acl := range acls {
dto := &models.DashboardAclInfoDTO{
dto := &m.DashboardAclInfoDTO{
Id: acl.Id,
OrgId: acl.OrgId,
DashboardId: acl.DashboardId,

View File

@ -12,7 +12,7 @@ import (
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/models"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
@ -20,23 +20,35 @@ import (
func TestDashboardApiEndpoint(t *testing.T) {
Convey("Given a dashboard with a parent folder which does not have an acl", t, func() {
fakeDash := models.NewDashboard("Child dash")
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 1
fakeDash.ParentId = 1
fakeDash.HasAcl = false
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
return nil
})
aclMockResp := []*models.DashboardAcl{}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
viewerRole := m.ROLE_VIEWER
editorRole := m.ROLE_EDITOR
aclMockResp := []*m.DashboardAclInfoDTO{
{Role: &viewerRole, Permission: m.PERMISSION_VIEW},
{Role: &editorRole, Permission: m.PERMISSION_EDIT},
}
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
cmd := models.SaveDashboardCommand{
bus.AddHandler("test", func(query *m.GetUserGroupsByUserQuery) error {
query.Result = []*m.UserGroup{}
return nil
})
cmd := m.SaveDashboardCommand{
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"parentId": fakeDash.ParentId,
"title": fakeDash.Title,
@ -45,7 +57,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}
Convey("When user is an Org Viewer", func() {
role := models.ROLE_VIEWER
role := m.ROLE_VIEWER
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
dash := GetDashboardShouldReturn200(sc)
@ -78,12 +90,12 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When user is an Org Read Only Editor", func() {
role := models.ROLE_READ_ONLY_EDITOR
role := m.ROLE_READ_ONLY_EDITOR
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
dash := GetDashboardShouldReturn200(sc)
Convey("Should be able to edit but not save the dashboard", func() {
So(dash.Meta.CanEdit, ShouldBeTrue)
Convey("Should be able to view but not save the dashboard", func() {
So(dash.Meta.CanEdit, ShouldBeFalse)
So(dash.Meta.CanSave, ShouldBeFalse)
})
})
@ -110,7 +122,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When user is an Org Editor", func() {
role := models.ROLE_EDITOR
role := m.ROLE_EDITOR
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
dash := GetDashboardShouldReturn200(sc)
@ -142,12 +154,12 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When saving a dashboard folder in another folder", func() {
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
query.Result.IsFolder = true
return nil
})
invalidCmd := models.SaveDashboardCommand{
invalidCmd := m.SaveDashboardCommand{
ParentId: fakeDash.ParentId,
IsFolder: true,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
@ -166,35 +178,35 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("Given a dashboard with a parent folder which has an acl", t, func() {
fakeDash := models.NewDashboard("Child dash")
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 1
fakeDash.ParentId = 1
fakeDash.HasAcl = true
aclMockResp := []*models.DashboardAcl{
aclMockResp := []*m.DashboardAclInfoDTO{
{
DashboardId: 1,
Permission: models.PERMISSION_EDIT,
Permission: m.PERMISSION_EDIT,
UserId: 200,
},
}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = aclMockResp
return nil
})
bus.AddHandler("test", func(query *models.GetDashboardQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
return nil
})
bus.AddHandler("test", func(query *models.GetUserGroupsByUserQuery) error {
query.Result = []*models.UserGroup{}
bus.AddHandler("test", func(query *m.GetUserGroupsByUserQuery) error {
query.Result = []*m.UserGroup{}
return nil
})
cmd := models.SaveDashboardCommand{
cmd := m.SaveDashboardCommand{
ParentId: fakeDash.ParentId,
Dashboard: simplejson.NewFromAny(map[string]interface{}{
"id": fakeDash.Id,
@ -204,7 +216,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
}
Convey("When user is an Org Viewer and has no permissions for this dashboard", func() {
role := models.ROLE_VIEWER
role := m.ROLE_VIEWER
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
@ -237,7 +249,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When user is an Org Editor and has no permissions for this dashboard", func() {
role := models.ROLE_EDITOR
role := m.ROLE_EDITOR
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/2", "/api/dashboards/:id", role, func(sc *scenarioContext) {
sc.handlerFunc = GetDashboard
@ -270,13 +282,13 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When user is an Org Viewer but has an edit permission", func() {
role := models.ROLE_VIEWER
role := m.ROLE_VIEWER
mockResult := []*models.DashboardAcl{
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_EDIT},
mockResult := []*m.DashboardAclInfoDTO{
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_EDIT},
}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = mockResult
return nil
})
@ -312,13 +324,13 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
Convey("When user is an Org Editor but has a view permission", func() {
role := models.ROLE_EDITOR
role := m.ROLE_EDITOR
mockResult := []*models.DashboardAcl{
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: models.PERMISSION_VIEW},
mockResult := []*m.DashboardAclInfoDTO{
{Id: 1, OrgId: 1, DashboardId: 2, UserId: 1, Permission: m.PERMISSION_VIEW},
}
bus.AddHandler("test", func(query *models.GetDashboardAclInfoListQuery) error {
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
query.Result = mockResult
return nil
})
@ -369,8 +381,8 @@ func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta
}
func CallGetDashboardVersion(sc *scenarioContext) {
bus.AddHandler("test", func(query *models.GetDashboardVersionQuery) error {
query.Result = &models.DashboardVersion{}
bus.AddHandler("test", func(query *m.GetDashboardVersionQuery) error {
query.Result = &m.DashboardVersion{}
return nil
})
@ -379,8 +391,8 @@ func CallGetDashboardVersion(sc *scenarioContext) {
}
func CallGetDashboardVersions(sc *scenarioContext) {
bus.AddHandler("test", func(query *models.GetDashboardVersionsQuery) error {
query.Result = []*models.DashboardVersionDTO{}
bus.AddHandler("test", func(query *m.GetDashboardVersionsQuery) error {
query.Result = []*m.DashboardVersionDTO{}
return nil
})
@ -389,7 +401,7 @@ func CallGetDashboardVersions(sc *scenarioContext) {
}
func CallDeleteDashboard(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *models.DeleteDashboardCommand) error {
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
return nil
})
@ -402,8 +414,8 @@ func CallPostDashboard(sc *scenarioContext) {
return nil
})
bus.AddHandler("test", func(cmd *models.SaveDashboardCommand) error {
cmd.Result = &models.Dashboard{Id: 2, Slug: "Dash", Version: 2}
bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error {
cmd.Result = &m.Dashboard{Id: 2, Slug: "Dash", Version: 2}
return nil
})
@ -414,7 +426,7 @@ func CallPostDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func postDashboardScenario(desc string, url string, routePattern string, role models.RoleType, cmd models.SaveDashboardCommand, fn scenarioFunc) {
func postDashboardScenario(desc string, url string, routePattern string, role m.RoleType, cmd m.SaveDashboardCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()

View File

@ -50,27 +50,50 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
return false, err
}
orgRole := g.user.OrgRole
if orgRole == m.ROLE_READ_ONLY_EDITOR {
orgRole = m.ROLE_VIEWER
}
userGroupAclItems := []*m.DashboardAclInfoDTO{}
for _, p := range acl {
// user match
if p.UserId == g.user.UserId && p.Permission >= permission {
return true, nil
}
// role match
if p.Role != nil {
if *p.Role == orgRole && p.Permission >= permission {
return true, nil
}
}
// remember this rule for later
if p.UserGroupId > 0 {
userGroupAclItems = append(userGroupAclItems, p)
}
}
// do we have group rules?
if len(userGroupAclItems) == 0 {
return false, nil
}
// load groups
userGroups, err := g.getUserGroups()
if err != nil {
return false, err
}
// evalute group rules
for _, p := range acl {
if p.UserId == g.user.UserId && p.Permission >= permission {
return true, nil
}
for _, ug := range userGroups {
if ug.Id == p.UserGroupId && p.Permission >= permission {
return true, nil
}
}
if p.Role != nil {
if *p.Role == g.user.OrgRole && p.Permission >= permission {
return true, nil
}
}
}
return false, nil