mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
dashboard and folder search with permissions
This commit is contained in:
parent
b84fd3a7ae
commit
8e8f3c4332
@ -261,8 +261,6 @@ func (hs *HttpServer) registerRoutes() {
|
|||||||
dashboardRoute.Get("/tags", GetDashboardTags)
|
dashboardRoute.Get("/tags", GetDashboardTags)
|
||||||
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
dashboardRoute.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
|
||||||
|
|
||||||
dashboardRoute.Get("/folders", wrap(GetFoldersForSignedInUser))
|
|
||||||
|
|
||||||
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
|
dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
|
||||||
dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
|
dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
|
||||||
dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
|
dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
|
||||||
|
@ -490,19 +490,3 @@ func GetDashboardTags(c *middleware.Context) {
|
|||||||
|
|
||||||
c.JSON(200, query.Result)
|
c.JSON(200, query.Result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFoldersForSignedInUser(c *middleware.Context) Response {
|
|
||||||
title := c.Query("query")
|
|
||||||
query := m.GetFoldersForSignedInUserQuery{
|
|
||||||
OrgId: c.OrgId,
|
|
||||||
SignedInUser: c.SignedInUser,
|
|
||||||
Title: title,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := bus.Dispatch(&query)
|
|
||||||
if err != nil {
|
|
||||||
return ApiError(500, "Failed to get folders from database", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Json(200, query.Result)
|
|
||||||
}
|
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/metrics"
|
"github.com/grafana/grafana/pkg/metrics"
|
||||||
"github.com/grafana/grafana/pkg/middleware"
|
"github.com/grafana/grafana/pkg/middleware"
|
||||||
|
"github.com/grafana/grafana/pkg/models"
|
||||||
"github.com/grafana/grafana/pkg/services/search"
|
"github.com/grafana/grafana/pkg/services/search"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,11 +16,16 @@ func Search(c *middleware.Context) {
|
|||||||
starred := c.Query("starred")
|
starred := c.Query("starred")
|
||||||
limit := c.QueryInt("limit")
|
limit := c.QueryInt("limit")
|
||||||
dashboardType := c.Query("type")
|
dashboardType := c.Query("type")
|
||||||
|
permission := models.PERMISSION_VIEW
|
||||||
|
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 1000
|
limit = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Query("permission") == "Edit" {
|
||||||
|
permission = models.PERMISSION_EDIT
|
||||||
|
}
|
||||||
|
|
||||||
dbids := make([]int64, 0)
|
dbids := make([]int64, 0)
|
||||||
for _, id := range c.QueryStrings("dashboardIds") {
|
for _, id := range c.QueryStrings("dashboardIds") {
|
||||||
dashboardId, err := strconv.ParseInt(id, 10, 64)
|
dashboardId, err := strconv.ParseInt(id, 10, 64)
|
||||||
@ -46,6 +52,7 @@ func Search(c *middleware.Context) {
|
|||||||
DashboardIds: dbids,
|
DashboardIds: dbids,
|
||||||
Type: dashboardType,
|
Type: dashboardType,
|
||||||
FolderIds: folderIds,
|
FolderIds: folderIds,
|
||||||
|
Permission: permission,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bus.Dispatch(&searchQuery)
|
err := bus.Dispatch(&searchQuery)
|
||||||
|
@ -270,18 +270,6 @@ type GetDashboardsBySlugQuery struct {
|
|||||||
Result []*Dashboard
|
Result []*Dashboard
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetFoldersForSignedInUserQuery struct {
|
|
||||||
OrgId int64
|
|
||||||
SignedInUser *SignedInUser
|
|
||||||
Title string
|
|
||||||
Result []*DashboardFolder
|
|
||||||
}
|
|
||||||
|
|
||||||
type DashboardFolder struct {
|
|
||||||
Id int64 `json:"id"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DashboardPermissionForUser struct {
|
type DashboardPermissionForUser struct {
|
||||||
DashboardId int64 `json:"dashboardId"`
|
DashboardId int64 `json:"dashboardId"`
|
||||||
Permission PermissionType `json:"permission"`
|
Permission PermissionType `json:"permission"`
|
||||||
|
@ -21,6 +21,7 @@ func searchHandler(query *Query) error {
|
|||||||
FolderIds: query.FolderIds,
|
FolderIds: query.FolderIds,
|
||||||
Tags: query.Tags,
|
Tags: query.Tags,
|
||||||
Limit: query.Limit,
|
Limit: query.Limit,
|
||||||
|
Permission: query.Permission,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bus.Dispatch(&dashQuery); err != nil {
|
if err := bus.Dispatch(&dashQuery); err != nil {
|
||||||
|
@ -52,6 +52,7 @@ type Query struct {
|
|||||||
Type string
|
Type string
|
||||||
DashboardIds []int64
|
DashboardIds []int64
|
||||||
FolderIds []int64
|
FolderIds []int64
|
||||||
|
Permission models.PermissionType
|
||||||
|
|
||||||
Result HitList
|
Result HitList
|
||||||
}
|
}
|
||||||
@ -66,7 +67,7 @@ type FindPersistedDashboardsQuery struct {
|
|||||||
FolderIds []int64
|
FolderIds []int64
|
||||||
Tags []string
|
Tags []string
|
||||||
Limit int
|
Limit int
|
||||||
IsBrowse bool
|
Permission models.PermissionType
|
||||||
|
|
||||||
Result HitList
|
Result HitList
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package sqlstore
|
package sqlstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -21,7 +22,6 @@ func init() {
|
|||||||
bus.AddHandler("sql", GetDashboardSlugById)
|
bus.AddHandler("sql", GetDashboardSlugById)
|
||||||
bus.AddHandler("sql", GetDashboardUIDById)
|
bus.AddHandler("sql", GetDashboardUIDById)
|
||||||
bus.AddHandler("sql", GetDashboardsByPluginId)
|
bus.AddHandler("sql", GetDashboardsByPluginId)
|
||||||
bus.AddHandler("sql", GetFoldersForSignedInUser)
|
|
||||||
bus.AddHandler("sql", GetDashboardPermissionsForUser)
|
bus.AddHandler("sql", GetDashboardPermissionsForUser)
|
||||||
bus.AddHandler("sql", GetDashboardsBySlug)
|
bus.AddHandler("sql", GetDashboardsBySlug)
|
||||||
}
|
}
|
||||||
@ -256,7 +256,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
|||||||
limit = 1000
|
limit = 1000
|
||||||
}
|
}
|
||||||
|
|
||||||
sb := NewSearchBuilder(query.SignedInUser, limit).
|
sb := NewSearchBuilder(query.SignedInUser, limit, query.Permission).
|
||||||
WithTags(query.Tags).
|
WithTags(query.Tags).
|
||||||
WithDashboardIdsIn(query.DashboardIds)
|
WithDashboardIdsIn(query.DashboardIds)
|
||||||
|
|
||||||
@ -279,6 +279,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
|
|||||||
var res []DashboardSearchProjection
|
var res []DashboardSearchProjection
|
||||||
|
|
||||||
sql, params := sb.ToSql()
|
sql, params := sb.ToSql()
|
||||||
|
fmt.Printf("%s, %v", sql, params)
|
||||||
sqlog.Info("sql", "sql", sql, "params", params)
|
sqlog.Info("sql", "sql", sql, "params", params)
|
||||||
err := x.Sql(sql, params...).Find(&res)
|
err := x.Sql(sql, params...).Find(&res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -358,54 +359,6 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFoldersForSignedInUser(query *m.GetFoldersForSignedInUserQuery) error {
|
|
||||||
query.Result = make([]*m.DashboardFolder, 0)
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if query.SignedInUser.OrgRole == m.ROLE_ADMIN {
|
|
||||||
sql := `SELECT distinct d.id, d.title
|
|
||||||
FROM dashboard AS d WHERE d.is_folder = ? AND d.org_id = ?
|
|
||||||
ORDER BY d.title ASC`
|
|
||||||
|
|
||||||
err = x.Sql(sql, dialect.BooleanStr(true), query.OrgId).Find(&query.Result)
|
|
||||||
} else {
|
|
||||||
params := make([]interface{}, 0)
|
|
||||||
sql := `SELECT distinct d.id, d.title
|
|
||||||
FROM dashboard AS d
|
|
||||||
LEFT JOIN dashboard_acl AS da ON d.id = da.dashboard_id
|
|
||||||
LEFT JOIN team_member AS ugm ON ugm.team_id = da.team_id
|
|
||||||
LEFT JOIN org_user ou ON ou.role = da.role AND ou.user_id = ?
|
|
||||||
LEFT JOIN org_user ouRole ON ouRole.role = 'Editor' AND ouRole.user_id = ? AND ouRole.org_id = ?`
|
|
||||||
params = append(params, query.SignedInUser.UserId)
|
|
||||||
params = append(params, query.SignedInUser.UserId)
|
|
||||||
params = append(params, query.OrgId)
|
|
||||||
|
|
||||||
sql += ` WHERE
|
|
||||||
d.org_id = ? AND
|
|
||||||
d.is_folder = ? AND
|
|
||||||
(
|
|
||||||
(d.has_acl = ? AND da.permission > 1 AND (da.user_id = ? OR ugm.user_id = ? OR ou.id IS NOT NULL))
|
|
||||||
OR (d.has_acl = ? AND ouRole.id IS NOT NULL)
|
|
||||||
)`
|
|
||||||
params = append(params, query.OrgId)
|
|
||||||
params = append(params, dialect.BooleanStr(true))
|
|
||||||
params = append(params, dialect.BooleanStr(true))
|
|
||||||
params = append(params, query.SignedInUser.UserId)
|
|
||||||
params = append(params, query.SignedInUser.UserId)
|
|
||||||
params = append(params, dialect.BooleanStr(false))
|
|
||||||
|
|
||||||
if len(query.Title) > 0 {
|
|
||||||
sql += " AND d.title " + dialect.LikeStr() + " ?"
|
|
||||||
params = append(params, "%"+query.Title+"%")
|
|
||||||
}
|
|
||||||
|
|
||||||
sql += ` ORDER BY d.title ASC`
|
|
||||||
err = x.Sql(sql, params...).Find(&query.Result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||||
return inTransaction(func(sess *DBSession) error {
|
return inTransaction(func(sess *DBSession) error {
|
||||||
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
||||||
|
@ -227,12 +227,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
|
|
||||||
Convey("Admin users", func() {
|
Convey("Admin users", func() {
|
||||||
Convey("Should have write access to all dashboard folders in their org", func() {
|
Convey("Should have write access to all dashboard folders in their org", func() {
|
||||||
query := m.GetFoldersForSignedInUserQuery{
|
query := search.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN},
|
SignedInUser: &m.SignedInUser{UserId: adminUser.Id, OrgRole: m.ROLE_ADMIN, OrgId: 1},
|
||||||
|
Permission: m.PERMISSION_VIEW,
|
||||||
|
Type: "dash-folder",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := GetFoldersForSignedInUser(&query)
|
err := SearchDashboards(&query)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 2)
|
So(len(query.Result), ShouldEqual, 2)
|
||||||
@ -260,13 +262,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("Editor users", func() {
|
Convey("Editor users", func() {
|
||||||
query := m.GetFoldersForSignedInUserQuery{
|
query := search.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR},
|
SignedInUser: &m.SignedInUser{UserId: editorUser.Id, OrgRole: m.ROLE_EDITOR, OrgId: 1},
|
||||||
|
Permission: m.PERMISSION_EDIT,
|
||||||
}
|
}
|
||||||
|
|
||||||
Convey("Should have write access to all dashboard folders with default ACL", func() {
|
Convey("Should have write access to all dashboard folders with default ACL", func() {
|
||||||
err := GetFoldersForSignedInUser(&query)
|
err := SearchDashboards(&query)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 2)
|
So(len(query.Result), ShouldEqual, 2)
|
||||||
@ -295,7 +298,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
|
Convey("Should have write access to one dashboard folder if default role changed to view for one folder", func() {
|
||||||
updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW)
|
updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW)
|
||||||
|
|
||||||
err := GetFoldersForSignedInUser(&query)
|
err := SearchDashboards(&query)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 1)
|
So(len(query.Result), ShouldEqual, 1)
|
||||||
@ -305,13 +308,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Convey("Viewer users", func() {
|
Convey("Viewer users", func() {
|
||||||
query := m.GetFoldersForSignedInUserQuery{
|
query := search.FindPersistedDashboardsQuery{
|
||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER},
|
SignedInUser: &m.SignedInUser{UserId: viewerUser.Id, OrgRole: m.ROLE_VIEWER, OrgId: 1},
|
||||||
|
Permission: m.PERMISSION_EDIT,
|
||||||
}
|
}
|
||||||
|
|
||||||
Convey("Should have no write access to any dashboard folders with default ACL", func() {
|
Convey("Should have no write access to any dashboard folders with default ACL", func() {
|
||||||
err := GetFoldersForSignedInUser(&query)
|
err := SearchDashboards(&query)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 0)
|
So(len(query.Result), ShouldEqual, 0)
|
||||||
@ -338,7 +342,7 @@ func TestDashboardFolderDataAccess(t *testing.T) {
|
|||||||
Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
|
Convey("Should be able to get one dashboard folder if default role changed to edit for one folder", func() {
|
||||||
updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT)
|
updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT)
|
||||||
|
|
||||||
err := GetFoldersForSignedInUser(&query)
|
err := SearchDashboards(&query)
|
||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
|
|
||||||
So(len(query.Result), ShouldEqual, 1)
|
So(len(query.Result), ShouldEqual, 1)
|
||||||
|
@ -18,12 +18,14 @@ type SearchBuilder struct {
|
|||||||
whereTypeFolder bool
|
whereTypeFolder bool
|
||||||
whereTypeDash bool
|
whereTypeDash bool
|
||||||
whereFolderIds []int64
|
whereFolderIds []int64
|
||||||
|
permission m.PermissionType
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int) *SearchBuilder {
|
func NewSearchBuilder(signedInUser *m.SignedInUser, limit int, permission m.PermissionType) *SearchBuilder {
|
||||||
searchBuilder := &SearchBuilder{
|
searchBuilder := &SearchBuilder{
|
||||||
signedInUser: signedInUser,
|
signedInUser: signedInUser,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
|
permission: permission,
|
||||||
}
|
}
|
||||||
|
|
||||||
return searchBuilder
|
return searchBuilder
|
||||||
@ -174,7 +176,7 @@ func (sb *SearchBuilder) buildSearchWhereClause() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.writeDashboardPermissionFilter(sb.signedInUser, m.PERMISSION_VIEW)
|
sb.writeDashboardPermissionFilter(sb.signedInUser, sb.permission)
|
||||||
|
|
||||||
if len(sb.whereTitle) > 0 {
|
if len(sb.whereTitle) > 0 {
|
||||||
sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
|
sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
|
||||||
|
@ -16,7 +16,8 @@ func TestSearchBuilder(t *testing.T) {
|
|||||||
OrgId: 1,
|
OrgId: 1,
|
||||||
UserId: 1,
|
UserId: 1,
|
||||||
}
|
}
|
||||||
sb := NewSearchBuilder(signedInUser, 1000)
|
|
||||||
|
sb := NewSearchBuilder(signedInUser, 1000, m.PERMISSION_VIEW)
|
||||||
|
|
||||||
Convey("When building a normal search", func() {
|
Convey("When building a normal search", func() {
|
||||||
sql, params := sb.IsStarred().WithTitle("test").ToSql()
|
sql, params := sb.IsStarred().WithTitle("test").ToSql()
|
||||||
|
@ -12,7 +12,7 @@ type SqlBuilder struct {
|
|||||||
params []interface{}
|
params []interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, minPermission m.PermissionType) {
|
func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, permission m.PermissionType) {
|
||||||
|
|
||||||
if user.OrgRole == m.ROLE_ADMIN {
|
if user.OrgRole == m.ROLE_ADMIN {
|
||||||
return
|
return
|
||||||
@ -40,6 +40,6 @@ func (sb *SqlBuilder) writeDashboardPermissionFilter(user *m.SignedInUser, minPe
|
|||||||
)
|
)
|
||||||
)`)
|
)`)
|
||||||
|
|
||||||
sb.params = append(sb.params, user.OrgId, minPermission, user.UserId, user.UserId)
|
sb.params = append(sb.params, user.OrgId, permission, user.UserId, user.UserId)
|
||||||
sb.params = append(sb.params, okRoles...)
|
sb.params = append(sb.params, okRoles...)
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,13 @@ export class FolderPickerCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOptions(query) {
|
getOptions(query) {
|
||||||
return this.backendSrv.get('api/dashboards/folders', { query: query }).then(result => {
|
const params = {
|
||||||
|
query: query,
|
||||||
|
type: 'dash-folder',
|
||||||
|
permission: 'Edit',
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.backendSrv.get('api/search', params).then(result => {
|
||||||
if (
|
if (
|
||||||
query === '' ||
|
query === '' ||
|
||||||
query.toLowerCase() === 'g' ||
|
query.toLowerCase() === 'g' ||
|
||||||
|
Loading…
Reference in New Issue
Block a user