IAM: Refactor user org hooks to use MutateRequest API (#113392)
* update with mutation hooks * add missing delete mutation
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user