grafana-iam: Use parent folder to authorize ResourcePermissions (#115008)
* `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
This commit is contained in:
@@ -0,0 +1,625 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user