From 176b0f8b48af78167e803e52a0669c30a4ec568c Mon Sep 17 00:00:00 2001 From: Jo Date: Fri, 7 Nov 2025 14:36:53 +0100 Subject: [PATCH] IAM: Refactor user org hooks to use MutateRequest API (#113392) * update with mutation hooks * add missing delete mutation --- .../iam/resource_permission_hooks_test.go | 13 +- pkg/registry/apis/iam/user_org_hooks.go | 201 +++++---------- pkg/registry/apis/iam/user_org_hooks_test.go | 232 ++++++++---------- 3 files changed, 175 insertions(+), 271 deletions(-) diff --git a/pkg/registry/apis/iam/resource_permission_hooks_test.go b/pkg/registry/apis/iam/resource_permission_hooks_test.go index ff696020fc1..ea79b11545b 100644 --- a/pkg/registry/apis/iam/resource_permission_hooks_test.go +++ b/pkg/registry/apis/iam/resource_permission_hooks_test.go @@ -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 { diff --git a/pkg/registry/apis/iam/user_org_hooks.go b/pkg/registry/apis/iam/user_org_hooks.go index 380d98ceac2..8f75ed5c970 100644 --- a/pkg/registry/apis/iam/user_org_hooks.go +++ b/pkg/registry/apis/iam/user_org_hooks.go @@ -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) } diff --git a/pkg/registry/apis/iam/user_org_hooks_test.go b/pkg/registry/apis/iam/user_org_hooks_test.go index 516b66e380f..021b067c33c 100644 --- a/pkg/registry/apis/iam/user_org_hooks_test.go +++ b/pkg/registry/apis/iam/user_org_hooks_test.go @@ -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) - }) -}