mirror of
https://github.com/bitwarden/server.git
synced 2024-12-01 13:43:23 +01:00
Merge branch 'jmccannon/pm-10319-fix-migration-script' into ac/jmccannon/pm-10317-email-user-domain-verified
This commit is contained in:
commit
11c4de388d
@ -539,7 +539,7 @@ public class OrganizationUsersController : Controller
|
|||||||
var userId = _userService.GetProperUserId(User);
|
var userId = _userService.GetProperUserId(User);
|
||||||
var result = await _removeOrganizationUserCommand.RemoveUsersAsync(orgId, model.Ids, userId.Value);
|
var result = await _removeOrganizationUserCommand.RemoveUsersAsync(orgId, model.Ids, userId.Value);
|
||||||
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
|
return new ListResponseModel<OrganizationUserBulkResponseModel>(result.Select(r =>
|
||||||
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));
|
new OrganizationUserBulkResponseModel(r.OrganizationUserId, r.ErrorMessage)));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
|
[RequireFeature(FeatureFlagKeys.AccountDeprovisioning)]
|
||||||
|
@ -1,14 +1,53 @@
|
|||||||
using Bit.Core.Entities;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Enums;
|
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
|
|
||||||
public interface IRemoveOrganizationUserCommand
|
public interface IRemoveOrganizationUserCommand
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a user from an organization.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization.</param>
|
||||||
|
/// <param name="userId">The ID of the user to remove.</param>
|
||||||
|
Task RemoveUserAsync(Guid organizationId, Guid userId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a user from an organization with a specified deleting user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization.</param>
|
||||||
|
/// <param name="organizationUserId">The ID of the organization user to remove.</param>
|
||||||
|
/// <param name="deletingUserId">The ID of the user performing the removal operation.</param>
|
||||||
Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a user from an organization using a system user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization.</param>
|
||||||
|
/// <param name="organizationUserId">The ID of the organization user to remove.</param>
|
||||||
|
/// <param name="eventSystemUser">The system user performing the removal operation.</param>
|
||||||
Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser);
|
Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser);
|
||||||
Task RemoveUserAsync(Guid organizationId, Guid userId);
|
|
||||||
Task<List<Tuple<OrganizationUser, string>>> RemoveUsersAsync(Guid organizationId,
|
/// <summary>
|
||||||
IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
/// Removes multiple users from an organization with a specified deleting user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization.</param>
|
||||||
|
/// <param name="organizationUserIds">The collection of organization user IDs to remove.</param>
|
||||||
|
/// <param name="deletingUserId">The ID of the user performing the removal operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A list of tuples containing the organization user id and the error message for each user that could not be removed, otherwise empty.
|
||||||
|
/// </returns>
|
||||||
|
Task<IEnumerable<(Guid OrganizationUserId, string ErrorMessage)>> RemoveUsersAsync(
|
||||||
|
Guid organizationId, IEnumerable<Guid> organizationUserIds, Guid? deletingUserId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes multiple users from an organization using a system user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization.</param>
|
||||||
|
/// <param name="organizationUserIds">The collection of organization user IDs to remove.</param>
|
||||||
|
/// <param name="eventSystemUser">The system user performing the removal operation.</param>
|
||||||
|
/// <returns>
|
||||||
|
/// A list of tuples containing the organization user id and the error message for each user that could not be removed, otherwise empty.
|
||||||
|
/// </returns>
|
||||||
|
Task<IEnumerable<(Guid OrganizationUserId, string ErrorMessage)>> RemoveUsersAsync(
|
||||||
|
Guid organizationId, IEnumerable<Guid> organizationUserIds, EventSystemUser eventSystemUser);
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,16 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
private readonly IPushRegistrationService _pushRegistrationService;
|
private readonly IPushRegistrationService _pushRegistrationService;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||||
|
private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly TimeProvider _timeProvider;
|
||||||
|
|
||||||
|
public const string UserNotFoundErrorMessage = "User not found.";
|
||||||
|
public const string UsersInvalidErrorMessage = "Users invalid.";
|
||||||
|
public const string RemoveYourselfErrorMessage = "You cannot remove yourself.";
|
||||||
|
public const string RemoveOwnerByNonOwnerErrorMessage = "Only owners can delete other owners.";
|
||||||
|
public const string RemoveLastConfirmedOwnerErrorMessage = "Organization must have at least one confirmed owner.";
|
||||||
|
public const string RemoveClaimedAccountErrorMessage = "Cannot remove member accounts claimed by the organization. To offboard a member, revoke or delete the account.";
|
||||||
|
|
||||||
public RemoveOrganizationUserCommand(
|
public RemoveOrganizationUserCommand(
|
||||||
IDeviceRepository deviceRepository,
|
IDeviceRepository deviceRepository,
|
||||||
@ -25,7 +35,10 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
IPushNotificationService pushNotificationService,
|
IPushNotificationService pushNotificationService,
|
||||||
IPushRegistrationService pushRegistrationService,
|
IPushRegistrationService pushRegistrationService,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery)
|
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||||
|
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
|
||||||
|
IFeatureService featureService,
|
||||||
|
TimeProvider timeProvider)
|
||||||
{
|
{
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -34,14 +47,27 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
_pushRegistrationService = pushRegistrationService;
|
_pushRegistrationService = pushRegistrationService;
|
||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||||
|
_getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery;
|
||||||
|
_featureService = featureService;
|
||||||
|
_timeProvider = timeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task RemoveUserAsync(Guid organizationId, Guid userId)
|
||||||
|
{
|
||||||
|
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
||||||
|
ValidateRemoveUser(organizationId, organizationUser);
|
||||||
|
|
||||||
|
await RepositoryRemoveUserAsync(organizationUser, deletingUserId: null, eventSystemUser: null);
|
||||||
|
|
||||||
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
public async Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, Guid? deletingUserId)
|
||||||
{
|
{
|
||||||
var organizationUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||||
ValidateDeleteUser(organizationId, organizationUser);
|
ValidateRemoveUser(organizationId, organizationUser);
|
||||||
|
|
||||||
await RepositoryDeleteUserAsync(organizationUser, deletingUserId);
|
await RepositoryRemoveUserAsync(organizationUser, deletingUserId, eventSystemUser: null);
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||||
}
|
}
|
||||||
@ -49,108 +75,79 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
public async Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
public async Task RemoveUserAsync(Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
||||||
{
|
{
|
||||||
var organizationUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
var organizationUser = await _organizationUserRepository.GetByIdAsync(organizationUserId);
|
||||||
ValidateDeleteUser(organizationId, organizationUser);
|
ValidateRemoveUser(organizationId, organizationUser);
|
||||||
|
|
||||||
await RepositoryDeleteUserAsync(organizationUser, null);
|
await RepositoryRemoveUserAsync(organizationUser, deletingUserId: null, eventSystemUser);
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RemoveUserAsync(Guid organizationId, Guid userId)
|
public async Task<IEnumerable<(Guid OrganizationUserId, string ErrorMessage)>> RemoveUsersAsync(
|
||||||
|
Guid organizationId, IEnumerable<Guid> organizationUserIds, Guid? deletingUserId)
|
||||||
{
|
{
|
||||||
var organizationUser = await _organizationUserRepository.GetByOrganizationAsync(organizationId, userId);
|
var result = await RemoveUsersInternalAsync(organizationId, organizationUserIds, deletingUserId, eventSystemUser: null);
|
||||||
ValidateDeleteUser(organizationId, organizationUser);
|
|
||||||
|
|
||||||
await RepositoryDeleteUserAsync(organizationUser, null);
|
var removedUsers = result.Where(r => string.IsNullOrEmpty(r.ErrorMessage)).Select(r => r.OrganizationUser).ToList();
|
||||||
|
if (removedUsers.Any())
|
||||||
await _eventService.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
{
|
||||||
|
DateTime? eventDate = _timeProvider.GetUtcNow().UtcDateTime;
|
||||||
|
await _eventService.LogOrganizationUserEventsAsync(
|
||||||
|
removedUsers.Select(ou => (ou, EventType.OrganizationUser_Removed, eventDate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<Tuple<OrganizationUser, string>>> RemoveUsersAsync(Guid organizationId,
|
return result.Select(r => (r.OrganizationUser.Id, r.ErrorMessage));
|
||||||
IEnumerable<Guid> organizationUsersId,
|
|
||||||
Guid? deletingUserId)
|
|
||||||
{
|
|
||||||
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId);
|
|
||||||
var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
if (!filteredUsers.Any())
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Users invalid.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId))
|
public async Task<IEnumerable<(Guid OrganizationUserId, string ErrorMessage)>> RemoveUsersAsync(
|
||||||
|
Guid organizationId, IEnumerable<Guid> organizationUserIds, EventSystemUser eventSystemUser)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
var result = await RemoveUsersInternalAsync(organizationId, organizationUserIds, deletingUserId: null, eventSystemUser);
|
||||||
|
|
||||||
|
var removedUsers = result.Where(r => string.IsNullOrEmpty(r.ErrorMessage)).Select(r => r.OrganizationUser).ToList();
|
||||||
|
if (removedUsers.Any())
|
||||||
|
{
|
||||||
|
DateTime? eventDate = _timeProvider.GetUtcNow().UtcDateTime;
|
||||||
|
await _eventService.LogOrganizationUserEventsAsync(
|
||||||
|
removedUsers.Select(ou => (ou, EventType.OrganizationUser_Removed, eventSystemUser, eventDate)));
|
||||||
}
|
}
|
||||||
|
|
||||||
var deletingUserIsOwner = false;
|
return result.Select(r => (r.OrganizationUser.Id, r.ErrorMessage));
|
||||||
if (deletingUserId.HasValue)
|
|
||||||
{
|
|
||||||
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<Tuple<OrganizationUser, string>>();
|
private void ValidateRemoveUser(Guid organizationId, OrganizationUser orgUser)
|
||||||
var deletedUserIds = new List<Guid>();
|
|
||||||
foreach (var orgUser in filteredUsers)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("You cannot remove yourself.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (orgUser.Type == OrganizationUserType.Owner && deletingUserId.HasValue && !deletingUserIsOwner)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("Only owners can delete other owners.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Removed);
|
|
||||||
|
|
||||||
if (orgUser.UserId.HasValue)
|
|
||||||
{
|
|
||||||
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
|
||||||
}
|
|
||||||
result.Add(Tuple.Create(orgUser, ""));
|
|
||||||
deletedUserIds.Add(orgUser.Id);
|
|
||||||
}
|
|
||||||
catch (BadRequestException e)
|
|
||||||
{
|
|
||||||
result.Add(Tuple.Create(orgUser, e.Message));
|
|
||||||
}
|
|
||||||
|
|
||||||
await _organizationUserRepository.DeleteManyAsync(deletedUserIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ValidateDeleteUser(Guid organizationId, OrganizationUser orgUser)
|
|
||||||
{
|
{
|
||||||
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
if (orgUser == null || orgUser.OrganizationId != organizationId)
|
||||||
{
|
{
|
||||||
throw new NotFoundException("User not found.");
|
throw new NotFoundException(UserNotFoundErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RepositoryDeleteUserAsync(OrganizationUser orgUser, Guid? deletingUserId)
|
private async Task RepositoryRemoveUserAsync(OrganizationUser orgUser, Guid? deletingUserId, EventSystemUser? eventSystemUser)
|
||||||
{
|
{
|
||||||
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId.Value)
|
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId.Value)
|
||||||
{
|
{
|
||||||
throw new BadRequestException("You cannot remove yourself.");
|
throw new BadRequestException(RemoveYourselfErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orgUser.Type == OrganizationUserType.Owner)
|
if (orgUser.Type == OrganizationUserType.Owner)
|
||||||
{
|
{
|
||||||
if (deletingUserId.HasValue && !await _currentContext.OrganizationOwner(orgUser.OrganizationId))
|
if (deletingUserId.HasValue && !await _currentContext.OrganizationOwner(orgUser.OrganizationId))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Only owners can delete other owners.");
|
throw new BadRequestException(RemoveOwnerByNonOwnerErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, new[] { orgUser.Id }, includeProvider: true))
|
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, new[] { orgUser.Id }, includeProvider: true))
|
||||||
{
|
{
|
||||||
throw new BadRequestException("Organization must have at least one confirmed owner.");
|
throw new BadRequestException(RemoveLastConfirmedOwnerErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null)
|
||||||
|
{
|
||||||
|
var managementStatus = await _getOrganizationUsersManagementStatusQuery.GetUsersOrganizationManagementStatusAsync(orgUser.OrganizationId, new[] { orgUser.Id });
|
||||||
|
if (managementStatus.TryGetValue(orgUser.Id, out var isManaged) && isManaged)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(RemoveClaimedAccountErrorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,4 +174,70 @@ public class RemoveOrganizationUserCommand : IRemoveOrganizationUserCommand
|
|||||||
organizationId.ToString());
|
organizationId.ToString());
|
||||||
await _pushNotificationService.PushSyncOrgKeysAsync(userId);
|
await _pushNotificationService.PushSyncOrgKeysAsync(userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<IEnumerable<(OrganizationUser OrganizationUser, string ErrorMessage)>> RemoveUsersInternalAsync(
|
||||||
|
Guid organizationId, IEnumerable<Guid> organizationUsersId, Guid? deletingUserId, EventSystemUser? eventSystemUser)
|
||||||
|
{
|
||||||
|
var orgUsers = await _organizationUserRepository.GetManyAsync(organizationUsersId);
|
||||||
|
var filteredUsers = orgUsers.Where(u => u.OrganizationId == organizationId).ToList();
|
||||||
|
|
||||||
|
if (!filteredUsers.Any())
|
||||||
|
{
|
||||||
|
throw new BadRequestException(UsersInvalidErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await _hasConfirmedOwnersExceptQuery.HasConfirmedOwnersExceptAsync(organizationId, organizationUsersId))
|
||||||
|
{
|
||||||
|
throw new BadRequestException(RemoveLastConfirmedOwnerErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
var deletingUserIsOwner = false;
|
||||||
|
if (deletingUserId.HasValue)
|
||||||
|
{
|
||||||
|
deletingUserIsOwner = await _currentContext.OrganizationOwner(organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var managementStatus = _featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning) && deletingUserId.HasValue && eventSystemUser == null
|
||||||
|
? await _getOrganizationUsersManagementStatusQuery.GetUsersOrganizationManagementStatusAsync(organizationId, filteredUsers.Select(u => u.Id))
|
||||||
|
: filteredUsers.ToDictionary(u => u.Id, u => false);
|
||||||
|
var result = new List<(OrganizationUser OrganizationUser, string ErrorMessage)>();
|
||||||
|
foreach (var orgUser in filteredUsers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (deletingUserId.HasValue && orgUser.UserId == deletingUserId)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(RemoveYourselfErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orgUser.Type == OrganizationUserType.Owner && deletingUserId.HasValue && !deletingUserIsOwner)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(RemoveOwnerByNonOwnerErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (managementStatus.TryGetValue(orgUser.Id, out var isManaged) && isManaged)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(RemoveClaimedAccountErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Add((orgUser, string.Empty));
|
||||||
|
}
|
||||||
|
catch (BadRequestException e)
|
||||||
|
{
|
||||||
|
result.Add((orgUser, e.Message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var organizationUsersToRemove = result.Where(r => string.IsNullOrEmpty(r.ErrorMessage)).Select(r => r.OrganizationUser).ToList();
|
||||||
|
if (organizationUsersToRemove.Any())
|
||||||
|
{
|
||||||
|
await _organizationUserRepository.DeleteManyAsync(organizationUsersToRemove.Select(ou => ou.Id));
|
||||||
|
foreach (var orgUser in organizationUsersToRemove.Where(ou => ou.UserId.HasValue))
|
||||||
|
{
|
||||||
|
await DeleteAndPushUserRegistrationAsync(organizationId, orgUser.UserId.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,5 +8,5 @@ public class CommandResult(IEnumerable<string> errors)
|
|||||||
public bool HasErrors => ErrorMessages.Count > 0;
|
public bool HasErrors => ErrorMessages.Count > 0;
|
||||||
public List<string> ErrorMessages { get; } = errors.ToList();
|
public List<string> ErrorMessages { get; } = errors.ToList();
|
||||||
|
|
||||||
public CommandResult() : this([]) { }
|
public CommandResult() : this(Array.Empty<string>()) { }
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using Bit.Core.Services;
|
|||||||
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Microsoft.Extensions.Time.Testing;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@ -18,38 +19,93 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
|||||||
public class RemoveOrganizationUserCommandTests
|
public class RemoveOrganizationUserCommandTests
|
||||||
{
|
{
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_Success(
|
public async Task RemoveUser_WithDeletingUserId_Success(
|
||||||
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser deletingUser,
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser deletingUser,
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
||||||
|
|
||||||
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
||||||
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
|
|
||||||
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
|
|
||||||
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
|
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(deletingUser.Id)
|
||||||
|
.Returns(deletingUser);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(deletingUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
|
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
|
||||||
|
|
||||||
await organizationUserRepository.Received(1).DeleteAsync(organizationUser);
|
// Assert
|
||||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(organizationUser);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory, BitAutoData]
|
||||||
[BitAutoData]
|
public async Task RemoveUser_WithDeletingUserId_WithAccountDeprovisioningEnabled_Success(
|
||||||
public async Task RemoveUser_NotFound_ThrowsException(SutProvider<RemoveOrganizationUserCommand> sutProvider,
|
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser deletingUser,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(deletingUser.Id)
|
||||||
|
.Returns(deletingUser);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(deletingUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.Received(1)
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(
|
||||||
|
organizationUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)));
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(organizationUser);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUser_WithDeletingUserId_NotFound_ThrowsException(
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider,
|
||||||
Guid organizationId, Guid organizationUserId)
|
Guid organizationId, Guid organizationUserId)
|
||||||
{
|
{
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory, BitAutoData]
|
||||||
[BitAutoData]
|
public async Task RemoveUser_WithDeletingUserId_MismatchingOrganizationId_ThrowsException(
|
||||||
public async Task RemoveUser_MismatchingOrganizationId_ThrowsException(
|
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId)
|
||||||
{
|
{
|
||||||
|
// Arrange
|
||||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
.GetByIdAsync(organizationUserId)
|
.GetByIdAsync(organizationUserId)
|
||||||
.Returns(new OrganizationUser
|
.Returns(new OrganizationUser
|
||||||
@ -58,92 +114,231 @@ public class RemoveOrganizationUserCommandTests
|
|||||||
OrganizationId = Guid.NewGuid()
|
OrganizationId = Guid.NewGuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_InvalidUser_ThrowsException(
|
public async Task RemoveUser_WithDeletingUserId_InvalidUser_ThrowsException(
|
||||||
OrganizationUser organizationUser, OrganizationUser deletingUser,
|
OrganizationUser organizationUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.Id, deletingUser.UserId));
|
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.Id, null));
|
||||||
Assert.Contains("User not found.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.UserNotFoundErrorMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_RemoveYourself_ThrowsException(OrganizationUser deletingUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
public async Task RemoveUser_WithDeletingUserId_RemoveYourself_ThrowsException(
|
||||||
|
OrganizationUser deletingUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
|
.GetByIdAsync(deletingUser.Id)
|
||||||
|
.Returns(deletingUser);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId));
|
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, deletingUser.Id, deletingUser.UserId));
|
||||||
Assert.Contains("You cannot remove yourself.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveYourselfErrorMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_NonOwnerRemoveOwner_ThrowsException(
|
public async Task RemoveUser_WithDeletingUserId_NonOwnerRemoveOwner_ThrowsException(
|
||||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||||
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
|
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
|
||||||
|
|
||||||
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
||||||
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
|
|
||||||
currentContext.OrganizationAdmin(deletingUser.OrganizationId).Returns(true);
|
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationAdmin(organizationUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
|
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
|
||||||
Assert.Contains("Only owners can delete other owners.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveOwnerByNonOwnerErrorMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_RemovingLastOwner_ThrowsException(
|
public async Task RemoveUser_WithDeletingUserId_RemovingLastOwner_ThrowsException(
|
||||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||||
OrganizationUser deletingUser,
|
OrganizationUser deletingUser,
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
var hasConfirmedOwnersExceptQuery = sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>();
|
|
||||||
|
|
||||||
organizationUser.OrganizationId = deletingUser.OrganizationId;
|
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);
|
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(
|
||||||
|
organizationUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||||
|
Arg.Any<bool>())
|
||||||
|
.Returns(false);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(deletingUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(deletingUser.OrganizationId, organizationUser.Id, null));
|
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, deletingUser.UserId));
|
||||||
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||||
hasConfirmedOwnersExceptQuery
|
await sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.HasConfirmedOwnersExceptAsync(
|
.HasConfirmedOwnersExceptAsync(
|
||||||
organizationUser.OrganizationId,
|
organizationUser.OrganizationId,
|
||||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), true);
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUserAsync_WithDeletingUserId_WithAccountDeprovisioningEnabled_WhenUserIsManaged_ThrowsException(
|
||||||
|
[OrganizationUser(status: OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser,
|
||||||
|
Guid deletingUserId,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(orgUser.Id)
|
||||||
|
.Returns(orgUser);
|
||||||
|
sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(orgUser.OrganizationId, Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id)))
|
||||||
|
.Returns(new Dictionary<Guid, bool> { { orgUser.Id, true } });
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RemoveUserAsync(orgUser.OrganizationId, orgUser.Id, deletingUserId));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveClaimedAccountErrorMessage, exception.Message);
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.Received(1)
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(orgUser.OrganizationId, Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id)));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_WithEventSystemUser_Success(
|
public async Task RemoveUser_WithEventSystemUser_Success(
|
||||||
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||||
|
EventSystemUser eventSystemUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(organizationUser);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUser_WithEventSystemUser_WithAccountDeprovisioningEnabled_Success(
|
||||||
|
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||||
|
EventSystemUser eventSystemUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteAsync(organizationUser);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RemoveUser_WithEventSystemUser_NotFound_ThrowsException(
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider,
|
||||||
|
Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, eventSystemUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RemoveUser_WithEventSystemUser_MismatchingOrganizationId_ThrowsException(
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid organizationUserId, EventSystemUser eventSystemUser)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUserId)
|
||||||
|
.Returns(new OrganizationUser
|
||||||
|
{
|
||||||
|
Id = organizationUserId,
|
||||||
|
OrganizationId = Guid.NewGuid()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||||
|
await sutProvider.Sut.RemoveUserAsync(organizationId, organizationUserId, eventSystemUser));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUser_WithEventSystemUser_RemovingLastOwner_ThrowsException(
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||||
EventSystemUser eventSystemUser,
|
EventSystemUser eventSystemUser,
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(organizationUser.Id)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(
|
||||||
|
organizationUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||||
|
Arg.Any<bool>())
|
||||||
|
.Returns(false);
|
||||||
|
|
||||||
organizationUserRepository.GetByIdAsync(organizationUser.Id).Returns(organizationUser);
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
await sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser);
|
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.Id, eventSystemUser));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Removed, eventSystemUser);
|
await sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.Received(1)
|
||||||
|
.HasConfirmedOwnersExceptAsync(
|
||||||
|
organizationUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@ -170,23 +365,26 @@ public class RemoveOrganizationUserCommandTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_ByUserId_NotFound_ThrowsException(SutProvider<RemoveOrganizationUserCommand> sutProvider,
|
public async Task RemoveUser_ByUserId_NotFound_ThrowsException(
|
||||||
Guid organizationId, Guid userId)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider, Guid organizationId, Guid userId)
|
||||||
{
|
{
|
||||||
|
// Act & Assert
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, userId));
|
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RemoveUserAsync(organizationId, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUser_ByUserId_InvalidUser_ThrowsException(OrganizationUser organizationUser,
|
public async Task RemoveUser_ByUserId_InvalidUser_ThrowsException(
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
OrganizationUser organizationUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
organizationUserRepository.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value).Returns(organizationUser);
|
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||||
|
.Returns(organizationUser);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
var exception = await Assert.ThrowsAsync<NotFoundException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.UserId.Value));
|
() => sutProvider.Sut.RemoveUserAsync(Guid.NewGuid(), organizationUser.UserId.Value));
|
||||||
Assert.Contains("User not found.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.UserNotFoundErrorMessage, exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
@ -194,21 +392,22 @@ public class RemoveOrganizationUserCommandTests
|
|||||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser organizationUser,
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
var hasConfirmedOwnersExceptQuery = sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>();
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value)
|
||||||
organizationUserRepository.GetByOrganizationAsync(organizationUser.OrganizationId, organizationUser.UserId!.Value).Returns(organizationUser);
|
.Returns(organizationUser);
|
||||||
hasConfirmedOwnersExceptQuery
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
.HasConfirmedOwnersExceptAsync(
|
.HasConfirmedOwnersExceptAsync(
|
||||||
organizationUser.OrganizationId,
|
organizationUser.OrganizationId,
|
||||||
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.Id)),
|
||||||
Arg.Any<bool>())
|
Arg.Any<bool>())
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value));
|
() => sutProvider.Sut.RemoveUserAsync(organizationUser.OrganizationId, organizationUser.UserId.Value));
|
||||||
Assert.Contains("Organization must have at least one confirmed owner.", exception.Message);
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||||
hasConfirmedOwnersExceptQuery
|
await sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
.Received(1)
|
.Received(1)
|
||||||
.HasConfirmedOwnersExceptAsync(
|
.HasConfirmedOwnersExceptAsync(
|
||||||
organizationUser.OrganizationId,
|
organizationUser.OrganizationId,
|
||||||
@ -217,93 +416,371 @@ public class RemoveOrganizationUserCommandTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RemoveUsers_FilterInvalid_ThrowsException(OrganizationUser organizationUser, OrganizationUser deletingUser,
|
public async Task RemoveUsers_WithDeletingUserId_Success(
|
||||||
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(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
|
||||||
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2,
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2)
|
||||||
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
|
||||||
{
|
{
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
// Arrange
|
||||||
var currentContext = sutProvider.GetDependency<ICurrentContext>();
|
var sutProvider = SutProviderFactory();
|
||||||
|
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
|
||||||
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
|
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
|
||||||
var organizationUsers = new[] { orgUser1, orgUser2 };
|
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||||
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(organizationUsers);
|
|
||||||
organizationUserRepository.GetByIdAsync(deletingUser.Id).Returns(deletingUser);
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(deletingUser.Id)
|
||||||
|
.Returns(deletingUser);
|
||||||
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
currentContext.OrganizationOwner(deletingUser.OrganizationId).Returns(true);
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(deletingUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(
|
||||||
|
deletingUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)))
|
||||||
|
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, false }, { orgUser2.Id, false } });
|
||||||
|
|
||||||
await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count());
|
||||||
|
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventsAsync(
|
||||||
|
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, DateTime? DateTime)>>(i =>
|
||||||
|
i.First().OrganizationUser.Id == orgUser1.Id
|
||||||
|
&& i.Last().OrganizationUser.Id == orgUser2.Id
|
||||||
|
&& i.All(u => u.DateTime == eventDate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_WithAccountDeprovisioningEnabled_Success(
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser deletingUser,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1, OrganizationUser orgUser2)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sutProvider = SutProviderFactory();
|
||||||
|
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
|
||||||
|
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
|
||||||
|
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetByIdAsync(deletingUser.Id)
|
||||||
|
.Returns(deletingUser);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
|
.OrganizationOwner(deletingUser.OrganizationId)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(
|
||||||
|
deletingUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)))
|
||||||
|
.Returns(new Dictionary<Guid, bool> { { orgUser1.Id, false }, { orgUser2.Id, false } });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count());
|
||||||
|
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.Received(1)
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(
|
||||||
|
deletingUser.OrganizationId,
|
||||||
|
Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventsAsync(
|
||||||
|
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, DateTime? DateTime)>>(i =>
|
||||||
|
i.First().OrganizationUser.Id == orgUser1.Id
|
||||||
|
&& i.Last().OrganizationUser.Id == orgUser2.Id
|
||||||
|
&& i.All(u => u.DateTime == eventDate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_WithMismatchingOrganizationId_ThrowsException(OrganizationUser organizationUser,
|
||||||
|
OrganizationUser deletingUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizationUsers = new[] { organizationUser };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.UsersInvalidErrorMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_RemoveYourself_ThrowsException(
|
||||||
|
OrganizationUser deletingUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizationUsers = new[] { deletingUser };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveYourselfErrorMessage, result.First().ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_NonOwnerRemoveOwner_ThrowsException(
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed)] OrganizationUser orgUser2,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Admin)] OrganizationUser deletingUser,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
orgUser1.OrganizationId = orgUser2.OrganizationId = deletingUser.OrganizationId;
|
||||||
|
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(deletingUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(deletingUser.OrganizationId, organizationUserIds, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveOwnerByNonOwnerErrorMessage, result.First().ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_RemovingManagedUser_WithAccountDeprovisioningEnabled_ThrowsException(
|
||||||
|
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.User)] OrganizationUser orgUser,
|
||||||
|
OrganizationUser deletingUser,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
orgUser.OrganizationId = deletingUser.OrganizationId;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id)))
|
||||||
|
.Returns(new[] { orgUser });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(orgUser.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(orgUser.OrganizationId, Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser.Id)))
|
||||||
|
.Returns(new Dictionary<Guid, bool> { { orgUser.Id, true } });
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(orgUser.OrganizationId, new[] { orgUser.Id }, deletingUser.UserId);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.DeleteManyAsync(default);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.LogOrganizationUserEventsAsync(Arg.Any<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, DateTime? DateTime)>>());
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveClaimedAccountErrorMessage, result.First().ErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithDeletingUserId_LastOwner_ThrowsException(
|
||||||
|
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizationUsers = new[] { orgUser };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner)
|
||||||
|
.Returns(organizationUsers);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RemoveUsersAsync(orgUser.OrganizationId, organizationUserIds, null));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithEventSystemUser_Success(
|
||||||
|
EventSystemUser eventSystemUser,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
|
||||||
|
OrganizationUser orgUser2)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sutProvider = SutProviderFactory();
|
||||||
|
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
|
||||||
|
orgUser1.OrganizationId = orgUser2.OrganizationId;
|
||||||
|
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(orgUser1.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(orgUser1.OrganizationId, organizationUserIds, eventSystemUser);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count());
|
||||||
|
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventsAsync(
|
||||||
|
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, EventSystemUser EventSystemUser, DateTime? DateTime)>>(
|
||||||
|
i => i.First().OrganizationUser.Id == orgUser1.Id
|
||||||
|
&& i.Last().OrganizationUser.Id == orgUser2.Id
|
||||||
|
&& i.All(u => u.EventSystemUser == eventSystemUser
|
||||||
|
&& u.DateTime == eventDate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithEventSystemUser_WithAccountDeprovisioningEnabled_Success(
|
||||||
|
EventSystemUser eventSystemUser,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.Owner)] OrganizationUser orgUser1,
|
||||||
|
OrganizationUser orgUser2)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var sutProvider = SutProviderFactory();
|
||||||
|
var eventDate = sutProvider.GetDependency<FakeTimeProvider>().GetUtcNow().UtcDateTime;
|
||||||
|
orgUser1.OrganizationId = orgUser2.OrganizationId;
|
||||||
|
var organizationUsers = new[] { orgUser1, orgUser2 };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.AccountDeprovisioning)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IHasConfirmedOwnersExceptQuery>()
|
||||||
|
.HasConfirmedOwnersExceptAsync(orgUser1.OrganizationId, Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RemoveUsersAsync(orgUser1.OrganizationId, organizationUserIds, eventSystemUser);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(2, result.Count());
|
||||||
|
Assert.All(result, r => Assert.Empty(r.ErrorMessage));
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationUsersManagementStatusQuery>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetUsersOrganizationManagementStatusAsync(default, default);
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.DeleteManyAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(orgUser1.Id) && i.Contains(orgUser2.Id)));
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventsAsync(
|
||||||
|
Arg.Is<IEnumerable<(OrganizationUser OrganizationUser, EventType EventType, EventSystemUser EventSystemUser, DateTime? DateTime)>>(
|
||||||
|
i => i.First().OrganizationUser.Id == orgUser1.Id
|
||||||
|
&& i.Last().OrganizationUser.Id == orgUser2.Id
|
||||||
|
&& i.All(u => u.EventSystemUser == eventSystemUser
|
||||||
|
&& u.DateTime == eventDate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithEventSystemUser_WithMismatchingOrganizationId_ThrowsException(
|
||||||
|
EventSystemUser eventSystemUser,
|
||||||
|
[OrganizationUser(type: OrganizationUserType.User)] OrganizationUser organizationUser,
|
||||||
|
SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizationUsers = new[] { organizationUser };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RemoveUsersAsync(Guid.NewGuid(), organizationUserIds, eventSystemUser));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.UsersInvalidErrorMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RemoveUsers_WithEventSystemUser_LastOwner_ThrowsException(
|
||||||
|
[OrganizationUser(status: OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser orgUser,
|
||||||
|
EventSystemUser eventSystemUser, SutProvider<RemoveOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var organizationUsers = new[] { orgUser };
|
||||||
|
var organizationUserIds = organizationUsers.Select(u => u.Id);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyAsync(default)
|
||||||
|
.ReturnsForAnyArgs(organizationUsers);
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByOrganizationAsync(orgUser.OrganizationId, OrganizationUserType.Owner)
|
||||||
|
.Returns(organizationUsers);
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RemoveUsersAsync(orgUser.OrganizationId, organizationUserIds, eventSystemUser));
|
||||||
|
Assert.Contains(RemoveOrganizationUserCommand.RemoveLastConfirmedOwnerErrorMessage, exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a new SutProvider with a FakeTimeProvider registered in the Sut.
|
||||||
|
/// </summary>
|
||||||
|
private static SutProvider<RemoveOrganizationUserCommand> SutProviderFactory()
|
||||||
|
{
|
||||||
|
return new SutProvider<RemoveOrganizationUserCommand>()
|
||||||
|
.WithFakeTimeProvider()
|
||||||
|
.Create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
CREATE PROCEDURE [dbo].[OrganizationUser_SetStatusForUsersById]
|
CREATE OR ALTER PROCEDURE[dbo].[OrganizationUser_SetStatusForUsersById]
|
||||||
@OrganizationUserIds AS NVARCHAR(MAX),
|
@OrganizationUserIds AS NVARCHAR(MAX),
|
||||||
@Status SMALLINT
|
@Status SMALLINT
|
||||||
AS
|
AS
|
||||||
|
Loading…
Reference in New Issue
Block a user