diff --git a/internal/api/grpc/user/v2/integration_test/user_test.go b/internal/api/grpc/user/v2/integration_test/user_test.go index 70e670bacc..eaf352c094 100644 --- a/internal/api/grpc/user/v2/integration_test/user_test.go +++ b/internal/api/grpc/user/v2/integration_test/user_test.go @@ -1756,9 +1756,8 @@ func TestServer_DeleteUser(t *testing.T) { projectResp, err := Instance.CreateProject(CTX) require.NoError(t, err) type args struct { - ctx context.Context req *user.DeleteUserRequest - prepare func(request *user.DeleteUserRequest) error + prepare func(*testing.T, *user.DeleteUserRequest) context.Context } tests := []struct { name string @@ -1769,23 +1768,21 @@ func TestServer_DeleteUser(t *testing.T) { { name: "remove, not existing", args: args{ - CTX, &user.DeleteUserRequest{ UserId: "notexisting", }, - func(request *user.DeleteUserRequest) error { return nil }, + func(*testing.T, *user.DeleteUserRequest) context.Context { return CTX }, }, wantErr: true, }, { name: "remove human, ok", args: args{ - ctx: CTX, req: &user.DeleteUserRequest{}, - prepare: func(request *user.DeleteUserRequest) error { + prepare: func(_ *testing.T, request *user.DeleteUserRequest) context.Context { resp := Instance.CreateHumanUser(CTX) request.UserId = resp.GetUserId() - return err + return CTX }, }, want: &user.DeleteUserResponse{ @@ -1798,12 +1795,11 @@ func TestServer_DeleteUser(t *testing.T) { { name: "remove machine, ok", args: args{ - ctx: CTX, req: &user.DeleteUserRequest{}, - prepare: func(request *user.DeleteUserRequest) error { + prepare: func(_ *testing.T, request *user.DeleteUserRequest) context.Context { resp := Instance.CreateMachineUser(CTX) request.UserId = resp.GetUserId() - return err + return CTX }, }, want: &user.DeleteUserResponse{ @@ -1816,15 +1812,37 @@ func TestServer_DeleteUser(t *testing.T) { { name: "remove dependencies, ok", args: args{ - ctx: CTX, req: &user.DeleteUserRequest{}, - prepare: func(request *user.DeleteUserRequest) error { + prepare: func(_ *testing.T, request *user.DeleteUserRequest) context.Context { resp := Instance.CreateHumanUser(CTX) request.UserId = resp.GetUserId() Instance.CreateProjectUserGrant(t, CTX, projectResp.GetId(), request.UserId) Instance.CreateProjectMembership(t, CTX, projectResp.GetId(), request.UserId) Instance.CreateOrgMembership(t, CTX, request.UserId) - return err + return CTX + }, + }, + want: &user.DeleteUserResponse{ + Details: &object.Details{ + ChangeDate: timestamppb.Now(), + ResourceOwner: Instance.DefaultOrg.Id, + }, + }, + }, + { + name: "remove self, ok", + args: args{ + req: &user.DeleteUserRequest{}, + prepare: func(t *testing.T, request *user.DeleteUserRequest) context.Context { + removeUser, err := Instance.Client.Mgmt.AddMachineUser(CTX, &mgmt.AddMachineUserRequest{ + UserName: gofakeit.Username(), + Name: gofakeit.Name(), + }) + request.UserId = removeUser.UserId + require.NoError(t, err) + tokenResp, err := Instance.Client.Mgmt.AddPersonalAccessToken(CTX, &mgmt.AddPersonalAccessTokenRequest{UserId: removeUser.UserId}) + require.NoError(t, err) + return integration.WithAuthorizationToken(UserCTX, tokenResp.Token) }, }, want: &user.DeleteUserResponse{ @@ -1837,10 +1855,8 @@ func TestServer_DeleteUser(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := tt.args.prepare(tt.args.req) - require.NoError(t, err) - - got, err := Client.DeleteUser(tt.args.ctx, tt.args.req) + ctx := tt.args.prepare(t, tt.args.req) + got, err := Client.DeleteUser(ctx, tt.args.req) if tt.wantErr { require.Error(t, err) return diff --git a/internal/command/permission_checks.go b/internal/command/permission_checks.go new file mode 100644 index 0000000000..bec2e9b7d4 --- /dev/null +++ b/internal/command/permission_checks.go @@ -0,0 +1,60 @@ +package command + +import ( + "context" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/v2/user" + "github.com/zitadel/zitadel/internal/zerrors" +) + +type PermissionCheck func(resourceOwner, aggregateID string) error + +func (c *Commands) newPermissionCheck(ctx context.Context, permission string, aggregateType eventstore.AggregateType) PermissionCheck { + return func(resourceOwner, aggregateID string) error { + if aggregateID == "" { + return zerrors.ThrowInternal(nil, "COMMAND-ulBlS", "Errors.IDMissing") + } + // For example if a write model didn't query any events, the resource owner is probably empty. + // In this case, we have to query an event on the given aggregate to get the resource owner. + if resourceOwner == "" { + r := NewResourceOwnerModel(authz.GetInstance(ctx).InstanceID(), aggregateType, aggregateID) + err := c.eventstore.FilterToQueryReducer(ctx, r) + if err != nil { + return err + } + resourceOwner = r.resourceOwner + } + if resourceOwner == "" { + return zerrors.ThrowNotFound(nil, "COMMAND-4g3xq", "Errors.NotFound") + } + return c.checkPermission(ctx, permission, resourceOwner, aggregateID) + } +} + +func (c *Commands) checkPermissionOnUser(ctx context.Context, permission string) PermissionCheck { + return func(resourceOwner, aggregateID string) error { + if aggregateID != "" && aggregateID == authz.GetCtxData(ctx).UserID { + return nil + } + return c.newPermissionCheck(ctx, permission, user.AggregateType)(resourceOwner, aggregateID) + } +} + +func (c *Commands) NewPermissionCheckUserWrite(ctx context.Context) PermissionCheck { + return c.checkPermissionOnUser(ctx, domain.PermissionUserWrite) +} + +func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error { + return c.checkPermissionOnUser(ctx, domain.PermissionUserDelete)(resourceOwner, userID) +} + +func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner, userID string) error { + return c.NewPermissionCheckUserWrite(ctx)(resourceOwner, userID) +} + +func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, resourceOwner, userID string) error { + return c.checkPermissionOnUser(ctx, domain.PermissionUserCredentialWrite)(resourceOwner, userID) +} diff --git a/internal/command/permission_checks_test.go b/internal/command/permission_checks_test.go new file mode 100644 index 0000000000..5c36dc14f9 --- /dev/null +++ b/internal/command/permission_checks_test.go @@ -0,0 +1,278 @@ +package command + +import ( + "context" + "database/sql" + "errors" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/domain" + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/eventstore/repository" + "github.com/zitadel/zitadel/internal/zerrors" +) + +func TestCommands_CheckPermission(t *testing.T) { + type fields struct { + eventstore func(*testing.T) *eventstore.Eventstore + domainPermissionCheck func(*testing.T) domain.PermissionCheck + } + type args struct { + ctx context.Context + permission string + aggregateType eventstore.AggregateType + resourceOwner, aggregateID string + } + type want struct { + err func(error) bool + } + ctx := context.Background() + filterErr := errors.New("filter error") + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "resource owner is given, no query", + fields: fields{ + domainPermissionCheck: mockDomainPermissionCheck( + ctx, + "permission", + "resourceOwner", + "aggregateID"), + }, + args: args{ + ctx: ctx, + permission: "permission", + resourceOwner: "resourceOwner", + aggregateID: "aggregateID", + }, + }, + { + name: "resource owner is empty, query for resource owner", + fields: fields{ + eventstore: expectEventstore( + expectFilter(&repository.Event{ + AggregateID: "aggregateID", + ResourceOwner: sql.NullString{String: "resourceOwner"}, + }), + ), + domainPermissionCheck: mockDomainPermissionCheck(ctx, "permission", "resourceOwner", "aggregateID"), + }, + args: args{ + ctx: ctx, + permission: "permission", + resourceOwner: "", + aggregateID: "aggregateID", + }, + }, + { + name: "resource owner is empty, query for resource owner, error", + fields: fields{ + eventstore: expectEventstore( + expectFilterError(filterErr), + ), + }, + args: args{ + ctx: ctx, + permission: "permission", + resourceOwner: "", + aggregateID: "aggregateID", + }, + want: want{ + err: func(err error) bool { + return errors.Is(err, filterErr) + }, + }, + }, + { + name: "resource owner is empty, query for resource owner, no events", + fields: fields{ + eventstore: expectEventstore( + expectFilter(), + ), + }, + args: args{ + ctx: ctx, + permission: "permission", + resourceOwner: "", + aggregateID: "aggregateID", + }, + want: want{ + err: zerrors.IsNotFound, + }, + }, + { + name: "no aggregateID, internal error", + args: args{ + ctx: ctx, + }, + want: want{ + err: zerrors.IsInternal, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + checkPermission: func(ctx context.Context, permission, orgID, resourceID string) (err error) { + assert.Failf(t, "Domain permission check should not be called", "Called c.checkPermission(%v,%v,%v,%v)", ctx, permission, orgID, resourceID) + return nil + }, + eventstore: expectEventstore()(t), + } + if tt.fields.domainPermissionCheck != nil { + c.checkPermission = tt.fields.domainPermissionCheck(t) + } + if tt.fields.eventstore != nil { + c.eventstore = tt.fields.eventstore(t) + } + err := c.newPermissionCheck(tt.args.ctx, tt.args.permission, tt.args.aggregateType)(tt.args.resourceOwner, tt.args.aggregateID) + if tt.want.err != nil { + assert.True(t, tt.want.err(err)) + } + }) + } +} + +func TestCommands_CheckPermissionUserWrite(t *testing.T) { + type fields struct { + domainPermissionCheck func(*testing.T) domain.PermissionCheck + } + type args struct { + ctx context.Context + resourceOwner, aggregateID string + } + type want struct { + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "self, no permission check", + args: args{ + ctx: authz.SetCtxData(context.Background(), authz.CtxData{ + UserID: "aggregateID", + }), + resourceOwner: "resourceOwner", + aggregateID: "aggregateID", + }, + }, + { + name: "not self, permission check", + fields: fields{ + domainPermissionCheck: mockDomainPermissionCheck( + context.Background(), + "user.write", + "resourceOwner", + "foreignAggregateID"), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "resourceOwner", + aggregateID: "foreignAggregateID", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + checkPermission: func(ctx context.Context, permission, orgID, resourceID string) (err error) { + assert.Failf(t, "Domain permission check should not be called", "Called c.checkPermission(%v,%v,%v,%v)", ctx, permission, orgID, resourceID) + return nil + }, + } + if tt.fields.domainPermissionCheck != nil { + c.checkPermission = tt.fields.domainPermissionCheck(t) + } + err := c.NewPermissionCheckUserWrite(tt.args.ctx)(tt.args.resourceOwner, tt.args.aggregateID) + if tt.want.err != nil { + assert.True(t, tt.want.err(err)) + } + }) + } +} + +func TestCommands_CheckPermissionUserDelete(t *testing.T) { + type fields struct { + domainPermissionCheck func(*testing.T) domain.PermissionCheck + } + type args struct { + ctx context.Context + resourceOwner, aggregateID string + } + type want struct { + err func(error) bool + } + userCtx := authz.SetCtxData(context.Background(), authz.CtxData{ + UserID: "aggregateID", + }) + tests := []struct { + name string + fields fields + args args + want want + }{ + { + name: "self, no permission check", + args: args{ + ctx: userCtx, + resourceOwner: "resourceOwner", + aggregateID: "aggregateID", + }, + }, + { + name: "not self, permission check", + fields: fields{ + domainPermissionCheck: mockDomainPermissionCheck( + context.Background(), + "user.delete", + "resourceOwner", + "foreignAggregateID"), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "resourceOwner", + aggregateID: "foreignAggregateID", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + checkPermission: func(ctx context.Context, permission, orgID, resourceID string) (err error) { + assert.Failf(t, "Domain permission check should not be called", "Called c.checkPermission(%v,%v,%v,%v)", ctx, permission, orgID, resourceID) + return nil + }, + } + if tt.fields.domainPermissionCheck != nil { + c.checkPermission = tt.fields.domainPermissionCheck(t) + } + err := c.checkPermissionDeleteUser(tt.args.ctx, tt.args.resourceOwner, tt.args.aggregateID) + if tt.want.err != nil { + assert.True(t, tt.want.err(err)) + } + }) + } +} + +func mockDomainPermissionCheck(expectCtx context.Context, expectPermission, expectResourceOwner, expectResourceID string) func(t *testing.T) domain.PermissionCheck { + return func(t *testing.T) domain.PermissionCheck { + return func(ctx context.Context, permission, orgID, resourceID string) (err error) { + assert.Equal(t, expectCtx, ctx) + assert.Equal(t, expectPermission, permission) + assert.Equal(t, expectResourceOwner, orgID) + assert.Equal(t, expectResourceID, resourceID) + return nil + } + } +} diff --git a/internal/command/user_v2.go b/internal/command/user_v2.go index 00ca85aaf4..5f8e8d6ff5 100644 --- a/internal/command/user_v2.go +++ b/internal/command/user_v2.go @@ -5,7 +5,6 @@ import ( "github.com/zitadel/logging" - "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/user" @@ -117,36 +116,6 @@ func (c *Commands) ReactivateUserV2(ctx context.Context, userID string) (*domain return writeModelToObjectDetails(&existingHuman.WriteModel), nil } -func (c *Commands) checkPermissionUpdateUser(ctx context.Context, resourceOwner, userID string) error { - if userID != "" && userID == authz.GetCtxData(ctx).UserID { - return nil - } - if err := c.checkPermission(ctx, domain.PermissionUserWrite, resourceOwner, userID); err != nil { - return err - } - return nil -} - -func (c *Commands) checkPermissionUpdateUserCredentials(ctx context.Context, resourceOwner, userID string) error { - if userID != "" && userID == authz.GetCtxData(ctx).UserID { - return nil - } - if err := c.checkPermission(ctx, domain.PermissionUserCredentialWrite, resourceOwner, userID); err != nil { - return err - } - return nil -} - -func (c *Commands) checkPermissionDeleteUser(ctx context.Context, resourceOwner, userID string) error { - if userID != "" && userID == authz.GetCtxData(ctx).UserID { - return nil - } - if err := c.checkPermission(ctx, domain.PermissionUserDelete, resourceOwner, userID); err != nil { - return err - } - return nil -} - func (c *Commands) userStateWriteModel(ctx context.Context, userID string) (writeModel *UserV2WriteModel, err error) { ctx, span := tracing.NewSpan(ctx) defer func() { span.EndWithError(err) }() diff --git a/internal/command/user_v2_invite.go b/internal/command/user_v2_invite.go index 1325d2e0c9..7760107146 100644 --- a/internal/command/user_v2_invite.go +++ b/internal/command/user_v2_invite.go @@ -73,12 +73,12 @@ func (c *Commands) ResendInviteCode(ctx context.Context, userID, resourceOwner, if err != nil { return nil, err } - if err := c.checkPermissionUpdateUser(ctx, existingCode.ResourceOwner, userID); err != nil { - return nil, err - } if !existingCode.UserState.Exists() { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound") } + if err := c.checkPermissionUpdateUser(ctx, existingCode.ResourceOwner, userID); err != nil { + return nil, err + } if !existingCode.CreationAllowed() { return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Gg42s", "Errors.User.AlreadyInitialised") } diff --git a/internal/command/user_v2_invite_test.go b/internal/command/user_v2_invite_test.go index efb57d86ad..817987e7e4 100644 --- a/internal/command/user_v2_invite_test.go +++ b/internal/command/user_v2_invite_test.go @@ -323,7 +323,21 @@ func TestCommands_ResendInviteCode(t *testing.T) { "missing permission", fields{ eventstore: expectEventstore( - expectFilter(), + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent(context.Background(), + &user.NewAggregate("userID", "org1").Aggregate, + "username", "firstName", + "lastName", + "nickName", + "displayName", + language.Afrikaans, + domain.GenderUnspecified, + "email", + false, + ), + ), + ), ), checkPermission: newMockPermissionCheckNotAllowed(), }, diff --git a/internal/command/user_v2_test.go b/internal/command/user_v2_test.go index 3eb9ecd6f7..685ad95253 100644 --- a/internal/command/user_v2_test.go +++ b/internal/command/user_v2_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/org" @@ -1081,13 +1082,14 @@ func TestCommandSide_ReactivateUserV2(t *testing.T) { } func TestCommandSide_RemoveUserV2(t *testing.T) { + ctxUserID := "ctxUserID" + ctx := authz.SetCtxData(context.Background(), authz.CtxData{UserID: ctxUserID}) type fields struct { eventstore func(*testing.T) *eventstore.Eventstore checkPermission domain.PermissionCheck } type ( args struct { - ctx context.Context userID string cascadingMemberships []*CascadingMembership grantIDs []string @@ -1110,7 +1112,6 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "", }, res: res{ @@ -1128,7 +1129,6 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ @@ -1143,7 +1143,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: expectEventstore( expectFilter( eventFromEventPusher( - user.NewHumanAddedEvent(context.Background(), + user.NewHumanAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", "firstname", @@ -1157,7 +1157,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), eventFromEventPusher( - user.NewUserRemovedEvent(context.Background(), + user.NewUserRemovedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", nil, @@ -1169,7 +1169,6 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ @@ -1184,7 +1183,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: expectEventstore( expectFilter( eventFromEventPusher( - user.NewHumanAddedEvent(context.Background(), + user.NewHumanAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", "firstname", @@ -1200,7 +1199,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), expectFilter( eventFromEventPusher( - org.NewDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, true, true, @@ -1209,7 +1208,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), expectPush( - user.NewUserRemovedEvent(context.Background(), + user.NewUserRemovedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", nil, @@ -1220,7 +1219,6 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ @@ -1235,7 +1233,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: expectEventstore( expectFilter( eventFromEventPusher( - user.NewHumanAddedEvent(context.Background(), + user.NewHumanAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", "firstname", @@ -1249,7 +1247,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), eventFromEventPusher( - user.NewHumanInitializedCheckSucceededEvent(context.Background(), + user.NewHumanInitializedCheckSucceededEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, ), ), @@ -1258,13 +1256,10 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckNotAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ - err: func(err error) bool { - return errors.Is(err, zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied")) - }, + err: zerrors.IsPermissionDenied, }, }, { @@ -1273,7 +1268,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: expectEventstore( expectFilter( eventFromEventPusher( - user.NewMachineAddedEvent(context.Background(), + user.NewMachineAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", "name", @@ -1283,7 +1278,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), eventFromEventPusher( - user.NewUserRemovedEvent(context.Background(), + user.NewUserRemovedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", nil, @@ -1292,10 +1287,8 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), ), - checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ @@ -1310,7 +1303,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: expectEventstore( expectFilter( eventFromEventPusher( - user.NewMachineAddedEvent(context.Background(), + user.NewMachineAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", "name", @@ -1322,7 +1315,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), expectFilter( eventFromEventPusher( - org.NewDomainPolicyAddedEvent(context.Background(), + org.NewDomainPolicyAddedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, true, true, @@ -1331,7 +1324,7 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { ), ), expectPush( - user.NewUserRemovedEvent(context.Background(), + user.NewUserRemovedEvent(ctx, &user.NewAggregate("user1", "org1").Aggregate, "username", nil, @@ -1342,7 +1335,6 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { checkPermission: newMockPermissionCheckAllowed(), }, args: args{ - ctx: context.Background(), userID: "user1", }, res: res{ @@ -1351,6 +1343,56 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { }, }, }, + { + name: "remove self, ok", + fields: fields{ + eventstore: expectEventstore( + expectFilter( + eventFromEventPusher( + user.NewHumanAddedEvent(ctx, + &user.NewAggregate(ctxUserID, "org1").Aggregate, + "username", + "firstname", + "lastname", + "nickname", + "displayname", + language.German, + domain.GenderUnspecified, + "email@test.ch", + true, + ), + ), + ), + expectFilter( + eventFromEventPusher( + org.NewDomainPolicyAddedEvent(ctx, + &user.NewAggregate(ctxUserID, "org1").Aggregate, + true, + true, + true, + ), + ), + ), + expectPush( + user.NewUserRemovedEvent(ctx, + &user.NewAggregate(ctxUserID, "org1").Aggregate, + "username", + nil, + true, + ), + ), + ), + checkPermission: newMockPermissionCheckNotAllowed(), + }, + args: args{ + userID: ctxUserID, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -1358,7 +1400,8 @@ func TestCommandSide_RemoveUserV2(t *testing.T) { eventstore: tt.fields.eventstore(t), checkPermission: tt.fields.checkPermission, } - got, err := r.RemoveUserV2(tt.args.ctx, tt.args.userID, "", tt.args.cascadingMemberships, tt.args.grantIDs...) + + got, err := r.RemoveUserV2(ctx, tt.args.userID, "", tt.args.cascadingMemberships, tt.args.grantIDs...) if tt.res.err == nil { assert.NoError(t, err) } diff --git a/proto/zitadel/user/v2/user_service.proto b/proto/zitadel/user/v2/user_service.proto index 00cb352f70..15bc2d7775 100644 --- a/proto/zitadel/user/v2/user_service.proto +++ b/proto/zitadel/user/v2/user_service.proto @@ -539,7 +539,7 @@ service UserService { option (zitadel.protoc_gen_zitadel.v2.options) = { auth_option: { - permission: "user.delete" + permission: "authenticated" } }; diff --git a/proto/zitadel/user/v2beta/user_service.proto b/proto/zitadel/user/v2beta/user_service.proto index 03bc36220e..f877252f51 100644 --- a/proto/zitadel/user/v2beta/user_service.proto +++ b/proto/zitadel/user/v2beta/user_service.proto @@ -563,7 +563,7 @@ service UserService { option (zitadel.protoc_gen_zitadel.v2.options) = { auth_option: { - permission: "user.delete" + permission: "authenticated" } };