570 lines
14 KiB
Go
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
|
|
})
|
|
}
|