mirror of https://github.com/grafana/grafana.git
				
				
				
			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.Post("/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard))
 | 
			
		||||
 | 
			
		||||
			dashboardRoute.Get("/folders", wrap(GetFoldersForSignedInUser))
 | 
			
		||||
 | 
			
		||||
			dashboardRoute.Group("/id/:dashboardId", func(dashIdRoute RouteRegister) {
 | 
			
		||||
				dashIdRoute.Get("/versions", wrap(GetDashboardVersions))
 | 
			
		||||
				dashIdRoute.Get("/versions/:id", wrap(GetDashboardVersion))
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -490,19 +490,3 @@ func GetDashboardTags(c *middleware.Context) {
 | 
			
		|||
 | 
			
		||||
	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/metrics"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/middleware"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/models"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/services/search"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,11 +16,16 @@ func Search(c *middleware.Context) {
 | 
			
		|||
	starred := c.Query("starred")
 | 
			
		||||
	limit := c.QueryInt("limit")
 | 
			
		||||
	dashboardType := c.Query("type")
 | 
			
		||||
	permission := models.PERMISSION_VIEW
 | 
			
		||||
 | 
			
		||||
	if limit == 0 {
 | 
			
		||||
		limit = 1000
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.Query("permission") == "Edit" {
 | 
			
		||||
		permission = models.PERMISSION_EDIT
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dbids := make([]int64, 0)
 | 
			
		||||
	for _, id := range c.QueryStrings("dashboardIds") {
 | 
			
		||||
		dashboardId, err := strconv.ParseInt(id, 10, 64)
 | 
			
		||||
| 
						 | 
				
			
			@ -46,6 +52,7 @@ func Search(c *middleware.Context) {
 | 
			
		|||
		DashboardIds: dbids,
 | 
			
		||||
		Type:         dashboardType,
 | 
			
		||||
		FolderIds:    folderIds,
 | 
			
		||||
		Permission:   permission,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := bus.Dispatch(&searchQuery)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -270,18 +270,6 @@ type GetDashboardsBySlugQuery struct {
 | 
			
		|||
	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 {
 | 
			
		||||
	DashboardId    int64          `json:"dashboardId"`
 | 
			
		||||
	Permission     PermissionType `json:"permission"`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,6 +21,7 @@ func searchHandler(query *Query) error {
 | 
			
		|||
		FolderIds:    query.FolderIds,
 | 
			
		||||
		Tags:         query.Tags,
 | 
			
		||||
		Limit:        query.Limit,
 | 
			
		||||
		Permission:   query.Permission,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := bus.Dispatch(&dashQuery); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,6 +52,7 @@ type Query struct {
 | 
			
		|||
	Type         string
 | 
			
		||||
	DashboardIds []int64
 | 
			
		||||
	FolderIds    []int64
 | 
			
		||||
	Permission   models.PermissionType
 | 
			
		||||
 | 
			
		||||
	Result HitList
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -66,7 +67,7 @@ type FindPersistedDashboardsQuery struct {
 | 
			
		|||
	FolderIds    []int64
 | 
			
		||||
	Tags         []string
 | 
			
		||||
	Limit        int
 | 
			
		||||
	IsBrowse     bool
 | 
			
		||||
	Permission   models.PermissionType
 | 
			
		||||
 | 
			
		||||
	Result HitList
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
package sqlstore
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +22,6 @@ func init() {
 | 
			
		|||
	bus.AddHandler("sql", GetDashboardSlugById)
 | 
			
		||||
	bus.AddHandler("sql", GetDashboardUIDById)
 | 
			
		||||
	bus.AddHandler("sql", GetDashboardsByPluginId)
 | 
			
		||||
	bus.AddHandler("sql", GetFoldersForSignedInUser)
 | 
			
		||||
	bus.AddHandler("sql", GetDashboardPermissionsForUser)
 | 
			
		||||
	bus.AddHandler("sql", GetDashboardsBySlug)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -256,7 +256,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
 | 
			
		|||
		limit = 1000
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sb := NewSearchBuilder(query.SignedInUser, limit).
 | 
			
		||||
	sb := NewSearchBuilder(query.SignedInUser, limit, query.Permission).
 | 
			
		||||
		WithTags(query.Tags).
 | 
			
		||||
		WithDashboardIdsIn(query.DashboardIds)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -279,6 +279,7 @@ func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSear
 | 
			
		|||
	var res []DashboardSearchProjection
 | 
			
		||||
 | 
			
		||||
	sql, params := sb.ToSql()
 | 
			
		||||
	fmt.Printf("%s, %v", sql, params)
 | 
			
		||||
	sqlog.Info("sql", "sql", sql, "params", params)
 | 
			
		||||
	err := x.Sql(sql, params...).Find(&res)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -358,54 +359,6 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
 | 
			
		|||
	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 {
 | 
			
		||||
	return inTransaction(func(sess *DBSession) error {
 | 
			
		||||
		dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -227,12 +227,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 | 
			
		|||
 | 
			
		||||
			Convey("Admin users", func() {
 | 
			
		||||
				Convey("Should have write access to all dashboard folders in their org", func() {
 | 
			
		||||
					query := m.GetFoldersForSignedInUserQuery{
 | 
			
		||||
					query := search.FindPersistedDashboardsQuery{
 | 
			
		||||
						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(len(query.Result), ShouldEqual, 2)
 | 
			
		||||
| 
						 | 
				
			
			@ -260,13 +262,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 | 
			
		|||
			})
 | 
			
		||||
 | 
			
		||||
			Convey("Editor users", func() {
 | 
			
		||||
				query := m.GetFoldersForSignedInUserQuery{
 | 
			
		||||
				query := search.FindPersistedDashboardsQuery{
 | 
			
		||||
					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() {
 | 
			
		||||
					err := GetFoldersForSignedInUser(&query)
 | 
			
		||||
					err := SearchDashboards(&query)
 | 
			
		||||
					So(err, ShouldBeNil)
 | 
			
		||||
 | 
			
		||||
					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() {
 | 
			
		||||
					updateTestDashboardWithAcl(folder1.Id, editorUser.Id, m.PERMISSION_VIEW)
 | 
			
		||||
 | 
			
		||||
					err := GetFoldersForSignedInUser(&query)
 | 
			
		||||
					err := SearchDashboards(&query)
 | 
			
		||||
					So(err, ShouldBeNil)
 | 
			
		||||
 | 
			
		||||
					So(len(query.Result), ShouldEqual, 1)
 | 
			
		||||
| 
						 | 
				
			
			@ -305,13 +308,14 @@ func TestDashboardFolderDataAccess(t *testing.T) {
 | 
			
		|||
			})
 | 
			
		||||
 | 
			
		||||
			Convey("Viewer users", func() {
 | 
			
		||||
				query := m.GetFoldersForSignedInUserQuery{
 | 
			
		||||
				query := search.FindPersistedDashboardsQuery{
 | 
			
		||||
					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() {
 | 
			
		||||
					err := GetFoldersForSignedInUser(&query)
 | 
			
		||||
					err := SearchDashboards(&query)
 | 
			
		||||
					So(err, ShouldBeNil)
 | 
			
		||||
 | 
			
		||||
					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() {
 | 
			
		||||
					updateTestDashboardWithAcl(folder1.Id, viewerUser.Id, m.PERMISSION_EDIT)
 | 
			
		||||
 | 
			
		||||
					err := GetFoldersForSignedInUser(&query)
 | 
			
		||||
					err := SearchDashboards(&query)
 | 
			
		||||
					So(err, ShouldBeNil)
 | 
			
		||||
 | 
			
		||||
					So(len(query.Result), ShouldEqual, 1)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -18,12 +18,14 @@ type SearchBuilder struct {
 | 
			
		|||
	whereTypeFolder     bool
 | 
			
		||||
	whereTypeDash       bool
 | 
			
		||||
	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{
 | 
			
		||||
		signedInUser: signedInUser,
 | 
			
		||||
		limit:        limit,
 | 
			
		||||
		permission:   permission,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
		sb.sql.WriteString(" AND dashboard.title " + dialect.LikeStr() + " ?")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,8 @@ func TestSearchBuilder(t *testing.T) {
 | 
			
		|||
			OrgId:  1,
 | 
			
		||||
			UserId: 1,
 | 
			
		||||
		}
 | 
			
		||||
		sb := NewSearchBuilder(signedInUser, 1000)
 | 
			
		||||
 | 
			
		||||
		sb := NewSearchBuilder(signedInUser, 1000, m.PERMISSION_VIEW)
 | 
			
		||||
 | 
			
		||||
		Convey("When building a normal search", func() {
 | 
			
		||||
			sql, params := sb.IsStarred().WithTitle("test").ToSql()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -12,7 +12,7 @@ type SqlBuilder struct {
 | 
			
		|||
	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 {
 | 
			
		||||
		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...)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,13 @@ export class FolderPickerCtrl {
 | 
			
		|||
  }
 | 
			
		||||
 | 
			
		||||
  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 (
 | 
			
		||||
        query === '' ||
 | 
			
		||||
        query.toLowerCase() === 'g' ||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue