Merge branch 'develop' into panel-title-menu-ux
This commit is contained in:
@@ -12,7 +12,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
Convey("Testing Alerting data access", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, "alert")
|
||||
testDash := insertTestDashboard("dashboard with alerts", 1, 0, false, "alert")
|
||||
|
||||
items := []*m.Alert{
|
||||
{
|
||||
@@ -192,7 +192,7 @@ func TestAlertingDataAccess(t *testing.T) {
|
||||
|
||||
err = DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
OrgId: 1,
|
||||
Slug: testDash.Slug,
|
||||
Id: testDash.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
@@ -3,6 +3,7 @@ package sqlstore
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
@@ -70,6 +71,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
}
|
||||
|
||||
err = setHasAcl(sess, dash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parentVersion := dash.Version
|
||||
affectedRows := int64(0)
|
||||
|
||||
@@ -79,9 +85,9 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Insert(dash)
|
||||
} else {
|
||||
dash.Version += 1
|
||||
dash.Version++
|
||||
dash.Data.Set("version", dash.Version)
|
||||
affectedRows, err = sess.Id(dash.Id).Update(dash)
|
||||
affectedRows, err = sess.MustCols("folder_id", "has_acl").Id(dash.Id).Update(dash)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -110,7 +116,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
return m.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
// delete existing tabs
|
||||
// delete existing tags
|
||||
_, err = sess.Exec("DELETE FROM dashboard_tag WHERE dashboard_id=?", dash.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -125,13 +131,37 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.Result = dash
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func setHasAcl(sess *DBSession, dash *m.Dashboard) error {
|
||||
// check if parent has acl
|
||||
if dash.FolderId > 0 {
|
||||
var parent m.Dashboard
|
||||
if hasParent, err := sess.Where("folder_id=?", dash.FolderId).Get(&parent); err != nil {
|
||||
return err
|
||||
} else if hasParent && parent.HasAcl {
|
||||
dash.HasAcl = true
|
||||
}
|
||||
}
|
||||
|
||||
// check if dash has its own acl
|
||||
if dash.Id > 0 {
|
||||
if res, err := sess.Query("SELECT 1 from dashboard_acl WHERE dashboard_id =?", dash.Id); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if len(res) > 0 {
|
||||
dash.HasAcl = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
dashboard := m.Dashboard{Slug: query.Slug, OrgId: query.OrgId, Id: query.Id}
|
||||
has, err := x.Get(&dashboard)
|
||||
@@ -148,48 +178,94 @@ func GetDashboard(query *m.GetDashboardQuery) error {
|
||||
}
|
||||
|
||||
type DashboardSearchProjection struct {
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
Id int64
|
||||
Title string
|
||||
Slug string
|
||||
Term string
|
||||
IsFolder bool
|
||||
FolderId int64
|
||||
FolderSlug string
|
||||
FolderTitle string
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
func findDashboards(query *search.FindPersistedDashboardsQuery) ([]DashboardSearchProjection, error) {
|
||||
limit := query.Limit
|
||||
if limit == 0 {
|
||||
limit = 1000
|
||||
}
|
||||
|
||||
var sql bytes.Buffer
|
||||
params := make([]interface{}, 0)
|
||||
|
||||
sql.WriteString(`SELECT
|
||||
dashboard.id,
|
||||
dashboard.title,
|
||||
dashboard.slug,
|
||||
dashboard_tag.term
|
||||
FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard_tag.dashboard_id = dashboard.id`)
|
||||
sql.WriteString(`
|
||||
SELECT
|
||||
dashboard.id,
|
||||
dashboard.title,
|
||||
dashboard.slug,
|
||||
dashboard_tag.term,
|
||||
dashboard.is_folder,
|
||||
dashboard.folder_id,
|
||||
folder.slug as folder_slug,
|
||||
folder.title as folder_title
|
||||
FROM (
|
||||
SELECT
|
||||
dashboard.id FROM dashboard
|
||||
LEFT OUTER JOIN dashboard_tag ON dashboard_tag.dashboard_id = dashboard.id
|
||||
`)
|
||||
|
||||
// add tags filter
|
||||
if len(query.Tags) > 0 {
|
||||
sql.WriteString(` WHERE dashboard_tag.term IN (?` + strings.Repeat(",?", len(query.Tags)-1) + `)`)
|
||||
for _, tag := range query.Tags {
|
||||
params = append(params, tag)
|
||||
}
|
||||
}
|
||||
|
||||
// this ends the inner select (tag filtered part)
|
||||
sql.WriteString(`
|
||||
GROUP BY dashboard.id HAVING COUNT(dashboard.id) >= ?
|
||||
ORDER BY dashboard.title ASC LIMIT ?) as ids`)
|
||||
params = append(params, len(query.Tags))
|
||||
params = append(params, limit)
|
||||
|
||||
sql.WriteString(`
|
||||
INNER JOIN dashboard on ids.id = dashboard.id
|
||||
LEFT OUTER JOIN dashboard folder on folder.id = dashboard.folder_id
|
||||
LEFT OUTER JOIN dashboard_tag on dashboard.id = dashboard_tag.dashboard_id`)
|
||||
if query.IsStarred {
|
||||
sql.WriteString(" INNER JOIN star on star.dashboard_id = dashboard.id")
|
||||
}
|
||||
|
||||
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 {
|
||||
sql.WriteString(" AND (")
|
||||
for i, dashboardId := range query.DashboardIds {
|
||||
if i != 0 {
|
||||
sql.WriteString(" OR")
|
||||
}
|
||||
|
||||
sql.WriteString(" dashboard.id = ?")
|
||||
sql.WriteString(` AND dashboard.id IN (?` + strings.Repeat(",?", len(query.DashboardIds)-1) + `)`)
|
||||
for _, dashboardId := range query.DashboardIds {
|
||||
params = append(params, dashboardId)
|
||||
}
|
||||
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_acl as da on d.folder_id = da.dashboard_id or d.id = da.dashboard_id
|
||||
LEFT JOIN user_group_member as ugm on ugm.user_group_id = da.user_group_id
|
||||
LEFT JOIN org_user ou on ou.role = da.role
|
||||
WHERE
|
||||
d.has_acl = 1 and
|
||||
(da.user_id = ? or ugm.user_id = ? or ou.id is not null)
|
||||
and d.org_id = ?
|
||||
))`
|
||||
|
||||
sql.WriteString(allowedDashboardsSubQuery)
|
||||
params = append(params, query.SignedInUser.UserId, query.SignedInUser.UserId, query.SignedInUser.OrgId)
|
||||
}
|
||||
|
||||
if len(query.Title) > 0 {
|
||||
@@ -197,15 +273,54 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
params = append(params, "%"+query.Title+"%")
|
||||
}
|
||||
|
||||
if len(query.Type) > 0 && query.Type == "dash-folder" {
|
||||
sql.WriteString(" AND dashboard.is_folder = 1")
|
||||
}
|
||||
|
||||
if len(query.Type) > 0 && query.Type == "dash-db" {
|
||||
sql.WriteString(" AND dashboard.is_folder = 0")
|
||||
}
|
||||
|
||||
if query.FolderId > 0 {
|
||||
sql.WriteString(" AND dashboard.folder_id = ?")
|
||||
params = append(params, query.FolderId)
|
||||
}
|
||||
|
||||
sql.WriteString(fmt.Sprintf(" ORDER BY dashboard.title ASC LIMIT 1000"))
|
||||
|
||||
var res []DashboardSearchProjection
|
||||
|
||||
err := x.Sql(sql.String(), params...).Find(&res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
res, err := findDashboards(query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
makeQueryResult(query, res)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getHitType(item DashboardSearchProjection) search.HitType {
|
||||
var hitType search.HitType
|
||||
if item.IsFolder {
|
||||
hitType = search.DashHitFolder
|
||||
} else {
|
||||
hitType = search.DashHitDB
|
||||
}
|
||||
|
||||
return hitType
|
||||
}
|
||||
|
||||
func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []DashboardSearchProjection) {
|
||||
query.Result = make([]*search.Hit, 0)
|
||||
hits := make(map[int64]*search.Hit)
|
||||
|
||||
@@ -213,11 +328,14 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
hit, exists := hits[item.Id]
|
||||
if !exists {
|
||||
hit = &search.Hit{
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: search.DashHitDB,
|
||||
Tags: []string{},
|
||||
Id: item.Id,
|
||||
Title: item.Title,
|
||||
Uri: "db/" + item.Slug,
|
||||
Type: getHitType(item),
|
||||
FolderId: item.FolderId,
|
||||
FolderTitle: item.FolderTitle,
|
||||
FolderSlug: item.FolderSlug,
|
||||
Tags: []string{},
|
||||
}
|
||||
query.Result = append(query.Result, hit)
|
||||
hits[item.Id] = hit
|
||||
@@ -226,8 +344,6 @@ func SearchDashboards(query *search.FindPersistedDashboardsQuery) error {
|
||||
hit.Tags = append(hit.Tags, item.Term)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
@@ -247,7 +363,7 @@ func GetDashboardTags(query *m.GetDashboardTagsQuery) error {
|
||||
|
||||
func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
dashboard := m.Dashboard{Slug: cmd.Slug, OrgId: cmd.OrgId}
|
||||
dashboard := m.Dashboard{Id: cmd.Id, OrgId: cmd.OrgId}
|
||||
has, err := sess.Get(&dashboard)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -261,6 +377,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 folder_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", SetDashboardAcl)
|
||||
bus.AddHandler("sql", UpdateDashboardAcl)
|
||||
bus.AddHandler("sql", RemoveDashboardAcl)
|
||||
bus.AddHandler("sql", GetDashboardAclInfoList)
|
||||
}
|
||||
|
||||
func UpdateDashboardAcl(cmd *m.UpdateDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
// delete existing items
|
||||
_, err := sess.Exec("DELETE FROM dashboard_acl WHERE dashboard_id=?", cmd.DashboardId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range cmd.Items {
|
||||
if item.UserId == 0 && item.UserGroupId == 0 && !item.Role.IsValid() {
|
||||
return m.ErrDashboardAclInfoMissing
|
||||
}
|
||||
|
||||
if item.DashboardId == 0 {
|
||||
return m.ErrDashboardPermissionDashboardEmpty
|
||||
}
|
||||
|
||||
sess.Nullable("user_id", "user_group_id")
|
||||
if _, err := sess.Insert(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{HasAcl: true}
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func SetDashboardAcl(cmd *m.SetDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if cmd.UserId == 0 && cmd.UserGroupId == 0 {
|
||||
return m.ErrDashboardAclInfoMissing
|
||||
}
|
||||
|
||||
if cmd.DashboardId == 0 {
|
||||
return m.ErrDashboardPermissionDashboardEmpty
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from "+dialect.Quote("dashboard_acl")+" WHERE dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
|
||||
entity := m.DashboardAcl{
|
||||
Permission: cmd.Permission,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("updated", "permission").Where("dashboard_id =? and (user_group_id=? or user_id=?)", cmd.DashboardId, cmd.UserGroupId, cmd.UserId).Update(&entity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
entity := m.DashboardAcl{
|
||||
OrgId: cmd.OrgId,
|
||||
UserGroupId: cmd.UserGroupId,
|
||||
UserId: cmd.UserId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
DashboardId: cmd.DashboardId,
|
||||
Permission: cmd.Permission,
|
||||
}
|
||||
|
||||
cols := []string{"org_id", "created", "updated", "dashboard_id", "permission"}
|
||||
|
||||
if cmd.UserId != 0 {
|
||||
cols = append(cols, "user_id")
|
||||
}
|
||||
|
||||
if cmd.UserGroupId != 0 {
|
||||
cols = append(cols, "user_group_id")
|
||||
}
|
||||
|
||||
_, err := sess.Cols(cols...).Insert(&entity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.Result = entity
|
||||
|
||||
// Update dashboard HasAcl flag
|
||||
dashboard := m.Dashboard{
|
||||
HasAcl: true,
|
||||
}
|
||||
|
||||
if _, err := sess.Cols("has_acl").Where("id=? OR folder_id=?", cmd.DashboardId, cmd.DashboardId).Update(&dashboard); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveDashboardAcl(cmd *m.RemoveDashboardAclCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSQL = "DELETE FROM " + dialect.Quote("dashboard_acl") + " WHERE org_id =? and id=?"
|
||||
_, err := sess.Exec(rawSQL, cmd.OrgId, cmd.AclId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
|
||||
dashboardFilter := fmt.Sprintf(`IN (
|
||||
SELECT %d
|
||||
UNION
|
||||
SELECT folder_id from dashboard where id = %d
|
||||
)`, query.DashboardId, query.DashboardId)
|
||||
|
||||
rawSQL := `
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
u.login AS user_login,
|
||||
u.email AS user_email,
|
||||
ug.name AS user_group
|
||||
FROM` + dialect.Quote("dashboard_acl") + ` as da
|
||||
LEFT OUTER JOIN ` + dialect.Quote("user") + ` AS u ON u.id = da.user_id
|
||||
LEFT OUTER JOIN user_group ug on ug.id = da.user_group_id
|
||||
WHERE dashboard_id ` + dashboardFilter + ` AND da.org_id = ?
|
||||
|
||||
-- Also include default permission if has_acl = 0
|
||||
|
||||
UNION
|
||||
SELECT
|
||||
da.id,
|
||||
da.org_id,
|
||||
da.dashboard_id,
|
||||
da.user_id,
|
||||
da.user_group_id,
|
||||
da.permission,
|
||||
da.role,
|
||||
da.created,
|
||||
da.updated,
|
||||
'' as user_login,
|
||||
'' as user_email,
|
||||
'' as user_group
|
||||
FROM dashboard_acl as da,
|
||||
dashboard as dash
|
||||
LEFT JOIN dashboard folder on dash.folder_id = folder.id
|
||||
WHERE dash.id = ? AND (dash.has_acl = 0 or folder.has_acl = 0) AND da.dashboard_id = -1
|
||||
`
|
||||
|
||||
query.Result = make([]*m.DashboardAclInfoDTO, 0)
|
||||
err := x.SQL(rawSQL, query.OrgId, query.DashboardId).Find(&query.Result)
|
||||
|
||||
for _, p := range query.Result {
|
||||
p.PermissionName = p.Permission.String()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestDashboardAclDataAccess(t *testing.T) {
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
Convey("Given a dashboard folder and a user", func() {
|
||||
currentUser := createUser("viewer", "Viewer", false)
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
childDash := insertTestDashboard("2 test dash", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
|
||||
Convey("When adding dashboard permission with userId and userGroupId set to 0", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldEqual, m.ErrDashboardAclInfoMissing)
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder with default permissions", func() {
|
||||
Convey("When reading dashboard acl should include acl for parent folder", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
defaultPermissionsId := -1
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given dashboard folder permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include acl for parent folder", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{DashboardId: childDash.Id, OrgId: 1}
|
||||
|
||||
err := GetDashboardAclInfoList(&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 := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include acl for parent folder and child", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{OrgId: 1, DashboardId: childDash.Id}
|
||||
|
||||
err := GetDashboardAclInfoList(&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("Given child dashboard permission in folder with no permissions", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: childDash.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When reading dashboard acl should include default acl for parent folder and the child acl", func() {
|
||||
query := m.GetDashboardAclInfoListQuery{OrgId: 1, DashboardId: childDash.Id}
|
||||
|
||||
err := GetDashboardAclInfoList(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
defaultPermissionsId := -1
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[0].Role, ShouldEqual, m.ROLE_VIEWER)
|
||||
So(query.Result[1].DashboardId, ShouldEqual, defaultPermissionsId)
|
||||
So(*query.Result[1].Role, ShouldEqual, m.ROLE_EDITOR)
|
||||
So(query.Result[2].DashboardId, ShouldEqual, childDash.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to add dashboard permission", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: currentUser.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(setDashAclCmd.Result.Id, ShouldEqual, 3)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].PermissionName, ShouldEqual, "Edit")
|
||||
So(q1.Result[0].UserId, ShouldEqual, currentUser.Id)
|
||||
So(q1.Result[0].UserLogin, ShouldEqual, currentUser.Login)
|
||||
So(q1.Result[0].UserEmail, ShouldEqual, currentUser.Email)
|
||||
So(q1.Result[0].Id, ShouldEqual, setDashAclCmd.Result.Id)
|
||||
|
||||
Convey("Should update hasAcl field to true for dashboard folder and its children", func() {
|
||||
q2 := &m.GetDashboardsQuery{DashboardIds: []int64{savedFolder.Id, childDash.Id}}
|
||||
err := GetDashboards(q2)
|
||||
So(err, ShouldBeNil)
|
||||
So(q2.Result[0].HasAcl, ShouldBeTrue)
|
||||
So(q2.Result[1].HasAcl, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: 1,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_ADMIN,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].UserId, ShouldEqual, 1)
|
||||
|
||||
})
|
||||
|
||||
Convey("Should be able to delete an existing permission", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given a user group", func() {
|
||||
group1 := m.CreateUserGroupCommand{Name: "group1 name", OrgId: 1}
|
||||
err := CreateUserGroup(&group1)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to add a user permission for a user group", func() {
|
||||
setDashAclCmd := m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_EDIT,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(&setDashAclCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q1.Result[0].Permission, ShouldEqual, m.PERMISSION_EDIT)
|
||||
So(q1.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
|
||||
Convey("Should be able to delete an existing permission for a user group", func() {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
AclId: setDashAclCmd.Result.Id,
|
||||
})
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Should be able to update an existing permission for a user group", func() {
|
||||
err := SetDashboardAcl(&m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserGroupId: group1.Result.Id,
|
||||
DashboardId: savedFolder.Id,
|
||||
Permission: m.PERMISSION_ADMIN,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q3 := &m.GetDashboardAclInfoListQuery{DashboardId: savedFolder.Id, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(q3)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q3.Result), ShouldEqual, 1)
|
||||
So(q3.Result[0].DashboardId, ShouldEqual, savedFolder.Id)
|
||||
So(q3.Result[0].Permission, ShouldEqual, m.PERMISSION_ADMIN)
|
||||
So(q3.Result[0].UserGroupId, ShouldEqual, group1.Result.Id)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -5,42 +5,35 @@ import (
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
"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, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Given saved dashboard", func() {
|
||||
savedDash := insertTestDashboard("test dash 23", 1, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, "prod")
|
||||
insertTestDashboard("test dash 67", 1, "prod", "webapp")
|
||||
savedFolder := insertTestDashboard("1 test dash folder", 1, 0, true, "prod", "webapp")
|
||||
savedDash := insertTestDashboard("test dash 23", 1, savedFolder.Id, false, "prod", "webapp")
|
||||
insertTestDashboard("test dash 45", 1, savedFolder.Id, false, "prod")
|
||||
insertTestDashboard("test dash 67", 1, 0, false, "prod", "webapp")
|
||||
|
||||
Convey("Should return dashboard model", func() {
|
||||
So(savedDash.Title, ShouldEqual, "test dash 23")
|
||||
So(savedDash.Slug, ShouldEqual, "test-dash-23")
|
||||
So(savedDash.Id, ShouldNotEqual, 0)
|
||||
So(savedDash.IsFolder, ShouldBeFalse)
|
||||
So(savedDash.FolderId, ShouldBeGreaterThan, 0)
|
||||
|
||||
So(savedFolder.Title, ShouldEqual, "1 test dash folder")
|
||||
So(savedFolder.Slug, ShouldEqual, "1-test-dash-folder")
|
||||
So(savedFolder.Id, ShouldNotEqual, 0)
|
||||
So(savedFolder.IsFolder, ShouldBeTrue)
|
||||
So(savedFolder.FolderId, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to get dashboard", func() {
|
||||
@@ -54,15 +47,14 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
So(query.Result.Title, ShouldEqual, "test dash 23")
|
||||
So(query.Result.Slug, ShouldEqual, "test-dash-23")
|
||||
So(query.Result.IsFolder, ShouldBeFalse)
|
||||
})
|
||||
|
||||
Convey("Should be able to delete dashboard", func() {
|
||||
insertTestDashboard("delete me", 1, "delete this")
|
||||
|
||||
dashboardSlug := slug.Make("delete me")
|
||||
dash := insertTestDashboard("delete me", 1, 0, false, "delete this")
|
||||
|
||||
err := DeleteDashboard(&m.DeleteDashboardCommand{
|
||||
Slug: dashboardSlug,
|
||||
Id: dash.Id,
|
||||
OrgId: 1,
|
||||
})
|
||||
|
||||
@@ -102,10 +94,11 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for dashboard", func() {
|
||||
Convey("Should be able to search for dashboard folder", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
Title: "test dash 23",
|
||||
OrgId: 1,
|
||||
Title: "1 test dash folder",
|
||||
OrgId: 1,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -113,14 +106,29 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
hit := query.Result[0]
|
||||
So(len(hit.Tags), ShouldEqual, 2)
|
||||
So(hit.Type, ShouldEqual, search.DashHitFolder)
|
||||
})
|
||||
|
||||
Convey("Should be able to search for a dashboard folder's children", func() {
|
||||
query := search.FindPersistedDashboardsQuery{
|
||||
OrgId: 1,
|
||||
FolderId: savedFolder.Id,
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
hit := query.Result[0]
|
||||
So(hit.Id, ShouldEqual, savedDash.Id)
|
||||
})
|
||||
|
||||
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{1, 2},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{2, 3},
|
||||
SignedInUser: &m.SignedInUser{OrgId: 1},
|
||||
}
|
||||
|
||||
err := SearchDashboards(&query)
|
||||
@@ -137,8 +145,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)
|
||||
@@ -161,6 +169,63 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
Convey("Should be able to update dashboard and remove folderId", func() {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
Overwrite: true,
|
||||
FolderId: 2,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
So(cmd.Result.FolderId, ShouldEqual, 2)
|
||||
|
||||
cmd = m.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": 1,
|
||||
"title": "folderId",
|
||||
"tags": []interface{}{},
|
||||
}),
|
||||
FolderId: 0,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err = SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetDashboardQuery{
|
||||
Slug: cmd.Result.Slug,
|
||||
OrgId: 1,
|
||||
}
|
||||
|
||||
err = GetDashboard(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Result.FolderId, 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,
|
||||
FolderId: 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}
|
||||
|
||||
@@ -171,7 +236,7 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Given two dashboards, one is starred dashboard by user 10, other starred by user 1", func() {
|
||||
starredDash := insertTestDashboard("starred dash", 1)
|
||||
starredDash := insertTestDashboard("starred dash", 1, 0, false)
|
||||
StarDashboard(&m.StarDashboardCommand{
|
||||
DashboardId: starredDash.Id,
|
||||
UserId: 10,
|
||||
@@ -183,7 +248,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)
|
||||
@@ -192,5 +257,256 @@ func TestDashboardDataAccess(t *testing.T) {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given one dashboard folder with two dashboards 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")
|
||||
childDash := 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and acl is set for dashboard child and folder has all permissions removed", func() {
|
||||
var otherUser int64 = 999
|
||||
aclId := updateTestDashboardWithAcl(folder.Id, otherUser, m.PERMISSION_EDIT)
|
||||
removeAcl(aclId)
|
||||
updateTestDashboardWithAcl(childDash.Id, otherUser, m.PERMISSION_EDIT)
|
||||
|
||||
Convey("should not return folder or child", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.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 to child", func() {
|
||||
updateTestDashboardWithAcl(childDash.Id, currentUser.Id, m.PERMISSION_EDIT)
|
||||
|
||||
Convey("should be able to search for child dashboard but not folder", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1}, OrgId: 1, DashboardIds: []int64{folder.Id, childDash.Id, dashInRoot.Id}}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 2)
|
||||
So(query.Result[0].Id, ShouldEqual, childDash.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("when the user is an admin", func() {
|
||||
Convey("should be able to search for child dash and 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, childDash.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].Id, ShouldEqual, folder.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Given two dashboard folders with one dashboard each and one dashboard in the root folder", func() {
|
||||
folder1 := insertTestDashboard("1 test dash folder", 1, 0, true, "prod")
|
||||
folder2 := insertTestDashboard("2 test dash folder", 1, 0, true, "prod")
|
||||
dashInRoot := insertTestDashboard("test dash 67", 1, 0, false, "prod")
|
||||
childDash1 := insertTestDashboard("child dash 1", 1, folder1.Id, false, "prod")
|
||||
childDash2 := insertTestDashboard("child dash 2", 1, folder2.Id, false, "prod")
|
||||
|
||||
currentUser := createUser("viewer", "Viewer", false)
|
||||
|
||||
Convey("and acl is set for one dashboard folder", func() {
|
||||
var otherUser int64 = 999
|
||||
updateTestDashboardWithAcl(folder1.Id, otherUser, m.PERMISSION_EDIT)
|
||||
|
||||
Convey("and a dashboard is moved from folder without acl to the folder with an acl", func() {
|
||||
movedDash := moveDashboard(1, childDash2.Data, folder1.Id)
|
||||
So(movedDash.HasAcl, ShouldBeTrue)
|
||||
|
||||
Convey("should not return folder with acl or its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder1.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and a dashboard is moved from folder with acl to the folder without an acl", func() {
|
||||
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
So(movedDash.HasAcl, ShouldBeFalse)
|
||||
|
||||
Convey("should return folder without acl and its children", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 4)
|
||||
So(query.Result[0].Id, ShouldEqual, folder2.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash1.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, childDash2.Id)
|
||||
So(query.Result[3].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("and a dashboard with an acl is moved to the folder without an acl", func() {
|
||||
updateTestDashboardWithAcl(childDash1.Id, otherUser, m.PERMISSION_EDIT)
|
||||
movedDash := moveDashboard(1, childDash1.Data, folder2.Id)
|
||||
So(movedDash.HasAcl, ShouldBeTrue)
|
||||
|
||||
Convey("should return folder without acl but not the dashboard with acl", func() {
|
||||
query := &search.FindPersistedDashboardsQuery{
|
||||
SignedInUser: &m.SignedInUser{UserId: currentUser.Id, OrgId: 1},
|
||||
OrgId: 1,
|
||||
DashboardIds: []int64{folder2.Id, childDash1.Id, childDash2.Id, dashInRoot.Id},
|
||||
}
|
||||
err := SearchDashboards(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
So(query.Result[0].Id, ShouldEqual, folder2.Id)
|
||||
So(query.Result[1].Id, ShouldEqual, childDash2.Id)
|
||||
So(query.Result[2].Id, ShouldEqual, dashInRoot.Id)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func insertTestDashboard(title string, orgId int64, folderId int64, isFolder bool, tags ...interface{}) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: folderId,
|
||||
IsFolder: isFolder,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": nil,
|
||||
"title": title,
|
||||
"tags": tags,
|
||||
}),
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
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) int64 {
|
||||
cmd := &m.SetDashboardAclCommand{
|
||||
OrgId: 1,
|
||||
UserId: userId,
|
||||
DashboardId: dashId,
|
||||
Permission: permissions,
|
||||
}
|
||||
|
||||
err := SetDashboardAcl(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result.Id
|
||||
}
|
||||
|
||||
func removeAcl(aclId int64) {
|
||||
err := RemoveDashboardAcl(&m.RemoveDashboardAclCommand{AclId: aclId, OrgId: 1})
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
func moveDashboard(orgId int64, dashboard *simplejson.Json, newFolderId int64) *m.Dashboard {
|
||||
cmd := m.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
FolderId: newFolderId,
|
||||
Dashboard: dashboard,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := SaveDashboard(&cmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
return cmd.Result
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ func GetDashboardVersion(query *m.GetDashboardVersionQuery) error {
|
||||
|
||||
// GetDashboardVersions gets all dashboard versions for the given dashboard ID.
|
||||
func GetDashboardVersions(query *m.GetDashboardVersionsQuery) error {
|
||||
if query.Limit == 0 {
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
err := x.Table("dashboard_version").
|
||||
Select(`dashboard_version.id,
|
||||
dashboard_version.dashboard_id,
|
||||
|
||||
@@ -28,7 +28,7 @@ func TestGetDashboardVersion(t *testing.T) {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Get a Dashboard ID and version ID", func() {
|
||||
savedDash := insertTestDashboard("test dash 26", 1, "diff")
|
||||
savedDash := insertTestDashboard("test dash 26", 1, 0, false, "diff")
|
||||
|
||||
query := m.GetDashboardVersionQuery{
|
||||
DashboardId: savedDash.Id,
|
||||
@@ -69,7 +69,7 @@ func TestGetDashboardVersion(t *testing.T) {
|
||||
func TestGetDashboardVersions(t *testing.T) {
|
||||
Convey("Testing dashboard versions retrieval", t, func() {
|
||||
InitTestDB(t)
|
||||
savedDash := insertTestDashboard("test dash 43", 1, "diff-all")
|
||||
savedDash := insertTestDashboard("test dash 43", 1, 0, false, "diff-all")
|
||||
|
||||
Convey("Get all versions for a given Dashboard ID", func() {
|
||||
query := m.GetDashboardVersionsQuery{DashboardId: savedDash.Id, OrgId: 1}
|
||||
|
||||
@@ -15,6 +15,7 @@ func InitTestDB(t *testing.T) {
|
||||
x, err := xorm.NewEngine(sqlutil.TestDB_Sqlite3.DriverName, sqlutil.TestDB_Sqlite3.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Mysql.DriverName, sqlutil.TestDB_Mysql.ConnStr)
|
||||
//x, err := xorm.NewEngine(sqlutil.TestDB_Postgres.DriverName, sqlutil.TestDB_Postgres.ConnStr)
|
||||
// x.ShowSQL()
|
||||
|
||||
// x.ShowSQL()
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addDashboardAclMigrations(mg *Migrator) {
|
||||
dashboardAclV1 := Table{
|
||||
Name: "dashboard_acl",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "dashboard_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "user_group_id", Type: DB_BigInt, Nullable: true},
|
||||
{Name: "permission", Type: DB_SmallInt, Default: "4"},
|
||||
{Name: "role", Type: DB_Varchar, Length: 20, Nullable: true},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"dashboard_id"}},
|
||||
{Cols: []string{"dashboard_id", "user_id"}, Type: UniqueIndex},
|
||||
{Cols: []string{"dashboard_id", "user_group_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create dashboard acl table", NewAddTableMigration(dashboardAclV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[0]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_user_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[1]))
|
||||
mg.AddMigration("add unique index dashboard_acl_dashboard_id_group_id", NewAddIndexMigration(dashboardAclV1, dashboardAclV1.Indices[2]))
|
||||
|
||||
const rawSQL = `
|
||||
INSERT INTO dashboard_acl
|
||||
(
|
||||
org_id,
|
||||
dashboard_id,
|
||||
permission,
|
||||
role,
|
||||
created,
|
||||
updated
|
||||
)
|
||||
VALUES
|
||||
(-1,-1, 1,'Viewer','2017-06-20','2017-06-20'),
|
||||
(-1,-1, 2,'Editor','2017-06-20','2017-06-20')
|
||||
`
|
||||
|
||||
mg.AddMigration("save default acl rules in dashboard_acl table", new(RawSqlMigration).
|
||||
Sqlite(rawSQL).
|
||||
Postgres(rawSQL).
|
||||
Mysql(rawSQL))
|
||||
}
|
||||
@@ -136,4 +136,18 @@ func addDashboardMigration(mg *Migrator) {
|
||||
mg.AddMigration("Update dashboard_tag table charset", NewTableCharsetMigration("dashboard_tag", []*Column{
|
||||
{Name: "term", Type: DB_NVarchar, Length: 50, Nullable: false},
|
||||
}))
|
||||
|
||||
// add column to store folder_id for dashboard folder structure
|
||||
mg.AddMigration("Add column folder_id in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "folder_id", Type: DB_BigInt, Nullable: true,
|
||||
}))
|
||||
|
||||
mg.AddMigration("Add column isFolder in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "is_folder", Type: DB_Bool, Nullable: false, Default: "0",
|
||||
}))
|
||||
|
||||
// add column to flag if dashboard has an ACL
|
||||
mg.AddMigration("Add column has_acl in dashboard", NewAddColumnMigration(dashboardV2, &Column{
|
||||
Name: "has_acl", Type: DB_Bool, Nullable: false, Default: "0",
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ func AddMigrations(mg *Migrator) {
|
||||
addAnnotationMig(mg)
|
||||
addTestDataMigrations(mg)
|
||||
addDashboardVersionMigration(mg)
|
||||
addUserGroupMigrations(mg)
|
||||
addDashboardAclMigrations(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package migrations
|
||||
|
||||
import . "github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
|
||||
func addUserGroupMigrations(mg *Migrator) {
|
||||
userGroupV1 := Table{
|
||||
Name: "user_group",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "name"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create user group table", NewAddTableMigration(userGroupV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index user_group.org_id", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[0]))
|
||||
mg.AddMigration("add unique index user_group_org_id_name", NewAddIndexMigration(userGroupV1, userGroupV1.Indices[1]))
|
||||
|
||||
userGroupMemberV1 := Table{
|
||||
Name: "user_group_member",
|
||||
Columns: []*Column{
|
||||
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
|
||||
{Name: "org_id", Type: DB_BigInt},
|
||||
{Name: "user_group_id", Type: DB_BigInt},
|
||||
{Name: "user_id", Type: DB_BigInt},
|
||||
{Name: "created", Type: DB_DateTime, Nullable: false},
|
||||
{Name: "updated", Type: DB_DateTime, Nullable: false},
|
||||
},
|
||||
Indices: []*Index{
|
||||
{Cols: []string{"org_id"}},
|
||||
{Cols: []string{"org_id", "user_group_id", "user_id"}, Type: UniqueIndex},
|
||||
},
|
||||
}
|
||||
|
||||
mg.AddMigration("create user group member table", NewAddTableMigration(userGroupMemberV1))
|
||||
|
||||
//------- indexes ------------------
|
||||
mg.AddMigration("add index user_group_member.org_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[0]))
|
||||
mg.AddMigration("add unique index user_group_member_org_id_user_group_id_user_id", NewAddIndexMigration(userGroupMemberV1, userGroupMemberV1.Indices[1]))
|
||||
}
|
||||
@@ -154,6 +154,57 @@ func TestAccountDataAccess(t *testing.T) {
|
||||
So(err, ShouldEqual, m.ErrLastOrgAdmin)
|
||||
})
|
||||
|
||||
Convey("Given an org user with dashboard permissions", func() {
|
||||
ac3cmd := m.CreateUserCommand{Login: "ac3", Email: "ac3@test.com", Name: "ac3 name", IsAdmin: false}
|
||||
err := CreateUser(&ac3cmd)
|
||||
So(err, ShouldBeNil)
|
||||
ac3 := ac3cmd.Result
|
||||
|
||||
orgUserCmd := m.AddOrgUserCommand{
|
||||
OrgId: ac1.OrgId,
|
||||
UserId: ac3.Id,
|
||||
Role: m.ROLE_VIEWER,
|
||||
}
|
||||
|
||||
err = AddOrgUser(&orgUserCmd)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := m.GetOrgUsersQuery{OrgId: ac1.OrgId}
|
||||
err = GetOrgUsers(&query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 3)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: ac1.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 2, OrgId: ac3.OrgId, UserId: ac3.Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("When org user is deleted", func() {
|
||||
cmdRemove := m.RemoveOrgUserCommand{OrgId: ac1.OrgId, UserId: ac3.Id}
|
||||
err := RemoveOrgUser(&cmdRemove)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should remove dependent permissions for deleted org user", func() {
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: ac1.OrgId}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should not remove dashboard permissions for same user in another org", func() {
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 2, OrgId: ac3.OrgId}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 1)
|
||||
So(permQuery.Result[0].OrgId, ShouldEqual, ac3.OrgId)
|
||||
So(permQuery.Result[0].UserId, ShouldEqual, ac3.Id)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -80,10 +80,17 @@ func GetOrgUsers(query *m.GetOrgUsersQuery) error {
|
||||
|
||||
func RemoveOrgUser(cmd *m.RemoveOrgUserCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSql = "DELETE FROM org_user WHERE org_id=? and user_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.OrgId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
deletes := []string{
|
||||
"DELETE FROM org_user WHERE org_id=? and user_id=?",
|
||||
"DELETE FROM dashboard_acl WHERE org_id=? and user_id = ?",
|
||||
"DELETE FROM user_group_member WHERE org_id=? and user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.OrgId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return validateOneAdminLeftInOrg(cmd.OrgId, sess)
|
||||
|
||||
@@ -396,6 +396,10 @@ func DeleteUser(cmd *m.DeleteUserCommand) error {
|
||||
deletes := []string{
|
||||
"DELETE FROM star WHERE user_id = ?",
|
||||
"DELETE FROM " + dialect.Quote("user") + " WHERE id = ?",
|
||||
"DELETE FROM org_user WHERE user_id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE user_id = ?",
|
||||
"DELETE FROM preferences WHERE user_id = ?",
|
||||
"DELETE FROM user_group_member WHERE user_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", CreateUserGroup)
|
||||
bus.AddHandler("sql", UpdateUserGroup)
|
||||
bus.AddHandler("sql", DeleteUserGroup)
|
||||
bus.AddHandler("sql", SearchUserGroups)
|
||||
bus.AddHandler("sql", GetUserGroupById)
|
||||
bus.AddHandler("sql", GetUserGroupsByUser)
|
||||
|
||||
bus.AddHandler("sql", AddUserGroupMember)
|
||||
bus.AddHandler("sql", RemoveUserGroupMember)
|
||||
bus.AddHandler("sql", GetUserGroupMembers)
|
||||
}
|
||||
|
||||
func CreateUserGroup(cmd *m.CreateUserGroupCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
|
||||
if isNameTaken, err := isUserGroupNameTaken(cmd.Name, 0, sess); err != nil {
|
||||
return err
|
||||
} else if isNameTaken {
|
||||
return m.ErrUserGroupNameTaken
|
||||
}
|
||||
|
||||
userGroup := m.UserGroup{
|
||||
Name: cmd.Name,
|
||||
OrgId: cmd.OrgId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(&userGroup)
|
||||
|
||||
cmd.Result = userGroup
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateUserGroup(cmd *m.UpdateUserGroupCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
|
||||
if isNameTaken, err := isUserGroupNameTaken(cmd.Name, cmd.Id, sess); err != nil {
|
||||
return err
|
||||
} else if isNameTaken {
|
||||
return m.ErrUserGroupNameTaken
|
||||
}
|
||||
|
||||
userGroup := m.UserGroup{
|
||||
Name: cmd.Name,
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
affectedRows, err := sess.Id(cmd.Id).Update(&userGroup)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if affectedRows == 0 {
|
||||
return m.ErrUserGroupNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func DeleteUserGroup(cmd *m.DeleteUserGroupCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from user_group WHERE id=?", cmd.Id); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
return m.ErrUserGroupNotFound
|
||||
}
|
||||
|
||||
deletes := []string{
|
||||
"DELETE FROM user_group_member WHERE user_group_id = ?",
|
||||
"DELETE FROM user_group WHERE id = ?",
|
||||
"DELETE FROM dashboard_acl WHERE user_group_id = ?",
|
||||
}
|
||||
|
||||
for _, sql := range deletes {
|
||||
_, err := sess.Exec(sql, cmd.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func isUserGroupNameTaken(name string, existingId int64, sess *DBSession) (bool, error) {
|
||||
var userGroup m.UserGroup
|
||||
exists, err := sess.Where("name=?", name).Get(&userGroup)
|
||||
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if exists && existingId != userGroup.Id {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func SearchUserGroups(query *m.SearchUserGroupsQuery) error {
|
||||
query.Result = m.SearchUserGroupQueryResult{
|
||||
UserGroups: make([]*m.UserGroup, 0),
|
||||
}
|
||||
queryWithWildcards := "%" + query.Query + "%"
|
||||
|
||||
sess := x.Table("user_group")
|
||||
sess.Where("org_id=?", query.OrgId)
|
||||
|
||||
if query.Query != "" {
|
||||
sess.Where("name LIKE ?", queryWithWildcards)
|
||||
}
|
||||
if query.Name != "" {
|
||||
sess.Where("name=?", query.Name)
|
||||
}
|
||||
sess.Asc("name")
|
||||
|
||||
offset := query.Limit * (query.Page - 1)
|
||||
sess.Limit(query.Limit, offset)
|
||||
sess.Cols("id", "name")
|
||||
if err := sess.Find(&query.Result.UserGroups); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userGroup := m.UserGroup{}
|
||||
|
||||
countSess := x.Table("user_group")
|
||||
if query.Query != "" {
|
||||
countSess.Where("name LIKE ?", queryWithWildcards)
|
||||
}
|
||||
if query.Name != "" {
|
||||
countSess.Where("name=?", query.Name)
|
||||
}
|
||||
count, err := countSess.Count(&userGroup)
|
||||
query.Result.TotalCount = count
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUserGroupById(query *m.GetUserGroupByIdQuery) error {
|
||||
var userGroup m.UserGroup
|
||||
exists, err := x.Id(query.Id).Get(&userGroup)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return m.ErrUserGroupNotFound
|
||||
}
|
||||
|
||||
query.Result = &userGroup
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUserGroupsByUser(query *m.GetUserGroupsByUserQuery) error {
|
||||
query.Result = make([]*m.UserGroup, 0)
|
||||
|
||||
sess := x.Table("user_group")
|
||||
sess.Join("INNER", "user_group_member", "user_group.id=user_group_member.user_group_id")
|
||||
sess.Where("user_group_member.user_id=?", query.UserId)
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func AddUserGroupMember(cmd *m.AddUserGroupMemberCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if res, err := sess.Query("SELECT 1 from user_group_member WHERE user_group_id=? and user_id=?", cmd.UserGroupId, cmd.UserId); err != nil {
|
||||
return err
|
||||
} else if len(res) == 1 {
|
||||
return m.ErrUserGroupMemberAlreadyAdded
|
||||
}
|
||||
|
||||
if res, err := sess.Query("SELECT 1 from user_group WHERE id=?", cmd.UserGroupId); err != nil {
|
||||
return err
|
||||
} else if len(res) != 1 {
|
||||
return m.ErrUserGroupNotFound
|
||||
}
|
||||
|
||||
entity := m.UserGroupMember{
|
||||
OrgId: cmd.OrgId,
|
||||
UserGroupId: cmd.UserGroupId,
|
||||
UserId: cmd.UserId,
|
||||
Created: time.Now(),
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
_, err := sess.Insert(&entity)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func RemoveUserGroupMember(cmd *m.RemoveUserGroupMemberCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSql = "DELETE FROM user_group_member WHERE user_group_id=? and user_id=?"
|
||||
_, err := sess.Exec(rawSql, cmd.UserGroupId, cmd.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func GetUserGroupMembers(query *m.GetUserGroupMembersQuery) error {
|
||||
query.Result = make([]*m.UserGroupMemberDTO, 0)
|
||||
sess := x.Table("user_group_member")
|
||||
sess.Join("INNER", "user", fmt.Sprintf("user_group_member.user_id=%s.id", x.Dialect().Quote("user")))
|
||||
sess.Where("user_group_member.user_group_id=?", query.UserGroupId)
|
||||
sess.Cols("user.org_id", "user_group_member.user_group_id", "user_group_member.user_id", "user.email", "user.login")
|
||||
sess.Asc("user.login", "user.email")
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestUserGroupCommandsAndQueries(t *testing.T) {
|
||||
|
||||
Convey("Testing User Group commands & queries", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
Convey("Given saved users and two user groups", func() {
|
||||
var userIds []int64
|
||||
for i := 0; i < 5; i++ {
|
||||
userCmd := &m.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
}
|
||||
err := CreateUser(userCmd)
|
||||
So(err, ShouldBeNil)
|
||||
userIds = append(userIds, userCmd.Result.Id)
|
||||
}
|
||||
|
||||
group1 := m.CreateUserGroupCommand{Name: "group1 name"}
|
||||
group2 := m.CreateUserGroupCommand{Name: "group2 name"}
|
||||
|
||||
err := CreateUserGroup(&group1)
|
||||
So(err, ShouldBeNil)
|
||||
err = CreateUserGroup(&group2)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should be able to create user groups and add users", func() {
|
||||
query := &m.SearchUserGroupsQuery{Name: "group1 name", Page: 1, Limit: 10}
|
||||
err = SearchUserGroups(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(query.Page, ShouldEqual, 1)
|
||||
|
||||
userGroup1 := query.Result.UserGroups[0]
|
||||
So(userGroup1.Name, ShouldEqual, "group1 name")
|
||||
|
||||
err = AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: userGroup1.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetUserGroupMembersQuery{UserGroupId: userGroup1.Id}
|
||||
err = GetUserGroupMembers(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(q1.Result[0].UserGroupId, ShouldEqual, userGroup1.Id)
|
||||
So(q1.Result[0].Login, ShouldEqual, "loginuser0")
|
||||
})
|
||||
|
||||
Convey("Should be able to search for user groups", func() {
|
||||
query := &m.SearchUserGroupsQuery{Query: "group", Page: 1}
|
||||
err = SearchUserGroups(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.UserGroups), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 2)
|
||||
|
||||
query2 := &m.SearchUserGroupsQuery{Query: ""}
|
||||
err = SearchUserGroups(query2)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query2.Result.UserGroups), ShouldEqual, 2)
|
||||
})
|
||||
|
||||
Convey("Should be able to return all user groups a user is member of", func() {
|
||||
groupId := group2.Result.Id
|
||||
err := AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: groupId, UserId: userIds[0]})
|
||||
|
||||
query := &m.GetUserGroupsByUserQuery{UserId: userIds[0]}
|
||||
err = GetUserGroupsByUser(query)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
So(query.Result[0].Name, ShouldEqual, "group2 name")
|
||||
})
|
||||
|
||||
Convey("Should be able to remove users from a group", func() {
|
||||
err = RemoveUserGroupMember(&m.RemoveUserGroupMemberCommand{UserGroupId: group1.Result.Id, UserId: userIds[0]})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
q1 := &m.GetUserGroupMembersQuery{UserGroupId: group1.Result.Id}
|
||||
err = GetUserGroupMembers(q1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(q1.Result), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("Should be able to remove a group with users and permissions", func() {
|
||||
groupId := group2.Result.Id
|
||||
err := AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: groupId, UserId: userIds[1]})
|
||||
So(err, ShouldBeNil)
|
||||
err = AddUserGroupMember(&m.AddUserGroupMemberCommand{OrgId: 1, UserGroupId: groupId, UserId: userIds[2]})
|
||||
So(err, ShouldBeNil)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: 1, Permission: m.PERMISSION_EDIT, UserGroupId: groupId})
|
||||
|
||||
err = DeleteUserGroup(&m.DeleteUserGroupCommand{Id: groupId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
query := &m.GetUserGroupByIdQuery{Id: groupId}
|
||||
err = GetUserGroupById(query)
|
||||
So(err, ShouldEqual, m.ErrUserGroupNotFound)
|
||||
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func TestUserDataAccess(t *testing.T) {
|
||||
@@ -14,80 +14,134 @@ func TestUserDataAccess(t *testing.T) {
|
||||
Convey("Testing DB", t, func() {
|
||||
InitTestDB(t)
|
||||
|
||||
var err error
|
||||
for i := 0; i < 5; i++ {
|
||||
err = CreateUser(&models.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
Convey("Given 5 users", func() {
|
||||
var err error
|
||||
var cmd *m.CreateUserCommand
|
||||
users := []m.User{}
|
||||
for i := 0; i < 5; i++ {
|
||||
cmd = &m.CreateUserCommand{
|
||||
Email: fmt.Sprint("user", i, "@test.com"),
|
||||
Name: fmt.Sprint("user", i),
|
||||
Login: fmt.Sprint("loginuser", i),
|
||||
}
|
||||
err = CreateUser(cmd)
|
||||
So(err, ShouldBeNil)
|
||||
users = append(users, cmd.Result)
|
||||
}
|
||||
|
||||
Convey("Can return the first page of users and a total count", func() {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
}
|
||||
|
||||
Convey("Can return the first page of users and a total count", func() {
|
||||
query := models.SearchUsersQuery{Query: "", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return the second page of users and a total count", func() {
|
||||
query := m.SearchUsersQuery{Query: "", Page: 2, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
|
||||
Convey("Can return the second page of users and a total count", func() {
|
||||
query := models.SearchUsersQuery{Query: "", Page: 2, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on user name", func() {
|
||||
query := m.SearchUsersQuery{Query: "use", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 2)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
|
||||
Convey("Can return list of users matching query on user name", func() {
|
||||
query := models.SearchUsersQuery{Query: "use", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 3)
|
||||
So(query.Result.TotalCount, ShouldEqual, 5)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
|
||||
query = models.SearchUsersQuery{Query: "ser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
|
||||
query = models.SearchUsersQuery{Query: "USER1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
query = m.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 0)
|
||||
So(query.Result.TotalCount, ShouldEqual, 0)
|
||||
})
|
||||
|
||||
query = models.SearchUsersQuery{Query: "idontexist", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on email", func() {
|
||||
query := m.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 0)
|
||||
So(query.Result.TotalCount, ShouldEqual, 0)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Can return list of users matching query on email", func() {
|
||||
query := models.SearchUsersQuery{Query: "ser1@test.com", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("Can return list of users matching query on login name", func() {
|
||||
query := m.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
})
|
||||
|
||||
Convey("Can return list of users matching query on login name", func() {
|
||||
query := models.SearchUsersQuery{Query: "loginuser1", Page: 1, Limit: 3}
|
||||
err = SearchUsers(&query)
|
||||
Convey("when a user is an org member and has been assigned permissions", func() {
|
||||
err = AddOrgUser(&m.AddOrgUserCommand{LoginOrEmail: users[0].Login, Role: m.ROLE_VIEWER, OrgId: users[0].OrgId})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(err, ShouldBeNil)
|
||||
So(len(query.Result.Users), ShouldEqual, 1)
|
||||
So(query.Result.TotalCount, ShouldEqual, 1)
|
||||
err = SetDashboardAcl(&m.SetDashboardAclCommand{DashboardId: 1, OrgId: users[0].OrgId, UserId: users[0].Id, Permission: m.PERMISSION_EDIT})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
err = SavePreferences(&m.SavePreferencesCommand{UserId: users[0].Id, OrgId: users[0].OrgId, HomeDashboardId: 1, Theme: "dark"})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("when the user is deleted", func() {
|
||||
err = DeleteUser(&m.DeleteUserCommand{UserId: users[0].Id})
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
Convey("Should delete connected org users and permissions", func() {
|
||||
query := &m.GetOrgUsersQuery{OrgId: 1}
|
||||
err = GetOrgUsersForTest(query)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(query.Result), ShouldEqual, 1)
|
||||
|
||||
permQuery := &m.GetDashboardAclInfoListQuery{DashboardId: 1, OrgId: 1}
|
||||
err = GetDashboardAclInfoList(permQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(len(permQuery.Result), ShouldEqual, 0)
|
||||
|
||||
prefsQuery := &m.GetPreferencesQuery{OrgId: users[0].OrgId, UserId: users[0].Id}
|
||||
err = GetPreferences(prefsQuery)
|
||||
So(err, ShouldBeNil)
|
||||
|
||||
So(prefsQuery.Result.OrgId, ShouldEqual, 0)
|
||||
So(prefsQuery.Result.UserId, ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func GetOrgUsersForTest(query *m.GetOrgUsersQuery) error {
|
||||
query.Result = make([]*m.OrgUserDTO, 0)
|
||||
sess := x.Table("org_user")
|
||||
sess.Join("LEFT ", "user", fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user")))
|
||||
sess.Where("org_user.org_id=?", query.OrgId)
|
||||
sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role")
|
||||
|
||||
err := sess.Find(&query.Result)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user