Files
grafana/pkg/registry/apis/iam/user_org_hooks_test.go
Jo 176b0f8b48 IAM: Refactor user org hooks to use MutateRequest API (#113392)
* update with mutation hooks

* add missing delete mutation
2025-11-07 14:36:53 +01:00

570 lines
14 KiB
Go

package iam
import (
"context"
"sync"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
"github.com/grafana/grafana/pkg/infra/log"
v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/stretchr/testify/require"
)
func TestAfterUserCreate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
t.Run("should create zanzana entry for user with Admin role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "df2p421det1q8c",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
testAdminRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-1", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
updateOp := op.GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "df2p421det1q8c", updateOp.User)
require.Equal(t, "Admin", updateOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testAdminRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
t.Run("should create zanzana entry for user with Editor role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "user123",
Namespace: "org-2",
},
Spec: iamv0.UserSpec{
Role: "Editor",
},
}
testEditorRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-2", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
updateOp := op.GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "user123", updateOp.User)
require.Equal(t, "Editor", updateOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testEditorRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
t.Run("should create zanzana entry for user with Viewer role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "viewer456",
Namespace: "org-3",
},
Spec: iamv0.UserSpec{
Role: "Viewer",
},
}
testViewerRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-3", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
updateOp := op.GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "viewer456", updateOp.User)
require.Equal(t, "Viewer", updateOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testViewerRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
t.Run("should skip when user has no role", func(t *testing.T) {
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "norole789",
Namespace: "org-4",
},
Spec: iamv0.UserSpec{
Role: "",
},
}
// Should not call zanzana client
b.zClient = nil
b.AfterUserCreate(&user, nil)
// If we get here without panic, the test passes
})
t.Run("should skip when zClient is nil", func(t *testing.T) {
builder := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
zClient: nil,
}
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
// Should return early without calling zanzana
builder.AfterUserCreate(&user, nil)
// If we get here without panic, the test passes
})
}
func TestBeginUserUpdate(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
t.Run("should update zanzana entry when role changes from Viewer to Admin", func(t *testing.T) {
wg.Add(1)
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Viewer",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
testRoleChange := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-1", req.Namespace)
require.Len(t, req.Operations, 2)
// First operation should be UpdateUserOrgRole with new role
updateOp := req.Operations[0].GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "testuser", updateOp.User)
require.Equal(t, "Admin", updateOp.Role)
// Second operation should be DeleteUserOrgRole with old role
deleteOp := req.Operations[1].GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "testuser", deleteOp.User)
require.Equal(t, "Viewer", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testRoleChange}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
finishFunc(context.Background(), true)
wg.Wait()
})
t.Run("should update role when new role is empty", func(t *testing.T) {
wg.Add(1)
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser2",
Namespace: "org-2",
},
Spec: iamv0.UserSpec{
Role: "Editor",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser2",
Namespace: "org-2",
},
Spec: iamv0.UserSpec{
Role: "",
},
}
testRemoveRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-2", req.Namespace)
require.Len(t, req.Operations, 2)
// First operation should be UpdateUserOrgRole with empty role
updateOp := req.Operations[0].GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "testuser2", updateOp.User)
require.Equal(t, "", updateOp.Role)
// Second operation should be DeleteUserOrgRole with old role
deleteOp := req.Operations[1].GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "testuser2", deleteOp.User)
require.Equal(t, "Editor", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testRemoveRole}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
finishFunc(context.Background(), true)
wg.Wait()
})
t.Run("should be able to add a new role when old role was empty", func(t *testing.T) {
wg.Add(1)
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser3",
Namespace: "org-3",
},
Spec: iamv0.UserSpec{
Role: "",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser3",
Namespace: "org-3",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
testAddRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-3", req.Namespace)
require.Len(t, req.Operations, 2)
// First operation should be UpdateUserOrgRole with new role
updateOp := req.Operations[0].GetUpdateUserOrgRole()
require.NotNil(t, updateOp)
require.Equal(t, "testuser3", updateOp.User)
require.Equal(t, "Admin", updateOp.Role)
// Second operation should be DeleteUserOrgRole with empty old role
deleteOp := req.Operations[1].GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "testuser3", deleteOp.User)
require.Equal(t, "", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testAddRole}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
finishFunc(context.Background(), true)
wg.Wait()
})
t.Run("should skip update when role hasn't changed", func(t *testing.T) {
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser4",
Namespace: "org-4",
},
Spec: iamv0.UserSpec{
Role: "Editor",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser4",
Namespace: "org-4",
},
Spec: iamv0.UserSpec{
Role: "Editor",
},
}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.Nil(t, finishFunc) // Should return nil when no update needed
})
t.Run("should not call zanzana when update fails", func(t *testing.T) {
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser5",
Namespace: "org-5",
},
Spec: iamv0.UserSpec{
Role: "Viewer",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser5",
Namespace: "org-5",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
callCount := 0
testNoCall := func(ctx context.Context, req *v1.MutateRequest) error {
callCount++
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testNoCall}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.NotNil(t, finishFunc)
// Call with success=false - should not trigger zanzana write
finishFunc(context.Background(), false)
require.Equal(t, 0, callCount, "zanzana should not be called when update fails")
})
t.Run("should skip when zClient is nil", func(t *testing.T) {
builder := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
zClient: nil,
}
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Viewer",
},
}
newUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
finishFunc, err := builder.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
require.Nil(t, finishFunc) // Should return nil when zClient is nil
})
}
func TestAfterUserDelete(t *testing.T) {
var wg sync.WaitGroup
b := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
}
t.Run("should delete zanzana entry for user with Admin role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "df2p421det1q8c",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
testDeleteAdmin := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-1", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
deleteOp := op.GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "df2p421det1q8c", deleteOp.User)
require.Equal(t, "Admin", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteAdmin}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
t.Run("should delete zanzana entry for user with Editor role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "editor123",
Namespace: "org-2",
},
Spec: iamv0.UserSpec{
Role: "Editor",
},
}
testDeleteEditor := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-2", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
deleteOp := op.GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "editor123", deleteOp.User)
require.Equal(t, "Editor", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteEditor}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
t.Run("should delete zanzana entry for user with Viewer role", func(t *testing.T) {
wg.Add(1)
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "viewer456",
Namespace: "org-3",
},
Spec: iamv0.UserSpec{
Role: "Viewer",
},
}
testDeleteViewer := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.Equal(t, "org-3", req.Namespace)
require.Len(t, req.Operations, 1)
op := req.Operations[0]
require.NotNil(t, op)
deleteOp := op.GetDeleteUserOrgRole()
require.NotNil(t, deleteOp)
require.Equal(t, "viewer456", deleteOp.User)
require.Equal(t, "Viewer", deleteOp.Role)
return nil
}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteViewer}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
t.Run("should skip when user has no role", func(t *testing.T) {
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "norole789",
Namespace: "org-4",
},
Spec: iamv0.UserSpec{
Role: "",
},
}
// Should not call zanzana client
b.zClient = nil
b.AfterUserDelete(&user, nil)
// If we get here without panic, the test passes
})
t.Run("should skip when zClient is nil", func(t *testing.T) {
builder := &IdentityAccessManagementAPIBuilder{
logger: log.NewNopLogger(),
zTickets: make(chan bool, 1),
zClient: nil,
}
user := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
Name: "testuser",
Namespace: "org-1",
},
Spec: iamv0.UserSpec{
Role: "Admin",
},
}
// Should return early without calling zanzana
builder.AfterUserDelete(&user, nil)
// If we get here without panic, the test passes
})
}