1
0
mirror of https://github.com/bitwarden/server synced 2025-12-21 02:33:30 +00:00

[PM-27131] Auto confirm policy requirement (#6649)

* Added Auto confirm policy enforcement requirement. Includes strict single org enforcement along with blocking provider users from joining orgs with auto confirm enabled.
This commit is contained in:
Jared McCannon
2025-12-15 15:40:00 -06:00
committed by GitHub
parent bead4f1d5a
commit e646b91a50
20 changed files with 1488 additions and 238 deletions

View File

@@ -9,6 +9,9 @@ using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business.Provider;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Enums;
@@ -60,6 +63,7 @@ public class ProviderService : IProviderService
private readonly IProviderBillingService _providerBillingService;
private readonly IPricingClient _pricingClient;
private readonly IProviderClientOrganizationSignUpCommand _providerClientOrganizationSignUpCommand;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
public ProviderService(IProviderRepository providerRepository, IProviderUserRepository providerUserRepository,
IProviderOrganizationRepository providerOrganizationRepository, IUserRepository userRepository,
@@ -69,7 +73,8 @@ public class ProviderService : IProviderService
ICurrentContext currentContext, IStripeAdapter stripeAdapter, IFeatureService featureService,
IDataProtectorTokenFactory<ProviderDeleteTokenable> providerDeleteTokenDataFactory,
IApplicationCacheService applicationCacheService, IProviderBillingService providerBillingService, IPricingClient pricingClient,
IProviderClientOrganizationSignUpCommand providerClientOrganizationSignUpCommand)
IProviderClientOrganizationSignUpCommand providerClientOrganizationSignUpCommand,
IPolicyRequirementQuery policyRequirementQuery)
{
_providerRepository = providerRepository;
_providerUserRepository = providerUserRepository;
@@ -90,6 +95,7 @@ public class ProviderService : IProviderService
_providerBillingService = providerBillingService;
_pricingClient = pricingClient;
_providerClientOrganizationSignUpCommand = providerClientOrganizationSignUpCommand;
_policyRequirementQuery = policyRequirementQuery;
}
public async Task<Provider> CompleteSetupAsync(Provider provider, Guid ownerUserId, string token, string key, TokenizedPaymentMethod paymentMethod, BillingAddress billingAddress)
@@ -117,6 +123,18 @@ public class ProviderService : IProviderService
throw new BadRequestException("Invalid owner.");
}
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var organizationAutoConfirmPolicyRequirement = await _policyRequirementQuery
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerUserId);
if (organizationAutoConfirmPolicyRequirement
.CannotCreateProvider())
{
throw new BadRequestException(new UserCannotJoinProvider().Message);
}
}
var customer = await _providerBillingService.SetupCustomer(provider, paymentMethod, billingAddress);
provider.GatewayCustomerId = customer.Id;
var subscription = await _providerBillingService.SetupSubscription(provider);
@@ -249,6 +267,18 @@ public class ProviderService : IProviderService
throw new BadRequestException("User email does not match invite.");
}
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var organizationAutoConfirmPolicyRequirement = await _policyRequirementQuery
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);
if (organizationAutoConfirmPolicyRequirement
.CannotJoinProvider())
{
throw new BadRequestException(new UserCannotJoinProvider().Message);
}
}
providerUser.Status = ProviderUserStatusType.Accepted;
providerUser.UserId = user.Id;
providerUser.Email = null;
@@ -294,6 +324,19 @@ public class ProviderService : IProviderService
throw new BadRequestException("Invalid user.");
}
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var organizationAutoConfirmPolicyRequirement = await _policyRequirementQuery
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);
if (organizationAutoConfirmPolicyRequirement
.CannotJoinProvider())
{
result.Add(Tuple.Create(providerUser, new UserCannotJoinProvider().Message));
continue;
}
}
providerUser.Status = ProviderUserStatusType.Confirmed;
providerUser.Key = keys[providerUser.Id];
providerUser.Email = null;

View File

@@ -1,12 +1,17 @@
using Bit.Commercial.Core.AdminConsole.Services;
using Bit.Commercial.Core.Test.AdminConsole.AutoFixture;
using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Business.Provider;
using Bit.Core.AdminConsole.Models.Business.Tokenables;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Payment.Models;
@@ -101,6 +106,57 @@ public class ProviderServiceTests
.ReplaceAsync(Arg.Is<ProviderUser>(pu => pu.UserId == user.Id && pu.ProviderId == provider.Id && pu.Key == key));
}
[Theory, BitAutoData]
public async Task CompleteSetupAsync_WithAutoConfirmEnabled_ThrowsUserCannotJoinProviderError(User user, Provider provider,
string key,
TokenizedPaymentMethod tokenizedPaymentMethod, BillingAddress billingAddress,
[ProviderUser] ProviderUser providerUser,
SutProvider<ProviderService> sutProvider)
{
providerUser.ProviderId = provider.Id;
providerUser.UserId = user.Id;
var userService = sutProvider.GetDependency<IUserService>();
userService.GetUserByIdAsync(user.Id).Returns(user);
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetByProviderUserAsync(provider.Id, user.Id).Returns(providerUser);
var dataProtectionProvider = DataProtectionProvider.Create("ApplicationName");
var protector = dataProtectionProvider.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>().CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
var providerBillingService = sutProvider.GetDependency<IProviderBillingService>();
var customer = new Customer { Id = "customer_id" };
providerBillingService.SetupCustomer(provider, tokenizedPaymentMethod, billingAddress).Returns(customer);
var subscription = new Subscription { Id = "subscription_id" };
providerBillingService.SetupSubscription(provider).Returns(subscription);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
var policyDetails = new List<PolicyDetails> { new() { OrganizationId = Guid.NewGuid(), IsProvider = false } };
var policyRequirement = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(policyRequirement);
sutProvider.Create();
var token = protector.Protect(
$"ProviderSetupInvite {provider.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.CompleteSetupAsync(provider, user.Id, token, key, tokenizedPaymentMethod,
billingAddress));
Assert.Equal(new UserCannotJoinProvider().Message, exception.Message);
}
[Theory, BitAutoData]
public async Task UpdateAsync_ProviderIdIsInvalid_Throws(Provider provider, SutProvider<ProviderService> sutProvider)
{
@@ -580,6 +636,132 @@ public class ProviderServiceTests
Assert.Equal(user.Id, pu.UserId);
}
[Theory, BitAutoData]
public async Task AcceptUserAsync_WithAutoConfirmEnabledAndPolicyExists_Throws(
[ProviderUser(ProviderUserStatusType.Invited)] ProviderUser providerUser,
User user,
SutProvider<ProviderService> sutProvider)
{
// Arrange
sutProvider.GetDependency<IProviderUserRepository>()
.GetByIdAsync(providerUser.Id)
.Returns(providerUser);
var protector = DataProtectionProvider
.Create("ApplicationName")
.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>()
.CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
providerUser.Email = user.Email;
var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
var policyDetails = new List<PolicyDetails>
{
new() { OrganizationId = Guid.NewGuid(), IsProvider = false }
};
var policyRequirement = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(policyRequirement);
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token));
Assert.Equal(new UserCannotJoinProvider().Message, exception.Message);
}
[Theory, BitAutoData]
public async Task AcceptUserAsync_WithAutoConfirmEnabledButNoPolicyExists_Success(
[ProviderUser(ProviderUserStatusType.Invited)] ProviderUser providerUser,
User user,
SutProvider<ProviderService> sutProvider)
{
// Arrange
sutProvider.GetDependency<IProviderUserRepository>()
.GetByIdAsync(providerUser.Id)
.Returns(providerUser);
var protector = DataProtectionProvider
.Create("ApplicationName")
.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>()
.CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
providerUser.Email = user.Email;
var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
var policyRequirement = new AutomaticUserConfirmationPolicyRequirement([]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(policyRequirement);
// Act
var pu = await sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token);
// Assert
Assert.Null(pu.Email);
Assert.Equal(ProviderUserStatusType.Accepted, pu.Status);
Assert.Equal(user.Id, pu.UserId);
}
[Theory, BitAutoData]
public async Task AcceptUserAsync_WithAutoConfirmDisabled_Success(
[ProviderUser(ProviderUserStatusType.Invited)] ProviderUser providerUser,
User user,
SutProvider<ProviderService> sutProvider)
{
// Arrange
sutProvider.GetDependency<IProviderUserRepository>()
.GetByIdAsync(providerUser.Id)
.Returns(providerUser);
var protector = DataProtectionProvider
.Create("ApplicationName")
.CreateProtector("ProviderServiceDataProtector");
sutProvider.GetDependency<IDataProtectionProvider>()
.CreateProtector("ProviderServiceDataProtector")
.Returns(protector);
sutProvider.Create();
providerUser.Email = user.Email;
var token = protector.Protect($"ProviderUserInvite {providerUser.Id} {user.Email} {CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow)}");
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(false);
// Act
var pu = await sutProvider.Sut.AcceptUserAsync(providerUser.Id, user, token);
// Assert
Assert.Null(pu.Email);
Assert.Equal(ProviderUserStatusType.Accepted, pu.Status);
Assert.Equal(user.Id, pu.UserId);
// Verify that policy check was never called when feature flag is disabled
await sutProvider.GetDependency<IPolicyRequirementQuery>()
.DidNotReceive()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id);
}
[Theory, BitAutoData]
public async Task ConfirmUsersAsync_NoValid(
[ProviderUser(ProviderUserStatusType.Invited)] ProviderUser pu1,
@@ -626,13 +808,131 @@ public class ProviderServiceTests
Assert.Equal("Invalid user.", result[2].Item2);
}
[Theory, BitAutoData]
public async Task ConfirmUsersAsync_WithAutoConfirmEnabledAndPolicyExists_ReturnsError(
[ProviderUser(ProviderUserStatusType.Accepted)] ProviderUser pu1, User u1,
Provider provider, User confirmingUser, SutProvider<ProviderService> sutProvider)
{
// Arrange
pu1.ProviderId = provider.Id;
pu1.UserId = u1.Id;
var providerUsers = new[] { pu1 };
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync([]).ReturnsForAnyArgs(providerUsers);
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([u1]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
var policyDetails = new List<PolicyDetails>
{
new() { OrganizationId = Guid.NewGuid(), IsProvider = false }
};
var policyRequirement = new AutomaticUserConfirmationPolicyRequirement(policyDetails);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(u1.Id)
.Returns(policyRequirement);
var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key");
// Act
var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, confirmingUser.Id);
// Assert
Assert.Single(result);
Assert.Equal(new UserCannotJoinProvider().Message, result[0].Item2);
// Verify user was not confirmed
await providerUserRepository.DidNotReceive().ReplaceAsync(Arg.Any<ProviderUser>());
}
[Theory, BitAutoData]
public async Task ConfirmUsersAsync_WithAutoConfirmEnabledButNoPolicyExists_Success(
[ProviderUser(ProviderUserStatusType.Accepted)] ProviderUser pu1, User u1,
Provider provider, User confirmingUser, SutProvider<ProviderService> sutProvider)
{
// Arrange
pu1.ProviderId = provider.Id;
pu1.UserId = u1.Id;
var providerUsers = new[] { pu1 };
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync([]).ReturnsForAnyArgs(providerUsers);
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([u1]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
var policyRequirement = new AutomaticUserConfirmationPolicyRequirement(new List<PolicyDetails>());
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(u1.Id)
.Returns(policyRequirement);
var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key");
// Act
var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, confirmingUser.Id);
// Assert
Assert.Single(result);
Assert.Equal("", result[0].Item2);
// Verify user was confirmed
await providerUserRepository.Received(1).ReplaceAsync(Arg.Is<ProviderUser>(pu =>
pu.Status == ProviderUserStatusType.Confirmed));
}
[Theory, BitAutoData]
public async Task ConfirmUsersAsync_WithAutoConfirmDisabled_Success(
[ProviderUser(ProviderUserStatusType.Accepted)] ProviderUser pu1, User u1,
Provider provider, User confirmingUser, SutProvider<ProviderService> sutProvider)
{
// Arrange
pu1.ProviderId = provider.Id;
pu1.UserId = u1.Id;
var providerUsers = new[] { pu1 };
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
providerUserRepository.GetManyAsync([]).ReturnsForAnyArgs(providerUsers);
sutProvider.GetDependency<IProviderRepository>().GetByIdAsync(provider.Id).Returns(provider);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([u1]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(false);
var dict = providerUsers.ToDictionary(pu => pu.Id, _ => "key");
// Act
var result = await sutProvider.Sut.ConfirmUsersAsync(pu1.ProviderId, dict, confirmingUser.Id);
// Assert
Assert.Single(result);
Assert.Equal("", result[0].Item2);
// Verify user was confirmed
await providerUserRepository.Received(1).ReplaceAsync(Arg.Is<ProviderUser>(pu =>
pu.Status == ProviderUserStatusType.Confirmed));
// Verify that policy check was never called when feature flag is disabled
await sutProvider.GetDependency<IPolicyRequirementQuery>()
.DidNotReceive()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(Arg.Any<Guid>());
}
[Theory, BitAutoData]
public async Task SaveUserAsync_UserIdIsInvalid_Throws(ProviderUser providerUser,
SutProvider<ProviderService> sutProvider)
{
providerUser.Id = default;
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveUserAsync(providerUser, default));
providerUser.Id = Guid.Empty;
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.SaveUserAsync(providerUser, Guid.Empty));
Assert.Equal("Invite the user first.", exception.Message);
}

View File

@@ -3,6 +3,7 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Models.Business.Tokenables;
@@ -34,6 +35,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
private readonly IFeatureService _featureService;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IAutomaticUserConfirmationPolicyEnforcementValidator _automaticUserConfirmationPolicyEnforcementValidator;
public AcceptOrgUserCommand(
IDataProtectionProvider dataProtectionProvider,
@@ -46,7 +48,8 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery)
IPolicyRequirementQuery policyRequirementQuery,
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator)
{
// TODO: remove data protector when old token validation removed
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
@@ -60,6 +63,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
_featureService = featureService;
_policyRequirementQuery = policyRequirementQuery;
_automaticUserConfirmationPolicyEnforcementValidator = automaticUserConfirmationPolicyEnforcementValidator;
}
public async Task<OrganizationUser> AcceptOrgUserByEmailTokenAsync(Guid organizationUserId, User user, string emailToken,
@@ -186,13 +190,19 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
}
}
// Enforce Single Organization Policy of organization user is trying to join
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(user.Id);
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
await ValidateAutomaticUserConfirmationPolicyAsync(orgUser, allOrgUsers, user);
}
// Enforce Single Organization Policy of organization user is trying to join
var invitedSingleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.SingleOrg, OrganizationUserStatusType.Invited);
if (hasOtherOrgs && invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
if (allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId)
&& invitedSingleOrgPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
{
throw new BadRequestException("You may not join this organization until you leave or remove all other organizations.");
}
@@ -255,4 +265,20 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
}
}
}
private async Task ValidateAutomaticUserConfirmationPolicyAsync(OrganizationUser orgUser,
ICollection<OrganizationUser> allOrgUsers, User user)
{
var error = (await _automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
new AutomaticUserConfirmationPolicyEnforcementRequest(orgUser.OrganizationId, allOrgUsers, user)))
.Match(
error => error.Message,
_ => string.Empty
);
if (!string.IsNullOrEmpty(error))
{
throw new BadRequestException(error);
}
}
}

View File

@@ -1,6 +1,7 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2;
@@ -8,6 +9,7 @@ using Bit.Core.AdminConsole.Utilities.v2.Validation;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
@@ -16,6 +18,8 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
IOrganizationUserRepository organizationUserRepository,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IPolicyRequirementQuery policyRequirementQuery,
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator,
IUserService userService,
IPolicyRepository policyRepository) : IAutomaticallyConfirmOrganizationUsersValidator
{
public async Task<ValidationResult<AutomaticallyConfirmOrganizationUserValidationRequest>> ValidateAsync(
@@ -61,7 +65,7 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
return Invalid(request, new UserDoesNotHaveTwoFactorEnabled());
}
if (await OrganizationUserConformsToSingleOrgPolicyAsync(request) is { } error)
if (await OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(request) is { } error)
{
return Invalid(request, error);
}
@@ -69,10 +73,8 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
return Valid(request);
}
private async Task<bool> OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(
AutomaticallyConfirmOrganizationUserValidationRequest request) =>
await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId,
PolicyType.AutomaticUserConfirmation) is { Enabled: true }
private async Task<bool> OrganizationHasAutomaticallyConfirmUsersPolicyEnabledAsync(AutomaticallyConfirmOrganizationUserValidationRequest request) =>
await policyRepository.GetByOrganizationIdTypeAsync(request.OrganizationId, PolicyType.AutomaticUserConfirmation) is { Enabled: true }
&& request.Organization is { UseAutomaticUserConfirmation: true };
private async Task<bool> OrganizationUserConformsToTwoFactorRequiredPolicyAsync(AutomaticallyConfirmOrganizationUserValidationRequest request)
@@ -87,30 +89,37 @@ public class AutomaticallyConfirmOrganizationUsersValidator(
.IsTwoFactorRequiredForOrganization(request.Organization!.Id);
}
private async Task<Error?> OrganizationUserConformsToSingleOrgPolicyAsync(
/// <summary>
/// Validates whether the specified organization user complies with the automatic user confirmation policy.
/// This includes checks across all organizations the user is associated with to ensure they meet the compliance criteria.
///
/// We are not checking single organization policy compliance here because automatically confirm users policy enforces
/// a stricter version and applies to all users. If you are compliant with Auto Confirm, you'll be in compliance with
/// Single Org.
/// </summary>
/// <param name="request">
/// The request model encapsulates the current organization, the user being validated, and all organization users associated
/// with that user.
/// </param>
/// <returns>
/// An <see cref="Error"/> if the user fails to meet the automatic user confirmation policy, or null if the validation succeeds.
/// </returns>
private async Task<Error?> OrganizationUserConformsToAutomaticUserConfirmationPolicyAsync(
AutomaticallyConfirmOrganizationUserValidationRequest request)
{
var allOrganizationUsersForUser = await organizationUserRepository
.GetManyByUserAsync(request.OrganizationUser!.UserId!.Value);
if (allOrganizationUsersForUser.Count == 1)
{
return null;
}
var user = await userService.GetUserByIdAsync(request.OrganizationUser!.UserId!.Value);
var policyRequirement = await policyRequirementQuery
.GetAsync<SingleOrganizationPolicyRequirement>(request.OrganizationUser!.UserId!.Value);
if (policyRequirement.IsSingleOrgEnabledForThisOrganization(request.Organization!.Id))
{
return new OrganizationEnforcesSingleOrgPolicy();
}
if (policyRequirement.IsSingleOrgEnabledForOrganizationsOtherThan(request.Organization.Id))
{
return new OtherOrganizationEnforcesSingleOrgPolicy();
}
return null;
return (await automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
new AutomaticUserConfirmationPolicyEnforcementRequest(
request.OrganizationId,
allOrganizationUsersForUser,
user)))
.Match<Error?>(
error => error,
_ => null
);
}
}

View File

@@ -8,6 +8,9 @@ public record UserIsNotUserType() : BadRequestError("Only organization users wit
public record UserIsNotAccepted() : BadRequestError("Cannot confirm user that has not accepted the invitation.");
public record OrganizationUserIdIsInvalid() : BadRequestError("Invalid organization user id.");
public record UserDoesNotHaveTwoFactorEnabled() : BadRequestError("User does not have two-step login enabled.");
public record OrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization until they leave or remove all other organizations");
public record OtherOrganizationEnforcesSingleOrgPolicy() : BadRequestError("Cannot confirm this member to the organization because they are in another organization which forbids it.");
public record UserCannotBelongToAnotherOrganization() : BadRequestError("Cannot confirm this member to the organization until they leave or remove all other organizations");
public record OtherOrganizationDoesNotAllowOtherMembership() : BadRequestError("Cannot confirm this member to the organization because they are in another organization which forbids it.");
public record AutomaticallyConfirmUsersPolicyIsNotEnabled() : BadRequestError("Cannot confirm this member because the Automatically Confirm Users policy is not enabled.");
public record ProviderUsersCannotJoin() : BadRequestError("An organization the user is a part of has enabled Automatic User Confirmation policy, and it does not support provider users joining.");
public record UserCannotJoinProvider() : BadRequestError("An organization the user is a part of has enabled Automatic User Confirmation policy, and it does not support the user joining a provider.");
public record CurrentOrganizationUserIsNotPresentInRequest() : BadRequestError("The current organization user does not exist in the request.");

View File

@@ -4,6 +4,7 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
@@ -33,6 +34,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
private readonly IPolicyRequirementQuery _policyRequirementQuery;
private readonly IFeatureService _featureService;
private readonly ICollectionRepository _collectionRepository;
private readonly IAutomaticUserConfirmationPolicyEnforcementValidator _automaticUserConfirmationPolicyEnforcementValidator;
public ConfirmOrganizationUserCommand(
IOrganizationRepository organizationRepository,
@@ -47,7 +49,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
IDeviceRepository deviceRepository,
IPolicyRequirementQuery policyRequirementQuery,
IFeatureService featureService,
ICollectionRepository collectionRepository)
ICollectionRepository collectionRepository,
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@@ -62,6 +65,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
_policyRequirementQuery = policyRequirementQuery;
_featureService = featureService;
_collectionRepository = collectionRepository;
_automaticUserConfirmationPolicyEnforcementValidator = automaticUserConfirmationPolicyEnforcementValidator;
}
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
@@ -127,6 +131,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
var organization = await _organizationRepository.GetByIdAsync(organizationId);
var allUsersOrgs = await _organizationUserRepository.GetManyByManyUsersAsync(validSelectedUserIds);
var users = await _userRepository.GetManyAsync(validSelectedUserIds);
var usersTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(validSelectedUserIds);
@@ -188,6 +193,25 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
await ValidateTwoFactorAuthenticationPolicyAsync(user, organizationId, userTwoFactorEnabled);
var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId);
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var error = (await _automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationId,
userOrgs,
user)))
.Match(
error => new BadRequestException(error.Message),
_ => null
);
if (error is not null)
{
throw error;
}
}
var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
var otherSingleOrgPolicies =
singleOrgPolicies.Where(p => p.OrganizationId != organizationId);
@@ -267,8 +291,7 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
return;
}
var organizationDataOwnershipPolicy =
await _policyRequirementQuery.GetAsync<OrganizationDataOwnershipPolicyRequirement>(organizationUser.UserId!.Value);
var organizationDataOwnershipPolicy = await _policyRequirementQuery.GetAsync<OrganizationDataOwnershipPolicyRequirement>(organizationUser.UserId!.Value);
if (!organizationDataOwnershipPolicy.RequiresDefaultCollectionOnConfirm(organizationUser.OrganizationId))
{
return;
@@ -311,8 +334,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
return;
}
var policyEligibleOrganizationUserIds =
await _policyRequirementQuery.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(organizationId);
var policyEligibleOrganizationUserIds = await _policyRequirementQuery
.GetManyByOrganizationIdAsync<OrganizationDataOwnershipPolicyRequirement>(organizationId);
var eligibleOrganizationUserIds = confirmedOrganizationUsers
.Where(ou => policyEligibleOrganizationUserIds.Contains(ou.Id))

View File

@@ -4,6 +4,7 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
@@ -29,7 +30,8 @@ public class RestoreOrganizationUserCommand(
IUserRepository userRepository,
IOrganizationService organizationService,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery) : IRestoreOrganizationUserCommand
IPolicyRequirementQuery policyRequirementQuery,
IAutomaticUserConfirmationPolicyEnforcementValidator automaticUserConfirmationPolicyEnforcementValidator) : IRestoreOrganizationUserCommand
{
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId)
{
@@ -300,6 +302,25 @@ public class RestoreOrganizationUserCommand(
{
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
}
if (featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var validationResult = await automaticUserConfirmationPolicyEnforcementValidator.IsCompliantAsync(
new AutomaticUserConfirmationPolicyEnforcementRequest(orgUser.OrganizationId,
allOrgUsers,
user!));
var badRequestException = validationResult.Match(
error => new BadRequestException(user.Email +
" is not compliant with the automatic user confirmation policy: " +
error.Message),
_ => null);
if (badRequestException is not null)
{
throw badRequestException;
}
}
}
private async Task<bool> IsTwoFactorRequiredForOrganizationAsync(Guid userId, Guid organizationId)

View File

@@ -3,6 +3,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Enums;
using Bit.Core.Billing.Organizations.Models;
@@ -43,7 +45,9 @@ public class CloudOrganizationSignUpCommand(
IPushNotificationService pushNotificationService,
ICollectionRepository collectionRepository,
IDeviceRepository deviceRepository,
IPricingClient pricingClient) : ICloudOrganizationSignUpCommand
IPricingClient pricingClient,
IPolicyRequirementQuery policyRequirementQuery,
IFeatureService featureService) : ICloudOrganizationSignUpCommand
{
public async Task<SignUpOrganizationResponse> SignUpOrganizationAsync(OrganizationSignup signup)
{
@@ -237,6 +241,17 @@ public class CloudOrganizationSignUpCommand(
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{
if (featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var requirement = await policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
if (requirement.CannotCreateNewOrganization())
{
throw new BadRequestException("You may not create an organization. You belong to an organization " +
"which has a policy that prohibits you from being a member of any other organization.");
}
}
var anySingleOrgPolicies = await policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{

View File

@@ -2,6 +2,8 @@
#nullable disable
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Entities;
@@ -28,6 +30,8 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
private readonly IGlobalSettings _globalSettings;
private readonly IPolicyService _policyService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IFeatureService _featureService;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
public InitPendingOrganizationCommand(
IOrganizationService organizationService,
@@ -37,7 +41,9 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
IDataProtectionProvider dataProtectionProvider,
IGlobalSettings globalSettings,
IPolicyService policyService,
IOrganizationUserRepository organizationUserRepository
IOrganizationUserRepository organizationUserRepository,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery
)
{
_organizationService = organizationService;
@@ -48,6 +54,8 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
_globalSettings = globalSettings;
_policyService = policyService;
_organizationUserRepository = organizationUserRepository;
_featureService = featureService;
_policyRequirementQuery = policyRequirementQuery;
}
public async Task InitPendingOrganizationAsync(User user, Guid organizationId, Guid organizationUserId, string publicKey, string privateKey, string collectionName, string emailToken)
@@ -113,6 +121,17 @@ public class InitPendingOrganizationCommand : IInitPendingOrganizationCommand
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var requirement = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
if (requirement.CannotCreateNewOrganization())
{
throw new BadRequestException("You may not create an organization. You belong to an organization " +
"which has a policy that prohibits you from being a member of any other organization.");
}
}
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{

View File

@@ -2,6 +2,8 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Billing.Organizations.Models;
using Bit.Core.Billing.Services;
@@ -31,6 +33,8 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
private readonly IPolicyService _policyService;
private readonly IGlobalSettings _globalSettings;
private readonly IStripePaymentService _paymentService;
private readonly IFeatureService _featureService;
private readonly IPolicyRequirementQuery _policyRequirementQuery;
public SelfHostedOrganizationSignUpCommand(
IOrganizationRepository organizationRepository,
@@ -44,7 +48,9 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
ILicensingService licensingService,
IPolicyService policyService,
IGlobalSettings globalSettings,
IStripePaymentService paymentService)
IStripePaymentService paymentService,
IFeatureService featureService,
IPolicyRequirementQuery policyRequirementQuery)
{
_organizationRepository = organizationRepository;
_organizationUserRepository = organizationUserRepository;
@@ -58,6 +64,8 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
_policyService = policyService;
_globalSettings = globalSettings;
_paymentService = paymentService;
_featureService = featureService;
_policyRequirementQuery = policyRequirementQuery;
}
public async Task<(Organization organization, OrganizationUser? organizationUser)> SignUpAsync(
@@ -103,6 +111,17 @@ public class SelfHostedOrganizationSignUpCommand : ISelfHostedOrganizationSignUp
private async Task ValidateSignUpPoliciesAsync(Guid ownerId)
{
if (_featureService.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers))
{
var requirement = await _policyRequirementQuery.GetAsync<AutomaticUserConfirmationPolicyRequirement>(ownerId);
if (requirement.CannotCreateNewOrganization())
{
throw new BadRequestException("You may not create an organization. You belong to an organization " +
"which has a policy that prohibits you from being a member of any other organization.");
}
}
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(ownerId, PolicyType.SingleOrg);
if (anySingleOrgPolicies)
{

View File

@@ -0,0 +1,44 @@
using Bit.Core.Entities;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
/// <summary>
/// Request object for <see cref="AutomaticUserConfirmationPolicyEnforcementValidator"/>
/// </summary>
public record AutomaticUserConfirmationPolicyEnforcementRequest
{
/// <summary>
/// Organization to be validated
/// </summary>
public Guid OrganizationId { get; }
/// <summary>
/// All organization users that match the provided user.
/// </summary>
public ICollection<OrganizationUser> AllOrganizationUsers { get; }
/// <summary>
/// User associated with the organization user to be confirmed
/// </summary>
public User User { get; }
/// <summary>
/// Request object for <see cref="AutomaticUserConfirmationPolicyEnforcementValidator"/>.
/// </summary>
/// <remarks>
/// This record is used to encapsulate the data required for handling the automatic confirmation policy enforcement.
/// </remarks>
/// <param name="organizationId">The organization to be validated.</param>
/// <param name="organizationUsers">All organization users that match the provided user.</param>
/// <param name="user">The user entity connecting all org users provided.</param>
public AutomaticUserConfirmationPolicyEnforcementRequest(
Guid organizationId,
IEnumerable<OrganizationUser> organizationUsers,
User user)
{
OrganizationId = organizationId;
AllOrganizationUsers = organizationUsers.ToArray();
User = user;
}
}

View File

@@ -0,0 +1,49 @@
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
public class AutomaticUserConfirmationPolicyEnforcementValidator(
IPolicyRequirementQuery policyRequirementQuery,
IProviderUserRepository providerUserRepository)
: IAutomaticUserConfirmationPolicyEnforcementValidator
{
public async Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync(
AutomaticUserConfirmationPolicyEnforcementRequest request)
{
var automaticUserConfirmationPolicyRequirement = await policyRequirementQuery
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(request.User.Id);
var currentOrganizationUser = request.AllOrganizationUsers
.FirstOrDefault(x => x.OrganizationId == request.OrganizationId
&& x.UserId == request.User.Id);
if (currentOrganizationUser is null)
{
return Invalid(request, new CurrentOrganizationUserIsNotPresentInRequest());
}
if (automaticUserConfirmationPolicyRequirement.IsEnabled(request.OrganizationId))
{
if ((await providerUserRepository.GetManyByUserAsync(request.User.Id)).Count != 0)
{
return Invalid(request, new ProviderUsersCannotJoin());
}
if (request.AllOrganizationUsers.Count > 1)
{
return Invalid(request, new UserCannotBelongToAnotherOrganization());
}
}
if (automaticUserConfirmationPolicyRequirement.IsEnabledForOrganizationsOtherThan(currentOrganizationUser.OrganizationId))
{
return Invalid(request, new OtherOrganizationDoesNotAllowOtherMembership());
}
return Valid(request);
}
}

View File

@@ -0,0 +1,28 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Utilities.v2.Validation;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
/// <summary>
/// Used to enforce the Automatic User Confirmation policy. It uses the <see cref="IPolicyRequirementQuery"/> to retrieve
/// the <see cref="AutomaticUserConfirmationPolicyRequirement"/>. It is used to check to make sure the given user is
/// valid for the Automatic User Confirmation policy. It also validates that the given user is not a provider
/// or a member of another organization regardless of status or type.
/// </summary>
public interface IAutomaticUserConfirmationPolicyEnforcementValidator
{
/// <summary>
/// Checks if the given user is compliant with the Automatic User Confirmation policy.
///
/// To be compliant, a user must
/// - not be a member of a provider
/// - not be a member of another organization
/// </summary>
/// <param name="request"></param>
/// <remarks>
/// This uses the validation result pattern to avoid throwing exceptions.
/// </remarks>
/// <returns>A validation result with the error message if applicable.</returns>
Task<ValidationResult<AutomaticUserConfirmationPolicyEnforcementRequest>> IsCompliantAsync(AutomaticUserConfirmationPolicyEnforcementRequest request);
}

View File

@@ -0,0 +1,48 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.Enums;
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
/// <summary>
/// Represents the enforcement status of the Automatic User Confirmation policy.
/// </summary>
/// <remarks>
/// The Automatic User Confirmation policy is enforced against all types of users regardless of status or type.
///
/// Users cannot:
/// <ul>
/// <li>Be a member of another organization (similar to Single Organization Policy)</li>
/// <li>Cannot be a provider</li>
/// </ul>
/// </remarks>
/// <param name="policyDetails">Collection of policy details that apply to this user id</param>
public class AutomaticUserConfirmationPolicyRequirement(IEnumerable<PolicyDetails> policyDetails) : IPolicyRequirement
{
public bool CannotBeGrantedEmergencyAccess() => policyDetails.Any();
public bool CannotJoinProvider() => policyDetails.Any();
public bool CannotCreateProvider() => policyDetails.Any();
public bool CannotCreateNewOrganization() => policyDetails.Any();
public bool IsEnabled(Guid organizationId) => policyDetails.Any(p => p.OrganizationId == organizationId);
public bool IsEnabledForOrganizationsOtherThan(Guid organizationId) =>
policyDetails.Any(p => p.OrganizationId != organizationId);
}
public class AutomaticUserConfirmationPolicyRequirementFactory : BasePolicyRequirementFactory<AutomaticUserConfirmationPolicyRequirement>
{
public override PolicyType PolicyType => PolicyType.AutomaticUserConfirmation;
protected override IEnumerable<OrganizationUserType> ExemptRoles => [];
protected override IEnumerable<OrganizationUserStatusType> ExemptStatuses => [];
protected override bool ExemptProviders => false;
public override AutomaticUserConfirmationPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails) =>
new(policyDetails);
}

View File

@@ -1,4 +1,5 @@
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
@@ -23,6 +24,8 @@ public static class PolicyServiceCollectionExtensions
services.AddPolicyRequirements();
services.AddPolicySideEffects();
services.AddPolicyUpdateEvents();
services.AddScoped<IAutomaticUserConfirmationPolicyEnforcementValidator, AutomaticUserConfirmationPolicyEnforcementValidator>();
}
[Obsolete("Use AddPolicyUpdateEvents instead.")]
@@ -69,5 +72,6 @@ public static class PolicyServiceCollectionExtensions
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireTwoFactorPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, MasterPasswordPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SingleOrganizationPolicyRequirementFactory>();
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, AutomaticUserConfirmationPolicyRequirementFactory>();
}
}

View File

@@ -2,6 +2,7 @@
using AutoFixture;
using AutoFixture.Xunit2;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.Enums;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
namespace Bit.Core.Test.AdminConsole.AutoFixture;
@@ -9,10 +10,16 @@ namespace Bit.Core.Test.AdminConsole.AutoFixture;
internal class OrganizationUserPolicyDetailsCustomization : ICustomization
{
public PolicyType Type { get; set; }
public OrganizationUserStatusType Status { get; set; }
public OrganizationUserType UserType { get; set; }
public bool IsProvider { get; set; }
public OrganizationUserPolicyDetailsCustomization(PolicyType type)
public OrganizationUserPolicyDetailsCustomization(PolicyType type, OrganizationUserStatusType status, OrganizationUserType userType, bool isProvider)
{
Type = type;
Status = status;
UserType = userType;
IsProvider = isProvider;
}
public void Customize(IFixture fixture)
@@ -20,6 +27,9 @@ internal class OrganizationUserPolicyDetailsCustomization : ICustomization
fixture.Customize<OrganizationUserPolicyDetails>(composer => composer
.With(o => o.OrganizationId, Guid.NewGuid())
.With(o => o.PolicyType, Type)
.With(o => o.OrganizationUserStatus, Status)
.With(o => o.OrganizationUserType, UserType)
.With(o => o.IsProvider, IsProvider)
.With(o => o.PolicyEnabled, true));
}
}
@@ -27,14 +37,25 @@ internal class OrganizationUserPolicyDetailsCustomization : ICustomization
public class OrganizationUserPolicyDetailsAttribute : CustomizeAttribute
{
private readonly PolicyType _type;
private readonly OrganizationUserStatusType _status;
private readonly OrganizationUserType _userType;
private readonly bool _isProvider;
public OrganizationUserPolicyDetailsAttribute(PolicyType type)
public OrganizationUserPolicyDetailsAttribute(PolicyType type) : this(type, OrganizationUserStatusType.Accepted, OrganizationUserType.User, false)
{
_type = type;
}
public OrganizationUserPolicyDetailsAttribute(PolicyType type, OrganizationUserStatusType status, OrganizationUserType userType, bool isProvider)
{
_type = type;
_status = status;
_userType = userType;
_isProvider = isProvider;
}
public override ICustomization GetCustomization(ParameterInfo parameter)
{
return new OrganizationUserPolicyDetailsCustomization(_type);
return new OrganizationUserPolicyDetailsCustomization(_type, _status, _userType, _isProvider);
}
}

View File

@@ -1,7 +1,9 @@
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Models.Business.Tokenables;
@@ -24,6 +26,7 @@ using Bit.Test.Common.Fakes;
using Microsoft.AspNetCore.DataProtection;
using NSubstitute;
using Xunit;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.Test.OrganizationFeatures.OrganizationUsers;
@@ -673,6 +676,79 @@ public class AcceptOrgUserCommandTests
Assert.Equal("User not found within organization.", exception.Message);
}
// Auto-confirm policy validation tests --------------------------------------------------------------------------
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithAutoConfirmIsNotEnabled_DoesNotCheckCompliance(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
// Act
var resultOrgUser = await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
// Assert
AssertValidAcceptedOrgUser(resultOrgUser, orgUser, user);
await sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>().DidNotReceiveWithAnyArgs()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>());
}
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithUserThatIsCompliantWithAutoConfirm_AcceptsUser(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
// Mock auto-confirm enforcement query to return valid (no auto-confirm restrictions)
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user)));
// Act
var resultOrgUser = await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
// Assert
AssertValidAcceptedOrgUser(resultOrgUser, orgUser, user);
await sutProvider.GetDependency<IOrganizationUserRepository>().Received(1).ReplaceAsync(
Arg.Is<OrganizationUser>(ou => ou.Id == orgUser.Id && ou.Status == OrganizationUserStatusType.Accepted));
}
[Theory]
[BitAutoData]
public async Task AcceptOrgUserAsync_WithAutoConfirmIsEnabledAndFailsCompliance_ThrowsBadRequestException(
SutProvider<AcceptOrgUserCommand> sutProvider,
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails,
OrganizationUser otherOrgUser)
{
// Arrange
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser, otherOrgUser], user),
new UserCannotBelongToAnotherOrganization()));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService));
// Should get auto-confirm error
Assert.Equal(new UserCannotBelongToAnotherOrganization().Message, exception.Message);
}
// Private helpers -------------------------------------------------------------------------------------------------
/// <summary>
@@ -716,7 +792,7 @@ public class AcceptOrgUserCommandTests
/// - Provides mock data for an admin to validate email functionality.
/// - Returns the corresponding organization for the given org ID.
/// </summary>
private void SetupCommonAcceptOrgUserMocks(SutProvider<AcceptOrgUserCommand> sutProvider, User user,
private static void SetupCommonAcceptOrgUserMocks(SutProvider<AcceptOrgUserCommand> sutProvider, User user,
Organization org,
OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
{
@@ -729,18 +805,12 @@ public class AcceptOrgUserCommandTests
// User is not part of any other orgs
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns(
Task.FromResult<ICollection<OrganizationUser>>(new List<OrganizationUser>())
);
.Returns([]);
// Org they are trying to join does not have single org policy
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg, OrganizationUserStatusType.Invited)
.Returns(
Task.FromResult<ICollection<OrganizationUserPolicyDetails>>(
new List<OrganizationUserPolicyDetails>()
)
);
.Returns([]);
// User is not part of any organization that applies the single org policy
sutProvider.GetDependency<IPolicyService>()
@@ -750,20 +820,24 @@ public class AcceptOrgUserCommandTests
// Org does not require 2FA
sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(user.Id,
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited)
.Returns(Task.FromResult<ICollection<OrganizationUserPolicyDetails>>(
new List<OrganizationUserPolicyDetails>()));
.Returns([]);
// Provide at least 1 admin to test email functionality
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByMinimumRoleAsync(orgUser.OrganizationId, OrganizationUserType.Admin)
.Returns(Task.FromResult<IEnumerable<OrganizationUserUserDetails>>(
new List<OrganizationUserUserDetails>() { adminUserDetails }
));
.Returns([adminUserDetails]);
// Return org
sutProvider.GetDependency<IOrganizationRepository>()
.GetByIdAsync(org.Id)
.Returns(Task.FromResult(org));
.Returns(org);
// Auto-confirm enforcement query returns valid by default (no restrictions)
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(request)
.Returns(Valid(request));
}

View File

@@ -5,6 +5,7 @@ using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.DeleteClaimedAccount;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
@@ -12,6 +13,7 @@ using Bit.Core.Billing.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.AdminConsole.AutoFixture;
using Bit.Core.Test.AutoFixture.OrganizationFixtures;
using Bit.Core.Test.AutoFixture.OrganizationUserFixtures;
@@ -19,6 +21,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUsers;
@@ -116,11 +119,11 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true, planType: PlanType.EnterpriseAnnually)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
User user,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
@@ -140,12 +143,23 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
.Returns([(user.Id, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(
new AutomaticUserConfirmationPolicyEnforcementRequest(organization.Id,
[organizationUser],
user)));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
@@ -319,11 +333,11 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
User user,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
@@ -343,12 +357,24 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
.Returns([(user.Id, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(
new AutomaticUserConfirmationPolicyEnforcementRequest(organization.Id,
[organizationUser],
user)));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
@@ -362,11 +388,11 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
User user,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
@@ -386,16 +412,28 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, false)]);
.Returns([(user.Id, false)]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<RequireTwoFactorPolicyRequirement>(userId)
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
.Returns(new RequireTwoFactorPolicyRequirement([])); // No 2FA policy
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(
new AutomaticUserConfirmationPolicyEnforcementRequest(organization.Id,
[organizationUser],
user)));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
@@ -403,128 +441,17 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnThisOrg_ReturnsError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
var singleOrgPolicyDetails = new PolicyDetails
{
OrganizationId = organization.Id,
PolicyType = PolicyType.SingleOrg
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails]));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithSingleOrgPolicyOnOtherOrg_ReturnsError(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
var otherOrgId = Guid.NewGuid(); // Different org
var singleOrgPolicyDetails = new PolicyDetails
{
OrganizationId = otherOrgId,
PolicyType = PolicyType.SingleOrg,
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([singleOrgPolicyDetails]));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OtherOrganizationEnforcesSingleOrgPolicy>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInSingleOrg_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
Guid userId,
User user,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
@@ -544,61 +471,22 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
.Returns([(user.Id, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]); // Single org
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(user.Id)
.Returns(user);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_UserInMultipleOrgs_WithNoSingleOrgPolicy_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
Guid userId,
Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = userId;
organizationUser.OrganizationId = organization.Id;
autoConfirmPolicy.Type = PolicyType.AutomaticUserConfirmation;
autoConfirmPolicy.Enabled = true;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(userId, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(userId)
.Returns([organizationUser, otherOrgUser]);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<SingleOrganizationPolicyRequirement>(userId)
.Returns(new SingleOrganizationPolicyRequirement([]));
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(
new AutomaticUserConfirmationPolicyEnforcementRequest(organization.Id,
[organizationUser],
user)));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
@@ -693,4 +581,59 @@ public class AutomaticallyConfirmOrganizationUsersValidatorTests
Assert.True(result.IsError);
Assert.IsType<AutomaticallyConfirmUsersPolicyIsNotEnabled>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task ValidateAsync_WithNonProviderUser_ReturnsValidResult(
SutProvider<AutomaticallyConfirmOrganizationUsersValidator> sutProvider,
[Organization(useAutomaticUserConfirmation: true)] Organization organization,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser organizationUser,
User user,
[Policy(PolicyType.AutomaticUserConfirmation)] Policy autoConfirmPolicy)
{
// Arrange
organizationUser.UserId = user.Id;
organizationUser.OrganizationId = organization.Id;
var request = new AutomaticallyConfirmOrganizationUserValidationRequest
{
PerformedBy = Substitute.For<IActingUser>(),
DefaultUserCollectionName = "test-collection",
OrganizationUser = organizationUser,
OrganizationUserId = organizationUser.Id,
Organization = organization,
OrganizationId = organization.Id,
Key = "test-key"
};
sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.AutomaticUserConfirmation)
.Returns(autoConfirmPolicy);
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<Guid>>())
.Returns([(user.Id, true)]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([organizationUser]);
sutProvider.GetDependency<IUserService>()
.GetUserByIdAsync(user.Id)
.Returns(user);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(
new AutomaticUserConfirmationPolicyEnforcementRequest(organization.Id,
[organizationUser],
user)));
// Act
var result = await sutProvider.Sut.ValidateAsync(request);
// Assert
Assert.True(result.IsValid);
}
}

View File

@@ -2,7 +2,9 @@
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
@@ -21,6 +23,7 @@ using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
using static Bit.Core.AdminConsole.Utilities.v2.Validation.ValidationResultHelpers;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.OrganizationUsers;
@@ -559,4 +562,256 @@ public class ConfirmOrganizationUserCommandTests
.DidNotReceive()
.UpsertDefaultCollectionsAsync(Arg.Any<Guid>(), Arg.Any<IEnumerable<Guid>>(), Arg.Any<string>());
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithAutoConfirmEnabledAndUserBelongsToAnotherOrg_ThrowsBadRequest(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser otherOrgUser, string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
otherOrgUser.UserId = user.Id;
otherOrgUser.OrganizationId = Guid.NewGuid(); // Different org
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser, otherOrgUser]);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([user]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(orgUser.Id, [orgUser, otherOrgUser], user),
new UserCannotBelongToAnotherOrganization()));
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id));
Assert.Equal(new UserCannotBelongToAnotherOrganization().Message, exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithAutoConfirmEnabledForOtherOrg_ThrowsBadRequest(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser otherOrgUser, string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
// Arrange
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
otherOrgUser.UserId = user.Id;
otherOrgUser.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser, otherOrgUser]);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([user]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser, otherOrgUser], user),
new OtherOrganizationDoesNotAllowOtherMembership()));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id));
Assert.Equal(new OtherOrganizationDoesNotAllowOtherMembership().Message, exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithAutoConfirmEnabledAndUserIsProvider_ThrowsBadRequest(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
// Arrange
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([user]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user),
new ProviderUsersCannotJoin()));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id));
Assert.Equal(new ProviderUsersCannotJoin().Message, exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithAutoConfirmNotApplicable_Succeeds(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
// Arrange
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([user]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Valid(new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser], user)));
// Act
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id);
// Assert
await sutProvider.GetDependency<IEventService>()
.Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
await sutProvider.GetDependency<IMailService>()
.Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager);
}
[Theory, BitAutoData]
public async Task ConfirmUserAsync_WithAutoConfirmValidationBeforeSingleOrgPolicy_ChecksAutoConfirmFirst(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
OrganizationUser otherOrgUser,
[OrganizationUserPolicyDetails(PolicyType.SingleOrg)] OrganizationUserPolicyDetails singleOrgPolicy,
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
// Arrange - Setup conditions that would fail BOTH auto-confirm AND single org policy
org.PlanType = PlanType.EnterpriseAnnually;
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
orgUser.UserId = user.Id;
otherOrgUser.UserId = user.Id;
otherOrgUser.OrganizationId = Guid.NewGuid();
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([orgUser]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser, otherOrgUser]);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>().GetManyAsync([]).ReturnsForAnyArgs([user]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
singleOrgPolicy.OrganizationId = org.Id;
sutProvider.GetDependency<IPolicyService>()
.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg)
.Returns([singleOrgPolicy]);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Any<AutomaticUserConfirmationPolicyEnforcementRequest>())
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser, otherOrgUser], user),
new UserCannotBelongToAnotherOrganization()));
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id));
Assert.Equal(new UserCannotBelongToAnotherOrganization().Message, exception.Message);
Assert.NotEqual("Cannot confirm this member to the organization until they leave or remove all other organizations.",
exception.Message);
}
[Theory, BitAutoData]
public async Task ConfirmUsersAsync_WithAutoConfirmEnabled_MixedResults(
Organization org, OrganizationUser confirmingUser,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser1,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser2,
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser3,
OrganizationUser otherOrgUser, User user1, User user2, User user3,
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
{
// Arrange
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;
otherOrgUser.UserId = user3.Id;
otherOrgUser.OrganizationId = Guid.NewGuid();
var orgUsers = new[] { orgUser1, orgUser2, orgUser3 };
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs(orgUsers);
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
sutProvider.GetDependency<IUserRepository>()
.GetManyAsync([]).ReturnsForAnyArgs([user1, user2, user3]);
sutProvider.GetDependency<IOrganizationUserRepository>()
.GetManyByManyUsersAsync([])
.ReturnsForAnyArgs([orgUser1, orgUser2, orgUser3, otherOrgUser]);
sutProvider.GetDependency<IFeatureService>()
.IsEnabled(FeatureFlagKeys.AutomaticConfirmUsers)
.Returns(true);
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Is<AutomaticUserConfirmationPolicyEnforcementRequest>(r => r.User.Id == user1.Id))
.Returns(Valid(new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser1], user1)));
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Is<AutomaticUserConfirmationPolicyEnforcementRequest>(r => r.User.Id == user2.Id))
.Returns(Valid(new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser2], user2)));
sutProvider.GetDependency<IAutomaticUserConfirmationPolicyEnforcementValidator>()
.IsCompliantAsync(Arg.Is<AutomaticUserConfirmationPolicyEnforcementRequest>(r => r.User.Id == user3.Id))
.Returns(Invalid(
new AutomaticUserConfirmationPolicyEnforcementRequest(org.Id, [orgUser3, otherOrgUser], user3),
new OtherOrganizationDoesNotAllowOtherMembership()));
var keys = orgUsers.ToDictionary(ou => ou.Id, _ => key);
// Act
var result = await sutProvider.Sut.ConfirmUsersAsync(confirmingUser.OrganizationId, keys, confirmingUser.Id);
// Assert
Assert.Equal(3, result.Count);
Assert.Empty(result[0].Item2);
Assert.Empty(result[1].Item2);
Assert.Equal(new OtherOrganizationDoesNotAllowOtherMembership().Message, result[2].Item2);
}
}

View File

@@ -0,0 +1,306 @@
using Bit.Core.AdminConsole.Entities.Provider;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.AutoConfirmUser;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.Enforcement.AutoConfirm;
[SutProviderCustomize]
public class AutomaticUserConfirmationPolicyEnforcementValidatorTests
{
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledAndUserIsProviderMember_ReturnsProviderUsersCannotJoinError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
ProviderUser providerUser,
User user)
{
// Arrange
organizationUser.UserId = providerUser.UserId = user.Id;
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([providerUser]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<ProviderUsersCannotJoin>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledOnOtherOrganization_ReturnsOtherOrganizationDoesNotAllowOtherMembershipError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrganizationUser,
User user)
{
// Arrange
organizationUser.UserId = user.Id;
otherOrganizationUser.UserId = user.Id;
var otherOrgId = Guid.NewGuid();
var policyDetails = new PolicyDetails
{
OrganizationId = otherOrgId, // Different from organizationUser.OrganizationId
PolicyType = PolicyType.AutomaticUserConfirmation
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser, otherOrganizationUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<OtherOrganizationDoesNotAllowOtherMembership>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyDisabledUserIsAMemberOfAnotherOrgReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange
organizationUser.UserId = user.Id;
otherOrgUser.UserId = user.Id;
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser, otherOrgUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledUserIsAMemberOfAnotherOrg_ReturnsCannotBeMemberOfAnotherOrgError(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange
organizationUser.UserId = user.Id;
otherOrgUser.UserId = user.Id;
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser, otherOrgUser],
user);
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation
};
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<UserCannotBelongToAnotherOrganization>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyEnabledAndChecksConditionsInCorrectOrder_ReturnsFirstFailure(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
ProviderUser providerUser,
User user)
{
// Arrange
var policyDetails = new PolicyDetails
{
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation,
OrganizationUserId = organizationUser.Id
};
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser, otherOrgUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([policyDetails]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([providerUser]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsError);
Assert.IsType<CurrentOrganizationUserIsNotPresentInRequest>(result.AsError);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyIsEnabledNoOtherOrganizationsAndNotAProvider_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
User user)
{
// Arrange
organizationUser.UserId = user.Id;
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([
new PolicyDetails
{
OrganizationUserId = organizationUser.Id,
OrganizationId = organizationUser.OrganizationId,
PolicyType = PolicyType.AutomaticUserConfirmation,
}
]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyDisabledForCurrentAndOtherOrg_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
User user)
{
// Arrange
otherOrgUser.UserId = organizationUser.UserId = user.Id;
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
[Theory]
[BitAutoData]
public async Task IsCompliantAsync_WithPolicyDisabledForCurrentAndOtherOrgAndIsProvider_ReturnsValid(
SutProvider<AutomaticUserConfirmationPolicyEnforcementValidator> sutProvider,
OrganizationUser organizationUser,
OrganizationUser otherOrgUser,
ProviderUser providerUser,
User user)
{
// Arrange
providerUser.UserId = otherOrgUser.UserId = organizationUser.UserId = user.Id;
var request = new AutomaticUserConfirmationPolicyEnforcementRequest(
organizationUser.OrganizationId,
[organizationUser],
user);
sutProvider.GetDependency<IPolicyRequirementQuery>()
.GetAsync<AutomaticUserConfirmationPolicyRequirement>(user.Id)
.Returns(new AutomaticUserConfirmationPolicyRequirement([]));
sutProvider.GetDependency<IProviderUserRepository>()
.GetManyByUserAsync(user.Id)
.Returns([providerUser]);
// Act
var result = await sutProvider.Sut.IsCompliantAsync(request);
// Assert
Assert.True(result.IsValid);
}
}