The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/tests/apis/iam/iam_test.go

340 lines
9.8 KiB

package identity
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user"
"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/tests/testsuite"
)
var gvrTeams = schema.GroupVersionResource{
Group: "iam.grafana.app",
Version: "v0alpha1",
Resource: "teams",
}
var gvrUsers = schema.GroupVersionResource{
Group: "iam.grafana.app",
Version: "v0alpha1",
Resource: "users",
}
func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestIntegrationIdentity(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: false, // required for experimental APIs
DisableAnonymous: true,
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service
},
})
_, err := helper.NewDiscoveryClient().ServerResourcesForGroupVersion("iam.grafana.app/v0alpha1")
require.NoError(t, err)
t.Run("read only views", func(t *testing.T) {
ctx := context.Background()
teamClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrTeams,
})
rsp, err := teamClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
found := teamClient.SanitizeJSONList(rsp, "name", "labels")
require.JSONEq(t, `{
"items": [
{
"apiVersion": "iam.grafana.app/v0alpha1",
"kind": "Team",
"metadata": {
"creationTimestamp": "${creationTimestamp}",
"name": "${name}",
"namespace": "default",
"resourceVersion": "${resourceVersion}"
},
"spec": {
"email": "staff@Org1",
"title": "staff"
},
"status": {}
}
]
}`, found)
// Org1 users
userClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrUsers,
})
rsp, err = userClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
// Get just the specs (avoids values that change with each deployment)
found = teamClient.SpecJSON(rsp)
require.JSONEq(t, `[
{
"disabled": false,
"email": "admin@localhost",
"emailVerified": false,
"grafanaAdmin": true,
"login": "admin",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "admin2-1",
"emailVerified": false,
"grafanaAdmin": true,
"login": "admin2-1",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "editor-1",
"emailVerified": false,
"grafanaAdmin": false,
"login": "editor-1",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "viewer-1",
"emailVerified": false,
"grafanaAdmin": false,
"login": "viewer-1",
"name": "",
"provisioned": false
}
]`, found)
// OrgB users
userClient = helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin, // super admin
Namespace: helper.Namespacer(helper.OrgB.Admin.Identity.GetOrgID()), // list values for orgB with super admin user
GVR: gvrUsers,
})
rsp, err = userClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
// Get just the specs (avoids values that change with each deployment)
found = teamClient.SpecJSON(rsp)
require.JSONEq(t, `[
{
"disabled": false,
"email": "admin2-1",
"emailVerified": false,
"grafanaAdmin": true,
"login": "admin2-1",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "admin2-2",
"emailVerified": false,
"grafanaAdmin": false,
"login": "admin2-2",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "editor-2",
"emailVerified": false,
"grafanaAdmin": false,
"login": "editor-2",
"name": "",
"provisioned": false
},
{
"disabled": false,
"email": "viewer-2",
"emailVerified": false,
"grafanaAdmin": false,
"login": "viewer-2",
"name": "",
"provisioned": false
}
]`, found)
})
}
func TestIntegrationUsers(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
// TODO: Figure out why rest.Mode4 is failing
modes := []rest.DualWriterMode{rest.Mode0, rest.Mode1, rest.Mode2, rest.Mode3}
for _, mode := range modes {
t.Run(fmt.Sprintf("User CRUD operations with dual writer mode %d", mode), func(t *testing.T) {
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
AppModeProduction: false,
DisableAnonymous: true,
APIServerStorageType: "unified",
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"users.iam.grafana.app": {
DualWriterMode: mode,
},
},
EnableFeatureToggles: []string{
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs,
},
})
doUserCRUDTestsUsingTheNewAPIs(t, helper)
if mode < 3 {
doUserCRUDTestsUsingTheLegacyAPIs(t, helper)
}
})
}
}
func doUserCRUDTestsUsingTheNewAPIs(t *testing.T, helper *apis.K8sTestHelper) {
t.Run("should create user and delete it using the new APIs", func(t *testing.T) {
ctx := context.Background()
userClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrUsers,
})
// Create the user
created, err := userClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/user-test-create-v0.yaml"), metav1.CreateOptions{})
require.NoError(t, err)
require.NotNil(t, created)
// Verify creation response
createdSpec := created.Object["spec"].(map[string]interface{})
require.Equal(t, "testuser1@example123.com", createdSpec["email"])
require.Equal(t, "testuser1", createdSpec["login"])
require.Equal(t, "Test User 1", createdSpec["name"])
require.Equal(t, false, createdSpec["provisioned"])
// Get the UID from created user for fetching
createdUID := created.GetName()
require.NotEmpty(t, createdUID)
_, err = userClient.Resource.List(ctx, metav1.ListOptions{})
require.NoError(t, err)
fetched, err := userClient.Resource.Get(ctx, createdUID, metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, fetched)
// Verify fetched user matches created user
fetchedSpec := fetched.Object["spec"].(map[string]interface{})
require.Equal(t, "testuser1@example123.com", fetchedSpec["email"])
require.Equal(t, "testuser1", fetchedSpec["login"])
require.Equal(t, "Test User 1", fetchedSpec["name"])
require.Equal(t, false, fetchedSpec["provisioned"])
// Verify metadata
require.Equal(t, createdUID, fetched.GetName())
require.Equal(t, "default", fetched.GetNamespace())
err = userClient.Resource.Delete(ctx, createdUID, metav1.DeleteOptions{})
require.NoError(t, err)
// Verify deletion
_, err = userClient.Resource.Get(ctx, createdUID, metav1.GetOptions{})
require.Error(t, err)
require.Contains(t, err.Error(), "not found")
})
t.Run("should not be able to create user when using a user with insufficient permissions", func(t *testing.T) {
for _, user := range []apis.User{
helper.Org1.Editor,
helper.Org1.Viewer,
} {
t.Run(fmt.Sprintf("with basic role: %s", user.Identity.GetOrgRole()), func(t *testing.T) {
ctx := context.Background()
userClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: user,
GVR: gvrUsers,
})
// Create the user
_, err := userClient.Resource.Create(ctx, helper.LoadYAMLOrJSONFile("testdata/user-test-create-v0.yaml"), metav1.CreateOptions{})
require.Error(t, err)
require.Contains(t, err.Error(), "unauthorized request")
})
}
})
}
func doUserCRUDTestsUsingTheLegacyAPIs(t *testing.T, helper *apis.K8sTestHelper) {
t.Run("should create user using legacy APIs and delete it using the new APIs", func(t *testing.T) {
ctx := context.Background()
userClient := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrUsers,
})
legacyUserPayload := `{
"name": "Test User 2",
"email": "testuser2@example.com",
"login": "testuser2",
"password": "password123"
}`
rsp := apis.DoRequest(helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: "POST",
Path: "/api/admin/users",
Body: []byte(legacyUserPayload),
}, &user.User{})
require.NotNil(t, rsp)
require.Equal(t, 200, rsp.Response.StatusCode)
require.NotEmpty(t, rsp.Result.UID)
// Now try to fetch the user via the new API
user, err := userClient.Resource.Get(context.Background(), rsp.Result.UID, metav1.GetOptions{})
require.NoError(t, err)
require.NotNil(t, user)
// Verify fetched user matches created user
userSpec := user.Object["spec"].(map[string]interface{})
require.Equal(t, "testuser2@example.com", userSpec["email"])
require.Equal(t, "testuser2", userSpec["login"])
require.Equal(t, "Test User 2", userSpec["name"])
require.Equal(t, false, userSpec["provisioned"])
// Verify metadata
require.Equal(t, rsp.Result.UID, user.GetName())
require.Equal(t, "default", user.GetNamespace())
// Now delete the user using the legacy API
deleteRsp := apis.DoRequest(helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: "DELETE",
Path: fmt.Sprintf("/api/admin/users/%d", rsp.Result.ID),
}, &apis.AnyResource{})
require.Equal(t, 200, deleteRsp.Response.StatusCode)
// Verify deletion
_, err = userClient.Resource.Get(ctx, rsp.Result.UID, metav1.GetOptions{})
require.Error(t, err)
require.Contains(t, err.Error(), "not found")
})
}