Dashboards: Move to integration tests (#108734)
This commit is contained in:
@@ -2,36 +2,20 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
|
||||||
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
|
|
||||||
"github.com/grafana/grafana/pkg/services/org"
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
||||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/search/sort"
|
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
||||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var testFeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch)
|
var testFeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch)
|
||||||
@@ -230,197 +214,6 @@ func TestIntegrationDashboardFolderDataAccess(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
// the maximux nested folder hierarchy starting from parent down to subfolders
|
|
||||||
nestedFolders := make([]*folder.Folder, 0, folder.MaxNestedFolderDepth+1)
|
|
||||||
|
|
||||||
var sqlStore db.DB
|
|
||||||
var cfg *setting.Cfg
|
|
||||||
const (
|
|
||||||
dashInRootTitle = "dashboard in root"
|
|
||||||
dashInParentTitle = "dashboard in parent"
|
|
||||||
dashInSubfolderTitle = "dashboard in subfolder"
|
|
||||||
)
|
|
||||||
var viewer *user.SignedInUser
|
|
||||||
|
|
||||||
setup := func() {
|
|
||||||
sqlStore, cfg = db.InitTestDBWithCfg(t)
|
|
||||||
cfg.AutoAssignOrg = true
|
|
||||||
cfg.AutoAssignOrgId = 1
|
|
||||||
cfg.AutoAssignOrgRole = string(org.RoleViewer)
|
|
||||||
|
|
||||||
tracer := tracing.InitializeTracerForTest()
|
|
||||||
quotaService := quotatest.New(false, nil)
|
|
||||||
|
|
||||||
// enable nested folders so that the folder table is populated for all the tests
|
|
||||||
features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
dashboardWriteStore, err := ProvideDashboardStore(sqlStore, cfg, features, tagimpl.ProvideService(sqlStore))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
|
|
||||||
require.NoError(t, err)
|
|
||||||
usrSvc, err := userimpl.ProvideService(
|
|
||||||
sqlStore, orgService, cfg, nil, nil, tracer,
|
|
||||||
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
usr := createUser(t, usrSvc, orgService, "viewer", false)
|
|
||||||
viewer = &user.SignedInUser{
|
|
||||||
UserID: usr.ID,
|
|
||||||
OrgID: usr.OrgID,
|
|
||||||
OrgRole: org.RoleViewer,
|
|
||||||
}
|
|
||||||
|
|
||||||
// create admin user in the same org
|
|
||||||
currentUserCmd := user.CreateUserCommand{Login: "admin", Email: "admin@test.com", Name: "an admin", IsAdmin: false, OrgID: viewer.OrgID}
|
|
||||||
u, err := usrSvc.Create(context.Background(), ¤tUserCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
admin := user.SignedInUser{
|
|
||||||
UserID: u.ID,
|
|
||||||
OrgID: u.OrgID,
|
|
||||||
OrgRole: org.RoleAdmin,
|
|
||||||
Permissions: map[int64]map[string][]string{u.OrgID: accesscontrol.GroupScopesByActionContext(context.Background(), []accesscontrol.Permission{
|
|
||||||
{
|
|
||||||
Action: dashboards.ActionFoldersCreate,
|
|
||||||
Scope: dashboards.ScopeFoldersAll,
|
|
||||||
}}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
require.NotEqual(t, viewer.UserID, admin.UserID)
|
|
||||||
|
|
||||||
folderStore := folderimpl.ProvideStore(sqlStore)
|
|
||||||
folderSvc := folderimpl.ProvideService(
|
|
||||||
folderStore, mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore),
|
|
||||||
nil, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, cfg, nil, tracing.InitializeTracerForTest(), nil, dualwrite.ProvideTestService(), sort.ProvideService(), apiserver.WithoutRestConfig)
|
|
||||||
|
|
||||||
parentUID := ""
|
|
||||||
for i := 0; ; i++ {
|
|
||||||
uid := fmt.Sprintf("f%d", i)
|
|
||||||
f, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{
|
|
||||||
UID: uid,
|
|
||||||
OrgID: admin.OrgID,
|
|
||||||
Title: uid,
|
|
||||||
SignedInUser: &admin,
|
|
||||||
ParentUID: parentUID,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, folder.ErrMaximumDepthReached) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Log("unexpected error", "error", err)
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
nestedFolders = append(nestedFolders, f)
|
|
||||||
|
|
||||||
parentUID = f.UID
|
|
||||||
}
|
|
||||||
require.LessOrEqual(t, 2, len(nestedFolders))
|
|
||||||
|
|
||||||
saveDashboardCmd := dashboards.SaveDashboardCommand{
|
|
||||||
UserID: admin.UserID,
|
|
||||||
OrgID: admin.OrgID,
|
|
||||||
IsFolder: false,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]any{
|
|
||||||
"title": dashInRootTitle,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
_, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
saveDashboardCmd = dashboards.SaveDashboardCommand{
|
|
||||||
UserID: admin.UserID,
|
|
||||||
OrgID: admin.OrgID,
|
|
||||||
IsFolder: false,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]any{
|
|
||||||
"title": dashInParentTitle,
|
|
||||||
}),
|
|
||||||
FolderUID: nestedFolders[0].UID,
|
|
||||||
}
|
|
||||||
_, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
saveDashboardCmd = dashboards.SaveDashboardCommand{
|
|
||||||
UserID: admin.UserID,
|
|
||||||
OrgID: admin.OrgID,
|
|
||||||
IsFolder: false,
|
|
||||||
Dashboard: simplejson.NewFromAny(map[string]any{
|
|
||||||
"title": dashInSubfolderTitle,
|
|
||||||
}),
|
|
||||||
FolderUID: nestedFolders[1].UID,
|
|
||||||
}
|
|
||||||
_, err = dashboardWriteStore.SaveDashboard(context.Background(), saveDashboardCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
nestedFolderTitles := make([]string, 0, len(nestedFolders))
|
|
||||||
for _, f := range nestedFolders {
|
|
||||||
nestedFolderTitles = append(nestedFolderTitles, f.Title)
|
|
||||||
}
|
|
||||||
|
|
||||||
testCases := []struct {
|
|
||||||
desc string
|
|
||||||
features featuremgmt.FeatureToggles
|
|
||||||
permissions map[string][]string
|
|
||||||
expectedTitles []string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
desc: "it should not return folder if ACL is not set for parent folder",
|
|
||||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch),
|
|
||||||
permissions: nil,
|
|
||||||
expectedTitles: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "it should not return subfolder if nested folders are disabled and the user has permission to read folders under parent folder",
|
|
||||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch),
|
|
||||||
permissions: map[string][]string{
|
|
||||||
dashboards.ActionFoldersRead: {fmt.Sprintf("folders:uid:%s", nestedFolders[0].UID)},
|
|
||||||
},
|
|
||||||
expectedTitles: []string{nestedFolders[0].Title},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
desc: "it should return subfolder if nested folders are enabled and the user has permission to read folders under parent folder",
|
|
||||||
features: featuremgmt.WithFeatures(featuremgmt.FlagPanelTitleSearch, featuremgmt.FlagNestedFolders),
|
|
||||||
permissions: map[string][]string{
|
|
||||||
dashboards.ActionFoldersRead: {fmt.Sprintf("folders:uid:%s", nestedFolders[0].UID)},
|
|
||||||
},
|
|
||||||
expectedTitles: nestedFolderTitles,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range testCases {
|
|
||||||
t.Run(tc.desc, func(t *testing.T) {
|
|
||||||
dashboardReadStore, err := ProvideDashboardStore(sqlStore, cfg, tc.features, tagimpl.ProvideService(sqlStore))
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
viewer.Permissions = map[int64]map[string][]string{viewer.OrgID: tc.permissions}
|
|
||||||
actest.AddUserPermissionToDB(t, sqlStore, viewer)
|
|
||||||
|
|
||||||
query := &dashboards.FindPersistedDashboardsQuery{
|
|
||||||
SignedInUser: viewer,
|
|
||||||
OrgId: viewer.OrgID,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := testSearchDashboards(dashboardReadStore, query)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Equal(t, len(tc.expectedTitles), len(res))
|
|
||||||
for i, tlt := range tc.expectedTitles {
|
|
||||||
assert.Equal(t, tlt, res[i].Title)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, dashboard *simplejson.Json,
|
func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, dashboard *simplejson.Json,
|
||||||
newFolderId int64, newFolderUID string) *dashboards.Dashboard {
|
newFolderId int64, newFolderUID string) *dashboards.Dashboard {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
@@ -437,15 +230,3 @@ func moveDashboard(t *testing.T, dashboardStore dashboards.Store, orgId int64, d
|
|||||||
|
|
||||||
return dash
|
return dash
|
||||||
}
|
}
|
||||||
|
|
||||||
func createUser(t *testing.T, userSrv user.Service, orgSrv org.Service, name string, isAdmin bool) user.User {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
o, err := orgSrv.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: fmt.Sprintf("test org %d", time.Now().UnixNano())})
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
currentUserCmd := user.CreateUserCommand{Login: name, Email: name + "@test.com", Name: "a " + name, IsAdmin: isAdmin, OrgID: o.ID}
|
|
||||||
currentUser, err := userSrv.Create(context.Background(), ¤tUserCmd)
|
|
||||||
require.NoError(t, err)
|
|
||||||
return *currentUser
|
|
||||||
}
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -47,8 +47,13 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||||
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testsuite.Run(m)
|
||||||
|
}
|
||||||
|
|
||||||
func TestDashboardService(t *testing.T) {
|
func TestDashboardService(t *testing.T) {
|
||||||
t.Run("Dashboard service tests", func(t *testing.T) {
|
t.Run("Dashboard service tests", func(t *testing.T) {
|
||||||
fakeStore := dashboards.FakeDashboardStore{}
|
fakeStore := dashboards.FakeDashboardStore{}
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
|
"github.com/grafana/grafana/pkg/services/org"
|
||||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/search/model"
|
"github.com/grafana/grafana/pkg/services/search/model"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/tests"
|
||||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
@@ -32,21 +35,243 @@ func TestMain(m *testing.M) {
|
|||||||
testsuite.Run(m)
|
testsuite.Run(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIntegrationDashboardServiceValidation(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
|
DisableAnonymous: true,
|
||||||
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
|
})
|
||||||
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
|
|
||||||
|
orgPayload := map[string]interface{}{
|
||||||
|
"name": "Org B",
|
||||||
|
}
|
||||||
|
orgPayloadBytes, err := json.Marshal(orgPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
orgURL := fmt.Sprintf("http://admin:admin@%s/api/orgs", grafanaListedAddr)
|
||||||
|
orgResp, err := http.Post(orgURL, "application/json", bytes.NewBuffer(orgPayloadBytes)) // nolint:gosec
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, orgResp.StatusCode)
|
||||||
|
err = orgResp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(org.RoleAdmin),
|
||||||
|
Login: "admin-org2",
|
||||||
|
Password: "admin",
|
||||||
|
IsAdmin: true,
|
||||||
|
OrgID: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
savedFolder := createFolder(t, grafanaListedAddr, "Saved folder")
|
||||||
|
savedDashInFolder := createDashboard(t, grafanaListedAddr, "Saved dash in folder", savedFolder.ID, savedFolder.UID) // nolint:staticcheck
|
||||||
|
savedDashInGeneralFolder := createDashboard(t, grafanaListedAddr, "Saved dashboard in general folder", 0, "")
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with non-existing id in org A", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": 123412321,
|
||||||
|
"title": "Expect error",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with existing ID from org A in org B", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin-org2", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": savedDashInFolder.ID, // nolint:staticcheck
|
||||||
|
"title": "Expect error",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with same UID in org A and org B, should be okay", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin-org2", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"title": "Saved dash in folder",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When creating a dashboard in General folder with same name as dashboard in other folder", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": "Saved dash in folder",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("When creating a dashboard in other folder with same name as dashboard in General folder", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder,
|
||||||
|
"title": "Dash with existing uid in other org",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When creating a folder with same name as dashboard in other folder", func(t *testing.T) {
|
||||||
|
f := createFolder(t, grafanaListedAddr, "Saved dashboard in general folder")
|
||||||
|
require.Equal(t, f.Title, "Saved dashboard in general folder")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard without id and uid and unique title in folder", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": "Unique",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with id 0", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": 0,
|
||||||
|
"title": "Dash with zero id",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard in non-existing folder", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": "no folder",
|
||||||
|
},
|
||||||
|
"folderUid": "non-existing-folder",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with incorrect version but no overwrite", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"version": 1,
|
||||||
|
},
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with current version and overwrite is true", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"version": savedDashInFolder.Version,
|
||||||
|
"title": "Saved dash in folder",
|
||||||
|
},
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When saving a dashboard with no version set and title set to a folder title", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"title": "Saved folder",
|
||||||
|
},
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
"overwrite": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When updating uid with id", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": savedDashInFolder.ID, // nolint:staticcheck
|
||||||
|
"uid": "new-uid",
|
||||||
|
"title": "Updated title",
|
||||||
|
},
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
"overwrite": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("When updating uid with a dashboard already using that uid", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": savedDashInFolder.ID, // nolint:staticcheck
|
||||||
|
"uid": savedDashInGeneralFolder.UID,
|
||||||
|
"title": "Updated title",
|
||||||
|
},
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
"overwrite": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When trying to update to a folder", func(t *testing.T) {
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "admin", "admin", map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"id": savedDashInFolder.ID, // nolint:staticcheck
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"title": "Updated title",
|
||||||
|
},
|
||||||
|
"isFolder": true,
|
||||||
|
"folderUid": savedDashInFolder.FolderUID,
|
||||||
|
"overwrite": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestIntegrationDashboardQuota(t *testing.T) {
|
func TestIntegrationDashboardQuota(t *testing.T) {
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
testDashboardQuota(t, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationDashboardQuotaK8s(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
testDashboardQuota(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testDashboardQuota(t *testing.T, featureToggles []string) {
|
|
||||||
// enable quota and set low dashboard quota
|
// enable quota and set low dashboard quota
|
||||||
// Setup Grafana and its Database
|
// Setup Grafana and its Database
|
||||||
dashboardQuota := int64(1)
|
dashboardQuota := int64(1)
|
||||||
@@ -54,7 +279,7 @@ func testDashboardQuota(t *testing.T, featureToggles []string) {
|
|||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableQuota: true,
|
EnableQuota: true,
|
||||||
DashboardOrgQuota: &dashboardQuota,
|
DashboardOrgQuota: &dashboardQuota,
|
||||||
EnableFeatureToggles: featureToggles,
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
@@ -110,27 +335,10 @@ func testDashboardQuota(t *testing.T, featureToggles []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationUpdatingProvisionionedDashboards(t *testing.T) {
|
func TestIntegrationUpdatingProvisionionedDashboards(t *testing.T) {
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testUpdatingProvisionionedDashboards(t, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationUpdatingProvisionionedDashboardsK8s(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
// will be the default in g12
|
|
||||||
testUpdatingProvisionionedDashboards(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testUpdatingProvisionionedDashboards(t *testing.T, featureToggles []string) {
|
|
||||||
// Setup Grafana and its Database
|
// Setup Grafana and its Database
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableFeatureToggles: featureToggles,
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
})
|
})
|
||||||
|
|
||||||
provDashboardsDir := filepath.Join(dir, "conf", "provisioning", "dashboards")
|
provDashboardsDir := filepath.Join(dir, "conf", "provisioning", "dashboards")
|
||||||
@@ -187,7 +395,7 @@ providers:
|
|||||||
var dashboardID int64
|
var dashboardID int64
|
||||||
for _, d := range *dashboardList {
|
for _, d := range *dashboardList {
|
||||||
dashboardUID = d.UID
|
dashboardUID = d.UID
|
||||||
dashboardID = d.ID
|
dashboardID = d.ID // nolint:staticcheck
|
||||||
}
|
}
|
||||||
assert.Equal(t, int64(1), dashboardID)
|
assert.Equal(t, int64(1), dashboardID)
|
||||||
|
|
||||||
@@ -281,34 +489,10 @@ providers:
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationCreate(t *testing.T) {
|
func TestIntegrationCreate(t *testing.T) {
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testCreate(t, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationCreateK8s(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testCreate(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationPreserveSchemaVersion(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testPreserveSchemaVersion(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testCreate(t *testing.T, featureToggles []string) {
|
|
||||||
// Setup Grafana and its Database
|
// Setup Grafana and its Database
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableFeatureToggles: featureToggles,
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
@@ -461,10 +645,10 @@ func intPtr(n int) *int {
|
|||||||
return &n
|
return &n
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPreserveSchemaVersion(t *testing.T, featureToggles []string) {
|
func TestIntegrationPreserveSchemaVersion(t *testing.T) {
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableFeatureToggles: featureToggles,
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
@@ -553,25 +737,9 @@ func testPreserveSchemaVersion(t *testing.T, featureToggles []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
|
func TestIntegrationImportDashboardWithLibraryPanels(t *testing.T) {
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testImportDashboardWithLibraryPanels(t, []string{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestIntegrationImportDashboardWithLibraryPanelsK8s(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("skipping integration test")
|
|
||||||
}
|
|
||||||
|
|
||||||
testImportDashboardWithLibraryPanels(t, []string{featuremgmt.FlagKubernetesClientDashboardsFolders})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testImportDashboardWithLibraryPanels(t *testing.T, featureToggles []string) {
|
|
||||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableFeatureToggles: featureToggles,
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
})
|
})
|
||||||
|
|
||||||
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
grafanaListedAddr, _ := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
@@ -762,3 +930,297 @@ func testImportDashboardWithLibraryPanels(t *testing.T, featureToggles []string)
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createDashboard(t *testing.T, grafanaListedAddr string, title string, folderID int64, folderUID string) *dashboards.Dashboard {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
err := json.NewEncoder(buf).Encode(map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": title,
|
||||||
|
},
|
||||||
|
"folderId": folderID,
|
||||||
|
"folderUid": folderUID,
|
||||||
|
"overwrite": true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://admin:admin@%s/api/dashboards/db", grafanaListedAddr)
|
||||||
|
// nolint:gosec
|
||||||
|
resp, err := http.Post(u, "application/json", buf)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
err := resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var saveResp struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
Slug string `json:"slug"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
UID string `json:"uid"`
|
||||||
|
URL string `json:"url"`
|
||||||
|
FolderUID string `json:"folderUid"`
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &saveResp)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotEmpty(t, saveResp.UID)
|
||||||
|
|
||||||
|
return &dashboards.Dashboard{
|
||||||
|
ID: saveResp.ID, // nolint:staticcheck
|
||||||
|
UID: saveResp.UID,
|
||||||
|
Slug: saveResp.Slug,
|
||||||
|
Version: int(saveResp.Version),
|
||||||
|
FolderUID: saveResp.FolderUID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func postDashboard(t *testing.T, grafanaListedAddr, user, password string, payload map[string]interface{}) (*http.Response, error) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(payload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://%s:%s@%s/api/dashboards/db", user, password, grafanaListedAddr)
|
||||||
|
return http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIntegrationDashboardServicePermissions(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
|
||||||
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||||
|
DisableAnonymous: true,
|
||||||
|
EnableFeatureToggles: []string{featuremgmt.FlagKubernetesClientDashboardsFolders},
|
||||||
|
})
|
||||||
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
||||||
|
tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(org.RoleEditor),
|
||||||
|
Login: "editor",
|
||||||
|
Password: "editor",
|
||||||
|
IsAdmin: false,
|
||||||
|
})
|
||||||
|
tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(org.RoleViewer),
|
||||||
|
Login: "viewer",
|
||||||
|
Password: "viewer",
|
||||||
|
IsAdmin: false,
|
||||||
|
})
|
||||||
|
savedFolder := createFolder(t, grafanaListedAddr, "Saved folder")
|
||||||
|
otherSavedFolder := createFolder(t, grafanaListedAddr, "Other saved folder")
|
||||||
|
savedDashInFolder := createDashboard(t, grafanaListedAddr, "Saved dash in folder", savedFolder.ID, savedFolder.UID) // nolint:staticcheck
|
||||||
|
savedDashInGeneralFolder := createDashboard(t, grafanaListedAddr, "Saved dashboard in general folder", 0, "")
|
||||||
|
|
||||||
|
t.Run("When creating a new dashboard in the General folder, requires create permissions scoped to the general folder", func(t *testing.T) {
|
||||||
|
dashboardPayload := map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": "Dash",
|
||||||
|
},
|
||||||
|
"overwrite": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://viewer:viewer@%s/api/dashboards/db", grafanaListedAddr)
|
||||||
|
resp, err := http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u = fmt.Sprintf("http://editor:editor@%s/api/dashboards/db", grafanaListedAddr)
|
||||||
|
resp, err = http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When creating a new dashboard in other folder, requires create permissions scoped to the other folder", func(t *testing.T) {
|
||||||
|
dashboardPayload := map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"title": "Dash",
|
||||||
|
},
|
||||||
|
"folderUid": otherSavedFolder.UID,
|
||||||
|
"overwrite": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "viewer", "viewer", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = postDashboard(t, grafanaListedAddr, "editor", "editor", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When creating a new dashboard by existing UID in folder, requires write permissions on the existing dashboard", func(t *testing.T) {
|
||||||
|
dashboardPayload := map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"title": "New dash",
|
||||||
|
},
|
||||||
|
"folderUid": savedFolder.UID,
|
||||||
|
"overwrite": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "viewer", "viewer", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = postDashboard(t, grafanaListedAddr, "editor", "editor", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When moving a dashboard by existing uid to other folder from General folder, requires dashboard creation permissions on the destination folder and write access to the dashboard", func(t *testing.T) {
|
||||||
|
dashboardPayload := map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInGeneralFolder.UID,
|
||||||
|
"title": "Dash",
|
||||||
|
},
|
||||||
|
"folderUid": otherSavedFolder.UID,
|
||||||
|
"overwrite": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "viewer", "viewer", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = postDashboard(t, grafanaListedAddr, "editor", "editor", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When moving a dashboard by existing uid to the General folder from other folder, requires dashboard creation permissions on the general folder and write access to the dashboard", func(t *testing.T) {
|
||||||
|
dashboardPayload := map[string]interface{}{
|
||||||
|
"dashboard": map[string]interface{}{
|
||||||
|
"uid": savedDashInFolder.UID,
|
||||||
|
"title": "Dash",
|
||||||
|
},
|
||||||
|
"folderUid": "",
|
||||||
|
"overwrite": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := postDashboard(t, grafanaListedAddr, "viewer", "viewer", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusForbidden, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
resp, err = postDashboard(t, grafanaListedAddr, "editor", "editor", dashboardPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("RBAC tests", func(t *testing.T) {
|
||||||
|
setFolderPermissions := func(t *testing.T, grafanaListedAddr string, folderUID string, permissions []map[string]interface{}) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
permissionPayload := map[string]interface{}{
|
||||||
|
"items": permissions,
|
||||||
|
}
|
||||||
|
|
||||||
|
payloadBytes, err := json.Marshal(permissionPayload)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://admin:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID)
|
||||||
|
resp, err := http.Post(u, "application/json", bytes.NewBuffer(payloadBytes)) // nolint:gosec
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
err = resp.Body.Close()
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
searchDashboards := func(t *testing.T, grafanaListedAddr string, userLogin, userPassword string) []map[string]interface{} {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
u := fmt.Sprintf("http://%s:%s@%s/api/search?type=dash-db", userLogin, userPassword, grafanaListedAddr)
|
||||||
|
resp, err := http.Get(u) // nolint:gosec
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
defer resp.Body.Close() // nolint:errcheck
|
||||||
|
|
||||||
|
var results []map[string]interface{}
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&results)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
noneUserID := tests.CreateUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
||||||
|
DefaultOrgRole: string(org.RoleNone),
|
||||||
|
Login: "noneuser",
|
||||||
|
Password: "noneuser",
|
||||||
|
IsAdmin: false,
|
||||||
|
})
|
||||||
|
parentFolder := createFolder(t, grafanaListedAddr, "parent")
|
||||||
|
childFolder := createFolder(t, grafanaListedAddr, "child")
|
||||||
|
createDashboard(t, grafanaListedAddr, "dashboard in root", 0, "")
|
||||||
|
createDashboard(t, grafanaListedAddr, "dashboard in parent", parentFolder.ID, parentFolder.UID) // nolint:staticcheck
|
||||||
|
createDashboard(t, grafanaListedAddr, "dashboard in child", childFolder.ID, childFolder.UID) // nolint:staticcheck
|
||||||
|
|
||||||
|
viewPermissions := []map[string]interface{}{
|
||||||
|
{
|
||||||
|
"permission": 1,
|
||||||
|
"userId": noneUserID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
t.Run("it should not return folder if ACL is not set for parent folder", func(t *testing.T) {
|
||||||
|
results := searchDashboards(t, grafanaListedAddr, "noneuser", "noneuser")
|
||||||
|
assert.Empty(t, results, "Should not return any dashboards when no permissions are set")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("it should return child folder when user has permission to read child folder", func(t *testing.T) {
|
||||||
|
setFolderPermissions(t, grafanaListedAddr, childFolder.UID, viewPermissions)
|
||||||
|
results := searchDashboards(t, grafanaListedAddr, "noneuser", "noneuser")
|
||||||
|
|
||||||
|
foundTitles := make([]string, 0)
|
||||||
|
for _, result := range results {
|
||||||
|
if title, ok := result["title"].(string); ok {
|
||||||
|
foundTitles = append(foundTitles, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, foundTitles, "dashboard in child", "Should return dashboard in child folder")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("it should return parent folder when user has permission to read parent folder but no permission to read child folder", func(t *testing.T) {
|
||||||
|
setFolderPermissions(t, grafanaListedAddr, parentFolder.UID, viewPermissions)
|
||||||
|
setFolderPermissions(t, grafanaListedAddr, childFolder.UID, []map[string]interface{}{})
|
||||||
|
|
||||||
|
results := searchDashboards(t, grafanaListedAddr, "noneuser", "noneuser")
|
||||||
|
|
||||||
|
foundTitles := make([]string, 0)
|
||||||
|
for _, result := range results {
|
||||||
|
if title, ok := result["title"].(string); ok {
|
||||||
|
foundTitles = append(foundTitles, title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Contains(t, foundTitles, "dashboard in parent", "Should return dashboard in parent folder")
|
||||||
|
assert.NotContains(t, foundTitles, "dashboard in child", "Should not return dashboard in child folder")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user