1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

[AC-607] Extract IOrganizationService.DeleteUserAsync into IRemoveOrganizationUserCommand (#4803)

* Add HasConfirmedOwnersExceptQuery class, interface and unit tests

* Register IHasConfirmedOwnersExceptQuery for dependency injection

* Replace OrganizationService.HasConfirmedOwnersExceptAsync with HasConfirmedOwnersExceptQuery

* Refactor DeleteManagedOrganizationUserAccountCommand to use IHasConfirmedOwnersExceptQuery

* Fix unit tests

* Extract IOrganizationService.RemoveUserAsync into IRemoveOrganizationUserCommand; Update unit tests

* Extract IOrganizationService.RemoveUsersAsync into IRemoveOrganizationUserCommand; Update unit tests

* Refactor RemoveUserAsync(Guid organizationId, Guid userId) to use ValidateDeleteUser

* Refactor RemoveOrganizationUserCommandTests to use more descriptive method names

* Refactor controller actions to accept Guid directly instead of parsing strings

* Add unit tests for removing OrganizationUser by UserId

* Refactor remove OrganizationUser by UserId method

* Add summary to IHasConfirmedOwnersExceptQuery
This commit is contained in:
Rui Tomé
2024-10-16 10:33:00 +01:00
committed by GitHub
parent 7408f3ee02
commit 93e49ffe74
28 changed files with 781 additions and 642 deletions

View File

@ -7,6 +7,7 @@ using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
@ -49,6 +50,7 @@ public class OrganizationsControllerTests : IDisposable
private readonly IProviderRepository _providerRepository;
private readonly IProviderBillingService _providerBillingService;
private readonly IDataProtectorTokenFactory<OrgDeleteTokenable> _orgDeleteTokenDataFactory;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
private readonly OrganizationsController _sut;
@ -72,6 +74,7 @@ public class OrganizationsControllerTests : IDisposable
_providerRepository = Substitute.For<IProviderRepository>();
_providerBillingService = Substitute.For<IProviderBillingService>();
_orgDeleteTokenDataFactory = Substitute.For<IDataProtectorTokenFactory<OrgDeleteTokenable>>();
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
_sut = new OrganizationsController(
_organizationRepository,
@ -91,7 +94,8 @@ public class OrganizationsControllerTests : IDisposable
_pushNotificationService,
_providerRepository,
_providerBillingService,
_orgDeleteTokenDataFactory);
_orgDeleteTokenDataFactory,
_removeOrganizationUserCommand);
}
public void Dispose()
@ -120,13 +124,12 @@ public class OrganizationsControllerTests : IDisposable
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => _sut.Leave(orgId.ToString()));
var exception = await Assert.ThrowsAsync<BadRequestException>(() => _sut.Leave(orgId));
Assert.Contains("Your organization's Single Sign-On settings prevent you from leaving.",
exception.Message);
await _organizationService.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
await _removeOrganizationUserCommand.DidNotReceiveWithAnyArgs().RemoveUserAsync(default, default);
}
[Theory]
@ -155,8 +158,9 @@ public class OrganizationsControllerTests : IDisposable
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
await _organizationService.RemoveUserAsync(orgId, user.Id);
await _organizationService.Received(1).RemoveUserAsync(orgId, user.Id);
await _sut.Leave(orgId);
await _removeOrganizationUserCommand.Received(1).RemoveUserAsync(orgId, user.Id);
}
[Theory, AutoData]

View File

@ -4,8 +4,8 @@ using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.Billing.Controllers;
using Bit.Api.Models.Request.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.Repositories;
@ -46,6 +46,7 @@ public class OrganizationsControllerTests : IDisposable
private readonly IAddSecretsManagerSubscriptionCommand _addSecretsManagerSubscriptionCommand;
private readonly IReferenceEventService _referenceEventService;
private readonly ISubscriberService _subscriberService;
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
private readonly OrganizationsController _sut;
@ -68,6 +69,7 @@ public class OrganizationsControllerTests : IDisposable
_addSecretsManagerSubscriptionCommand = Substitute.For<IAddSecretsManagerSubscriptionCommand>();
_referenceEventService = Substitute.For<IReferenceEventService>();
_subscriberService = Substitute.For<ISubscriberService>();
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
_sut = new OrganizationsController(
_organizationRepository,
@ -91,36 +93,6 @@ public class OrganizationsControllerTests : IDisposable
_sut?.Dispose();
}
[Theory]
[InlineAutoData(true, false)]
[InlineAutoData(false, true)]
[InlineAutoData(false, false)]
public async Task OrganizationsController_UserCanLeaveOrganizationThatDoesntProvideKeyConnector(
bool keyConnectorEnabled, bool userUsesKeyConnector, Guid orgId, User user)
{
var ssoConfig = new SsoConfig
{
Id = default,
Data = new SsoConfigurationData
{
MemberDecryptionType = keyConnectorEnabled
? MemberDecryptionType.KeyConnector
: MemberDecryptionType.MasterPassword
}.Serialize(),
Enabled = true,
OrganizationId = orgId,
};
user.UsesKeyConnector = userUsesKeyConnector;
_currentContext.OrganizationUser(orgId).Returns(true);
_ssoConfigRepository.GetByOrganizationIdAsync(orgId).Returns(ssoConfig);
_userService.GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>()).Returns(user);
await _organizationService.RemoveUserAsync(orgId, user.Id);
await _organizationService.Received(1).RemoveUserAsync(orgId, user.Id);
}
[Theory, AutoData]
public async Task OrganizationsController_PostUpgrade_UserCannotEditSubscription_ThrowsNotFoundException(
Guid organizationId,

View File

@ -40,7 +40,7 @@ public class DeleteManagedOrganizationUserAccountCommandTests
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)))
.Returns(new Dictionary<Guid, bool> { { organizationUser.Id, true } });
sutProvider.GetDependency<IOrganizationService>()
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)),
@ -184,7 +184,7 @@ public class DeleteManagedOrganizationUserAccountCommandTests
.OrganizationOwner(organizationUser.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationService>()
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organizationUser.Id)),
@ -399,8 +399,8 @@ public class DeleteManagedOrganizationUserAccountCommandTests
.OrganizationOwner(orgUser.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationService>()
.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, Arg.Any<IEnumerable<Guid>>(), true)
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, Arg.Any<IEnumerable<Guid>>(), Arg.Any<bool>())
.Returns(false);
// Act

View File

@ -0,0 +1,139 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
[SutProviderCustomize]
public class HasConfirmedOwnersExceptQueryTests
{
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithConfirmedOwner_WithNoException_ReturnsTrue(
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_ExcludingConfirmedOwner_ReturnsFalse(
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid> { owner.Id }, true);
Assert.False(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithInvitedOwner_ReturnsFalse(
Organization organization,
[OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.False(result);
}
[Theory]
[BitAutoData(true)]
[BitAutoData(false)]
public async Task HasConfirmedOwnersExcept_WithConfirmedProviderUser_IncludeProviderTrue_ReturnsTrue(
bool includeProvider,
Organization organization,
ProviderUser providerUser,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
providerUser.Status = ProviderUserStatusType.Confirmed;
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organization.Id, ProviderUserStatusType.Confirmed)
.Returns(new List<ProviderUser> { providerUser });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), includeProvider);
Assert.Equal(includeProvider, result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithConfirmedOwners_ReturnsTrue(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
ICollection<OrganizationUser> owners,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(owners);
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithConfirmedProviders_ReturnsTrue(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
ICollection<ProviderUser> providerUsers,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser>());
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)
.Returns(providerUsers);
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithNoConfirmedOwnersOrProviders_ReturnsFalse(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
SutProvider<HasConfirmedOwnersExceptQuery> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser>());
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)
.Returns(new List<ProviderUser>());
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.False(result);
}
}

View File

@ -1,9 +1,12 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
@ -14,33 +17,38 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
[SutProviderCustomize]
public class RemoveOrganizationUserCommandTests
{
[Theory]
[BitAutoData]
public async Task RemoveUser_Success(SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
[Theory, BitAutoData]
public async Task RemoveUser_Success(
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
.Returns(new OrganizationUser
{
Id = organizationUserId,
OrganizationId = organizationId
});
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null);
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.GetDependency<IOrganizationService>().Received(1).RemoveUserAsync(organizationId, organizationUserId, null);
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
await organizationUserRepository.Received(1).DeleteAsync(organizationUser);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
}
[Theory]
[BitAutoData]
public async Task RemoveUser_NotFound_Throws(SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
public async Task RemoveUser_NotFound_ThrowsException(SutProvider<RemoveOrganizationUserCommand> sutProvider,
Guid organizationId, Guid organizationUserId)
{
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
}
[Theory]
[BitAutoData]
public async Task RemoveUser_MismatchingOrganizationId_Throws(SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
public async Task RemoveUser_MismatchingOrganizationId_ThrowsException(
SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
@ -53,20 +61,249 @@ public class RemoveOrganizationUserCommandTests
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
}
[Theory]
[BitAutoData]
public async Task RemoveUser_WithEventSystemUser_Success(SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
[Theory, BitAutoData]
public async Task RemoveUser_InvalidUser_ThrowsException(
OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetByIdAsync(organizationUserId)
.Returns(new OrganizationUser
{
Id = organizationUserId,
OrganizationId = organizationId
});
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, eventSystemUser);
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
await sutProvider.GetDependency<IOrganizationService>().Received(1).RemoveUserAsync(organizationId, organizationUserId, eventSystemUser);
var exception = await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.Id, deletingUser.UserId));
Assert.Contains("User not found.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_RemoveYourself_ThrowsException(OrganizationUser deletingUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId));
Assert.Contains("You cannot remove yourself.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_NonOwnerRemoveOwner_ThrowsException(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
currentContext.OrganizationAdmin(deletingUser.OrganizationId).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
Assert.Contains("Only owners can delete other owners.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_RemovingLastOwner_ThrowsException(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var hasConfirmedOwnersExceptQuery = sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(
deletingUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), Arg.Any<bool>())
.Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
hasConfirmedOwnersExceptQuery
.Received(1)
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), true);
}
[Theory, BitAutoData]
public async Task RemoveUser_WithEventSystemUser_Success(
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
EventSystemUser eventSystemUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
}
[Theory, BitAutoData]
public async Task RemoveUser_ByUserId_Success(
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
.Returns(organizationUser);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
Arg.Any<bool>())
.Returns(true);
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
}
[Theory, BitAutoData]
public async Task RemoveUser_ByUserId_NotFound_ThrowsException(SutProvider<RemoveOrganizationUserCommand> sutProvider,
Guid organizationId, Guid userId)
{
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, userId));
}
[Theory, BitAutoData]
public async Task RemoveUser_ByUserId_InvalidUser_ThrowsException(OrganizationUser organizationUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value).Returns(organizationUser);
var exception = await Assert.ThrowsAsync<NotFoundException>(
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.UserId.Value));
Assert.Contains("User not found.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_ByUserId_RemovingLastOwner_ThrowsException(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var hasConfirmedOwnersExceptQuery = sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>();
organizationUserRepository.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value).Returns(organizationUser);
hasConfirmedOwnersExceptQuery
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
Arg.Any<bool>())
.Returns(false);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
hasConfirmedOwnersExceptQuery
.Received(1)
.HasConfirmedOwnersExceptAsync(
organizationUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
Arg.Any<bool>());
}
[Theory, BitAutoData]
public async Task RemoveUsers_FilterInvalid_ThrowsException(OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { organizationUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId));
Assert.Contains("Users invalid.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUsers_RemoveYourself_ThrowsException(
OrganizationUser deletingUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { deletingUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("You cannot remove yourself.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task RemoveUsers_NonOwnerRemoveOwner_ThrowsException(
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
[OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser2,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("Only owners can delete other owners.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task RemoveUsers_LastOwner_ThrowsException(
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { orgUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner).Returns(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUsersAsync(orgUser.OrganizationId, organizationUserIds, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUsers_Success(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2,
SutProvider<RemoveOrganizationUserCommand> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
}
}

View File

@ -1,6 +1,7 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Core.Enums;
@ -169,7 +170,7 @@ public class UpdateOrganizationUserCommandTests
await organizationService.Received(1).ValidateOrganizationCustomPermissionsEnabledAsync(
newUserData.OrganizationId,
newUserData.Type);
await organizationService.Received(1).HasConfirmedOwnersExceptAsync(
await sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>().Received(1).HasConfirmedOwnersExceptAsync(
newUserData.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(newUserData.Id)));
}
@ -187,7 +188,7 @@ public class UpdateOrganizationUserCommandTests
newUser.UserId = oldUser.UserId;
newUser.OrganizationId = oldUser.OrganizationId = organization.Id;
organizationUserRepository.GetByIdAsync(oldUser.Id).Returns(oldUser);
organizationService
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(
oldUser.OrganizationId,
Arg.Is<IEnumerable<Guid>>(i => i.Contains(oldUser.Id)))

View File

@ -43,6 +43,8 @@ using Xunit;
using Organization = Bit.Core.AdminConsole.Entities.Organization;
using OrganizationUser = Bit.Core.Entities.OrganizationUser;
#nullable enable
namespace Bit.Core.Test.Services;
[SutProviderCustomize]
@ -77,8 +79,9 @@ public class OrganizationServiceTests
.Returns(existingUsers);
organizationUserRepository.GetCountByOrganizationIdAsync(org.Id)
.Returns(existingUsers.Count);
organizationUserRepository.GetManyByOrganizationAsync(org.Id, OrganizationUserType.Owner)
.Returns(existingUsers.Select(u => new OrganizationUser { Status = OrganizationUserStatusType.Confirmed, Type = OrganizationUserType.Owner, Id = u.Id }).ToList());
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(org.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(org.Id).Returns(true);
// Mock tokenable factory to return a token that expires in 5 days
@ -147,8 +150,9 @@ public class OrganizationServiceTests
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetManyByOrganizationAsync(org.Id, OrganizationUserType.Owner)
.Returns(existingUsers.Select(u => new OrganizationUser { Status = OrganizationUserStatusType.Confirmed, Type = OrganizationUserType.Owner, Id = u.Id }).ToList());
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(org.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
@ -708,8 +712,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
@ -737,8 +742,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
@ -817,8 +823,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { invitor });
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
@ -835,7 +842,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
), OrganizationCustomize, BitAutoData]
public async Task InviteUser_Passes(Organization organization, OrganizationUserInvite invite, string externalId,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// This method is only used to invite 1 user at a time
@ -851,8 +857,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
@ -864,6 +868,10 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
@ -905,7 +913,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
), OrganizationCustomize, BitAutoData]
public async Task InviteUser_UserAlreadyInvited_Throws(Organization organization, OrganizationUserInvite invite, string externalId,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// This method is only used to invite 1 user at a time
@ -926,8 +933,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
@ -939,6 +944,10 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
@ -987,7 +996,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
), OrganizationCustomize, BitAutoData]
public async Task InviteUsers_Passes(Organization organization, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
@ -1000,8 +1008,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
// Mock tokenable factory to return a token that expires in 5 days
sutProvider.GetDependency<IOrgUserInviteTokenableFactory>()
@ -1013,6 +1019,10 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
);
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
@ -1034,7 +1044,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
), OrganizationCustomize, BitAutoData]
public async Task InviteUsers_WithEventSystemUser_Passes(Organization organization, EventSystemUser eventSystemUser, IEnumerable<(OrganizationUserInvite invite, string externalId)> invites,
OrganizationUser invitor,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
SutProvider<OrganizationService> sutProvider)
{
// Setup FakeDataProtectorTokenFactory for creating new tokens - this must come first in order to avoid resetting mocks
@ -1052,8 +1061,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationRepository.GetByIdAsync(organization.Id).Returns(organization);
organizationUserRepository.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new[] { owner });
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
SetupOrgUserRepositoryCreateAsyncMock(organizationUserRepository);
SetupOrgUserRepositoryCreateManyAsyncMock(organizationUserRepository);
@ -1181,196 +1191,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
}
[Theory, BitAutoData]
public async Task RemoveUser_InvalidUser(OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.Id, deletingUser.UserId));
Assert.Contains("User not valid.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_RemoveYourself(OrganizationUser deletingUser, SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId));
Assert.Contains("You cannot remove yourself.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_NonOwnerRemoveOwner(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
currentContext.OrganizationAdmin(deletingUser.OrganizationId).Returns(true);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
Assert.Contains("Only owners can delete other owners.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_LastOwner(
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { organizationUser });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUser_Success(
OrganizationUser organizationUser,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, organizationUser });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
}
[Theory, BitAutoData]
public async Task RemoveUser_WithEventSystemUser_Success(
OrganizationUser organizationUser,
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser, EventSystemUser eventSystemUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
organizationUser.OrganizationId = deletingUser.OrganizationId;
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, organizationUser });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, eventSystemUser);
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
}
[Theory, BitAutoData]
public async Task RemoveUsers_FilterInvalid(OrganizationUser organizationUser, OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { organizationUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId));
Assert.Contains("Users invalid.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUsers_RemoveYourself(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
OrganizationUser deletingUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { deletingUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(default, default).ReturnsForAnyArgs(new[] { orgUser });
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("You cannot remove yourself.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task RemoveUsers_NonOwnerRemoveOwner(
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
[OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser2,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(default, default).ReturnsForAnyArgs(new[] { orgUser2 });
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
Assert.Contains("Only owners can delete other owners.", result[0].Item2);
}
[Theory, BitAutoData]
public async Task RemoveUsers_LastOwner(
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var organizationUsers = new[] { orgUser };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner).Returns(organizationUsers);
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RemoveUsersAsync(orgUser.OrganizationId, organizationUserIds, null));
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
}
[Theory, BitAutoData]
public async Task RemoveUsers_Success(
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2,
SutProvider<OrganizationService> sutProvider)
{
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var currentContext = sutProvider.GetDependency<ICurrentContext>();
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
var organizationUsers = new[] { orgUser1, orgUser2 };
var organizationUserIds = organizationUsers.Select(u => u.Id);
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
organizationUserRepository.GetManyByOrganizationAsync(deletingUser.OrganizationId, OrganizationUserType.Owner)
.Returns(new[] { deletingUser, orgUser1 });
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
}
[Theory, BitAutoData]
public async Task ConfirmUser_InvalidStatus(OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Invited)] OrganizationUser orgUser, string key,
@ -1858,17 +1678,24 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
await applicationCacheService.DidNotReceiveWithAnyArgs().DeleteOrganizationAbilityAsync(default);
}
private void RestoreRevokeUser_Setup(Organization organization, OrganizationUser restoringUser,
OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider,
OrganizationUserType restoringUserType = OrganizationUserType.Owner)
private void RestoreRevokeUser_Setup(
Organization organization,
OrganizationUser? requestingOrganizationUser,
OrganizationUser targetOrganizationUser,
SutProvider<OrganizationService> sutProvider)
{
if (requestingOrganizationUser != null)
{
requestingOrganizationUser.OrganizationId = organization.Id;
}
targetOrganizationUser.OrganizationId = organization.Id;
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organizationUser.OrganizationId).Returns(organization);
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(true);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
organizationUserRepository.GetManyByOrganizationAsync(organizationUser.OrganizationId, restoringUserType)
.Returns(new[] { restoringUser });
sutProvider.GetDependency<ICurrentContext>().OrganizationOwner(organization.Id).Returns(requestingOrganizationUser != null && requestingOrganizationUser.Type is OrganizationUserType.Owner);
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(requestingOrganizationUser != null && (requestingOrganizationUser.Type is OrganizationUserType.Owner or OrganizationUserType.Admin));
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
.HasConfirmedOwnersExceptAsync(organization.Id, Arg.Any<IEnumerable<Guid>>())
.Returns(true);
}
[Theory, BitAutoData]
@ -1887,10 +1714,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
[Theory, BitAutoData]
public async Task RevokeUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
public async Task RevokeUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
@ -1917,10 +1743,9 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
}
[Theory, BitAutoData]
public async Task RestoreUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
public async Task RestoreUser_WithEventSystemUser_Success(Organization organization, [OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser, EventSystemUser eventSystemUser, SutProvider<OrganizationService> sutProvider)
{
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
RestoreRevokeUser_Setup(organization, null, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
@ -1953,11 +1778,12 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
[Theory]
[BitAutoData(OrganizationUserType.Admin)]
[BitAutoData(OrganizationUserType.Custom)]
public async Task RestoreUser_AdminRestoreOwner_Fails(OrganizationUserType restoringUserType, Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser restoringUser,
public async Task RestoreUser_AdminRestoreOwner_Fails(OrganizationUserType restoringUserType,
Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser restoringUser,
[OrganizationUser(OrganizationUserStatusType.Revoked, OrganizationUserType.Owner)] OrganizationUser organizationUser, SutProvider<OrganizationService> sutProvider)
{
restoringUser.Type = restoringUserType;
RestoreRevokeUser_Setup(organization, restoringUser, organizationUser, sutProvider, OrganizationUserType.Admin);
RestoreRevokeUser_Setup(organization, restoringUser, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
@ -2028,9 +1854,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
SutProvider<OrganizationService> sutProvider)
{
organizationUser.Email = null;
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.UserId.Value)))
@ -2040,6 +1863,10 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
@ -2152,14 +1979,15 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
SutProvider<OrganizationService> sutProvider)
{
organizationUser.Email = null;
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
RestoreRevokeUser_Setup(organization, owner, organizationUser, sutProvider);
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var eventService = sutProvider.GetDependency<IEventService>();
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
.Returns(new[] { new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication } });
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
@ -2199,58 +2027,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithConfirmedOwner_ReturnsTrue(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_ExcludingConfirmedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid> { owner.Id }, true);
Assert.False(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExcept_WithInvitedOwner_ReturnsFalse(Organization organization, [OrganizationUser(OrganizationUserStatusType.Invited, OrganizationUserType.Owner)] OrganizationUser owner, SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organization.Id, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser> { owner });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), true);
Assert.False(result);
}
[Theory]
[BitAutoData(true)]
[BitAutoData(false)]
public async Task HasConfirmedOwnersExcept_WithConfirmedProviderUser_IncludeProviderTrue_ReturnsTrue(bool includeProvider, Organization organization, ProviderUser providerUser, SutProvider<OrganizationService> sutProvider)
{
providerUser.Status = ProviderUserStatusType.Confirmed;
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organization.Id, ProviderUserStatusType.Confirmed)
.Returns(new List<ProviderUser> { providerUser });
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organization.Id, new List<Guid>(), includeProvider);
Assert.Equal(includeProvider, result);
}
[Theory]
[BitAutoData(PlanType.TeamsAnnually)]
[BitAutoData(PlanType.TeamsMonthly)]
@ -2474,61 +2250,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
Assert.Contains("custom users can only grant the same custom permissions that they have.", exception.Message.ToLowerInvariant());
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithConfirmedOwners_ReturnsTrue(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
ICollection<OrganizationUser> owners,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(owners);
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithConfirmedProviders_ReturnsTrue(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
ICollection<ProviderUser> providerUsers,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser>());
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)
.Returns(providerUsers);
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.True(result);
}
[Theory, BitAutoData]
public async Task HasConfirmedOwnersExceptAsync_WithNoConfirmedOwnersOrProviders_ReturnsFalse(
Guid organizationId,
IEnumerable<Guid> organizationUsersId,
SutProvider<OrganizationService> sutProvider)
{
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByOrganizationAsync(organizationId, OrganizationUserType.Owner)
.Returns(new List<OrganizationUser>());
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByOrganizationAsync(organizationId, ProviderUserStatusType.Confirmed)
.Returns(new List<ProviderUser>());
var result = await sutProvider.Sut.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId);
Assert.False(result);
}
[Theory]
[BitAutoData(OrganizationUserType.Owner)]
[BitAutoData(OrganizationUserType.Admin)]

View File

@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services.Implementations;
using Bit.Core.Auth.Entities;
@ -353,6 +354,7 @@ public class PolicyServiceTests
});
var organizationService = Substitute.For<IOrganizationService>();
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
var utcNow = DateTime.UtcNow;
@ -360,20 +362,20 @@ public class PolicyServiceTests
await sutProvider.Sut.SaveAsync(policy, organizationService, savingUserId);
await organizationService.Received()
await removeOrganizationUserCommand.Received()
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWithout2FA.Id, savingUserId);
await sutProvider.GetDependency<IMailService>().Received()
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWithout2FA.Email);
await organizationService.DidNotReceive()
await removeOrganizationUserCommand.DidNotReceive()
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserInvited.Id, savingUserId);
await sutProvider.GetDependency<IMailService>().DidNotReceive()
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserInvited.Email);
await organizationService.DidNotReceive()
await removeOrganizationUserCommand.DidNotReceive()
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWith2FA.Id, savingUserId);
await sutProvider.GetDependency<IMailService>().DidNotReceive()
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWith2FA.Email);
await organizationService.DidNotReceive()
await removeOrganizationUserCommand.DidNotReceive()
.RemoveUserAsync(policy.OrganizationId, orgUserDetailAdmin.Id, savingUserId);
await sutProvider.GetDependency<IMailService>().DidNotReceive()
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailAdmin.Email);
@ -467,6 +469,7 @@ public class PolicyServiceTests
});
var organizationService = Substitute.For<IOrganizationService>();
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
var savingUserId = Guid.NewGuid();
@ -475,7 +478,7 @@ public class PolicyServiceTests
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
await organizationService.DidNotReceiveWithAnyArgs()
await removeOrganizationUserCommand.DidNotReceiveWithAnyArgs()
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
await sutProvider.GetDependency<IMailService>().DidNotReceiveWithAnyArgs()

View File

@ -1,5 +1,6 @@
using System.Text.Json;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Enums;
@ -263,7 +264,8 @@ public class UserServiceTests
sutProvider.GetDependency<IStripeSyncService>(),
new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>(),
sutProvider.GetDependency<IFeatureService>(),
sutProvider.GetDependency<IPremiumUserBillingService>()
sutProvider.GetDependency<IPremiumUserBillingService>(),
sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
);
var actualIsVerified = await sut.VerifySecretAsync(user, secret);