mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge branch 'master' into readonly_dashboards
This commit is contained in:
commit
d7688241c6
@ -1,6 +1,6 @@
|
||||
[run]
|
||||
init_cmds = [
|
||||
["go", "run", "build.go", "build"],
|
||||
["go", "run", "build.go", "build-server"],
|
||||
["./bin/grafana-server", "cfg:app_mode=development"]
|
||||
]
|
||||
watch_all = true
|
||||
|
@ -17,6 +17,7 @@
|
||||
* **IE11**: IE 11 compatibility [#11165](https://github.com/grafana/grafana/issues/11165)
|
||||
* **Scrolling**: Better scrolling experience [#11053](https://github.com/grafana/grafana/issues/11053), [#11252](https://github.com/grafana/grafana/issues/11252), [#10836](https://github.com/grafana/grafana/issues/10836), [#11185](https://github.com/grafana/grafana/issues/11185), [#11168](https://github.com/grafana/grafana/issues/11168)
|
||||
* **Docker**: Improved docker image (breaking changes regarding file ownership) [grafana-docker #141](https://github.com/grafana/grafana-docker/issues/141), thx [@Spindel](https://github.com/Spindel), [@ChristianKniep](https://github.com/ChristianKniep), [@brancz](https://github.com/brancz) and [@jangaraj](https://github.com/jangaraj)
|
||||
* **Folders** A folder admin cannot add user/team permissions for folder/its dashboards [#11173](https://github.com/grafana/grafana/issues/11173)
|
||||
|
||||
### Minor
|
||||
|
||||
|
@ -149,8 +149,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// team (admin permission required)
|
||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
|
||||
teamsRoute.Get("/search", wrap(SearchTeams))
|
||||
teamsRoute.Post("/", bind(m.CreateTeamCommand{}), wrap(CreateTeam))
|
||||
teamsRoute.Put("/:teamId", bind(m.UpdateTeamCommand{}), wrap(UpdateTeam))
|
||||
teamsRoute.Delete("/:teamId", wrap(DeleteTeamByID))
|
||||
@ -159,6 +157,12 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
teamsRoute.Delete("/:teamId/members/:userId", wrap(RemoveTeamMember))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// team without requirement of user to be org admin
|
||||
apiRoute.Group("/teams", func(teamsRoute RouteRegister) {
|
||||
teamsRoute.Get("/:teamId", wrap(GetTeamByID))
|
||||
teamsRoute.Get("/search", wrap(SearchTeams))
|
||||
})
|
||||
|
||||
// org information available to all users.
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Get("/", wrap(GetOrgCurrent))
|
||||
@ -170,7 +174,6 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Put("/", bind(dtos.UpdateOrgForm{}), wrap(UpdateOrgCurrent))
|
||||
orgRoute.Put("/address", bind(dtos.UpdateOrgAddressForm{}), wrap(UpdateOrgAddressCurrent))
|
||||
orgRoute.Post("/users", quota("user"), bind(m.AddOrgUserCommand{}), wrap(AddOrgUserToCurrentOrg))
|
||||
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||
orgRoute.Patch("/users/:userId", bind(m.UpdateOrgUserCommand{}), wrap(UpdateOrgUserForCurrentOrg))
|
||||
orgRoute.Delete("/users/:userId", wrap(RemoveOrgUserForCurrentOrg))
|
||||
|
||||
@ -184,6 +187,11 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
orgRoute.Put("/preferences", bind(dtos.UpdatePrefsCmd{}), wrap(UpdateOrgPreferences))
|
||||
}, reqOrgAdmin)
|
||||
|
||||
// current org without requirement of user to be org admin
|
||||
apiRoute.Group("/org", func(orgRoute RouteRegister) {
|
||||
orgRoute.Get("/users", wrap(GetOrgUsersForCurrentOrg))
|
||||
})
|
||||
|
||||
// create new org
|
||||
apiRoute.Post("/orgs", quota("org"), bind(m.CreateOrgCommand{}), wrap(CreateOrg))
|
||||
|
||||
|
@ -225,6 +225,10 @@ func GetFolderUrl(folderUid string, slug string) string {
|
||||
return fmt.Sprintf("%s/dashboards/f/%s/%s", setting.AppSubUrl, folderUid, slug)
|
||||
}
|
||||
|
||||
type ValidateDashboardBeforeSaveResult struct {
|
||||
IsParentFolderChanged bool
|
||||
}
|
||||
|
||||
//
|
||||
// COMMANDS
|
||||
//
|
||||
@ -269,6 +273,7 @@ type ValidateDashboardBeforeSaveCommand struct {
|
||||
OrgId int64
|
||||
Dashboard *Dashboard
|
||||
Overwrite bool
|
||||
Result *ValidateDashboardBeforeSaveResult
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -103,6 +103,16 @@ func (dr *dashboardServiceImpl) buildSaveDashboardCommand(dto *SaveDashboardDTO,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if validateBeforeSaveCmd.Result.IsParentFolderChanged {
|
||||
folderGuardian := guardian.New(dash.FolderId, dto.OrgId, dto.User)
|
||||
if canSave, err := folderGuardian.CanSave(); err != nil || !canSave {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, models.ErrDashboardUpdateAccessDenied
|
||||
}
|
||||
}
|
||||
|
||||
if validateProvisionedDashboard {
|
||||
isDashboardProvisioned := &models.IsDashboardProvisionedQuery{DashboardId: dash.Id}
|
||||
err := bus.Dispatch(isDashboardProvisioned)
|
||||
|
@ -53,6 +53,7 @@ func TestDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -97,6 +98,7 @@ func TestDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -140,6 +142,7 @@ func TestDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -176,6 +179,7 @@ func TestDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -32,6 +32,7 @@ func TestFolderService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return models.ErrDashboardUpdateAccessDenied
|
||||
})
|
||||
|
||||
@ -92,6 +93,7 @@ func TestFolderService(t *testing.T) {
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(cmd *models.ValidateDashboardBeforeSaveCommand) error {
|
||||
cmd.Result = &models.ValidateDashboardBeforeSaveResult{}
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -173,7 +173,7 @@ func (g *dashboardGuardianImpl) CheckPermissionBeforeUpdate(permission m.Permiss
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return g.checkAcl(permission, acl)
|
||||
return g.checkAcl(permission, existingPermissions)
|
||||
}
|
||||
|
||||
// GetAcl returns dashboard acl
|
||||
|
File diff suppressed because it is too large
Load Diff
256
pkg/services/guardian/guardian_util_test.go
Normal file
256
pkg/services/guardian/guardian_util_test.go
Normal file
@ -0,0 +1,256 @@
|
||||
package guardian
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
m "github.com/grafana/grafana/pkg/models"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
type scenarioContext struct {
|
||||
t *testing.T
|
||||
orgRoleScenario string
|
||||
permissionScenario string
|
||||
g DashboardGuardian
|
||||
givenUser *m.SignedInUser
|
||||
givenDashboardID int64
|
||||
givenPermissions []*m.DashboardAclInfoDTO
|
||||
givenTeams []*m.Team
|
||||
updatePermissions []*m.DashboardAcl
|
||||
expectedFlags permissionFlags
|
||||
callerFile string
|
||||
callerLine int
|
||||
}
|
||||
|
||||
type scenarioFunc func(c *scenarioContext)
|
||||
|
||||
func orgRoleScenario(desc string, t *testing.T, role m.RoleType, fn scenarioFunc) {
|
||||
user := &m.SignedInUser{
|
||||
UserId: userID,
|
||||
OrgId: orgID,
|
||||
OrgRole: role,
|
||||
}
|
||||
guard := New(dashboardID, orgID, user)
|
||||
sc := &scenarioContext{
|
||||
t: t,
|
||||
orgRoleScenario: desc,
|
||||
givenUser: user,
|
||||
givenDashboardID: dashboardID,
|
||||
g: guard,
|
||||
}
|
||||
|
||||
Convey(desc, func() {
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func permissionScenario(desc string, dashboardID int64, sc *scenarioContext, permissions []*m.DashboardAclInfoDTO, fn scenarioFunc) {
|
||||
bus.ClearBusHandlers()
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetDashboardAclInfoListQuery) error {
|
||||
if query.OrgId != sc.givenUser.OrgId {
|
||||
sc.reportFailure("Invalid organization id for GetDashboardAclInfoListQuery", sc.givenUser.OrgId, query.OrgId)
|
||||
}
|
||||
if query.DashboardId != sc.givenDashboardID {
|
||||
sc.reportFailure("Invalid dashboard id for GetDashboardAclInfoListQuery", sc.givenDashboardID, query.DashboardId)
|
||||
}
|
||||
|
||||
query.Result = permissions
|
||||
return nil
|
||||
})
|
||||
|
||||
teams := []*m.Team{}
|
||||
|
||||
for _, p := range permissions {
|
||||
if p.TeamId > 0 {
|
||||
teams = append(teams, &m.Team{Id: p.TeamId})
|
||||
}
|
||||
}
|
||||
|
||||
bus.AddHandler("test", func(query *m.GetTeamsByUserQuery) error {
|
||||
if query.OrgId != sc.givenUser.OrgId {
|
||||
sc.reportFailure("Invalid organization id for GetTeamsByUserQuery", sc.givenUser.OrgId, query.OrgId)
|
||||
}
|
||||
if query.UserId != sc.givenUser.UserId {
|
||||
sc.reportFailure("Invalid user id for GetTeamsByUserQuery", sc.givenUser.UserId, query.UserId)
|
||||
}
|
||||
|
||||
query.Result = teams
|
||||
return nil
|
||||
})
|
||||
|
||||
sc.permissionScenario = desc
|
||||
sc.g = New(dashboardID, sc.givenUser.OrgId, sc.givenUser)
|
||||
sc.givenDashboardID = dashboardID
|
||||
sc.givenPermissions = permissions
|
||||
sc.givenTeams = teams
|
||||
|
||||
Convey(desc, func() {
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
type permissionType uint8
|
||||
|
||||
const (
|
||||
USER permissionType = 1 << iota
|
||||
TEAM
|
||||
EDITOR
|
||||
VIEWER
|
||||
)
|
||||
|
||||
func (p permissionType) String() string {
|
||||
names := map[uint8]string{
|
||||
uint8(USER): "user",
|
||||
uint8(TEAM): "team",
|
||||
uint8(EDITOR): "editor role",
|
||||
uint8(VIEWER): "viewer role",
|
||||
}
|
||||
return names[uint8(p)]
|
||||
}
|
||||
|
||||
type permissionFlags uint8
|
||||
|
||||
const (
|
||||
NO_ACCESS permissionFlags = 1 << iota
|
||||
CAN_ADMIN
|
||||
CAN_EDIT
|
||||
CAN_SAVE
|
||||
CAN_VIEW
|
||||
FULL_ACCESS = CAN_ADMIN | CAN_EDIT | CAN_SAVE | CAN_VIEW
|
||||
EDITOR_ACCESS = CAN_EDIT | CAN_SAVE | CAN_VIEW
|
||||
VIEWER_ACCESS = CAN_VIEW
|
||||
)
|
||||
|
||||
func (flag permissionFlags) canAdmin() bool {
|
||||
return flag&CAN_ADMIN != 0
|
||||
}
|
||||
|
||||
func (flag permissionFlags) canEdit() bool {
|
||||
return flag&CAN_EDIT != 0
|
||||
}
|
||||
|
||||
func (flag permissionFlags) canSave() bool {
|
||||
return flag&CAN_SAVE != 0
|
||||
}
|
||||
|
||||
func (flag permissionFlags) canView() bool {
|
||||
return flag&CAN_VIEW != 0
|
||||
}
|
||||
|
||||
func (flag permissionFlags) noAccess() bool {
|
||||
return flag&(CAN_ADMIN|CAN_EDIT|CAN_SAVE|CAN_VIEW) == 0
|
||||
}
|
||||
|
||||
func (f permissionFlags) String() string {
|
||||
r := []string{}
|
||||
|
||||
if f.canAdmin() {
|
||||
r = append(r, "admin")
|
||||
}
|
||||
|
||||
if f.canEdit() {
|
||||
r = append(r, "edit")
|
||||
}
|
||||
|
||||
if f.canSave() {
|
||||
r = append(r, "save")
|
||||
}
|
||||
|
||||
if f.canView() {
|
||||
r = append(r, "view")
|
||||
}
|
||||
|
||||
if f.noAccess() {
|
||||
r = append(r, "<no access>")
|
||||
}
|
||||
|
||||
return strings.Join(r[:], ", ")
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) reportSuccess() {
|
||||
So(true, ShouldBeTrue)
|
||||
}
|
||||
|
||||
func (sc *scenarioContext) reportFailure(desc string, expected interface{}, actual interface{}) {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(sc.orgRoleScenario)
|
||||
buf.WriteString(" ")
|
||||
buf.WriteString(sc.permissionScenario)
|
||||
buf.WriteString("\n ")
|
||||
buf.WriteString(desc)
|
||||
buf.WriteString("\n")
|
||||
buf.WriteString(fmt.Sprintf("Source test: %s:%d\n", sc.callerFile, sc.callerLine))
|
||||
buf.WriteString(fmt.Sprintf("Expected: %v\n", expected))
|
||||
buf.WriteString(fmt.Sprintf("Actual: %v\n", actual))
|
||||
buf.WriteString("Context:")
|
||||
buf.WriteString(fmt.Sprintf("\n Given user: orgRole=%s, id=%d, orgId=%d", sc.givenUser.OrgRole, sc.givenUser.UserId, sc.givenUser.OrgId))
|
||||
buf.WriteString(fmt.Sprintf("\n Given dashboard id: %d", sc.givenDashboardID))
|
||||
|
||||
for i, p := range sc.givenPermissions {
|
||||
r := "<nil>"
|
||||
if p.Role != nil {
|
||||
r = string(*p.Role)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("\n Given permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
|
||||
}
|
||||
|
||||
for i, t := range sc.givenTeams {
|
||||
buf.WriteString(fmt.Sprintf("\n Given team (%d): id=%d", i, t.Id))
|
||||
}
|
||||
|
||||
for i, p := range sc.updatePermissions {
|
||||
r := "<nil>"
|
||||
if p.Role != nil {
|
||||
r = string(*p.Role)
|
||||
}
|
||||
buf.WriteString(fmt.Sprintf("\n Update permission (%d): dashboardId=%d, userId=%d, teamId=%d, role=%v, permission=%s", i, p.DashboardId, p.UserId, p.TeamId, r, p.Permission.String()))
|
||||
}
|
||||
|
||||
sc.t.Fatalf(buf.String())
|
||||
}
|
||||
|
||||
func newCustomUserPermission(dashboardID int64, userID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, UserId: userID, Permission: permission}
|
||||
}
|
||||
|
||||
func newDefaultUserPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return newCustomUserPermission(dashboardID, userID, permission)
|
||||
}
|
||||
|
||||
func newCustomTeamPermission(dashboardID int64, teamID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, TeamId: teamID, Permission: permission}
|
||||
}
|
||||
|
||||
func newDefaultTeamPermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return newCustomTeamPermission(dashboardID, teamID, permission)
|
||||
}
|
||||
|
||||
func newAdminRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &adminRole, Permission: permission}
|
||||
}
|
||||
|
||||
func newEditorRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &editorRole, Permission: permission}
|
||||
}
|
||||
|
||||
func newViewerRolePermission(dashboardID int64, permission m.PermissionType) *m.DashboardAcl {
|
||||
return &m.DashboardAcl{OrgId: orgID, DashboardId: dashboardID, Role: &viewerRole, Permission: permission}
|
||||
}
|
||||
|
||||
func toDto(acl *m.DashboardAcl) *m.DashboardAclInfoDTO {
|
||||
return &m.DashboardAclInfoDTO{
|
||||
OrgId: acl.OrgId,
|
||||
DashboardId: acl.DashboardId,
|
||||
UserId: acl.UserId,
|
||||
TeamId: acl.TeamId,
|
||||
Role: acl.Role,
|
||||
Permission: acl.Permission,
|
||||
PermissionName: acl.Permission.String(),
|
||||
}
|
||||
}
|
@ -544,6 +544,10 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
|
||||
dash.SetId(existingByUid.Id)
|
||||
dash.SetUid(existingByUid.Uid)
|
||||
existing = existingByUid
|
||||
|
||||
if !dash.IsFolder {
|
||||
cmd.Result.IsParentFolderChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if (existing.IsFolder && !dash.IsFolder) ||
|
||||
@ -551,6 +555,10 @@ func getExistingDashboardByIdOrUidForUpdate(sess *DBSession, cmd *m.ValidateDash
|
||||
return m.ErrDashboardTypeMismatch
|
||||
}
|
||||
|
||||
if !dash.IsFolder && dash.FolderId != existing.FolderId {
|
||||
cmd.Result.IsParentFolderChanged = true
|
||||
}
|
||||
|
||||
// check for is someone else has written in between
|
||||
if dash.Version != existing.Version {
|
||||
if cmd.Overwrite {
|
||||
@ -586,6 +594,10 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
|
||||
return m.ErrDashboardFolderWithSameNameAsDashboard
|
||||
}
|
||||
|
||||
if !dash.IsFolder && (dash.FolderId != existing.FolderId || dash.Id == 0) {
|
||||
cmd.Result.IsParentFolderChanged = true
|
||||
}
|
||||
|
||||
if cmd.Overwrite {
|
||||
dash.SetId(existing.Id)
|
||||
dash.SetUid(existing.Uid)
|
||||
@ -599,6 +611,7 @@ func getExistingDashboardByTitleAndFolder(sess *DBSession, cmd *m.ValidateDashbo
|
||||
}
|
||||
|
||||
func ValidateDashboardBeforeSave(cmd *m.ValidateDashboardBeforeSaveCommand) (err error) {
|
||||
cmd.Result = &m.ValidateDashboardBeforeSaveResult{}
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
if err = getExistingDashboardByIdOrUidForUpdate(sess, cmd); err != nil {
|
||||
return err
|
||||
|
@ -78,7 +78,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
Convey("Given organization B", func() {
|
||||
var otherOrgId int64 = 2
|
||||
|
||||
Convey("When saving a dashboard with id that are saved in organization A", func() {
|
||||
Convey("When creating a dashboard with same id as dashboard in organization A", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -97,7 +97,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
|
||||
permissionScenario("Given user has permission to save", true, func(sc *dashboardPermissionScenarioContext) {
|
||||
Convey("When saving a dashboard with uid that are saved in organization A", func() {
|
||||
Convey("When creating a dashboard with same uid as dashboard in organization A", func() {
|
||||
var otherOrgId int64 = 2
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: otherOrgId,
|
||||
@ -110,7 +110,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
res := callSaveWithResult(cmd)
|
||||
|
||||
Convey("It should create dashboard in other organization", func() {
|
||||
Convey("It should create a new dashboard in organization B", func() {
|
||||
So(res, ShouldNotBeNil)
|
||||
|
||||
query := models.GetDashboardQuery{OrgId: otherOrgId, Uid: savedDashInFolder.Uid}
|
||||
@ -130,7 +130,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
permissionScenario("Given user has no permission to save", false, func(sc *dashboardPermissionScenarioContext) {
|
||||
|
||||
Convey("When trying to create a new dashboard in the General folder", func() {
|
||||
Convey("When creating a new dashboard in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -142,7 +142,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
Convey("It should create dashboard guardian for General Folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
@ -152,7 +152,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to create a new dashboard in other folder", func() {
|
||||
Convey("When creating a new dashboard in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -165,7 +165,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and rsult in access denied error", func() {
|
||||
Convey("It should create dashboard guardian for other folder with correct arguments and rsult in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
@ -175,7 +175,54 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in the General folder", func() {
|
||||
Convey("When creating a new dashboard by existing title in folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"title": savedDashInFolder.Title,
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When creating a new dashboard by existing uid in folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "New dash",
|
||||
}),
|
||||
FolderId: savedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, savedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When updating a dashboard by existing id in the General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -189,7 +236,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
@ -199,7 +246,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update a dashboard by existing id in other folder", func() {
|
||||
Convey("When updating a dashboard by existing id in other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -213,7 +260,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should call dashboard guardian with correct arguments and result in access denied error", func() {
|
||||
Convey("It should create dashboard guardian for dashboard with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
@ -222,6 +269,102 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When moving a dashboard by existing id to other folder from General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInGeneralFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: otherSavedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When moving a dashboard by existing id to the General folder from other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"id": savedDashInFolder.Id,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: 0,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When moving a dashboard by existing uid to other folder from General folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInGeneralFolder.Uid,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: otherSavedFolder.Id,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for other folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, otherSavedFolder.Id)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When moving a dashboard by existing uid to the General folder from other folder", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: testOrgId,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
"uid": savedDashInFolder.Uid,
|
||||
"title": "Dash",
|
||||
}),
|
||||
FolderId: 0,
|
||||
UserId: 10000,
|
||||
Overwrite: true,
|
||||
}
|
||||
|
||||
err := callSaveWithError(cmd)
|
||||
|
||||
Convey("It should create dashboard guardian for General folder with correct arguments and result in access denied error", func() {
|
||||
So(err, ShouldNotBeNil)
|
||||
So(err, ShouldEqual, models.ErrDashboardUpdateAccessDenied)
|
||||
|
||||
So(sc.dashboardGuardianMock.DashId, ShouldEqual, 0)
|
||||
So(sc.dashboardGuardianMock.OrgId, ShouldEqual, cmd.OrgId)
|
||||
So(sc.dashboardGuardianMock.User.UserId, ShouldEqual, cmd.UserId)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Given user has permission to save
|
||||
@ -672,7 +815,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using id", func() {
|
||||
Convey("When updating existing folder to a dashboard using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -691,7 +834,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using id", func() {
|
||||
Convey("When updating existing dashboard to a folder using id", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -710,7 +853,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using uid", func() {
|
||||
Convey("When updating existing folder to a dashboard using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -729,7 +872,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using uid", func() {
|
||||
Convey("When updating existing dashboard to a folder using uid", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -748,7 +891,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing folder to a dashboard using title", func() {
|
||||
Convey("When updating existing folder to a dashboard using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -766,7 +909,7 @@ func TestIntegratedDashboardService(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("When trying to update existing dashboard to a folder using title", func() {
|
||||
Convey("When updating existing dashboard to a folder using title", func() {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: 1,
|
||||
Dashboard: simplejson.NewFromAny(map[string]interface{}{
|
||||
@ -854,23 +997,6 @@ func callSaveWithError(cmd models.SaveDashboardCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func dashboardServiceScenario(desc string, mock *guardian.FakeDashboardGuardian, fn scenarioFunc) {
|
||||
Convey(desc, func() {
|
||||
origNewDashboardGuardian := guardian.New
|
||||
guardian.MockDashboardGuardian(mock)
|
||||
|
||||
sc := &scenarioContext{
|
||||
dashboardGuardianMock: mock,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
guardian.New = origNewDashboardGuardian
|
||||
}()
|
||||
|
||||
fn(sc)
|
||||
})
|
||||
}
|
||||
|
||||
func saveTestDashboard(title string, orgId int64, folderId int64) *models.Dashboard {
|
||||
cmd := models.SaveDashboardCommand{
|
||||
OrgId: orgId,
|
||||
|
@ -19,9 +19,12 @@ export class FolderPickerCtrl {
|
||||
newFolderNameTouched: boolean;
|
||||
hasValidationError: boolean;
|
||||
validationError: any;
|
||||
isEditor: boolean;
|
||||
|
||||
/** @ngInject */
|
||||
constructor(private backendSrv, private validationSrv) {
|
||||
constructor(private backendSrv, private validationSrv, private contextSrv) {
|
||||
this.isEditor = this.contextSrv.isEditor;
|
||||
|
||||
if (!this.labelClass) {
|
||||
this.labelClass = 'width-7';
|
||||
}
|
||||
@ -38,19 +41,20 @@ export class FolderPickerCtrl {
|
||||
|
||||
return this.backendSrv.get('api/search', params).then(result => {
|
||||
if (
|
||||
query === '' ||
|
||||
this.isEditor &&
|
||||
(query === '' ||
|
||||
query.toLowerCase() === 'g' ||
|
||||
query.toLowerCase() === 'ge' ||
|
||||
query.toLowerCase() === 'gen' ||
|
||||
query.toLowerCase() === 'gene' ||
|
||||
query.toLowerCase() === 'gener' ||
|
||||
query.toLowerCase() === 'genera' ||
|
||||
query.toLowerCase() === 'general'
|
||||
query.toLowerCase() === 'general')
|
||||
) {
|
||||
result.unshift({ title: this.rootName, id: 0 });
|
||||
}
|
||||
|
||||
if (this.enableCreateNew && query === '') {
|
||||
if (this.isEditor && this.enableCreateNew && query === '') {
|
||||
result.unshift({ title: '-- New Folder --', id: -1 });
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user