mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
refactoring dashboad folder acl checks
This commit is contained in:
@@ -41,7 +41,7 @@ func GetDashboard(c *middleware.Context) Response {
|
||||
return rsp
|
||||
}
|
||||
|
||||
guardian := guardian.NewDashboardGuardian(dash, c.SignedInUser)
|
||||
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
|
||||
|
||||
if canView, err := guardian.CanView(); err != nil {
|
||||
return ApiError(500, "Error while checking dashboard permissions", err)
|
||||
|
||||
@@ -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) {
|
||||
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() {
|
||||
sc.handlerFunc = GetDashboardAcl
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
@@ -101,11 +96,6 @@ func TestDashboardAclApiEndpoint(t *testing.T) {
|
||||
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) {
|
||||
|
||||
bus.AddHandler("test2", func(query *models.GetAllowedDashboardsQuery) error {
|
||||
query.Result = []int64{}
|
||||
return nil
|
||||
})
|
||||
|
||||
Convey("Should not be able to access ACL", func() {
|
||||
sc.handlerFunc = GetDashboardAcl
|
||||
sc.fakeReqWithParams("GET", sc.url, map[string]string{}).exec()
|
||||
|
||||
@@ -130,7 +130,7 @@ func GetPlaylistItems(c *middleware.Context) Response {
|
||||
func GetPlaylistDashboards(c *middleware.Context) Response {
|
||||
playlistId := c.ParamsInt64(":id")
|
||||
|
||||
playlists, err := LoadPlaylistDashboards(c.OrgId, c.UserId, playlistId)
|
||||
playlists, err := LoadPlaylistDashboards(c.OrgId, c.SignedInUser, playlistId)
|
||||
if err != nil {
|
||||
return ApiError(500, "Could not load dashboards", err)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func populateDashboardsById(dashboardByIds []int64, dashboardIdOrder map[int64]i
|
||||
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)
|
||||
|
||||
if len(dashboardByTag) > 0 {
|
||||
@@ -42,7 +42,7 @@ func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string, dashb
|
||||
searchQuery := search.Query{
|
||||
Title: "",
|
||||
Tags: []string{tag},
|
||||
UserId: userId,
|
||||
SignedInUser: signedInUser,
|
||||
Limit: 100,
|
||||
IsStarred: false,
|
||||
OrgId: orgId,
|
||||
@@ -64,7 +64,7 @@ func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string, dashb
|
||||
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)
|
||||
|
||||
dashboardByIds := make([]int64, 0)
|
||||
@@ -89,7 +89,7 @@ func LoadPlaylistDashboards(orgId, userId, playlistId int64) (dtos.PlaylistDashb
|
||||
|
||||
var k, _ = populateDashboardsById(dashboardByIds, dashboardIdOrder)
|
||||
result = append(result, k...)
|
||||
result = append(result, populateDashboardsByTag(orgId, userId, dashboardByTag, dashboardTagOrder)...)
|
||||
result = append(result, populateDashboardsByTag(orgId, signedInUser, dashboardByTag, dashboardTagOrder)...)
|
||||
|
||||
sort.Sort(result)
|
||||
return result, nil
|
||||
|
||||
@@ -26,9 +26,9 @@ func Search(c *middleware.Context) {
|
||||
mode = "list"
|
||||
}
|
||||
|
||||
dbids := make([]int, 0)
|
||||
dbids := make([]int64, 0)
|
||||
for _, id := range c.QueryStrings("dashboardIds") {
|
||||
dashboardId, err := strconv.Atoi(id)
|
||||
dashboardId, err := strconv.ParseInt(id, 10, 64)
|
||||
if err == nil {
|
||||
dbids = append(dbids, dashboardId)
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func Search(c *middleware.Context) {
|
||||
searchQuery := search.Query{
|
||||
Title: query,
|
||||
Tags: tags,
|
||||
UserId: c.UserId,
|
||||
SignedInUser: c.SignedInUser,
|
||||
Limit: limit,
|
||||
IsStarred: starred == "true",
|
||||
OrgId: c.OrgId,
|
||||
|
||||
@@ -88,7 +88,9 @@ type GetDashboardPermissionsQuery struct {
|
||||
Result []*DashboardAclInfoDTO
|
||||
}
|
||||
|
||||
type GetDashboardAclQuery struct {
|
||||
// Returns dashboard acl list items and parent folder items
|
||||
type GetInheritedDashboardAclQuery struct {
|
||||
DashboardId int64
|
||||
OrgId int64
|
||||
Result []*DashboardAcl
|
||||
}
|
||||
|
||||
@@ -192,11 +192,3 @@ type GetDashboardSlugByIdQuery struct {
|
||||
Id int64
|
||||
Result string
|
||||
}
|
||||
|
||||
type GetAllowedDashboardsQuery struct {
|
||||
UserId int64
|
||||
OrgId int64
|
||||
DashList []int64
|
||||
|
||||
Result []int64
|
||||
}
|
||||
|
||||
@@ -7,49 +7,44 @@ import (
|
||||
|
||||
type DashboardGuardian struct {
|
||||
user *m.SignedInUser
|
||||
dashboard *m.Dashboard
|
||||
acl []*m.DashboardAclInfoDTO
|
||||
dashId int64
|
||||
orgId int64
|
||||
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{
|
||||
user: user,
|
||||
dashboard: dash,
|
||||
dashId: dashId,
|
||||
orgId: orgId,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanSave() (bool, error) {
|
||||
if !g.dashboard.HasAcl {
|
||||
return g.user.HasRole(m.ROLE_EDITOR), nil
|
||||
}
|
||||
|
||||
return g.HasPermission(m.PERMISSION_EDIT)
|
||||
return g.HasPermission(m.PERMISSION_EDIT, m.ROLE_EDITOR)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanEdit() (bool, error) {
|
||||
if !g.dashboard.HasAcl {
|
||||
return g.user.HasRole(m.ROLE_READ_ONLY_EDITOR), nil
|
||||
}
|
||||
|
||||
return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT)
|
||||
return g.HasPermission(m.PERMISSION_READ_ONLY_EDIT, m.ROLE_READ_ONLY_EDITOR)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) CanView() (bool, error) {
|
||||
if !g.dashboard.HasAcl {
|
||||
return g.user.HasRole(m.ROLE_VIEWER), nil
|
||||
}
|
||||
|
||||
return g.HasPermission(m.PERMISSION_VIEW)
|
||||
return g.HasPermission(m.PERMISSION_VIEW, m.ROLE_VIEWER)
|
||||
}
|
||||
|
||||
func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, error) {
|
||||
userGroups, err := g.getUserGroups()
|
||||
func (g *DashboardGuardian) HasPermission(permission m.PermissionType, fallbackRole m.RoleType) (bool, error) {
|
||||
acl, err := g.getAcl()
|
||||
if err != nil {
|
||||
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 {
|
||||
return false, err
|
||||
}
|
||||
@@ -70,17 +65,12 @@ func (g *DashboardGuardian) HasPermission(permission m.PermissionType) (bool, er
|
||||
}
|
||||
|
||||
// Returns dashboard acl
|
||||
func (g *DashboardGuardian) getAcl() ([]*m.DashboardAclInfoDTO, error) {
|
||||
func (g *DashboardGuardian) getAcl() ([]*m.DashboardAcl, error) {
|
||||
if g.acl != nil {
|
||||
return g.acl, nil
|
||||
}
|
||||
|
||||
dashId := g.dashboard.Id
|
||||
if g.dashboard.ParentId != 0 {
|
||||
dashId = g.dashboard.ParentId
|
||||
}
|
||||
|
||||
query := m.GetDashboardPermissionsQuery{DashboardId: dashId}
|
||||
query := m.GetInheritedDashboardAclQuery{DashboardId: g.dashId, OrgId: g.orgId}
|
||||
if err := bus.Dispatch(&query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,9 +40,8 @@ func searchHandler(query *Query) error {
|
||||
|
||||
dashQuery := FindPersistedDashboardsQuery{
|
||||
Title: query.Title,
|
||||
UserId: query.UserId,
|
||||
SignedInUser: query.SignedInUser,
|
||||
IsStarred: query.IsStarred,
|
||||
OrgId: query.OrgId,
|
||||
DashboardIds: query.DashboardIds,
|
||||
Type: query.Type,
|
||||
ParentId: query.FolderId,
|
||||
@@ -88,7 +87,7 @@ func searchHandler(query *Query) error {
|
||||
}
|
||||
|
||||
// add isStarred info
|
||||
if err := setIsStarredFlagOnSearchResults(query.UserId, hits); err != nil {
|
||||
if err := setIsStarredFlagOnSearchResults(query.SignedInUser.UserId, hits); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ func TestSearch(t *testing.T) {
|
||||
|
||||
Convey("Given search query", t, func() {
|
||||
jsonDashIndex = NewJsonDashIndex("../../../public/dashboards/")
|
||||
query := Query{Limit: 2000}
|
||||
query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}}
|
||||
|
||||
bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
|
||||
query.Result = HitList{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package search
|
||||
|
||||
import "strings"
|
||||
import "github.com/grafana/grafana/pkg/models"
|
||||
|
||||
type HitType string
|
||||
|
||||
@@ -43,11 +44,11 @@ type Query struct {
|
||||
Title string
|
||||
Tags []string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
SignedInUser *models.SignedInUser
|
||||
Limit int
|
||||
IsStarred bool
|
||||
Type string
|
||||
DashboardIds []int
|
||||
DashboardIds []int64
|
||||
FolderId int64
|
||||
Mode string
|
||||
|
||||
@@ -57,9 +58,9 @@ type Query struct {
|
||||
type FindPersistedDashboardsQuery struct {
|
||||
Title string
|
||||
OrgId int64
|
||||
UserId int64
|
||||
SignedInUser *models.SignedInUser
|
||||
IsStarred bool
|
||||
DashboardIds []int
|
||||
DashboardIds []int64
|
||||
Type string
|
||||
ParentId int64
|
||||
Mode string
|
||||
|
||||
@@ -154,6 +154,8 @@ type DashboardSearchProjection struct {
|
||||
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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
@@ -116,6 +117,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
Title: "test dash 23",
|
||||
OrgId: 1,
|
||||
Mode: "tree",
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -134,6 +136,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "1 test dash folder",
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -148,6 +151,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
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(¤tUserCmd)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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), "[]")
|
||||
}
|
||||
@@ -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(¤tUserCmd)
|
||||
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([
|
||||
'./prefs_control',
|
||||
'./user_groups_ctrl',
|
||||
'./user_group_details_ctrl',
|
||||
'./create_user_group_modal',
|
||||
], function () {});
|
||||
|
||||
38
public/app/features/org/create_user_group_modal.ts
Normal file
38
public/app/features/org/create_user_group_modal.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
///<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);
|
||||
@@ -1,21 +1,24 @@
|
||||
<div class="modal-body" ng-controller="UserGroupsCtrl">
|
||||
<div class="modal-body">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-header-title">
|
||||
Create User Group
|
||||
<span class="p-l-1">Create User Group</span>
|
||||
</h2>
|
||||
<a class="modal-header-close" ng-click="dismiss();">
|
||||
|
||||
<a class="modal-header-close" ng-click="ctrl.dismiss();">
|
||||
<i class="fa fa-remove"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="modal-content">
|
||||
<form name="createUserGroupForm" class="gf-form-group">
|
||||
<form name="ctrl.createUserGroupForm" class="gf-form-group" novalidate>
|
||||
<div class="p-t-2">
|
||||
<div class="gf-form-inline">
|
||||
<div class="gf-form max-width-21">
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.userGroupName' placeholder="Name"></input>
|
||||
<input type="text" class="gf-form-input" ng-model='ctrl.userGroupName' required give-focus="true" placeholder="Enter User Group Name"></input>
|
||||
</div>
|
||||
<div class="gf-form">
|
||||
<button class="btn gf-form-btn btn-success" ng-click="ctrl.createUserGroup();dismiss();">Create</button>
|
||||
<button class="btn gf-form-btn btn-success" ng-click="ctrl.createUserGroup();ctrl.dismiss();">Create</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
///<reference path="../../headers/common.d.ts" />
|
||||
|
||||
import coreModule from 'app/core/core_module';
|
||||
import {appEvents} from 'app/core/core';
|
||||
|
||||
export class UserGroupsCtrl {
|
||||
userGroups: any;
|
||||
@@ -10,7 +11,6 @@ export class UserGroupsCtrl {
|
||||
totalPages: number;
|
||||
showPaging = false;
|
||||
query: any = '';
|
||||
userGroupName: any = '';
|
||||
navModel: any;
|
||||
|
||||
/** @ngInject */
|
||||
@@ -40,14 +40,6 @@ export class UserGroupsCtrl {
|
||||
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) {
|
||||
this.$scope.appEvent('confirm-modal', {
|
||||
title: 'Delete',
|
||||
@@ -66,13 +58,9 @@ export class UserGroupsCtrl {
|
||||
}
|
||||
|
||||
openUserGroupModal() {
|
||||
var modalScope = this.$scope.$new();
|
||||
modalScope.createUserGroup = this.createUserGroup.bind(this);
|
||||
|
||||
this.$scope.appEvent('show-modal', {
|
||||
src: 'public/app/features/org/partials/create_user_group.html',
|
||||
modalClass: 'modal--narrow',
|
||||
scope: modalScope
|
||||
appEvents.emit('show-modal', {
|
||||
templateHtml: '<create-user-group-modal></create-user-group-modal>',
|
||||
modalClass: 'modal--narrow'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user