* Add config provider and integrate with wire setup * Refactor quota service to use config provider for configuration management * Enhance OSSConfigProvider to include logging and update ProvideService to return an error. Refactor server initialization to handle potential errors from config provider. Remove unnecessary wire binding for OSSConfigProvider. * Update CODEOWNERS to include the configprovider package under the grafana-backend-services-squad. * Refactor quota service initialization to include context in multiple service providers. Update tests and service implementations to ensure proper context handling during service creation.
380 lines
15 KiB
Go
380 lines
15 KiB
Go
package libraryelements_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana-openapi-client-go/models"
|
|
"github.com/grafana/grafana/pkg/configprovider"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/libraryelements/model"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"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/tests/testinfra"
|
|
)
|
|
|
|
func TestIntegrationLibraryElementPermissions(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{})
|
|
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
cfgProvider, err := configprovider.ProvideService(env.Cfg)
|
|
require.NoError(t, err)
|
|
quotaService := quotaimpl.ProvideService(context.Background(), env.SQLStore, cfgProvider)
|
|
orgService, err := orgimpl.ProvideService(env.SQLStore, env.Cfg, quotaService)
|
|
require.NoError(t, err)
|
|
|
|
sharedOrg, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"})
|
|
require.NoError(t, err)
|
|
|
|
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "viewer",
|
|
Login: "viewer",
|
|
OrgID: sharedOrg.ID,
|
|
})
|
|
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleEditor),
|
|
Password: "editor",
|
|
Login: "editor",
|
|
OrgID: sharedOrg.ID,
|
|
})
|
|
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "admin",
|
|
Login: "admin2",
|
|
OrgID: sharedOrg.ID,
|
|
})
|
|
|
|
uid := ""
|
|
t.Run("create", func(t *testing.T) {
|
|
t.Run("When viewer tries to create a library panel in the General folder, it should fail", func(t *testing.T) {
|
|
createLibraryElement(t, grafanaListedAddr, "viewer", "viewer", "", http.StatusForbidden)
|
|
})
|
|
|
|
t.Run("When a user tries to create a library panel in a folder that doesn't exist, it should fail", func(t *testing.T) {
|
|
createLibraryElement(t, grafanaListedAddr, "admin2", "admin", "non-existent-folder-uid", http.StatusBadRequest)
|
|
})
|
|
|
|
t.Run("When editor tries to create a library panel in the General folder, it should succeed", func(t *testing.T) {
|
|
uid = createLibraryElement(t, grafanaListedAddr, "editor", "editor", "", http.StatusOK)
|
|
require.NotEmpty(t, uid)
|
|
require.NotEqual(t, uid, "")
|
|
})
|
|
})
|
|
|
|
t.Run("move to folder", func(t *testing.T) {
|
|
folderUID := createTestFolder(t, grafanaListedAddr)
|
|
|
|
t.Run("When viewer tries to move library panel to folder, it should fail", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, folderUID, http.StatusForbidden)
|
|
})
|
|
|
|
t.Run("When a user tries to patch a library panel by moving it to a folder that doesn't exist, it should fail", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "admin2", "admin", uid, "non-existent-folder-uid", http.StatusBadRequest)
|
|
})
|
|
|
|
t.Run("When editor tries to move library panel to folder, it should succeed", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, folderUID, http.StatusOK)
|
|
})
|
|
})
|
|
|
|
t.Run("move to general folder", func(t *testing.T) {
|
|
t.Run("When viewer tries to move library panel back to general, it should fail", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, "", http.StatusForbidden)
|
|
})
|
|
|
|
t.Run("When editor tries to move library panel back to general, it should succeed", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, "", http.StatusOK)
|
|
})
|
|
})
|
|
|
|
t.Run("get", func(t *testing.T) {
|
|
t.Run("When viewer tries to get library panel, it should succeed", func(t *testing.T) {
|
|
getLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, http.StatusOK)
|
|
})
|
|
|
|
t.Run("When editor tries to get library panel, it should succeed", func(t *testing.T) {
|
|
getLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, http.StatusOK)
|
|
})
|
|
})
|
|
|
|
t.Run("get all", func(t *testing.T) {
|
|
t.Run("When viewer tries to get all library elements, it should succeed", func(t *testing.T) {
|
|
getAllLibraryElements(t, grafanaListedAddr, "viewer", "viewer", http.StatusOK, 1)
|
|
})
|
|
|
|
t.Run("When editor tries to get all library elements, it should succeed", func(t *testing.T) {
|
|
getAllLibraryElements(t, grafanaListedAddr, "editor", "editor", http.StatusOK, 1)
|
|
})
|
|
})
|
|
|
|
t.Run("delete", func(t *testing.T) {
|
|
t.Run("When viewer tries to delete library panel, it should fail", func(t *testing.T) {
|
|
deleteLibraryElement(t, grafanaListedAddr, "viewer", "viewer", uid, http.StatusForbidden)
|
|
})
|
|
|
|
t.Run("When editor tries to delete library panel, it should succeed", func(t *testing.T) {
|
|
deleteLibraryElement(t, grafanaListedAddr, "editor", "editor", uid, http.StatusOK)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestIntegrationLibraryElementGranularPermissions(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{})
|
|
grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, path)
|
|
cfgProvider, err := configprovider.ProvideService(env.Cfg)
|
|
require.NoError(t, err)
|
|
quotaService := quotaimpl.ProvideService(context.Background(), env.SQLStore, cfgProvider)
|
|
orgService, err := orgimpl.ProvideService(env.SQLStore, env.Cfg, quotaService)
|
|
require.NoError(t, err)
|
|
|
|
sharedOrg, err := orgService.CreateWithMember(context.Background(), &org.CreateOrgCommand{Name: "test org"})
|
|
require.NoError(t, err)
|
|
|
|
userID := createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleViewer),
|
|
Password: "granular-viewer",
|
|
Login: "granular-viewer",
|
|
OrgID: sharedOrg.ID,
|
|
})
|
|
createUserInOrg(t, env.SQLStore, env.Cfg, user.CreateUserCommand{
|
|
DefaultOrgRole: string(org.RoleAdmin),
|
|
Password: "admin",
|
|
Login: "admin2",
|
|
OrgID: sharedOrg.ID,
|
|
})
|
|
|
|
folder1UID := createTestFolder(t, grafanaListedAddr)
|
|
folder2UID := createTestFolder(t, grafanaListedAddr)
|
|
folder3UID := createTestFolder(t, grafanaListedAddr)
|
|
|
|
// viewer only has access to folder 1 & 3
|
|
grantFolderPermissions(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder1UID, userID)
|
|
grantFolderPermissions(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder3UID, userID)
|
|
// revoke view access to folder2
|
|
revokeFolderPermissions(t, grafanaListedAddr, folder2UID, userID)
|
|
|
|
uid := ""
|
|
t.Run("granular createpermissions", func(t *testing.T) {
|
|
t.Run("When viewer has write access to folder1, they can create library element in folder1", func(t *testing.T) {
|
|
uid = createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder1UID, http.StatusOK)
|
|
require.NotEmpty(t, uid)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have read access to folder2, they cannot create library element in folder2", func(t *testing.T) {
|
|
createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", folder2UID, http.StatusBadRequest)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have write access to general folder, they cannot create library element in general", func(t *testing.T) {
|
|
createLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", "", http.StatusForbidden)
|
|
})
|
|
})
|
|
|
|
t.Run("granular move permissions", func(t *testing.T) {
|
|
t.Run("When viewer has write access to folder3 and folder1, they can move library element from folder1 to folder3", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, folder3UID, http.StatusOK)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have read access to folder2, they cannot move library element to folder2", func(t *testing.T) {
|
|
patchLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, folder2UID, http.StatusBadRequest)
|
|
})
|
|
})
|
|
|
|
inGeneralFolder := createLibraryElement(t, grafanaListedAddr, "admin2", "admin", "", http.StatusOK)
|
|
inFolder2 := createLibraryElement(t, grafanaListedAddr, "admin2", "admin", folder2UID, http.StatusOK)
|
|
|
|
t.Run("granular read permissions", func(t *testing.T) {
|
|
t.Run("When viewer has read access to folder1, they can get library element from folder1", func(t *testing.T) {
|
|
getLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, http.StatusOK)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have read access to folder2, they cannot get library element from folder2", func(t *testing.T) {
|
|
getLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inFolder2, http.StatusNotFound)
|
|
})
|
|
|
|
t.Run("When viewer has limited folder access, they only see library elements from accessible folders", func(t *testing.T) {
|
|
getAllLibraryElements(t, grafanaListedAddr, "granular-viewer", "granular-viewer", http.StatusOK, 2)
|
|
})
|
|
})
|
|
|
|
t.Run("granular delete permissions", func(t *testing.T) {
|
|
t.Run("When viewer has write access to folder1, they can delete library element from folder1", func(t *testing.T) {
|
|
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", uid, http.StatusOK)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have write access to folder2, they cannot delete library element from folder2", func(t *testing.T) {
|
|
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inFolder2, http.StatusForbidden)
|
|
})
|
|
|
|
t.Run("When viewer doesn't have write access to general folder, they cannot delete library element from general", func(t *testing.T) {
|
|
deleteLibraryElement(t, grafanaListedAddr, "granular-viewer", "granular-viewer", inGeneralFolder, http.StatusForbidden)
|
|
})
|
|
})
|
|
}
|
|
|
|
/*
|
|
Helper functions
|
|
*/
|
|
|
|
func createLibraryElement(t *testing.T, grafanaListedAddr, user, password, folderUID string, expectedStatus int) string {
|
|
m := map[string]interface{}{
|
|
"datasource": "${DS_GDEV-TESTDATA}",
|
|
"id": 1,
|
|
"title": "Text - Library Panel",
|
|
"type": "text",
|
|
"description": "A description",
|
|
}
|
|
createRequest := map[string]interface{}{
|
|
"name": "Library Panel Name",
|
|
"model": m,
|
|
"folderUid": folderUID,
|
|
"kind": int64(1),
|
|
}
|
|
|
|
resp := makeHTTPRequest(t, "POST", fmt.Sprintf("http://%s:%s@%s/api/library-elements", user, password, grafanaListedAddr), createRequest, expectedStatus)
|
|
if expectedStatus == http.StatusOK {
|
|
var result model.LibraryElementResponse
|
|
err := json.Unmarshal(resp, &result)
|
|
require.NoError(t, err)
|
|
return result.Result.UID
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func patchLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid, folderUID string, expectedStatus int) {
|
|
version := getLibraryElementVersion(t, grafanaListedAddr, user, password, uid)
|
|
patchRequest := map[string]interface{}{
|
|
"folderUid": folderUID,
|
|
"version": version,
|
|
"kind": 1,
|
|
}
|
|
makeHTTPRequest(t, "PATCH", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), patchRequest, expectedStatus)
|
|
}
|
|
|
|
func deleteLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid string, expectedStatus int) {
|
|
makeHTTPRequest(t, "DELETE", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, expectedStatus)
|
|
}
|
|
|
|
func getLibraryElement(t *testing.T, grafanaListedAddr, user, password, uid string, expectedStatus int) {
|
|
makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, expectedStatus)
|
|
}
|
|
|
|
func getAllLibraryElements(t *testing.T, grafanaListedAddr, user, password string, expectedStatus int, expectedLength int) {
|
|
resp := makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements", user, password, grafanaListedAddr), nil, expectedStatus)
|
|
if expectedStatus == http.StatusOK {
|
|
var result model.LibraryElementSearchResponse
|
|
err := json.Unmarshal(resp, &result)
|
|
require.NoError(t, err)
|
|
require.Len(t, result.Result.Elements, expectedLength)
|
|
}
|
|
}
|
|
|
|
func getLibraryElementVersion(t *testing.T, grafanaListedAddr, user, password, uid string) int {
|
|
resp := makeHTTPRequest(t, "GET", fmt.Sprintf("http://%s:%s@%s/api/library-elements/%s", user, password, grafanaListedAddr, uid), nil, http.StatusOK)
|
|
var getResult model.LibraryElementResponse
|
|
err := json.Unmarshal(resp, &getResult)
|
|
require.NoError(t, err)
|
|
|
|
return int(getResult.Result.Version)
|
|
}
|
|
|
|
func createTestFolder(t *testing.T, grafanaListedAddr string) string {
|
|
folderRequest := map[string]interface{}{
|
|
"title": "Test Folder",
|
|
}
|
|
resp := makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders", grafanaListedAddr), folderRequest, http.StatusOK)
|
|
var folder models.Folder
|
|
err := json.Unmarshal(resp, &folder)
|
|
require.NoError(t, err)
|
|
return folder.UID
|
|
}
|
|
|
|
func grantFolderPermissions(t *testing.T, grafanaListedAddr, user, password, folderUID string, userID int64) {
|
|
permissionRequest := map[string]interface{}{
|
|
"items": []map[string]interface{}{
|
|
{
|
|
"userId": userID,
|
|
"permission": 2, // edit permission
|
|
},
|
|
},
|
|
}
|
|
makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID), permissionRequest, http.StatusOK)
|
|
}
|
|
|
|
func revokeFolderPermissions(t *testing.T, grafanaListedAddr, folderUID string, userID int64) {
|
|
permissionRequest := map[string]interface{}{
|
|
"items": []map[string]interface{}{},
|
|
}
|
|
makeHTTPRequest(t, "POST", fmt.Sprintf("http://admin2:admin@%s/api/folders/%s/permissions", grafanaListedAddr, folderUID), permissionRequest, http.StatusOK)
|
|
}
|
|
|
|
func makeHTTPRequest(t *testing.T, method, url string, body interface{}, expectedStatus int) []byte {
|
|
var req *http.Request
|
|
var err error
|
|
|
|
if body != nil {
|
|
buf := &bytes.Buffer{}
|
|
err = json.NewEncoder(buf).Encode(body)
|
|
require.NoError(t, err)
|
|
req, err = http.NewRequest(method, url, buf)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
} else {
|
|
req, err = http.NewRequest(method, url, nil)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
// nolint:errcheck
|
|
defer resp.Body.Close()
|
|
require.Equal(t, expectedStatus, resp.StatusCode)
|
|
|
|
respBody, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return respBody
|
|
}
|
|
|
|
func createUserInOrg(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 {
|
|
t.Helper()
|
|
|
|
cfg.AutoAssignOrg = true
|
|
cfg.AutoAssignOrgId = 1
|
|
|
|
cfgProvider, err := configprovider.ProvideService(cfg)
|
|
require.NoError(t, err)
|
|
quotaService := quotaimpl.ProvideService(context.Background(), db, cfgProvider)
|
|
orgService, err := orgimpl.ProvideService(db, cfg, quotaService)
|
|
require.NoError(t, err)
|
|
usrSvc, err := userimpl.ProvideService(
|
|
db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
|
|
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
u, err := usrSvc.Create(context.Background(), &cmd)
|
|
require.NoError(t, err)
|
|
return u.ID
|
|
}
|