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