* `grafana-iam`: Fetch target parent folder * WIP add different ParentProviders * Add version * Move code to a different file * Instantiate resourceParentProvider * same import name * imports * Add tests * Remove unecessary test * forgot wire * WIP integration tests * Add test to cover list * Fix caching problem in integration tests * comments * Logger and comments * Add lazy creation and caching * Instantiate clients only once * Rerun wire gen
626 lines
24 KiB
Go
626 lines
24 KiB
Go
package identity
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
|
|
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
"github.com/grafana/grafana/pkg/apiserver/rest"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/tests/apis"
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
|
"github.com/grafana/grafana/pkg/util/testutil"
|
|
)
|
|
|
|
var gvrResourcePermissions = schema.GroupVersionResource{
|
|
Group: "iam.grafana.app",
|
|
Version: "v0alpha1",
|
|
Resource: "resourcepermissions",
|
|
}
|
|
|
|
var gvrFolders = schema.GroupVersionResource{
|
|
Group: "folder.grafana.app",
|
|
Version: "v1beta1",
|
|
Resource: "folders",
|
|
}
|
|
|
|
var gvrDashboards = schema.GroupVersionResource{
|
|
Group: "dashboard.grafana.app",
|
|
Version: "v1beta1",
|
|
Resource: "dashboards",
|
|
}
|
|
|
|
type permission struct {
|
|
kind string
|
|
name string
|
|
verb string
|
|
}
|
|
|
|
func newPermission(kind, name, verb string) permission {
|
|
return permission{
|
|
kind: kind,
|
|
name: name,
|
|
verb: verb,
|
|
}
|
|
}
|
|
|
|
func (p permission) ToMap() map[string]interface{} {
|
|
return map[string]interface{}{
|
|
"kind": p.kind,
|
|
"name": p.name,
|
|
"verb": p.verb,
|
|
}
|
|
}
|
|
|
|
func newPermissionMaps(permissions ...permission) []map[string]interface{} {
|
|
permissionsMaps := make([]map[string]interface{}, len(permissions))
|
|
for i, permission := range permissions {
|
|
permissionsMaps[i] = permission.ToMap()
|
|
}
|
|
return permissionsMaps
|
|
}
|
|
|
|
type k8sTestClients struct {
|
|
rpAdmin *apis.K8sResourceClient
|
|
rpEditor *apis.K8sResourceClient
|
|
rpViewer *apis.K8sResourceClient
|
|
}
|
|
|
|
func newk8sTestHelperClients(helper *apis.K8sTestHelper) *k8sTestClients {
|
|
return &k8sTestClients{
|
|
rpAdmin: helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: helper.Org1.Admin,
|
|
Namespace: helper.Namespacer(helper.Org1.OrgID),
|
|
GVR: gvrResourcePermissions,
|
|
}),
|
|
rpEditor: helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: helper.Org1.Editor,
|
|
Namespace: helper.Namespacer(helper.Org1.OrgID),
|
|
GVR: gvrResourcePermissions,
|
|
}),
|
|
rpViewer: helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: helper.Org1.Viewer,
|
|
Namespace: helper.Namespacer(helper.Org1.OrgID),
|
|
GVR: gvrResourcePermissions,
|
|
}),
|
|
}
|
|
}
|
|
|
|
func TestIntegrationResourcePermissions(t *testing.T) {
|
|
testutil.SkipIntegrationTestInShortMode(t)
|
|
|
|
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3}
|
|
for _, mode := range modes {
|
|
if mode >= rest.Mode3 {
|
|
t.Skip("Skipping ResourcePermission tests for Mode3+ because default permissions are not written through the new APIs")
|
|
continue
|
|
}
|
|
t.Run(fmt.Sprintf("ResourcePermission CRUD with dual writer mode %d", mode), func(t *testing.T) {
|
|
// Turn off authorization cache so permission changes apply right away in tests
|
|
t.Setenv("GF_AUTHORIZATION_CACHE_TTL", "0s")
|
|
|
|
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
|
AppModeProduction: false,
|
|
DisableAnonymous: true,
|
|
APIServerStorageType: "unified",
|
|
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
|
|
"resourcepermissions.iam.grafana.app": {
|
|
DualWriterMode: mode,
|
|
},
|
|
"folders.folder.grafana.app": {
|
|
DualWriterMode: mode,
|
|
},
|
|
"dashboards.dashboard.grafana.app": {
|
|
DualWriterMode: mode,
|
|
},
|
|
},
|
|
EnableFeatureToggles: []string{
|
|
featuremgmt.FlagKubernetesAuthzResourcePermissionApis,
|
|
// Prevents nested folders from having default permissions
|
|
featuremgmt.FlagKubernetesDashboards,
|
|
},
|
|
})
|
|
|
|
// Work around the default permissions applied on root folders
|
|
// so we can test the ResourcePermission APIs without the default permissions interfering
|
|
parentFolder := createRootFolderWithoutDefaultPermissions(t, helper)
|
|
parentUID := parentFolder.GetName()
|
|
|
|
clients := newk8sTestHelperClients(helper)
|
|
doResourcePermissionCRUDTests(t, helper, clients, parentUID)
|
|
doResourcePermissionAuthzTests(t, helper, clients, parentUID)
|
|
doResourcePermissionHierarchyTests(t, helper, clients, parentUID)
|
|
doResourcePermissionListFilteringTests(t, helper, clients, parentUID)
|
|
// TODO: Add tests for External JWT authentication
|
|
// doResourcePermissionAccessPolicyTests(t, helper)
|
|
})
|
|
}
|
|
}
|
|
|
|
func doResourcePermissionCRUDTests(t *testing.T, helper *apis.K8sTestHelper, clients *k8sTestClients, parentUID string) {
|
|
t.Run("should create/get/update/delete ResourcePermission using the new APIs", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create ResourcePermission for the folder
|
|
permission := newPermission("ServiceAccount", helper.Org1.ViewerServiceAccount.UID, "view")
|
|
toCreate := createResourcePermissionObject(parentUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
|
|
created, err := clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
|
|
createdName := created.GetName()
|
|
require.NotEmpty(t, createdName)
|
|
|
|
// Verify spec
|
|
spec := created.Object["spec"].(map[string]interface{})
|
|
resource := spec["resource"].(map[string]interface{})
|
|
require.Equal(t, gvrFolders.Group, resource["apiGroup"])
|
|
require.Equal(t, gvrFolders.Resource, resource["resource"])
|
|
require.Equal(t, parentUID, resource["name"])
|
|
|
|
// Get the ResourcePermission
|
|
fetched, err := clients.rpAdmin.Resource.Get(ctx, createdName, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, fetched)
|
|
require.Equal(t, createdName, fetched.GetName())
|
|
|
|
// Update the ResourcePermission
|
|
fetched.Object["spec"].(map[string]interface{})["permissions"] = newPermissionMaps(
|
|
newPermission("User", helper.Org1.Viewer.Identity.GetIdentifier(), "edit"),
|
|
)
|
|
updated, err := clients.rpAdmin.Resource.Update(ctx, fetched, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, updated)
|
|
|
|
updatedSpec := updated.Object["spec"].(map[string]interface{})
|
|
permissions := updatedSpec["permissions"].([]interface{})
|
|
require.Len(t, permissions, 1)
|
|
perm := permissions[0].(map[string]interface{})
|
|
require.Equal(t, helper.Org1.Viewer.Identity.GetIdentifier(), perm["name"])
|
|
require.Equal(t, "edit", perm["verb"])
|
|
|
|
// Delete should work
|
|
err = clients.rpAdmin.Resource.Delete(ctx, createdName, metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
|
|
_, err = clients.rpAdmin.Resource.Get(ctx, createdName, metav1.GetOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(404), statusErr.ErrStatus.Code)
|
|
})
|
|
|
|
t.Run("should return 404 for non-existent ResourcePermission", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
_, err := clients.rpAdmin.Resource.Get(ctx, "folder.grafana.app-folders-unknown", metav1.GetOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(404), statusErr.ErrStatus.Code)
|
|
})
|
|
}
|
|
|
|
func doResourcePermissionAuthzTests(t *testing.T, helper *apis.K8sTestHelper, clients *k8sTestClients, parentUID string) {
|
|
t.Run("admin can create/update/delete ResourcePermission", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "test-folder-admin", parentUID)
|
|
folderUID := folder.GetName()
|
|
|
|
permission := newPermission("User", helper.Org1.Admin.Identity.GetIdentifier(), "admin")
|
|
|
|
toCreate := createResourcePermissionObject(folderUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
created, err := clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
|
|
createdName := created.GetName()
|
|
|
|
// Get the created object
|
|
fetched, err := clients.rpAdmin.Resource.Get(ctx, createdName, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, fetched)
|
|
|
|
// Update should work
|
|
permission = newPermission("Team", helper.Org1.Staff.UID, "edit")
|
|
fetched.Object["spec"].(map[string]interface{})["permissions"] = []interface{}{permission.ToMap()}
|
|
|
|
_, err = clients.rpAdmin.Resource.Update(ctx, fetched, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// Delete should work
|
|
err = clients.rpAdmin.Resource.Delete(ctx, createdName, metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("editor cannot create ResourcePermission (insufficient permissions)", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "test-folder-editor-deny", parentUID)
|
|
folderUID := folder.GetName()
|
|
|
|
permission := newPermission("User", helper.Org1.Editor.Identity.GetIdentifier(), "admin")
|
|
toCreate := createResourcePermissionObject(folderUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err := clients.rpEditor.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
|
|
})
|
|
|
|
t.Run("viewer cannot create ResourcePermission (insufficient permissions)", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "test-folder-viewer-deny", parentUID)
|
|
folderUID := folder.GetName()
|
|
|
|
permission := newPermission("User", helper.Org1.Viewer.Identity.GetIdentifier(), "admin")
|
|
toCreate := createResourcePermissionObject(folderUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err := clients.rpViewer.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
|
|
})
|
|
|
|
t.Run("viewer can update ResourcePermission of folder they can admin", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "test-folder-viewer-admin", parentUID)
|
|
folderUID := folder.GetName()
|
|
|
|
// Grant admin permissions to the viewer
|
|
permission := newPermission("User", helper.Org1.Viewer.Identity.GetIdentifier(), "admin")
|
|
toCreate := createResourcePermissionObject(folderUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err := clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// As a Viewer we should now be able to get and update the ResourcePermission of the folder
|
|
fetched, err := clients.rpViewer.Resource.Get(ctx, "folder.grafana.app-folders-"+folderUID, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, fetched)
|
|
|
|
// Update the ResourcePermission to grant editor permissions
|
|
fetched.Object["spec"].(map[string]interface{})["permissions"] = newPermissionMaps(
|
|
newPermission("BasicRole", "Editor", "edit"),
|
|
newPermission("User", helper.Org1.Viewer.Identity.GetIdentifier(), "admin"),
|
|
)
|
|
|
|
_, err = clients.rpViewer.Resource.Update(ctx, fetched, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func doResourcePermissionHierarchyTests(t *testing.T, helper *apis.K8sTestHelper, clients *k8sTestClients, parentUID string) {
|
|
permission := newPermission("BasicRole", "Editor", "admin")
|
|
|
|
t.Run("should respect folder hierarchy for folder permissions", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
sub1 := createTestFolder(t, helper, helper.Org1.Admin, "sub1-folder-hierarchy", parentUID)
|
|
sub1UID := sub1.GetName()
|
|
|
|
sub2 := createTestFolder(t, helper, helper.Org1.Admin, "sub2-folder-hierarchy", sub1UID)
|
|
sub2UID := sub2.GetName()
|
|
|
|
toCreate := createResourcePermissionObject(sub2UID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err := clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("editor can update ResourcePermission of sub2", func(t *testing.T) {
|
|
fetched, err := clients.rpEditor.Resource.Get(ctx, "folder.grafana.app-folders-"+sub2UID, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
fetched.Object["spec"].(map[string]interface{})["permissions"] = newPermissionMaps(
|
|
newPermission("BasicRole", "Editor", "admin"),
|
|
newPermission("User", helper.Org1.Viewer.Identity.GetIdentifier(), "edit"),
|
|
)
|
|
_, err = clients.rpEditor.Resource.Update(ctx, fetched, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("editor cannot create ResourcePermission of sub1", func(t *testing.T) {
|
|
toCreate := createResourcePermissionObject(sub1UID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err := clients.rpEditor.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
|
|
})
|
|
|
|
// Delete the ResourcePermission of sub2
|
|
err = clients.rpAdmin.Resource.Delete(ctx, "folder.grafana.app-folders-"+sub2UID, metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// Create a new ResourcePermission for sub2
|
|
toCreate = createResourcePermissionObject(sub1UID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
_, err = clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("editor can create ResourcePermission of sub2 with parent folder permission", func(t *testing.T) {
|
|
toCreate := createResourcePermissionObject(sub2UID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
|
|
_, err = clients.rpEditor.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
fetched, err := clients.rpEditor.Resource.Get(ctx, "folder.grafana.app-folders-"+sub2UID, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, fetched)
|
|
|
|
permissions := fetched.Object["spec"].(map[string]interface{})["permissions"]
|
|
require.Len(t, permissions, 1)
|
|
})
|
|
})
|
|
|
|
t.Run("should respect folder hierarchy for dashboard permissions", func(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
// Create folder and a nested dashboard
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "sub1-dashboard-hierarchy", parentUID)
|
|
folderUID := folder.GetName()
|
|
|
|
dashboard := createTestDashboard(t, helper, helper.Org1.Admin, "sub2-dashboard", folderUID)
|
|
dashboardUID := dashboard.GetName()
|
|
|
|
// Verify dashboard has parent folder annotation
|
|
annotations := dashboard.GetAnnotations()
|
|
require.Equal(t, folderUID, annotations[utils.AnnoKeyFolder])
|
|
|
|
t.Run("editor cannot create ResourcePermission of dashboard without parent folder permission", func(t *testing.T) {
|
|
toCreate := createResourcePermissionObject(dashboardUID, gvrDashboards.Group, gvrDashboards.Resource, permission)
|
|
_, err := clients.rpEditor.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(403), statusErr.ErrStatus.Code)
|
|
})
|
|
|
|
// Admin creates ResourcePermission for parent folder
|
|
toCreate := createResourcePermissionObject(folderUID, gvrFolders.Group, gvrFolders.Resource, permission)
|
|
created, err := clients.rpAdmin.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
|
|
t.Run("editor can create ResourcePermission of dashboard with parent folder permission", func(t *testing.T) {
|
|
toCreate := createResourcePermissionObject(dashboardUID, gvrDashboards.Group, gvrDashboards.Resource, permission)
|
|
_, err := clients.rpEditor.Resource.Create(ctx, toCreate, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
fetched, err := clients.rpEditor.Resource.Get(ctx, "dashboard.grafana.app-dashboards-"+dashboardUID, metav1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, fetched)
|
|
|
|
permissions := fetched.Object["spec"].(map[string]interface{})["permissions"]
|
|
require.Len(t, permissions, 1)
|
|
})
|
|
})
|
|
}
|
|
|
|
func doResourcePermissionListFilteringTests(t *testing.T, helper *apis.K8sTestHelper, clients *k8sTestClients, parentUID string) {
|
|
viewerCanAdmin := newPermission("BasicRole", "Viewer", "admin")
|
|
viewerCanView := newPermission("BasicRole", "Viewer", "view")
|
|
editorCanAdmin := newPermission("BasicRole", "Editor", "admin")
|
|
|
|
ctx := context.Background()
|
|
|
|
// Create two folders
|
|
editorFolder := createTestFolder(t, helper, helper.Org1.Admin, "editor-only-folder", parentUID)
|
|
editorFolderUID := editorFolder.GetName()
|
|
|
|
viewerFolder := createTestFolder(t, helper, helper.Org1.Admin, "viewer-only-folder", parentUID)
|
|
viewerFolderUID := viewerFolder.GetName()
|
|
|
|
dashboardViewerCanAdmin := createTestDashboard(t, helper, helper.Org1.Admin, "dashboard-in-editor-folder-viewer-can-admin", editorFolderUID)
|
|
dashboardViewerCanAdminUID := dashboardViewerCanAdmin.GetName()
|
|
dashboardViewerCanView := createTestDashboard(t, helper, helper.Org1.Admin, "dashboard-in-editor-folder-viewer-can-view", editorFolderUID)
|
|
dashboardViewerCanViewUID := dashboardViewerCanView.GetName()
|
|
|
|
// Grant admin permissions to the viewer on folder2 and dashboard1
|
|
rp1 := createResourcePermissionObject(viewerFolderUID, gvrFolders.Group, gvrFolders.Resource, viewerCanAdmin)
|
|
_, err := clients.rpAdmin.Resource.Create(ctx, rp1, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
rp2 := createResourcePermissionObject(dashboardViewerCanAdminUID, gvrDashboards.Group, gvrDashboards.Resource, viewerCanAdmin)
|
|
_, err = clients.rpAdmin.Resource.Create(ctx, rp2, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
// Grant admin permissions to the editor on folder1
|
|
rp3 := createResourcePermissionObject(editorFolderUID, gvrFolders.Group, gvrFolders.Resource, editorCanAdmin)
|
|
_, err = clients.rpAdmin.Resource.Create(ctx, rp3, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
// Grant view permissions to the viewer on dashboard2
|
|
rp4 := createResourcePermissionObject(dashboardViewerCanViewUID, gvrDashboards.Group, gvrDashboards.Resource, viewerCanView)
|
|
_, err = clients.rpAdmin.Resource.Create(ctx, rp4, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Admin can list all ResourcePermissions", func(t *testing.T) {
|
|
// Admin can list all ResourcePermissions
|
|
list, err := clients.rpAdmin.Resource.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, list)
|
|
|
|
// Check that all expected items are present (there may be more from other tests)
|
|
itemNames := getNamesFromList(list)
|
|
require.Contains(t, itemNames, rp1.GetName(), "Admin should see viewer folder permission")
|
|
require.Contains(t, itemNames, rp2.GetName(), "Admin should see dashboard viewer can admin permission")
|
|
require.Contains(t, itemNames, rp3.GetName(), "Admin should see editor folder permission")
|
|
require.Contains(t, itemNames, rp4.GetName(), "Admin should see dashboard viewer can view permission")
|
|
})
|
|
|
|
t.Run("Viewer can list ResourcePermissions of folder2 and dashboard1", func(t *testing.T) {
|
|
list, err := clients.rpViewer.Resource.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, list)
|
|
|
|
itemNames := getNamesFromList(list)
|
|
// Viewer should see permissions for resources they can admin
|
|
require.Contains(t, itemNames, rp1.GetName(), "Viewer should see viewer folder permission")
|
|
require.Contains(t, itemNames, rp2.GetName(), "Viewer should see dashboard viewer can admin permission")
|
|
|
|
// Viewer should NOT see permissions for resources they cannot admin
|
|
require.NotContains(t, itemNames, rp3.GetName(), "Viewer should NOT see editor folder permission")
|
|
require.NotContains(t, itemNames, rp4.GetName(), "Viewer should NOT see dashboard viewer can view permission")
|
|
})
|
|
t.Run("Editor can list ResourcePermissions of folder1 and its nested dashboards", func(t *testing.T) {
|
|
list, err := clients.rpEditor.Resource.List(ctx, metav1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, list)
|
|
|
|
itemNames := getNamesFromList(list)
|
|
// Editor has admin on editorFolder, so they should see:
|
|
// - rp3 (editorFolder permission)
|
|
// - rp2 (dashboard in editorFolder with admin permission)
|
|
// - rp4 (dashboard in editorFolder with view permission)
|
|
require.Contains(t, itemNames, rp2.GetName(), "Editor should see dashboard admin permission in their folder")
|
|
require.Contains(t, itemNames, rp3.GetName(), "Editor should see their folder permission")
|
|
require.Contains(t, itemNames, rp4.GetName(), "Editor should see dashboard view permission in their folder")
|
|
|
|
// Editor should NOT see permissions for viewerFolder
|
|
require.NotContains(t, itemNames, rp1.GetName(), "Editor should NOT see viewer-only folder permission")
|
|
})
|
|
}
|
|
|
|
// Helper functions
|
|
|
|
func createTestFolder(t *testing.T, helper *apis.K8sTestHelper, user apis.User, title string, parentUID string) *unstructured.Unstructured {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
folderClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: user,
|
|
Namespace: helper.Namespacer(user.Identity.GetOrgID()),
|
|
GVR: gvrFolders,
|
|
})
|
|
metadata := map[string]interface{}{
|
|
"generateName": "test-folder-",
|
|
"namespace": helper.Namespacer(user.Identity.GetOrgID()),
|
|
}
|
|
|
|
if parentUID != "" {
|
|
metadata["annotations"] = map[string]interface{}{
|
|
utils.AnnoKeyFolder: parentUID,
|
|
}
|
|
}
|
|
|
|
folder := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "folder.grafana.app/v1beta1",
|
|
"kind": "Folder",
|
|
"metadata": metadata,
|
|
"spec": map[string]interface{}{
|
|
"title": title,
|
|
},
|
|
},
|
|
}
|
|
|
|
created, err := folderClient.Resource.Create(ctx, folder, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
return created
|
|
}
|
|
|
|
// Helper function to delete default permissions
|
|
func deleteDefaultPermissions(t *testing.T, client *apis.K8sResourceClient, resourceName string) {
|
|
ctx := context.Background()
|
|
// Delete the resource permission
|
|
err := client.Resource.Delete(ctx, resourceName, metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
// Check if the resource permission is deleted
|
|
_, err = client.Resource.Get(ctx, resourceName, metav1.GetOptions{})
|
|
require.Error(t, err)
|
|
var statusErr *errors.StatusError
|
|
require.ErrorAs(t, err, &statusErr)
|
|
require.Equal(t, int32(404), statusErr.ErrStatus.Code)
|
|
}
|
|
|
|
// Helper function to create a root folder without default permissions
|
|
func createRootFolderWithoutDefaultPermissions(t *testing.T, helper *apis.K8sTestHelper) *unstructured.Unstructured {
|
|
t.Helper()
|
|
|
|
// Create folder as admin
|
|
folder := createTestFolder(t, helper, helper.Org1.Admin, "root-without-permissions", "")
|
|
folderUID := folder.GetName()
|
|
|
|
// Delete default permissions
|
|
rpClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: helper.Org1.Admin,
|
|
Namespace: helper.Namespacer(helper.Org1.OrgID),
|
|
GVR: gvrResourcePermissions,
|
|
})
|
|
|
|
// It would be better to create the folder without default permissions, but this is a workaround for the time being
|
|
deleteDefaultPermissions(t, rpClient, "folder.grafana.app-folders-"+folderUID)
|
|
|
|
return folder
|
|
}
|
|
|
|
func createTestDashboard(t *testing.T, helper *apis.K8sTestHelper, user apis.User, title, folderUID string) *unstructured.Unstructured {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
dashboardClient := helper.GetResourceClient(apis.ResourceClientArgs{
|
|
User: user,
|
|
Namespace: helper.Namespacer(user.Identity.GetOrgID()),
|
|
GVR: gvrDashboards,
|
|
})
|
|
|
|
dashboard := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "dashboard.grafana.app/v1beta1",
|
|
"kind": "Dashboard",
|
|
"metadata": map[string]interface{}{
|
|
"generateName": "test-dashboard-",
|
|
"namespace": helper.Namespacer(user.Identity.GetOrgID()),
|
|
"annotations": map[string]interface{}{
|
|
utils.AnnoKeyFolder: folderUID,
|
|
},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"title": title,
|
|
},
|
|
},
|
|
}
|
|
|
|
created, err := dashboardClient.Resource.Create(ctx, dashboard, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
return created
|
|
}
|
|
|
|
func createResourcePermissionObject(resourceName, apiGroup, resource string, permissions ...permission) *unstructured.Unstructured {
|
|
permissionMaps := newPermissionMaps(permissions...)
|
|
return &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": iamv0.GROUP + "/" + iamv0.VERSION,
|
|
"kind": "ResourcePermission",
|
|
"metadata": map[string]interface{}{
|
|
"name": apiGroup + "-" + resource + "-" + resourceName,
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"resource": map[string]interface{}{
|
|
"apiGroup": apiGroup,
|
|
"resource": resource,
|
|
"name": resourceName,
|
|
},
|
|
"permissions": permissionMaps,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func getNamesFromList(list *unstructured.UnstructuredList) []string {
|
|
names := make([]string, len(list.Items))
|
|
for i, item := range list.Items {
|
|
names[i] = item.GetName()
|
|
}
|
|
return names
|
|
}
|