Prechádzať zdrojové kódy

refactoring dashboad folder acl checks

Torkel Ödegaard 8 rokov pred
rodič
commit
d6341162cb

+ 1 - 1
pkg/api/dashboard.go

@@ -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)

+ 0 - 10
pkg/api/dashboard_acl_test.go

@@ -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()

+ 1 - 1
pkg/api/playlist.go

@@ -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)
 	}

+ 9 - 9
pkg/api/playlist_play.go

@@ -34,18 +34,18 @@ 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 {
 		for _, tag := range dashboardByTag {
 			searchQuery := search.Query{
-				Title:     "",
-				Tags:      []string{tag},
-				UserId:    userId,
-				Limit:     100,
-				IsStarred: false,
-				OrgId:     orgId,
+				Title:        "",
+				Tags:         []string{tag},
+				SignedInUser: signedInUser,
+				Limit:        100,
+				IsStarred:    false,
+				OrgId:        orgId,
 			}
 
 			if err := bus.Dispatch(&searchQuery); err == nil {
@@ -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

+ 3 - 3
pkg/api/search.go

@@ -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,

+ 3 - 1
pkg/models/dashboard_acl.go

@@ -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
 }

+ 0 - 8
pkg/models/dashboards.go

@@ -192,11 +192,3 @@ type GetDashboardSlugByIdQuery struct {
 	Id     int64
 	Result string
 }
-
-type GetAllowedDashboardsQuery struct {
-	UserId   int64
-	OrgId    int64
-	DashList []int64
-
-	Result []int64
-}

+ 22 - 32
pkg/services/guardian/models.go → pkg/services/guardian/guardian.go

@@ -6,50 +6,45 @@ import (
 )
 
 type DashboardGuardian struct {
-	user      *m.SignedInUser
-	dashboard *m.Dashboard
-	acl       []*m.DashboardAclInfoDTO
-	groups    []*m.UserGroup
+	user   *m.SignedInUser
+	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,
+		user:   user,
+		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
 	}

+ 2 - 3
pkg/services/search/handlers.go

@@ -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
 	}
 

+ 1 - 1
pkg/services/search/handlers_test.go

@@ -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{

+ 5 - 4
pkg/services/search/models.go

@@ -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

+ 35 - 26
pkg/services/sqlstore/dashboard.go

@@ -148,12 +148,14 @@ func GetDashboard(query *m.GetDashboardQuery) error {
 }
 
 type DashboardSearchProjection struct {
-	Id       int64
-	Title    string
-	Slug     string
-	Term     string
-	IsFolder bool
-	ParentId int64
+	Id          int64
+	Title       string
+	Slug        string
+	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 {

+ 7 - 9
pkg/services/sqlstore/dashboard_acl.go

@@ -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,

+ 41 - 0
pkg/services/sqlstore/dashboard_acl_test.go

@@ -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,

+ 123 - 12
pkg/services/sqlstore/dashboard_test.go

@@ -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 {
@@ -113,9 +114,10 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should be able to search for dashboard and return in folder hierarchy", func() {
 				query := search.FindPersistedDashboardsQuery{
-					Title: "test dash 23",
-					OrgId: 1,
-					Mode:  "tree",
+					Title:        "test dash 23",
+					OrgId:        1,
+					Mode:         "tree",
+					SignedInUser: &m.SignedInUser{OrgId: 1},
 				}
 
 				err := SearchDashboards(&query)
@@ -132,8 +134,9 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should be able to search for dashboard folder", func() {
 				query := search.FindPersistedDashboardsQuery{
-					Title: "1 test dash folder",
-					OrgId: 1,
+					Title:        "1 test dash folder",
+					OrgId:        1,
+					SignedInUser: &m.SignedInUser{OrgId: 1},
 				}
 
 				err := SearchDashboards(&query)
@@ -146,8 +149,9 @@ func TestDashboardDataAccess(t *testing.T) {
 
 			Convey("Should be able to search for a dashboard folder's children", func() {
 				query := search.FindPersistedDashboardsQuery{
-					OrgId:    1,
-					ParentId: savedFolder.Id,
+					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(&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)
+}

+ 0 - 42
pkg/services/sqlstore/guardian.go

@@ -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), "[]")
-}

+ 0 - 87
pkg/services/sqlstore/guardian_test.go

@@ -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(&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)
-}

+ 1 - 0
public/app/features/org/all.js

@@ -10,4 +10,5 @@ define([
   './prefs_control',
   './user_groups_ctrl',
   './user_group_details_ctrl',
+  './create_user_group_modal',
 ], function () {});

+ 38 - 0
public/app/features/org/create_user_group_modal.ts

@@ -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);

+ 15 - 12
public/app/features/org/partials/create_user_group.html

@@ -1,23 +1,26 @@
-<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">
-			<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>
-				</div>
-				<div class="gf-form">
-					<button class="btn gf-form-btn btn-success" ng-click="ctrl.createUserGroup();dismiss();">Create</button>
-				</div>
-			</div>
+		<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' 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();ctrl.dismiss();">Create</button>
+          </div>
+        </div>
+      </div>
 		</form>
 	</div>
 </div>

+ 4 - 16
public/app/features/org/user_groups_ctrl.ts

@@ -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'
     });
   }
 }