1
0
mirror of https://github.com/bitwarden/server.git synced 2025-04-04 12:40:22 -05:00

[PM-12493] Extract ConfirmUser methods from OrganizationService into commands (#5505)

* 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 clarity
This commit is contained in:
Rui Tomé 2025-03-24 17:05:46 +00:00 committed by GitHub
parent d345937ecc
commit 24b63f2dcd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 548 additions and 434 deletions

View File

@ -60,6 +60,7 @@ public class OrganizationUsersController : Controller
private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IFeatureService _featureService;
private readonly IPricingClient _pricingClient;
private readonly IConfirmOrganizationUserCommand _confirmOrganizationUserCommand;
public OrganizationUsersController(
IOrganizationRepository organizationRepository,
@ -84,7 +85,8 @@ public class OrganizationUsersController : Controller
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
IPolicyRequirementQuery policyRequirementQuery,
IFeatureService featureService,
IPricingClient pricingClient)
IPricingClient pricingClient,
IConfirmOrganizationUserCommand confirmOrganizationUserCommand)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@ -109,6 +111,7 @@ public class OrganizationUsersController : Controller
_policyRequirementQuery = policyRequirementQuery;
_featureService = featureService;
_pricingClient = pricingClient;
_confirmOrganizationUserCommand = confirmOrganizationUserCommand;
}
[HttpGet("{id}")]
@ -308,7 +311,7 @@ public class OrganizationUsersController : Controller
await _organizationService.InitPendingOrganization(user.Id, orgId, organizationUserId, model.Keys.PublicKey, model.Keys.EncryptedPrivateKey, model.CollectionName);
await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService);
await _organizationService.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id);
await _confirmOrganizationUserCommand.ConfirmUserAsync(orgId, organizationUserId, model.Key, user.Id);
}
[HttpPost("{organizationUserId}/accept")]
@ -364,7 +367,7 @@ public class OrganizationUsersController : Controller
}
var userId = _userService.GetProperUserId(User);
var result = await _organizationService.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value);
var result = await _confirmOrganizationUserCommand.ConfirmUserAsync(orgGuidId, new Guid(id), model.Key, userId.Value);
}
[HttpPost("confirm")]
@ -378,7 +381,7 @@ public class OrganizationUsersController : Controller
}
var userId = _userService.GetProperUserId(User);
var results = await _organizationService.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value);
var results = await _confirmOrganizationUserCommand.ConfirmUsersAsync(orgGuidId, model.ToDictionary(), userId.Value);
return new ListResponseModel<OrganizationUserBulkResponseModel>(results.Select(r =>
new OrganizationUserBulkResponseModel(r.Item1.Id, r.Item2)));

View File

@ -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());
}
}

View File

@ -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);
}

View File

@ -38,9 +38,6 @@ public interface IOrganizationService
IEnumerable<(OrganizationUserInvite invite, string externalId)> invites);
Task<IEnumerable<Tuple<OrganizationUser, string>>> ResendInvitesAsync(Guid organizationId, Guid? invitingUserId, IEnumerable<Guid> organizationUsersId);
Task ResendInviteAsync(Guid organizationId, Guid? invitingUserId, Guid organizationUserId, bool initOrganization = false);
Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key, Guid confirmingUserId);
Task<List<Tuple<OrganizationUser, string>>> ConfirmUsersAsync(Guid organizationId, Dictionary<Guid, string> keys,
Guid confirmingUserId);
Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId);
Task ImportAsync(Guid organizationId, IEnumerable<ImportedGroup> groups,
IEnumerable<ImportedOrganizationUser> newUsers, IEnumerable<string> removeUserExternalIds,

View File

@ -1127,98 +1127,6 @@ public class OrganizationService : IOrganizationService
);
}
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 GetOrgById(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;
}
internal async Task<(bool canScale, string failureReason)> CanScaleAsync(
Organization organization,
int seatsToAdd)
@ -1305,32 +1213,7 @@ public class OrganizationService : IOrganizationService
}
}
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.");
}
}
public async Task UpdateUserResetPasswordEnrollmentAsync(Guid organizationId, Guid userId, string resetPasswordKey, Guid? callingUserId)
{
@ -1640,15 +1523,6 @@ public class OrganizationService : IOrganizationService
await _groupRepository.UpdateUsersAsync(group.Id, users);
}
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);

View File

@ -116,6 +116,7 @@ public static class OrganizationServiceCollectionExtensions
services.AddScoped<IUpdateOrganizationUserCommand, UpdateOrganizationUserCommand>();
services.AddScoped<IUpdateOrganizationUserGroupsCommand, UpdateOrganizationUserGroupsCommand>();
services.AddScoped<IDeleteManagedOrganizationUserAccountCommand, DeleteManagedOrganizationUserAccountCommand>();
services.AddScoped<IConfirmOrganizationUserCommand, ConfirmOrganizationUserCommand>();
}
private static void AddOrganizationApiKeyCommandsQueries(this IServiceCollection services)

View File

@ -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);
}
}

View File

@ -24,7 +24,6 @@ using Bit.Core.Platform.Push;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Settings;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
using Bit.Core.Tokens;
@ -978,306 +977,6 @@ OrganizationUserInvite invite, SutProvider<OrganizationService> sutProvider)
sutProvider.GetDependency<ICurrentContext>().ManageUsers(organization.Id).Returns(true);
}
[Theory, BitAutoData]
public async Task ConfirmUser_InvalidStatus(OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Invited)] OrganizationUser orgUser, string key,
SutProvider<OrganizationService> 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 ConfirmUser_WrongOrganization(OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, string key,
SutProvider<OrganizationService> 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 ConfirmUserToFree_AlreadyFreeAdminOrOwner_Throws(OrganizationUserType userType, Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<OrganizationService> 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 ConfirmUserToNonFree_AlreadyFreeAdminOrOwner_DoesNotThrow(PlanType planType, OrganizationUserType orgUserType, Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<OrganizationService> 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 ConfirmUser_AsUser_SingleOrgPolicy_AppliedFromConfirmingOrg_Throws(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg, [OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy,
string key, SutProvider<OrganizationService> 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 ConfirmUser_AsUser_SingleOrgPolicy_AppliedFromOtherOrg_Throws(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg, [OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy,
string key, SutProvider<OrganizationService> 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 ConfirmUser_AsOwnerOrAdmin_SingleOrgPolicy_ExcludedViaUserType_Success(
OrganizationUserType userType, Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg,
string key, SutProvider<OrganizationService> 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 ConfirmUser_TwoFactorPolicy_NotEnabled_Throws(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser orgUserAnotherOrg,
[OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy,
string key, SutProvider<OrganizationService> 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 ConfirmUser_TwoFactorPolicy_Enabled_Success(Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
[OrganizationUserPolicyDetails(PolicyType.TwoFactorAuthentication)] OrganizationUserPolicyDetails twoFactorPolicy,
string key, SutProvider<OrganizationService> 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 ConfirmUsers_Success(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<OrganizationService> 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);
}
[Theory, BitAutoData]
public async Task UpdateOrganizationKeysAsync_WithoutManageResetPassword_Throws(Guid orgId, string publicKey,
string privateKey, SutProvider<OrganizationService> sutProvider)