Commit d6341162 by Torkel Ödegaard

refactoring dashboad folder acl checks

parents b494fd76 d1e1c4be
...@@ -41,7 +41,7 @@ func GetDashboard(c *middleware.Context) Response { ...@@ -41,7 +41,7 @@ func GetDashboard(c *middleware.Context) Response {
return rsp return rsp
} }
guardian := guardian.NewDashboardGuardian(dash, c.SignedInUser) guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
if canView, err := guardian.CanView(); err != nil { if canView, err := guardian.CanView(); err != nil {
return ApiError(500, "Error while checking dashboard permissions", err) return ApiError(500, "Error while checking dashboard permissions", err)
......
...@@ -48,11 +48,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) { ...@@ -48,11 +48,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
mockResult = append(mockResult, &models.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT}) mockResult = append(mockResult, &models.DashboardAclInfoDTO{Id: 1, OrgId: 1, DashboardId: 1, UserId: 1, Permissions: models.PERMISSION_EDIT})
bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
query.Result = []int64{1}
return nil
})
Convey("Should be able to access ACL", func() { Convey("Should be able to access ACL", func() {
sc.handlerFunc = GetDashboardAcl sc.handlerFunc = GetDashboardAcl
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
...@@ -101,11 +96,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) { ...@@ -101,11 +96,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
Convey("When user is editor and not in the ACL", func() { Convey("When user is editor and not in the ACL", func() {
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) { loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/1/acl", "/api/dashboards/:id/acl", models.ROLE_EDITOR, func(sc *scenarioContext) {
bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
query.Result = []int64{}
return nil
})
Convey("Should not be able to access ACL", func() { Convey("Should not be able to access ACL", func() {
sc.handlerFunc = GetDashboardAcl sc.handlerFunc = GetDashboardAcl
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec() sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
......
...@@ -130,7 +130,7 @@ func GetPlaylistItems(c *middleware.Context) Response { ...@@ -130,7 +130,7 @@ func GetPlaylistItems(c *middleware.Context) Response {
func GetPlaylistDashboards(c *middleware.Context) Response { func GetPlaylistDashboards(c *middleware.Context) Response {
playlistId := c.ParamsInt64(":id") playlistId := c.ParamsInt64(":id")
playlists, err := LoadPlaylistDashboards(c.OrgId, c.UserId, playlistId) playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId)
if err != nil { if err != nil {
return ApiError(500, "Could not load dashboards", err) return ApiError(500, "Could not load dashboards", err)
} }
......
...@@ -34,18 +34,18 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i ...@@ -34,18 +34,18 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
return result, nil return result, nil
} }
func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice { func populateDashboardsByTag(orgId int64, signedInUser *m.SignedInUser, dashboardByTag []string, dashboardTagOrder map[string]int) dtos.PlaylistDashboardsSlice {
result := make(dtos.PlaylistDashboardsSlice, 0) result := make(dtos.PlaylistDashboardsSlice, 0)
if len(dashboardByTag) > 0 { if len(dashboardByTag) > 0 {
for _, tag := range dashboardByTag { for _, tag := range dashboardByTag {
searchQuery := search.Query{ searchQuery := search.Query{
Title: "", Title: "",
Tags: []string{tag}, Tags: []string{tag},
UserId: userId, SignedInUser: signedInUser,
Limit: 100, Limit: 100,
IsStarred: false, IsStarred: false,
OrgId: orgId, OrgId: orgId,
} }
if err := bus.Dispatch(&searchQuery); err == nil { if err := bus.Dispatch(&searchQuery); err == nil {
...@@ -64,7 +64,7 @@ func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string, dashb ...@@ -64,7 +64,7 @@ func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string, dashb
return result return result
} }
func LoadPlaylistDashboards(orgId, userId, playlistId int64) (dtos.PlaylistDashboardsSlice, error) { func LoadPlaylistDashboards(orgId int64, signedInUser *m.SignedInUser, playlistId int64) (dtos.PlaylistDashboardsSlice, error) {
playlistItems, _ := LoadPlaylistItems(playlistId) playlistItems, _ := LoadPlaylistItems(playlistId)
dashboardByIds := make([]int64, 0) dashboardByIds := make([]int64, 0)
...@@ -89,7 +89,7 @@ func LoadPlaylistDashboards(orgId, userId, playlistId int64) (dtos.PlaylistDashb ...@@ -89,7 +89,7 @@ func LoadPlaylistDashboards(orgId, userId, playlistId int64) (dtos.PlaylistDashb
var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder) var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder)
result = append(result, k...) result = append(result, k...)
result = append(result, populateDashboardsByTag(orgId, userId, dashboardByTag, dashboardTagOrder)...) result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...)
sort.Sort(result) sort.Sort(result)
return result, nil return result, nil
......
...@@ -26,9 +26,9 @@ func Search(c *middleware.Context) { ...@@ -26,9 +26,9 @@ func Search(c *middleware.Context) {
mode = "list" mode = "list"
} }
dbids := make([]int, 0) dbids := make([]int64, 0)
for _, id := range c.QueryStrings("dashboardIds") { for _, id := range c.QueryStrings("dashboardIds") {
dashboardId, err := strconv.Atoi(id) dashboardId, err := strconv.ParseInt(id, 10, 64)
if err == nil { if err == nil {
dbids = append(dbids, dashboardId) dbids = append(dbids, dashboardId)
} }
...@@ -37,7 +37,7 @@ func Search(c *middleware.Context) { ...@@ -37,7 +37,7 @@ func Search(c *middleware.Context) {
searchQuery := search.Query{ searchQuery := search.Query{
Title: query, Title: query,
Tags: tags, Tags: tags,
UserId: c.UserId, SignedInUser: c.SignedInUser,
Limit: limit, Limit: limit,
IsStarred: starred == "true", IsStarred: starred == "true",
OrgId: c.OrgId, OrgId: c.OrgId,
......
...@@ -88,7 +88,9 @@ type GetDashboardPermissionsQuery struct { ...@@ -88,7 +88,9 @@ type GetDashboardPermissionsQuery struct {
Result []*DashboardAclInfoDTO Result []*DashboardAclInfoDTO
} }
type GetDashboardAclQuery struct { // Returns dashboard acl list items and parent folder items
type GetInheritedDashboardAclQuery struct {
DashboardId int64 DashboardId int64
OrgId int64
Result []*DashboardAcl Result []*DashboardAcl
} }
...@@ -192,11 +192,3 @@ type GetDashboardSlugByIdQuery struct { ...@@ -192,11 +192,3 @@ type GetDashboardSlugByIdQuery struct {
Id int64 Id int64
Result string Result string
} }
type GetAllowedDashboardsQuery struct {
UserId int64
OrgId int64
DashList []int64
Result []int64
}
...@@ -6,50 +6,45 @@ import ( ...@@ -6,50 +6,45 @@ import (
) )
type DashboardGuardian struct { type DashboardGuardian struct {
user *m.SignedInUser user *m.SignedInUser
dashboard *m.Dashboard dashId int64
acl []*m.DashboardAclInfoDTO orgId int64
groups []*m.UserGroup acl []*m.DashboardAcl
groups []*m.UserGroup
} }
func NewDashboardGuardian(dash *m.Dashboard, user *m.SignedInUser) *DashboardGuardian { func NewDashboardGuardian(dashId int64, orgId int64, user *m.SignedInUser) *DashboardGuardian {
return &DashboardGuardian{ return &DashboardGuardian{
user: user, user: user,
dashboard: dash, dashId: dashId,
orgId: orgId,
} }
} }
func (g *DashboardGuardian) CanSave() (bool, error) { func (g *DashboardGuardian) CanSave() (bool, error) {
if !g.dashboard.HasAcl { return g.HasPermission(m.PERMISSION_EDIT, m.ROLE_EDITOR)
return g.user.HasRole(m.ROLE_EDITOR), nil
}
return g.HasPermission(m.PERMISSION_EDIT)
} }
func (g *DashboardGuardian) CanEdit() (bool, error) { func (g *DashboardGuardian) CanEdit() (bool, error) {
if !g.dashboard.HasAcl { return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT, m.ROLE_READ_ONLY_EDITOR)
return g.user.HasRole(m.ROLE_READ_ONLY_EDITOR), nil
}
return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT)
} }
func (g *DashboardGuardian) CanView() (bool, error) { func (g *DashboardGuardian) CanView() (bool, error) {
if !g.dashboard.HasAcl { return g.HasPermission(m.PERMISSION_VIEW, m.ROLE_VIEWER)
return g.user.HasRole(m.ROLE_VIEWER), nil
}
return g.HasPermission(m.PERMISSION_VIEW)
} }
func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, error) { func (g *DashboardGuardian) HasPermission(permission m.PermissionType, fallbackRole m.RoleType) (bool, error) {
userGroups, err := g.getUserGroups() acl, err := g.getAcl()
if err != nil { if err != nil {
return false, err return false, err
} }
acl, err := g.getAcl() // if no acl use org role to determine permission
if len(acl) == 0 {
return g.user.HasRole(fallbackRole), nil
}
userGroups, err := g.getUserGroups()
if err != nil { if err != nil {
return false, err return false, err
} }
...@@ -70,17 +65,12 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er ...@@ -70,17 +65,12 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
} }
// Returns dashboard acl // Returns dashboard acl
func (g *DashboardGuardian) getAcl() ([]*m.DashboardAclInfoDTO, error) { func (g *DashboardGuardian) getAcl() ([]*m.DashboardAcl, error) {
if g.acl != nil { if g.acl != nil {
return g.acl, nil return g.acl, nil
} }
dashId := g.dashboard.Id query := m.GetInheritedDashboardAclQuery{DashboardId: g.dashId, OrgId: g.orgId}
if g.dashboard.ParentId != 0 {
dashId = g.dashboard.ParentId
}
query := m.GetDashboardPermissionsQuery{DashboardId: dashId}
if err := bus.Dispatch(&query); err != nil { if err := bus.Dispatch(&query); err != nil {
return nil, err return nil, err
} }
......
...@@ -40,9 +40,8 @@ func searchHandler(query *Query) error { ...@@ -40,9 +40,8 @@ func searchHandler(query *Query) error {
dashQuery := FindPersistedDashboardsQuery{ dashQuery := FindPersistedDashboardsQuery{
Title: query.Title, Title: query.Title,
UserId: query.UserId, SignedInUser: query.SignedInUser,
IsStarred: query.IsStarred, IsStarred: query.IsStarred,
OrgId: query.OrgId,
DashboardIds: query.DashboardIds, DashboardIds: query.DashboardIds,
Type: query.Type, Type: query.Type,
ParentId: query.FolderId, ParentId: query.FolderId,
...@@ -88,7 +87,7 @@ func searchHandler(query *Query) error { ...@@ -88,7 +87,7 @@ func searchHandler(query *Query) error {
} }
// add isStarred info // add isStarred info
if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil { if err := setIsStarredFlagOnSearchResults(query.SignedInUser.UserId, hits); err != nil {
return err return err
} }
......
...@@ -12,7 +12,7 @@ func TestSearch(t *testing.T) { ...@@ -12,7 +12,7 @@ func TestSearch(t *testing.T) {
Convey("Given search query", t, func() { Convey("Given search query", t, func() {
jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/") jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/")
query := Query{Limit: 2000} query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}}
bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error { bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
query.Result = HitList{ query.Result = HitList{
......
package search package search
import "strings" import "strings"
import "github.com/grafana/grafana/pkg/models"
type HitType string type HitType string
...@@ -43,11 +44,11 @@ type Query struct { ...@@ -43,11 +44,11 @@ type Query struct {
Title string Title string
Tags []string Tags []string
OrgId int64 OrgId int64
UserId int64 SignedInUser *models.SignedInUser
Limit int Limit int
IsStarred bool IsStarred bool
Type string Type string
DashboardIds []int DashboardIds []int64
FolderId int64 FolderId int64
Mode string Mode string
...@@ -57,9 +58,9 @@ type Query struct { ...@@ -57,9 +58,9 @@ type Query struct {
type FindPersistedDashboardsQuery struct { type FindPersistedDashboardsQuery struct {
Title string Title string
OrgId int64 OrgId int64
UserId int64 SignedInUser *models.SignedInUser
IsStarred bool IsStarred bool
DashboardIds []int DashboardIds []int64
Type string Type string
ParentId int64 ParentId int64
Mode string Mode string
......
...@@ -148,12 +148,14 @@ func GetDashboard(query *m.GetDashboardQuery) error { ...@@ -148,12 +148,14 @@ func GetDashboard(query *m.GetDashboardQuery) error {
} }
type DashboardSearchProjection struct { type DashboardSearchProjection struct {
Id int64 Id int64
Title string Title string
Slug string Slug string
Term string Term string
IsFolder bool IsFolder bool
ParentId int64 ParentId int64
FolderSlug string
FolderTitle string
} }
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) { func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
...@@ -166,8 +168,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear ...@@ -166,8 +168,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
dashboard.slug, dashboard.slug,
dashboard_tag.term, dashboard_tag.term,
dashboard.is_folder, dashboard.is_folder,
dashboard.parent_id dashboard.parent_id,
f.slug as folder_slug,
f.title as folder_title
FROM dashboard 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`) LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
if query.IsStarred { if query.IsStarred {
...@@ -175,12 +180,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear ...@@ -175,12 +180,11 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
} }
sql.WriteString(` WHERE dashboard.org_id=?`) sql.WriteString(` WHERE dashboard.org_id=?`)
params = append(params, query.SignedInUser.OrgId)
params = append(params, query.OrgId)
if query.IsStarred { if query.IsStarred {
sql.WriteString(` AND star.user_id=?`) sql.WriteString(` AND star.user_id=?`)
params = append(params, query.UserId) params = append(params, query.SignedInUser.UserId)
} }
if len(query.DashboardIds) > 0 { if len(query.DashboardIds) > 0 {
...@@ -196,6 +200,23 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear ...@@ -196,6 +200,23 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
sql.WriteString(")") 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 { if len(query.Title) > 0 {
sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?") sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
params = append(params, "%"+query.Title+"%") params = append(params, "%"+query.Title+"%")
...@@ -250,26 +271,13 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error { ...@@ -250,26 +271,13 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
// appends parent folders for any hits to the search result // appends parent folders for any hits to the search result
func appendDashboardFolders(res []DashboardSearchProjection) ([]DashboardSearchProjection, error) { func appendDashboardFolders(res []DashboardSearchProjection) ([]DashboardSearchProjection, error) {
var dashboardFolderIds []int64
for _, item := range res { for _, item := range res {
if item.ParentId > 0 { 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{ res = append(res, DashboardSearchProjection{
Id: folder.Id, Id: item.ParentId,
IsFolder: true, IsFolder: true,
Slug: folder.Slug, Slug: item.FolderSlug,
Title: folder.Title, Title: item.FolderTitle,
}) })
} }
} }
...@@ -371,6 +379,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error { ...@@ -371,6 +379,7 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
"DELETE FROM dashboard WHERE id = ?", "DELETE FROM dashboard WHERE id = ?",
"DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?", "DELETE FROM playlist_item WHERE type = 'dashboard_by_id' AND value = ?",
"DELETE FROM dashboard_version WHERE dashboard_id = ?", "DELETE FROM dashboard_version WHERE dashboard_id = ?",
"DELETE FROM dashboard WHERE parent_id = ?",
} }
for _, sql := range deletes { for _, sql := range deletes {
......
...@@ -11,7 +11,7 @@ func init() { ...@@ -11,7 +11,7 @@ func init() {
bus.AddHandler("sql", AddOrUpdateDashboardPermission) bus.AddHandler("sql", AddOrUpdateDashboardPermission)
bus.AddHandler("sql", RemoveDashboardPermission) bus.AddHandler("sql", RemoveDashboardPermission)
bus.AddHandler("sql", GetDashboardPermissions) bus.AddHandler("sql", GetDashboardPermissions)
bus.AddHandler("sql", GetDashboardAcl) bus.AddHandler("sql", GetInheritedDashboardAcl)
} }
func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error { func AddOrUpdateDashboardPermission(cmd *m.AddOrUpdateDashboardPermissionCommand) error {
...@@ -86,33 +86,31 @@ func RemoveDashboardPermission(cmd *m.RemoveDashboardPermissionCommand) 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 rawSQL := `SELECT
da.id, da.id,
da.org_id, da.org_id,
da.id,
da.dashboard_id, da.dashboard_id,
da.user_id, da.user_id,
da.user_group_id, da.user_group_id,
da.permissions, da.permissions,
da.created, da.created,
da.updated, da.updated
FROM` + dialect.Quote("dashboard_acl") + ` as da FROM dashboard_acl as da
WHERE dashboard_id IN ( WHERE da.dashboard_id IN (
SELECT id FROM dashboard where id = ? SELECT id FROM dashboard where id = ?
UNION UNION
SELECT parent_id from dashboard where id = ? SELECT parent_id from dashboard where id = ?
)` ) AND org_id = ?`
query.Result = make([]*m.DashboardAcl, 0) 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 { func GetDashboardPermissions(query *m.GetDashboardPermissionsQuery) error {
rawSQL := `SELECT rawSQL := `SELECT
da.id, da.id,
da.org_id, da.org_id,
da.id,
da.dashboard_id, da.dashboard_id,
da.user_id, da.user_id,
da.user_group_id, da.user_group_id,
......
...@@ -25,6 +25,47 @@ func TestDashboardAclDataAccess(t *testing.T) { ...@@ -25,6 +25,47 @@ func TestDashboardAclDataAccess(t *testing.T) {
So(err, ShouldEqual, m.ErrDashboardPermissionUserOrUserGroupEmpty) 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() { Convey("Should be able to add dashboard permission", func() {
err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{ err := AddOrUpdateDashboardPermission(&m.AddOrUpdateDashboardPermissionCommand{
OrgId: 1, OrgId: 1,
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models" m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/search" "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 { func insertTestDashboard(title string, orgId int64, parentId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
...@@ -113,9 +114,10 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -113,9 +114,10 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard and return in folder hierarchy", func() { Convey("Should be able to search for dashboard and return in folder hierarchy", func() {
query := search.FindPersistedDashboardsQuery{ query := search.FindPersistedDashboardsQuery{
Title: "test dash 23", Title: "test dash 23",
OrgId: 1, OrgId: 1,
Mode: "tree", Mode: "tree",
SignedInUser: &m.SignedInUser{OrgId: 1},
} }
err := SearchDashboards(&query) err := SearchDashboards(&query)
...@@ -132,8 +134,9 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -132,8 +134,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for dashboard folder", func() { Convey("Should be able to search for dashboard folder", func() {
query := search.FindPersistedDashboardsQuery{ query := search.FindPersistedDashboardsQuery{
Title: "1 test dash folder", Title: "1 test dash folder",
OrgId: 1, OrgId: 1,
SignedInUser: &m.SignedInUser{OrgId: 1},
} }
err := SearchDashboards(&query) err := SearchDashboards(&query)
...@@ -146,8 +149,9 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -146,8 +149,9 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("Should be able to search for a dashboard folder's children", func() { Convey("Should be able to search for a dashboard folder's children", func() {
query := search.FindPersistedDashboardsQuery{ query := search.FindPersistedDashboardsQuery{
OrgId: 1, OrgId: 1,
ParentId: savedFolder.Id, ParentId: savedFolder.Id,
SignedInUser: &m.SignedInUser{OrgId: 1},
} }
err := SearchDashboards(&query) err := SearchDashboards(&query)
...@@ -161,9 +165,9 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -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 search for dashboard by dashboard ids", func() {
Convey("should be able to find two dashboards by id", func() { Convey("should be able to find two dashboards by id", func() {
query := search.FindPersistedDashboardsQuery{ query := search.FindPersistedDashboardsQuery{
DashboardIds: []int{2, 3}, DashboardIds: []int64{2, 3},
OrgId: 1,
Mode: "tree", Mode: "tree",
SignedInUser: &m.SignedInUser{OrgId: 1},
} }
err := SearchDashboards(&query) err := SearchDashboards(&query)
...@@ -180,8 +184,8 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -180,8 +184,8 @@ func TestDashboardDataAccess(t *testing.T) {
Convey("DashboardIds that does not exists should not cause errors", func() { Convey("DashboardIds that does not exists should not cause errors", func() {
query := search.FindPersistedDashboardsQuery{ query := search.FindPersistedDashboardsQuery{
DashboardIds: []int{1000}, DashboardIds: []int64{1000},
OrgId: 1, SignedInUser: &m.SignedInUser{OrgId: 1},
} }
err := SearchDashboards(&query) err := SearchDashboards(&query)
...@@ -244,6 +248,23 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -244,6 +248,23 @@ func TestDashboardDataAccess(t *testing.T) {
So(query.Result.ParentId, ShouldEqual, 0) 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() { Convey("Should be able to get dashboard tags", func() {
query := m.GetDashboardTagsQuery{OrgId: 1} query := m.GetDashboardTagsQuery{OrgId: 1}
...@@ -266,7 +287,7 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -266,7 +287,7 @@ func TestDashboardDataAccess(t *testing.T) {
}) })
Convey("Should be able to search for starred dashboards", func() { 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) err := SearchDashboards(&query)
So(err, ShouldBeNil) So(err, ShouldBeNil)
...@@ -275,5 +296,95 @@ func TestDashboardDataAccess(t *testing.T) { ...@@ -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)
}
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), "[]")
}
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)
}
...@@ -10,4 +10,5 @@ define([ ...@@ -10,4 +10,5 @@ define([
'./prefs_control', './prefs_control',
'./user_groups_ctrl', './user_groups_ctrl',
'./user_group_details_ctrl', './user_group_details_ctrl',
'./create_user_group_modal',
], function () {}); ], function () {});
///<reference path="../../headers/common.d.ts" />
import coreModule from 'app/core/core_module';
import appEvents from 'app/core/app_events';
import _ from 'lodash';
export class CreateUserGroupCtrl {
userGroupName = '';
/** @ngInject */
constructor(private backendSrv, private $scope, $sce, private $location) {
}
createUserGroup() {
this.backendSrv.post('/api/user-groups', {name: this.userGroupName}).then((result) => {
if (result.userGroupId) {
this.$location.path('/org/user-groups/edit/' + result.userGroupId);
}
this.dismiss();
});
}
dismiss() {
appEvents.emit('hide-modal');
}
}
export function createUserGroupModal() {
return {
restrict: 'E',
templateUrl: 'public/app/features/org/partials/create_user_group.html',
controller: CreateUserGroupCtrl,
bindToController: true,
controllerAs: 'ctrl',
};
}
coreModule.directive('createUserGroupModal', createUserGroupModal);
<div class="modal-body" ng-controller="UserGroupsCtrl"> <div class="modal-body">
<div class="modal-header"> <div class="modal-header">
<h2 class="modal-header-title"> <h2 class="modal-header-title">
Create User Group <span class="p-l-1">Create User Group</span>
</h2> </h2>
<a class="modal-header-close" ng-click="dismiss();">
<a class="modal-header-close" ng-click="ctrl.dismiss();">
<i class="fa fa-remove"></i> <i class="fa fa-remove"></i>
</a> </a>
</div> </div>
<div class="modal-content"> <div class="modal-content">
<form name="createUserGroupForm" class="gf-form-group"> <form name="ctrl.createUserGroupForm" class="gf-form-group" novalidate>
<div class="gf-form-inline"> <div class="p-t-2">
<div class="gf-form max-width-21"> <div class="gf-form-inline">
<input type="text" class="gf-form-input" ng-model='ctrl.userGroupName' placeholder="Name"></input> <div class="gf-form max-width-21">
</div> <input type="text" class="gf-form-input" ng-model='ctrl.userGroupName' required give-focus="true" placeholder="Enter User Group Name"></input>
<div class="gf-form"> </div>
<button class="btn gf-form-btn btn-success" ng-click="ctrl.createUserGroup();dismiss();">Create</button> <div class="gf-form">
</div> <button class="btn gf-form-btn btn-success" ng-click="ctrl.createUserGroup();ctrl.dismiss();">Create</button>
</div> </div>
</div>
</div>
</form> </form>
</div> </div>
</div> </div>
///<reference path="../../headers/common.d.ts" /> ///<reference path="../../headers/common.d.ts" />
import coreModule from 'app/core/core_module'; import coreModule from 'app/core/core_module';
import {appEvents} from 'app/core/core';
export class UserGroupsCtrl { export class UserGroupsCtrl {
userGroups: any; userGroups: any;
...@@ -10,7 +11,6 @@ export class UserGroupsCtrl { ...@@ -10,7 +11,6 @@ export class UserGroupsCtrl {
totalPages: number; totalPages: number;
showPaging = false; showPaging = false;
query: any = ''; query: any = '';
userGroupName: any = '';
navModel: any; navModel: any;
/** @ngInject */ /** @ngInject */
...@@ -40,14 +40,6 @@ export class UserGroupsCtrl { ...@@ -40,14 +40,6 @@ export class UserGroupsCtrl {
this.get(); this.get();
} }
createUserGroup() {
this.backendSrv.post('/api/user-groups', {name: this.userGroupName}).then((result) => {
if (result.userGroupId) {
this.$location.path('/org/user-groups/edit/' + result.userGroupId);
}
});
}
deleteUserGroup(userGroup) { deleteUserGroup(userGroup) {
this.$scope.appEvent('confirm-modal', { this.$scope.appEvent('confirm-modal', {
title: 'Delete', title: 'Delete',
...@@ -66,13 +58,9 @@ export class UserGroupsCtrl { ...@@ -66,13 +58,9 @@ export class UserGroupsCtrl {
} }
openUserGroupModal() { openUserGroupModal() {
var modalScope = this.$scope.$new(); appEvents.emit('show-modal', {
modalScope.createUserGroup = this.createUserGroup.bind(this); templateHtml: '<create-user-group-modal></create-user-group-modal>',
modalClass: 'modal--narrow'
this.$scope.appEvent('show-modal', {
src: 'public/app/features/org/partials/create_user_group.html',
modalClass: 'modal--narrow',
scope: modalScope
}); });
} }
} }
......
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