IAM: Refactor user org hooks to use MutateRequest API (#113392)

* update with mutation hooks

* add missing delete mutation
This commit is contained in:
Jo
2025-11-07 14:36:53 +01:00
committed by GitHub
parent 33390a1483
commit 176b0f8b48
3 changed files with 175 additions and 271 deletions
@@ -16,8 +16,9 @@ import (
type FakeZanzanaClient struct {
zanzana.Client
writeCallback func(context.Context, *v1.WriteRequest) error
readCallback func(context.Context, *v1.ReadRequest) (*v1.ReadResponse, error)
writeCallback func(context.Context, *v1.WriteRequest) error
readCallback func(context.Context, *v1.ReadRequest) (*v1.ReadResponse, error)
mutateCallback func(context.Context, *v1.MutateRequest) error
}
// Read implements zanzana.Client.
@@ -33,6 +34,14 @@ func (f *FakeZanzanaClient) Write(ctx context.Context, req *v1.WriteRequest) err
return f.writeCallback(ctx, req)
}
// Mutate implements zanzana.Client.
func (f *FakeZanzanaClient) Mutate(ctx context.Context, req *v1.MutateRequest) error {
if f.mutateCallback != nil {
return f.mutateCallback(ctx, req)
}
return nil
}
func requireTuplesMatch(t *testing.T, actual []*v1.TupleKey, expected []*v1.TupleKey, msgAndArgs ...interface{}) {
t.Helper()
for _, exp := range expected {
+68 -133
View File
@@ -10,27 +10,8 @@ import (
iamv0 "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1"
v1 "github.com/grafana/grafana/pkg/services/authz/proto/v1"
"github.com/grafana/grafana/pkg/services/authz/zanzana"
)
// createUserBasicRoleTuple creates a tuple for a user's basic role assignment
func createUserBasicRoleTuple(userUID, orgRole string) *v1.TupleKey {
if orgRole == "" {
return nil
}
basicRole := zanzana.TranslateBasicRole(orgRole)
if basicRole == "" {
return nil
}
return &v1.TupleKey{
User: zanzana.NewTupleEntry(zanzana.TypeUser, userUID, ""),
Relation: zanzana.RelationAssignee,
Object: zanzana.NewTupleEntry(zanzana.TypeRole, basicRole, ""),
}
}
// AfterUserCreate is a post-create hook that writes the user's basic role assignment to Zanzana (openFGA)
func (b *IdentityAccessManagementAPIBuilder) AfterUserCreate(obj runtime.Object, _ *metav1.CreateOptions) {
if b.zClient == nil {
@@ -43,24 +24,24 @@ func (b *IdentityAccessManagementAPIBuilder) AfterUserCreate(obj runtime.Object,
return
}
resourceType := "user"
operation := "create"
// Skip if user has no role assigned
if user.Spec.Role == "" {
b.logger.Debug("user has no role assigned, skipping basic role sync",
"namespace", user.Namespace,
"userUID", user.Name,
"name", user.Name,
)
return
}
resourceType := "user"
operation := "create"
// Grab a ticket to write to Zanzana
wait := time.Now()
b.zTickets <- true
hooksWaitHistogram.WithLabelValues(resourceType, operation).Observe(time.Since(wait).Seconds())
go func(u *iamv0.User) {
go func(namespace, subjectName, role, resourceType, operation string) {
start := time.Now()
status := "success"
@@ -70,44 +51,38 @@ func (b *IdentityAccessManagementAPIBuilder) AfterUserCreate(obj runtime.Object,
hooksOperationCounter.WithLabelValues(resourceType, operation, status).Inc()
}()
tuple := createUserBasicRoleTuple(u.Name, u.Spec.Role)
if tuple == nil {
b.logger.Warn("failed to create user basic role tuple",
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
)
status = "failure"
return
}
b.logger.Debug("writing user basic role to zanzana",
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
"namespace", namespace,
"name", subjectName,
"role", role,
)
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
defer cancel()
err := b.zClient.Write(ctx, &v1.WriteRequest{
Namespace: u.Namespace,
Writes: &v1.WriteRequestWrites{
TupleKeys: []*v1.TupleKey{tuple},
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
Namespace: namespace,
Operations: []*v1.MutateOperation{
{
Operation: &v1.MutateOperation_UpdateUserOrgRole{
UpdateUserOrgRole: &v1.UpdateUserOrgRoleOperation{User: subjectName, Role: role},
},
},
},
})
if err != nil {
status = "failure"
b.logger.Error("failed to write user basic role to zanzana",
"err", err,
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
"namespace", namespace,
"name", subjectName,
"role", role,
)
} else {
hooksTuplesCounter.WithLabelValues(resourceType, operation, "write").Inc()
}
}(user.DeepCopy())
}(user.Namespace, user.Name, user.Spec.Role, resourceType, operation)
}
// BeginUserUpdate is a pre-update hook that gets called on user updates
@@ -142,7 +117,7 @@ func (b *IdentityAccessManagementAPIBuilder) BeginUserUpdate(ctx context.Context
b.zTickets <- true
hooksWaitHistogram.WithLabelValues("user", "update").Observe(time.Since(wait).Seconds())
go func(old, new *iamv0.User) {
go func(namespace, subjectName, oldRole, newRole string) {
start := time.Now()
status := "success"
@@ -153,72 +128,40 @@ func (b *IdentityAccessManagementAPIBuilder) BeginUserUpdate(ctx context.Context
}()
b.logger.Debug("updating user basic role in zanzana",
"namespace", new.Namespace,
"userUID", new.Name,
"oldRole", old.Spec.Role,
"newRole", new.Spec.Role,
"namespace", namespace,
"name", subjectName,
"oldRole", oldRole,
"newRole", newRole,
)
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
defer cancel()
req := &v1.WriteRequest{
Namespace: new.Namespace,
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
Namespace: namespace,
Operations: []*v1.MutateOperation{
{
Operation: &v1.MutateOperation_UpdateUserOrgRole{
UpdateUserOrgRole: &v1.UpdateUserOrgRoleOperation{User: subjectName, Role: newRole},
},
}, {
Operation: &v1.MutateOperation_DeleteUserOrgRole{
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{User: subjectName, Role: oldRole},
},
},
},
})
if err != nil {
status = "failure"
b.logger.Error("failed to update user basic role in zanzana",
"err", err,
"namespace", namespace,
"name", subjectName,
"role", newRole,
"oldRole", oldRole,
)
}
// Delete old role tuple if it existed
if old.Spec.Role != "" {
oldTuple := createUserBasicRoleTuple(old.Name, old.Spec.Role)
if oldTuple != nil {
deleteTuple := tupleToTupleKeyWithoutCondition(oldTuple)
req.Deletes = &v1.WriteRequestDeletes{
TupleKeys: []*v1.TupleKeyWithoutCondition{deleteTuple},
}
b.logger.Debug("deleting old user basic role from zanzana",
"namespace", new.Namespace,
"userUID", new.Name,
"role", old.Spec.Role,
)
}
}
// Write new role tuple if it exists
if new.Spec.Role != "" {
newTuple := createUserBasicRoleTuple(new.Name, new.Spec.Role)
if newTuple != nil {
req.Writes = &v1.WriteRequestWrites{
TupleKeys: []*v1.TupleKey{newTuple},
}
b.logger.Debug("writing new user basic role to zanzana",
"namespace", new.Namespace,
"userUID", new.Name,
"role", new.Spec.Role,
)
}
}
// Only make the request if there are deletes or writes
if (req.Deletes != nil && len(req.Deletes.TupleKeys) > 0) || (req.Writes != nil && len(req.Writes.TupleKeys) > 0) {
err := b.zClient.Write(ctx, req)
if err != nil {
status = "failure"
b.logger.Error("failed to update user basic role in zanzana",
"err", err,
"namespace", new.Namespace,
"userUID", new.Name,
)
} else {
if req.Deletes != nil && len(req.Deletes.TupleKeys) > 0 {
hooksTuplesCounter.WithLabelValues("user", "update", "delete").Inc()
}
if req.Writes != nil && len(req.Writes.TupleKeys) > 0 {
hooksTuplesCounter.WithLabelValues("user", "update", "write").Inc()
}
}
} else {
b.logger.Debug("no tuples to update in zanzana", "namespace", new.Namespace)
}
}(oldUser.DeepCopy(), newUser.DeepCopy())
}(oldUser.Namespace, oldUser.Name, oldUser.Spec.Role, newUser.Spec.Role)
}, nil
}
@@ -241,7 +184,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterUserDelete(obj runtime.Object,
if user.Spec.Role == "" {
b.logger.Debug("user had no role assigned, skipping basic role sync",
"namespace", user.Namespace,
"userUID", user.Name,
"name", user.Name,
)
return
}
@@ -250,7 +193,7 @@ func (b *IdentityAccessManagementAPIBuilder) AfterUserDelete(obj runtime.Object,
b.zTickets <- true
hooksWaitHistogram.WithLabelValues(resourceType, operation).Observe(time.Since(wait).Seconds())
go func(u *iamv0.User) {
go func(namespace, subjectName, role string) {
start := time.Now()
status := "success"
@@ -260,44 +203,36 @@ func (b *IdentityAccessManagementAPIBuilder) AfterUserDelete(obj runtime.Object,
hooksOperationCounter.WithLabelValues(resourceType, operation, status).Inc()
}()
tuple := createUserBasicRoleTuple(u.Name, u.Spec.Role)
if tuple == nil {
b.logger.Warn("failed to create user basic role tuple for deletion",
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
)
status = "failure"
return
}
deleteTuple := tupleToTupleKeyWithoutCondition(tuple)
b.logger.Debug("deleting user basic role from zanzana",
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
"namespace", namespace,
"name", subjectName,
"role", role,
)
ctx, cancel := context.WithTimeout(context.Background(), defaultWriteTimeout)
defer cancel()
err := b.zClient.Write(ctx, &v1.WriteRequest{
Namespace: u.Namespace,
Deletes: &v1.WriteRequestDeletes{
TupleKeys: []*v1.TupleKeyWithoutCondition{deleteTuple},
err := b.zClient.Mutate(ctx, &v1.MutateRequest{
Namespace: namespace,
Operations: []*v1.MutateOperation{
{
Operation: &v1.MutateOperation_DeleteUserOrgRole{
DeleteUserOrgRole: &v1.DeleteUserOrgRoleOperation{User: subjectName, Role: role},
},
},
},
})
if err != nil {
status = "failure"
b.logger.Error("failed to delete user basic role from zanzana",
"err", err,
"namespace", u.Namespace,
"userUID", u.Name,
"role", u.Spec.Role,
"namespace", namespace,
"name", subjectName,
"role", role,
)
} else {
hooksTuplesCounter.WithLabelValues(resourceType, operation, "delete").Inc()
}
}(user.DeepCopy())
}(user.Namespace, user.Name, user.Spec.Role)
}
+96 -136
View File
@@ -33,21 +33,22 @@ func TestAfterUserCreate(t *testing.T) {
},
}
testAdminRole := func(ctx context.Context, req *v1.WriteRequest) error {
testAdminRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.NotNil(t, req.Writes)
require.Len(t, req.Writes.TupleKeys, 1)
require.Equal(t, "org-1", req.Namespace)
require.Len(t, req.Operations, 1)
tuple := req.Writes.TupleKeys[0]
require.Equal(t, "user:df2p421det1q8c", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_admin", tuple.Object)
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{writeCallback: testAdminRole}
b.zClient = &FakeZanzanaClient{mutateCallback: testAdminRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
@@ -64,21 +65,22 @@ func TestAfterUserCreate(t *testing.T) {
},
}
testEditorRole := func(ctx context.Context, req *v1.WriteRequest) error {
testEditorRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.NotNil(t, req.Writes)
require.Len(t, req.Writes.TupleKeys, 1)
require.Equal(t, "org-2", req.Namespace)
require.Len(t, req.Operations, 1)
tuple := req.Writes.TupleKeys[0]
require.Equal(t, "user:user123", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_editor", tuple.Object)
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{writeCallback: testEditorRole}
b.zClient = &FakeZanzanaClient{mutateCallback: testEditorRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
@@ -95,21 +97,22 @@ func TestAfterUserCreate(t *testing.T) {
},
}
testViewerRole := func(ctx context.Context, req *v1.WriteRequest) error {
testViewerRole := func(ctx context.Context, req *v1.MutateRequest) error {
defer wg.Done()
require.NotNil(t, req)
require.NotNil(t, req.Writes)
require.Len(t, req.Writes.TupleKeys, 1)
require.Equal(t, "org-3", req.Namespace)
require.Len(t, req.Operations, 1)
tuple := req.Writes.TupleKeys[0]
require.Equal(t, "user:viewer456", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_viewer", tuple.Object)
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{writeCallback: testViewerRole}
b.zClient = &FakeZanzanaClient{mutateCallback: testViewerRole}
b.AfterUserCreate(&user, nil)
wg.Wait()
})
@@ -184,31 +187,28 @@ func TestBeginUserUpdate(t *testing.T) {
},
}
testRoleChange := func(ctx context.Context, req *v1.WriteRequest) error {
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)
// Should delete old role
require.NotNil(t, req.Deletes)
require.Len(t, req.Deletes.TupleKeys, 1)
deleteTuple := req.Deletes.TupleKeys[0]
require.Equal(t, "user:testuser", deleteTuple.User)
require.Equal(t, "assignee", deleteTuple.Relation)
require.Equal(t, "role:basic_viewer", deleteTuple.Object)
// 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)
// Should write new role
require.NotNil(t, req.Writes)
require.Len(t, req.Writes.TupleKeys, 1)
writeTuple := req.Writes.TupleKeys[0]
require.Equal(t, "user:testuser", writeTuple.User)
require.Equal(t, "assignee", writeTuple.Relation)
require.Equal(t, "role:basic_admin", writeTuple.Object)
// 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{writeCallback: testRoleChange}
b.zClient = &FakeZanzanaClient{mutateCallback: testRoleChange}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
@@ -218,7 +218,7 @@ func TestBeginUserUpdate(t *testing.T) {
wg.Wait()
})
t.Run("should delete old role when new role is empty", func(t *testing.T) {
t.Run("should update role when new role is empty", func(t *testing.T) {
wg.Add(1)
oldUser := iamv0.User{
ObjectMeta: metav1.ObjectMeta{
@@ -240,26 +240,28 @@ func TestBeginUserUpdate(t *testing.T) {
},
}
testRemoveRole := func(ctx context.Context, req *v1.WriteRequest) error {
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)
// Should delete old role
require.NotNil(t, req.Deletes)
require.Len(t, req.Deletes.TupleKeys, 1)
deleteTuple := req.Deletes.TupleKeys[0]
require.Equal(t, "user:testuser2", deleteTuple.User)
require.Equal(t, "assignee", deleteTuple.Relation)
require.Equal(t, "role:basic_editor", deleteTuple.Object)
// 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)
// Should not write new role
require.Nil(t, req.Writes)
// 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{writeCallback: testRemoveRole}
b.zClient = &FakeZanzanaClient{mutateCallback: testRemoveRole}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
@@ -291,26 +293,28 @@ func TestBeginUserUpdate(t *testing.T) {
},
}
testAddRole := func(ctx context.Context, req *v1.WriteRequest) error {
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)
// Should not delete old role (was empty)
require.Nil(t, req.Deletes)
// 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)
// Should write new role
require.NotNil(t, req.Writes)
require.Len(t, req.Writes.TupleKeys, 1)
writeTuple := req.Writes.TupleKeys[0]
require.Equal(t, "user:testuser3", writeTuple.User)
require.Equal(t, "assignee", writeTuple.Relation)
require.Equal(t, "role:basic_admin", writeTuple.Object)
// 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{writeCallback: testAddRole}
b.zClient = &FakeZanzanaClient{mutateCallback: testAddRole}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
@@ -368,12 +372,12 @@ func TestBeginUserUpdate(t *testing.T) {
}
callCount := 0
testNoCall := func(ctx context.Context, req *v1.WriteRequest) error {
testNoCall := func(ctx context.Context, req *v1.MutateRequest) error {
callCount++
return nil
}
b.zClient = &FakeZanzanaClient{writeCallback: testNoCall}
b.zClient = &FakeZanzanaClient{mutateCallback: testNoCall}
finishFunc, err := b.BeginUserUpdate(context.Background(), &newUser, &oldUser, nil)
require.NoError(t, err)
@@ -437,25 +441,23 @@ func TestAfterUserDelete(t *testing.T) {
},
}
testDeleteAdmin := func(ctx context.Context, req *v1.WriteRequest) error {
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)
// Should have deletes but no writes
require.NotNil(t, req.Deletes)
require.Len(t, req.Deletes.TupleKeys, 1)
require.Nil(t, req.Writes)
deleteTuple := req.Deletes.TupleKeys[0]
require.Equal(t, "user:df2p421det1q8c", deleteTuple.User)
require.Equal(t, "assignee", deleteTuple.Relation)
require.Equal(t, "role:basic_admin", deleteTuple.Object)
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{writeCallback: testDeleteAdmin}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteAdmin}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
@@ -472,22 +474,23 @@ func TestAfterUserDelete(t *testing.T) {
},
}
testDeleteEditor := func(ctx context.Context, req *v1.WriteRequest) error {
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)
require.NotNil(t, req.Deletes)
require.Len(t, req.Deletes.TupleKeys, 1)
deleteTuple := req.Deletes.TupleKeys[0]
require.Equal(t, "user:editor123", deleteTuple.User)
require.Equal(t, "assignee", deleteTuple.Relation)
require.Equal(t, "role:basic_editor", deleteTuple.Object)
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{writeCallback: testDeleteEditor}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteEditor}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
@@ -504,22 +507,23 @@ func TestAfterUserDelete(t *testing.T) {
},
}
testDeleteViewer := func(ctx context.Context, req *v1.WriteRequest) error {
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)
require.NotNil(t, req.Deletes)
require.Len(t, req.Deletes.TupleKeys, 1)
deleteTuple := req.Deletes.TupleKeys[0]
require.Equal(t, "user:viewer456", deleteTuple.User)
require.Equal(t, "assignee", deleteTuple.Relation)
require.Equal(t, "role:basic_viewer", deleteTuple.Object)
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{writeCallback: testDeleteViewer}
b.zClient = &FakeZanzanaClient{mutateCallback: testDeleteViewer}
b.AfterUserDelete(&user, nil)
wg.Wait()
})
@@ -563,47 +567,3 @@ func TestAfterUserDelete(t *testing.T) {
// If we get here without panic, the test passes
})
}
func TestCreateUserBasicRoleTuple(t *testing.T) {
t.Run("should create tuple for Admin role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user123", "Admin")
require.NotNil(t, tuple)
require.Equal(t, "user:user123", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_admin", tuple.Object)
})
t.Run("should create tuple for Editor role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user456", "Editor")
require.NotNil(t, tuple)
require.Equal(t, "user:user456", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_editor", tuple.Object)
})
t.Run("should create tuple for Viewer role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user789", "Viewer")
require.NotNil(t, tuple)
require.Equal(t, "user:user789", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_viewer", tuple.Object)
})
t.Run("should create tuple for None role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user000", "None")
require.NotNil(t, tuple)
require.Equal(t, "user:user000", tuple.User)
require.Equal(t, "assignee", tuple.Relation)
require.Equal(t, "role:basic_none", tuple.Object)
})
t.Run("should return nil for empty role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user123", "")
require.Nil(t, tuple)
})
t.Run("should return nil for invalid role", func(t *testing.T) {
tuple := createUserBasicRoleTuple("user123", "InvalidRole")
require.Nil(t, tuple)
})
}