refactoring dashboad folder acl checks

This commit is contained in:
Torkel Ödegaard
2017-06-19 11:54:37 -04:00
21 changed files with 311 additions and 277 deletions
+35 -26
View File
@@ -148,12 +148,14 @@ func GetDashboard(query *m.GetDashboardQuery) error {
}
type DashboardSearchProjection struct {
Id int64
Title string
Slug string
Term string
IsFolder bool
ParentId int64
Id int64
Title string
Slug string
Term string
IsFolder bool
ParentId int64
FolderSlug string
FolderTitle string
}
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
@@ -166,8 +168,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
dashboard.slug,
dashboard_tag.term,
dashboard.is_folder,
dashboard.parent_id
dashboard.parent_id,
f.slug as folder_slug,
f.title as folder_title
FROM dashboard
LEFT OUTER JOIN dashboard f on f.id = dashboard.parent_id
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
if query.IsStarred {
@@ -175,12 +180,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
}
sql.WriteString(` WHERE dashboard.org_id=?`)
params = append(params, query.OrgId)
params = append(params, query.SignedInUser.OrgId)
if query.IsStarred {
sql.WriteString(` AND star.user_id=?`)
params = append(params, query.UserId)
params = append(params, query.SignedInUser.UserId)
}
if len(query.DashboardIds) > 0 {
@@ -196,6 +200,23 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
sql.WriteString(")")
}
if query.SignedInUser.OrgRole != m.ROLE_ADMIN {
allowedDashboardsSubQuery := ` AND (dashboard.has_acl = 0 OR dashboard.id in (
SELECT distinct d.id AS DashboardId
FROM dashboard AS d
LEFT JOIN dashboard AS df ON d.parent_id = df.id
LEFT JOIN dashboard_acl as dfa on d.parent_id = dfa.dashboard_id or d.id = dfa.dashboard_id
LEFT JOIN user_group_member as ugm on ugm.user_group_id = dfa.user_group_id
WHERE
d.has_acl = 1 and
(dfa.user_id = ? or ugm.user_id = ?)
and d.org_id = ?
))`
sql.WriteString(allowedDashboardsSubQuery)
params = append(params, query.SignedInUser.UserId, query.SignedInUser.UserId, query.SignedInUser.OrgId)
}
if len(query.Title) > 0 {
sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
params = append(params, "%"+query.Title+"%")
@@ -250,26 +271,13 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
// appends parent folders for any hits to the search result
func appendDashboardFolders(res []DashboardSearchProjection) ([]DashboardSearchProjection, error) {
var dashboardFolderIds []int64
for _, item := range res {
if item.ParentId > 0 {
dashboardFolderIds = append(dashboardFolderIds, item.ParentId)
}
}
if len(dashboardFolderIds) > 0 {
folderQuery := &m.GetDashboardsQuery{DashboardIds: dashboardFolderIds}
err := GetDashboards(folderQuery)
if err != nil {
return nil, err
}
for _, folder := range folderQuery.Result {
res = append(res, DashboardSearchProjection{
Id: folder.Id,
Id: item.ParentId,
IsFolder: true,
Slug: folder.Slug,
Title: folder.Title,
Slug: item.FolderSlug,
Title: item.FolderTitle,
})
}
}
@@ -371,6 +379,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
"DELETE FROM dashboard WHERE id = ?",
"DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?",
"DELETE FROM dashboard_version WHERE dashboard_id = ?",
"DELETE FROM dashboard WHERE parent_id = ?",
}
for _, sql := range deletes {
+7 -9
View File
@@ -11,7 +11,7 @@ func init() {
bus.AddHandler("sql", AddOrUpdateDashboardPermission)
bus.AddHandler("sql", RemoveDashboardPermission)
bus.AddHandler("sql", GetDashboardPermissions)
bus.AddHandler("sql", GetDashboardAcl)
bus.AddHandler("sql", GetInheritedDashboardAcl)
}
func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error {
@@ -86,33 +86,31 @@ func RemoveDashboardPermission(cmd *m.RemoveDashboardPermissionCommand) error {
})
}
func GetDashboardAcl(query *m.GetDashboardAclQuery) error {
func GetInheritedDashboardAcl(query *m.GetInheritedDashboardAclQuery) error {
rawSQL := `SELECT
da.id,
da.org_id,
da.id,
da.dashboard_id,
da.user_id,
da.user_group_id,
da.permissions,
da.created,
da.updated,
FROM` + dialect.Quote("dashboard_acl") + ` as da
WHERE dashboard_id IN (
da.updated
FROM dashboard_acl as da
WHERE da.dashboard_id IN (
SELECT id FROM dashboard where id = ?
UNION
SELECT parent_id from dashboard where id = ?
)`
) AND org_id = ?`
query.Result = make([]*m.DashboardAcl, 0)
return x.SQL(rawSQL, query.DashboardId).Find(&query.Result)
return x.SQL(rawSQL, query.DashboardId, query.DashboardId, query.OrgId).Find(&query.Result)
}
func GetDashboardPermissions(query *m.GetDashboardPermissionsQuery) error {
rawSQL := `SELECT
da.id,
da.org_id,
da.id,
da.dashboard_id,
da.user_id,
da.user_group_id,
@@ -25,6 +25,47 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(err, ShouldEqual, m.ErrDashboardPermissionUserOrUserGroupEmpty)
})
Convey("Given dashboard folder permission", func() {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: savedFolder.Id,
Permissions: m.PERMISSION_EDIT,
})
So(err, ShouldBeNil)
Convey("When reading dashboard acl should include acl for parent folder", func() {
query := m.GetInheritedDashboardAclQuery{OrgId: 1, DashboardId: childDash.Id}
err := GetDashboardAcl(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
})
Convey("Given child dashboard permission", func() {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1,
UserId: currentUser.Id,
DashboardId: childDash.Id,
Permissions: m.PERMISSION_EDIT,
})
So(err, ShouldBeNil)
Convey("When reading dashboard acl should include acl for parent folder and child", func() {
query := m.GetInheritedDashboardAclQuery{OrgId: 1, DashboardId: childDash.Id}
err := GetDashboardAcl(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
So(query.Result[1].DashboardId, ShouldEqual, childDash.Id)
})
})
})
Convey("Should be able to add dashboard permission", func() {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1,
+123 -12
View File
@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search"
"github.com/grafana/grafana/pkg/setting"
)
func insertTestDashboard(title string, orgId int64, parentId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
@@ -113,9 +114,10 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard and return in folder hierarchy", func() {
query := search.FindPersistedDashboardsQuery{
Title: "test dash 23",
OrgId: 1,
Mode: "tree",
Title: "test dash 23",
OrgId: 1,
Mode: "tree",
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
@@ -132,8 +134,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard folder", func() {
query := search.FindPersistedDashboardsQuery{
Title: "1 test dash folder",
OrgId: 1,
Title: "1 test dash folder",
OrgId: 1,
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
@@ -146,8 +149,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for a dashboard folder's children", func() {
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
ParentId: savedFolder.Id,
OrgId: 1,
ParentId: savedFolder.Id,
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
@@ -161,9 +165,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard by dashboard ids", func() {
Convey("should be able to find two dashboards by id", func() {
query := search.FindPersistedDashboardsQuery{
DashboardIds: []int{2, 3},
OrgId: 1,
DashboardIds: []int64{2, 3},
Mode: "tree",
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
@@ -180,8 +184,8 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("DashboardIds that does not exists should not cause errors", func() {
query := search.FindPersistedDashboardsQuery{
DashboardIds: []int{1000},
OrgId: 1,
DashboardIds: []int64{1000},
SignedInUser: &m.SignedInUser{OrgId: 1},
}
err := SearchDashboards(&query)
@@ -244,6 +248,23 @@ func TestDashboardDataAccess(t *testing.T) {
So(query.Result.ParentId, ShouldEqual, 0)
})
Convey("Should be able to delete a dashboard folder and its children", func() {
deleteCmd := &m.DeleteDashboardCommand{Id: savedFolder.Id}
err := DeleteDashboard(deleteCmd)
So(err, ShouldBeNil)
query := search.FindPersistedDashboardsQuery{
OrgId: 1,
ParentId: savedFolder.Id,
SignedInUser: &m.SignedInUser{},
}
err = SearchDashboards(&query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 0)
})
Convey("Should be able to get dashboard tags", func() {
query := m.GetDashboardTagsQuery{OrgId: 1}
@@ -266,7 +287,7 @@ func TestDashboardDataAccess(t *testing.T) {
})
Convey("Should be able to search for starred dashboards", func() {
query := search.FindPersistedDashboardsQuery{OrgId: 1, UserId: 10, IsStarred: true}
query := search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: 10, OrgId: 1}, IsStarred: true}
err := SearchDashboards(&query)
So(err, ShouldBeNil)
@@ -275,5 +296,95 @@ func TestDashboardDataAccess(t *testing.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")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
Convey("and no acls are set", func() {
Convey("should return all dashboards", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("and acl is set for dashboard folder", func() {
var otherUser int64 = 999
updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
Convey("should not return folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 1)
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
})
Convey("when the user is given permission", func() {
updateTestDashboardWithAcl(folder.Id, currentUser.Id, m.PERMISSION_EDIT)
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, dashInRoot.Id}}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
Convey("when the user is an admin", func() {
Convey("should be able to access folder", func() {
query := &search.FindPersistedDashboardsQuery{
SignedInUser: &m.SignedInUser{
UserId: currentUser.Id,
OrgId: 1,
OrgRole: m.ROLE_ADMIN,
},
OrgId: 1,
DashboardIds: []int64{folder.Id, dashInRoot.Id},
}
err := SearchDashboards(query)
So(err, ShouldBeNil)
So(len(query.Result), ShouldEqual, 2)
So(query.Result[0].Id, ShouldEqual, folder.Id)
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
})
})
})
})
})
}
func createUser(name string, role string, isAdmin bool) m.User {
setting.AutoAssignOrg = true
setting.AutoAssignOrgRole = role
currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
err := CreateUser(&currentUserCmd)
So(err, ShouldBeNil)
q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
GetUserOrgList(&q1)
So(q1.Result[0].Role, ShouldEqual, role)
return currentUserCmd.Result
}
func updateTestDashboardWithAcl(dashId int64, userId int64, permissions m.PermissionType) {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1,
UserId: userId,
DashboardId: dashId,
Permissions: permissions,
})
So(err, ShouldBeNil)
}
-42
View File
@@ -1,42 +0,0 @@
package sqlstore
import (
"fmt"
"strings"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
func init() {
bus.AddHandler("sql", GetAllowedDashboards)
}
func GetAllowedDashboards(query *m.GetAllowedDashboardsQuery) error {
dashboardIds := arrayToString(query.DashList, ",")
rawSQL := `select distinct d.id as DashboardId
from dashboard as d
left join dashboard as df on d.parent_id = df.id
left join dashboard_acl as dfa on d.parent_id = dfa.dashboard_id or d.id = dfa.dashboard_id
left join user_group_member as ugm on ugm.user_group_id = dfa.user_group_id
where (
(d.has_acl = 1 and (dfa.user_id = ? or ugm.user_id = ? or df.created_by = ? or (d.is_folder = 1 and d.created_by = ?)))
or d.has_acl = 0)
and d.org_id = ?`
rawSQL = fmt.Sprintf("%v and d.id in(%v)", rawSQL, dashboardIds)
query.Result = make([]int64, 0)
err := x.SQL(rawSQL, query.UserId, query.UserId, query.UserId, query.UserId, query.OrgId).Find(&query.Result)
if err != nil {
return err
}
return nil
}
func arrayToString(a []int64, delim string) string {
return strings.Trim(strings.Replace(fmt.Sprint(a), " ", delim, -1), "[]")
}
-87
View File
@@ -1,87 +0,0 @@
package sqlstore
import (
"testing"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
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")
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
insertTestDashboard("test dash 23", 1, folder.Id, false, "prod", "webapp")
insertTestDashboard("test dash 45", 1, folder.Id, false, "prod")
currentUser := createUser("viewer", "Viewer", false)
Convey("and no acls are set", func() {
Convey("should return all dashboards", func() {
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() {
var otherUser int64 = 999
updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
Convey("should not return folder", func() {
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)
})
Convey("when the user is given permission", func() {
updateTestDashboardWithAcl(folder.Id, currentUser.Id, m.PERMISSION_EDIT)
Convey("should folder", func() {
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)
})
})
})
})
})
}
func createUser(name string, role string, isAdmin bool) m.User {
setting.AutoAssignOrg = true
setting.AutoAssignOrgRole = role
currentUserCmd := m.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin}
err := CreateUser(&currentUserCmd)
So(err, ShouldBeNil)
q1 := m.GetUserOrgListQuery{UserId: currentUserCmd.Result.Id}
GetUserOrgList(&q1)
So(q1.Result[0].Role, ShouldEqual, role)
return currentUserCmd.Result
}
func updateTestDashboardWithAcl(dashId int64, userId int64, permission m.PermissionType) {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1,
UserId: userId,
DashboardId: dashId,
Permissions: permission,
})
So(err, ShouldBeNil)
}