Browse Source
* Add ConfirmOrganizationUserCommand and IConfirmOrganizationUserCommand interface for managing organization user confirmations * Add unit tests for ConfirmOrganizationUserCommand to validate user confirmation scenarios * Register ConfirmOrganizationUserCommand for dependency injection * Refactor OrganizationUsersController to utilize IConfirmOrganizationUserCommand for user confirmation processes * Remove ConfirmUserAsync and ConfirmUsersAsync methods from IOrganizationService and OrganizationService * Rename test methods in ConfirmOrganizationUserCommandTests for clarity and consistency * Update test method name in ConfirmOrganizationUserCommandTests for improved claritypull/5550/head
8 changed files with 548 additions and 434 deletions
@ -0,0 +1,186 @@
@@ -0,0 +1,186 @@
|
||||
using Bit.Core.AdminConsole.Enums; |
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; |
||||
using Bit.Core.AdminConsole.Services; |
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; |
||||
using Bit.Core.Billing.Enums; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Platform.Push; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.Services; |
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; |
||||
|
||||
public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand |
||||
{ |
||||
private readonly IOrganizationRepository _organizationRepository; |
||||
private readonly IOrganizationUserRepository _organizationUserRepository; |
||||
private readonly IUserRepository _userRepository; |
||||
private readonly IEventService _eventService; |
||||
private readonly IMailService _mailService; |
||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery; |
||||
private readonly IPushNotificationService _pushNotificationService; |
||||
private readonly IPushRegistrationService _pushRegistrationService; |
||||
private readonly IPolicyService _policyService; |
||||
private readonly IDeviceRepository _deviceRepository; |
||||
|
||||
public ConfirmOrganizationUserCommand( |
||||
IOrganizationRepository organizationRepository, |
||||
IOrganizationUserRepository organizationUserRepository, |
||||
IUserRepository userRepository, |
||||
IEventService eventService, |
||||
IMailService mailService, |
||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, |
||||
IPushNotificationService pushNotificationService, |
||||
IPushRegistrationService pushRegistrationService, |
||||
IPolicyService policyService, |
||||
IDeviceRepository deviceRepository) |
||||
{ |
||||
_organizationRepository = organizationRepository; |
||||
_organizationUserRepository = organizationUserRepository; |
||||
_userRepository = userRepository; |
||||
_eventService = eventService; |
||||
_mailService = mailService; |
||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery; |
||||
_pushNotificationService = pushNotificationService; |
||||
_pushRegistrationService = pushRegistrationService; |
||||
_policyService = policyService; |
||||
_deviceRepository = deviceRepository; |
||||
} |
||||
|
||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, |
||||
Guid confirmingUserId) |
||||
{ |
||||
var result = await ConfirmUsersAsync( |
||||
organizationId, |
||||
new Dictionary<Guid, string>() { { organizationUserId, key } }, |
||||
confirmingUserId); |
||||
|
||||
if (!result.Any()) |
||||
{ |
||||
throw new BadRequestException("User not valid."); |
||||
} |
||||
|
||||
var (orgUser, error) = result[0]; |
||||
if (error != "") |
||||
{ |
||||
throw new BadRequestException(error); |
||||
} |
||||
return orgUser; |
||||
} |
||||
|
||||
public async Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys, |
||||
Guid confirmingUserId) |
||||
{ |
||||
var selectedOrganizationUsers = await _organizationUserRepository.GetManyAsync(keys.Keys); |
||||
var validSelectedOrganizationUsers = selectedOrganizationUsers |
||||
.Where(u => u.Status == OrganizationUserStatusType.Accepted && u.OrganizationId == organizationId && u.UserId != null) |
||||
.ToList(); |
||||
|
||||
if (!validSelectedOrganizationUsers.Any()) |
||||
{ |
||||
return new List<Tuple<OrganizationUser, string>>(); |
||||
} |
||||
|
||||
var validSelectedUserIds = validSelectedOrganizationUsers.Select(u => u.UserId.Value).ToList(); |
||||
|
||||
var organization = await _organizationRepository.GetByIdAsync(organizationId); |
||||
var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds); |
||||
var users = await _userRepository.GetManyAsync(validSelectedUserIds); |
||||
var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds); |
||||
|
||||
var keyedFilteredUsers = validSelectedOrganizationUsers.ToDictionary(u => u.UserId.Value, u => u); |
||||
var keyedOrganizationUsers = allUsersOrgs.GroupBy(u => u.UserId.Value) |
||||
.ToDictionary(u => u.Key, u => u.ToList()); |
||||
|
||||
var succeededUsers = new List<OrganizationUser>(); |
||||
var result = new List<Tuple<OrganizationUser, string>>(); |
||||
|
||||
foreach (var user in users) |
||||
{ |
||||
if (!keyedFilteredUsers.ContainsKey(user.Id)) |
||||
{ |
||||
continue; |
||||
} |
||||
var orgUser = keyedFilteredUsers[user.Id]; |
||||
var orgUsers = keyedOrganizationUsers.GetValueOrDefault(user.Id, new List<OrganizationUser>()); |
||||
try |
||||
{ |
||||
if (organization.PlanType == PlanType.Free && (orgUser.Type == OrganizationUserType.Admin |
||||
|| orgUser.Type == OrganizationUserType.Owner)) |
||||
{ |
||||
// Since free organizations only supports a few users there is not much point in avoiding N+1 queries for this. |
||||
var adminCount = await _organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(user.Id); |
||||
if (adminCount > 0) |
||||
{ |
||||
throw new BadRequestException("User can only be an admin of one free organization."); |
||||
} |
||||
} |
||||
|
||||
var twoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled; |
||||
await CheckPoliciesAsync(organizationId, user, orgUsers, twoFactorEnabled); |
||||
orgUser.Status = OrganizationUserStatusType.Confirmed; |
||||
orgUser.Key = keys[orgUser.Id]; |
||||
orgUser.Email = null; |
||||
|
||||
await _eventService.LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); |
||||
await _mailService.SendOrganizationConfirmedEmailAsync(organization.DisplayName(), user.Email, orgUser.AccessSecretsManager); |
||||
await DeleteAndPushUserRegistrationAsync(organizationId, user.Id); |
||||
succeededUsers.Add(orgUser); |
||||
result.Add(Tuple.Create(orgUser, "")); |
||||
} |
||||
catch (BadRequestException e) |
||||
{ |
||||
result.Add(Tuple.Create(orgUser, e.Message)); |
||||
} |
||||
} |
||||
|
||||
await _organizationUserRepository.ReplaceManyAsync(succeededUsers); |
||||
|
||||
return result; |
||||
} |
||||
|
||||
private async Task CheckPoliciesAsync(Guid organizationId, User user, |
||||
ICollection<OrganizationUser> userOrgs, bool twoFactorEnabled) |
||||
{ |
||||
// Enforce Two Factor Authentication Policy for this organization |
||||
var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication)) |
||||
.Any(p => p.OrganizationId == organizationId); |
||||
if (orgRequiresTwoFactor && !twoFactorEnabled) |
||||
{ |
||||
throw new BadRequestException("User does not have two-step login enabled."); |
||||
} |
||||
|
||||
var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId); |
||||
var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg); |
||||
var otherSingleOrgPolicies = |
||||
singleOrgPolicies.Where(p => p.OrganizationId != organizationId); |
||||
// Enforce Single Organization Policy for this organization |
||||
if (hasOtherOrgs && singleOrgPolicies.Any(p => p.OrganizationId == organizationId)) |
||||
{ |
||||
throw new BadRequestException("Cannot confirm this member to the organization until they leave or remove all other organizations."); |
||||
} |
||||
// Enforce Single Organization Policy of other organizations user is a member of |
||||
if (otherSingleOrgPolicies.Any()) |
||||
{ |
||||
throw new BadRequestException("Cannot confirm this member to the organization because they are in another organization which forbids it."); |
||||
} |
||||
} |
||||
|
||||
private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId) |
||||
{ |
||||
var devices = await GetUserDeviceIdsAsync(userId); |
||||
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(devices, |
||||
organizationId.ToString()); |
||||
await _pushNotificationService.PushSyncOrgKeysAsync(userId); |
||||
} |
||||
|
||||
private async Task<IEnumerable<string>> GetUserDeviceIdsAsync(Guid userId) |
||||
{ |
||||
var devices = await _deviceRepository.GetManyByUserIdAsync(userId); |
||||
return devices |
||||
.Where(d => !string.IsNullOrWhiteSpace(d.PushToken)) |
||||
.Select(d => d.Id.ToString()); |
||||
} |
||||
} |
||||
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
using Bit.Core.Entities; |
||||
|
||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; |
||||
|
||||
/// <summary> |
||||
/// Command to confirm organization users who have accepted their invitations. |
||||
/// </summary> |
||||
public interface IConfirmOrganizationUserCommand |
||||
{ |
||||
/// <summary> |
||||
/// Confirms a single organization user who has accepted their invitation. |
||||
/// </summary> |
||||
/// <param name="organizationId">The ID of the organization.</param> |
||||
/// <param name="organizationUserId">The ID of the organization user to confirm.</param> |
||||
/// <param name="key">The encrypted organization key for the user.</param> |
||||
/// <param name="confirmingUserId">The ID of the user performing the confirmation.</param> |
||||
/// <returns>The confirmed organization user.</returns> |
||||
/// <exception cref="BadRequestException">Thrown when the user is not valid or cannot be confirmed.</exception> |
||||
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId); |
||||
|
||||
/// <summary> |
||||
/// Confirms multiple organization users who have accepted their invitations. |
||||
/// </summary> |
||||
/// <param name="organizationId">The ID of the organization.</param> |
||||
/// <param name="keys">A dictionary mapping organization user IDs to their encrypted organization keys.</param> |
||||
/// <param name="confirmingUserId">The ID of the user performing the confirmation.</param> |
||||
/// <returns>A list of tuples containing the organization user and an error message (if any).</returns> |
||||
Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys, |
||||
Guid confirmingUserId); |
||||
} |
||||
@ -0,0 +1,324 @@
@@ -0,0 +1,324 @@
|
||||
using Bit.Core.AdminConsole.Entities; |
||||
using Bit.Core.AdminConsole.Enums; |
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers; |
||||
using Bit.Core.AdminConsole.Services; |
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces; |
||||
using Bit.Core.Billing.Enums; |
||||
using Bit.Core.Entities; |
||||
using Bit.Core.Enums; |
||||
using Bit.Core.Exceptions; |
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers; |
||||
using Bit.Core.Repositories; |
||||
using Bit.Core.Services; |
||||
using Bit.Core.Test.AdminConsole.AutoFixture; |
||||
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 ConfirmOrganizationUserCommandTests |
||||
{ |
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_WithInvalidStatus_ThrowsBadRequestException(OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Invited)] OrganizationUser orgUser, string key, |
||||
SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
|
||||
organizationUserRepository.GetByIdAsync(orgUser.Id).Returns(orgUser); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("User not valid.", exception.Message); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_WithWrongOrganization_ThrowsBadRequestException(OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, string key, |
||||
SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
|
||||
organizationUserRepository.GetByIdAsync(orgUser.Id).Returns(orgUser); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(confirmingUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("User not valid.", exception.Message); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(OrganizationUserType.Admin)] |
||||
[BitAutoData(OrganizationUserType.Owner)] |
||||
public async Task ConfirmUserAsync_ToFree_WithExistingAdminOrOwner_ThrowsBadRequestException(OrganizationUserType userType, Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
|
||||
org.PlanType = PlanType.Free; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = user.Id; |
||||
orgUser.Type = userType; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(orgUser.UserId.Value).Returns(1); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("User can only be an admin of one free organization.", exception.Message); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(PlanType.Custom, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.Custom, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually2020, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually2020, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually2019, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseAnnually2019, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly2020, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly2020, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly2019, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.EnterpriseMonthly2019, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.FamiliesAnnually, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.FamiliesAnnually, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.FamiliesAnnually2019, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.FamiliesAnnually2019, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsAnnually, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsAnnually, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsAnnually2020, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsAnnually2020, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsAnnually2019, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsAnnually2019, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsMonthly, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsMonthly, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsMonthly2020, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsMonthly2020, OrganizationUserType.Owner)] |
||||
[BitAutoData(PlanType.TeamsMonthly2019, OrganizationUserType.Admin)] |
||||
[BitAutoData(PlanType.TeamsMonthly2019, OrganizationUserType.Owner)] |
||||
public async Task ConfirmUserAsync_ToNonFree_WithExistingFreeAdminOrOwner_Succeeds(PlanType planType, OrganizationUserType orgUserType, Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
|
||||
org.PlanType = planType; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = user.Id; |
||||
orgUser.Type = orgUserType; |
||||
orgUser.AccessSecretsManager = false; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetCountByFreeOrganizationAdminUserAsync(orgUser.UserId.Value).Returns(1); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id); |
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); |
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email); |
||||
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1)); |
||||
} |
||||
|
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_AsUser_WithSingleOrgPolicyAppliedFromConfirmingOrg_ThrowsBadRequestException(Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
OrganizationUser orgUserAnotherOrg, [OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
var policyService = sutProvider.GetDependency<IPolicyService>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser.Status = OrganizationUserStatusType.Accepted; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg }); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
singleOrgPolicy.OrganizationId = org.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg).Returns(new[] { singleOrgPolicy }); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", exception.Message); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_AsUser_WithSingleOrgPolicyAppliedFromOtherOrg_ThrowsBadRequestException(Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
OrganizationUser orgUserAnotherOrg, [OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
var policyService = sutProvider.GetDependency<IPolicyService>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser.Status = OrganizationUserStatusType.Accepted; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg }); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
singleOrgPolicy.OrganizationId = orgUserAnotherOrg.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg).Returns(new[] { singleOrgPolicy }); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("Cannot confirm this member to the organization because they are in another organization which forbids it.", exception.Message); |
||||
} |
||||
|
||||
[Theory] |
||||
[BitAutoData(OrganizationUserType.Admin)] |
||||
[BitAutoData(OrganizationUserType.Owner)] |
||||
public async Task ConfirmUserAsync_AsOwnerOrAdmin_WithSingleOrgPolicy_ExcludedViaUserType_Success( |
||||
OrganizationUserType userType, Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
OrganizationUser orgUserAnotherOrg, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser.Type = userType; |
||||
orgUser.Status = OrganizationUserStatusType.Accepted; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id; |
||||
orgUser.AccessSecretsManager = true; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg }); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id); |
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed); |
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, true); |
||||
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1)); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_WithTwoFactorPolicyAndTwoFactorDisabled_ThrowsBadRequestException(Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
OrganizationUser orgUserAnotherOrg, |
||||
[OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
var policyService = sutProvider.GetDependency<IPolicyService>(); |
||||
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = orgUserAnotherOrg.UserId = user.Id; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationUserRepository.GetManyByManyUsersAsync(default).ReturnsForAnyArgs(new[] { orgUserAnotherOrg }); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
twoFactorPolicy.OrganizationId = org.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); |
||||
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id))) |
||||
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, false) }); |
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>( |
||||
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id)); |
||||
Assert.Contains("User does not have two-step login enabled.", exception.Message); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUserAsync_WithTwoFactorPolicyAndTwoFactorEnabled_Succeeds(Organization org, OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user, |
||||
[OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
var policyService = sutProvider.GetDependency<IPolicyService>(); |
||||
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser.UserId = user.Id; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser }); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user }); |
||||
twoFactorPolicy.OrganizationId = org.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); |
||||
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id))) |
||||
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, true) }); |
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id); |
||||
} |
||||
|
||||
[Theory, BitAutoData] |
||||
public async Task ConfirmUsersAsync_WithMultipleUsers_ReturnsExpectedMixedResults(Organization org, |
||||
OrganizationUser confirmingUser, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser1, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser2, |
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser3, |
||||
OrganizationUser anotherOrgUser, User user1, User user2, User user3, |
||||
[OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy, |
||||
[OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy, |
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider) |
||||
{ |
||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>(); |
||||
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>(); |
||||
var userRepository = sutProvider.GetDependency<IUserRepository>(); |
||||
var policyService = sutProvider.GetDependency<IPolicyService>(); |
||||
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(); |
||||
|
||||
org.PlanType = PlanType.EnterpriseAnnually; |
||||
orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = confirmingUser.OrganizationId = org.Id; |
||||
orgUser1.UserId = user1.Id; |
||||
orgUser2.UserId = user2.Id; |
||||
orgUser3.UserId = user3.Id; |
||||
anotherOrgUser.UserId = user3.Id; |
||||
var orgUsers = new[] { orgUser1, orgUser2, orgUser3 }; |
||||
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(orgUsers); |
||||
organizationRepository.GetByIdAsync(org.Id).Returns(org); |
||||
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user1, user2, user3 }); |
||||
twoFactorPolicy.OrganizationId = org.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(Arg.Any<Guid>(), PolicyType.TwoFactorAuthentication).Returns(new[] { twoFactorPolicy }); |
||||
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user1.Id) && ids.Contains(user2.Id) && ids.Contains(user3.Id))) |
||||
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() |
||||
{ |
||||
(user1.Id, true), |
||||
(user2.Id, false), |
||||
(user3.Id, true) |
||||
}); |
||||
singleOrgPolicy.OrganizationId = org.Id; |
||||
policyService.GetPoliciesApplicableToUserAsync(user3.Id, PolicyType.SingleOrg) |
||||
.Returns(new[] { singleOrgPolicy }); |
||||
organizationUserRepository.GetManyByManyUsersAsync(default) |
||||
.ReturnsForAnyArgs(new[] { orgUser1, orgUser2, orgUser3, anotherOrgUser }); |
||||
|
||||
var keys = orgUsers.ToDictionary(ou => ou.Id, _ => key); |
||||
var result = await sutProvider.Sut.ConfirmUsersAsync(confirmingUser.OrganizationId, keys, confirmingUser.Id); |
||||
Assert.Contains("", result[0].Item2); |
||||
Assert.Contains("User does not have two-step login enabled.", result[1].Item2); |
||||
Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", result[2].Item2); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue