mirror of
https://github.com/bitwarden/server
synced 2026-01-01 16:13:33 +00:00
[PM-27766] Add policy for blocking account creation from claimed domains. (#6537)
* Add policy for blocking account creation from claimed domains. * dotnet format * check as part of email verification * add feature flag * fix tests * try to fix dates on database integration tests * PR feedback from claude * remove claude local settings * pr feedback * format * fix test * create or alter * PR feedback * PR feedback * Update src/Core/Constants.cs Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com> * fix merge issues * fix tests --------- Co-authored-by: Thomas Rittson <31796059+eliykat@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationDomains.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class BlockClaimedDomainAccountCreationPolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_NoVerifiedDomains_ValidationError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You must claim at least one domain to turn on this policy", result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_EnablingPolicy_HasVerifiedDomains_Success(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_NoValidation(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_NoVerifiedDomains_ValidationError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You must claim at least one domain to turn on this policy", result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_EnablingPolicy_HasVerifiedDomains_Success(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.HasVerifiedDomainsAsync(policyUpdate.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_WithSavePolicyModel_DisablingPolicy_NoValidation(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, false)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(savePolicyModel, null);
|
||||
|
||||
// Assert
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_FeatureFlagDisabled_ReturnsError(
|
||||
[PolicyUpdate(PolicyType.BlockClaimedDomainAccountCreation, true)] PolicyUpdate policyUpdate,
|
||||
SutProvider<BlockClaimedDomainAccountCreationPolicyValidator> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(false);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, null);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("This feature is not enabled", result);
|
||||
await sutProvider.GetDependency<IOrganizationHasVerifiedDomainsQuery>()
|
||||
.DidNotReceive()
|
||||
.HasVerifiedDomainsAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Type_ReturnsBlockClaimedDomainAccountCreation()
|
||||
{
|
||||
// Arrange
|
||||
var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null, null);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Equal(PolicyType.BlockClaimedDomainAccountCreation, validator.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredPolicies_ReturnsEmpty()
|
||||
{
|
||||
// Arrange
|
||||
var validator = new BlockClaimedDomainAccountCreationPolicyValidator(null, null);
|
||||
|
||||
// Act
|
||||
var requiredPolicies = validator.RequiredPolicies.ToList();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(requiredPolicies);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUser_Succeeds(SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Success);
|
||||
@@ -62,6 +68,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUser_WhenCreateUserFails_ReturnsIdentityResultFailed(SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Failed());
|
||||
@@ -416,6 +428,138 @@ public class RegisterUserCommandTests
|
||||
Assert.Equal(expectedErrorMessage, exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_BlockedDomainFromDifferentOrg_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
var blockingOrganizationId = Guid.NewGuid(); // Different org that has the domain blocked
|
||||
orgUser.OrganizationId = Guid.NewGuid(); // The org they're trying to join
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Mock the new overload that excludes the organization - it should return true (domain IS blocked by another org)
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com", orgUser.OrganizationId)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_BlockedDomainFromSameOrg_Succeeds(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@company-domain.com";
|
||||
user.ReferenceData = null;
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
// The organization owns the domain and is trying to invite the user
|
||||
orgUser.OrganizationId = Guid.NewGuid();
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Mock the new overload - it should return false (domain is NOT blocked by OTHER orgs)
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("company-domain.com", orgUser.OrganizationId)
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user, masterPasswordHash)
|
||||
.Returns(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
await sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.Received(1)
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("company-domain.com", orgUser.OrganizationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationInviteToken_WithValidTokenButNullOrgUser_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, OrganizationUser orgUser, string orgInviteToken, Guid orgUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@example.com";
|
||||
orgUser.Email = user.Email;
|
||||
orgUser.Id = orgUserId;
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<OrgUserInviteTokenable>>()
|
||||
.TryUnprotect(orgInviteToken, out Arg.Any<OrgUserInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = orgInviteTokenable;
|
||||
return true;
|
||||
});
|
||||
|
||||
// Mock GetByIdAsync to return null - simulating a deleted or non-existent organization user
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetByIdAsync(orgUserId)
|
||||
.Returns((OrganizationUser)null);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationInviteToken(user, masterPasswordHash, orgInviteToken, orgUserId));
|
||||
Assert.Equal("Invalid organization user invitation.", exception.Message);
|
||||
|
||||
// Verify that GetByIdAsync was called
|
||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.Received(1)
|
||||
.GetByIdAsync(orgUserId);
|
||||
|
||||
// Verify that user creation was never attempted
|
||||
await sutProvider.GetDependency<IUserService>()
|
||||
.DidNotReceive()
|
||||
.CreateUserAsync(Arg.Any<User>(), Arg.Any<string>());
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// RegisterUserViaEmailVerificationToken tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
@@ -425,6 +569,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUserViaEmailVerificationToken_Succeeds(SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -457,6 +607,12 @@ public class RegisterUserCommandTests
|
||||
public async Task RegisterUserViaEmailVerificationToken_InvalidToken_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -495,6 +651,12 @@ public class RegisterUserCommandTests
|
||||
string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((true, new OrganizationSponsorship()));
|
||||
@@ -524,6 +686,12 @@ public class RegisterUserCommandTests
|
||||
string masterPasswordHash, string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((false, new OrganizationSponsorship()));
|
||||
@@ -561,9 +729,14 @@ public class RegisterUserCommandTests
|
||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
emergencyAccess.Email = user.Email;
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -597,9 +770,14 @@ public class RegisterUserCommandTests
|
||||
string masterPasswordHash, EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
emergencyAccess.Email = "wrong@email.com";
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
@@ -640,6 +818,8 @@ public class RegisterUserCommandTests
|
||||
User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
@@ -662,6 +842,10 @@ public class RegisterUserCommandTests
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user, masterPasswordHash)
|
||||
.Returns(IdentityResult.Success);
|
||||
@@ -691,6 +875,8 @@ public class RegisterUserCommandTests
|
||||
User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
@@ -713,6 +899,10 @@ public class RegisterUserCommandTests
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Using sutProvider in the parameters of the function means that the constructor has already run for the
|
||||
// command so we have to recreate it in order for our mock overrides to be used.
|
||||
sutProvider.Create();
|
||||
@@ -762,6 +952,66 @@ public class RegisterUserCommandTests
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Domain blocking tests (BlockClaimedDomainAccountCreation policy)
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUser(user));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
|
||||
// Verify user creation was never attempted
|
||||
await sutProvider.GetDependency<IUserService>()
|
||||
.DidNotReceive()
|
||||
.CreateUserAsync(Arg.Any<User>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_AllowedDomain_Succeeds(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@allowed-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowed-domain.com")
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CreateUserAsync(user)
|
||||
.Returns(IdentityResult.Success);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.RegisterUser(user);
|
||||
|
||||
// Assert
|
||||
Assert.True(result.Succeeded);
|
||||
await sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.Received(1)
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowed-domain.com");
|
||||
}
|
||||
|
||||
// SendWelcomeEmail tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
[Theory]
|
||||
@@ -799,6 +1049,194 @@ public class RegisterUserCommandTests
|
||||
.SendFreeOrgOrFamilyOrgUserWelcomeEmailAsync(user, organization.Name);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaEmailVerificationToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new RegistrationEmailVerificationTokenable(user.Email, user.Name, receiveMarketingMaterials);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string orgSponsoredFreeFamilyPlanInviteToken)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IValidateRedemptionTokenCommand>()
|
||||
.ValidateRedemptionTokenAsync(orgSponsoredFreeFamilyPlanInviteToken, user.Email)
|
||||
.Returns((true, new OrganizationSponsorship()));
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, masterPasswordHash, orgSponsoredFreeFamilyPlanInviteToken));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaAcceptEmergencyAccessInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
EmergencyAccess emergencyAccess, string acceptEmergencyAccessInviteToken, Guid acceptEmergencyAccessId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
emergencyAccess.Email = user.Email;
|
||||
emergencyAccess.Id = acceptEmergencyAccessId;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<EmergencyAccessInviteTokenable>>()
|
||||
.TryUnprotect(acceptEmergencyAccessInviteToken, out Arg.Any<EmergencyAccessInviteTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new EmergencyAccessInviteTokenable(emergencyAccess, 10);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaAcceptEmergencyAccessInviteToken(user, masterPasswordHash, acceptEmergencyAccessInviteToken, acceptEmergencyAccessId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaProviderInviteToken_BlockedDomain_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, Guid providerUserId)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "user@blocked-domain.com";
|
||||
|
||||
// Start with plaintext
|
||||
var nowMillis = CoreHelpers.ToEpocMilliseconds(DateTime.UtcNow);
|
||||
var decryptedProviderInviteToken = $"ProviderUserInvite {providerUserId} {user.Email} {nowMillis}";
|
||||
|
||||
// Get the byte array of the plaintext
|
||||
var decryptedProviderInviteTokenByteArray = Encoding.UTF8.GetBytes(decryptedProviderInviteToken);
|
||||
|
||||
// Base64 encode the byte array (this is passed to protector.protect(bytes))
|
||||
var base64EncodedProviderInvToken = WebEncoders.Base64UrlEncode(decryptedProviderInviteTokenByteArray);
|
||||
|
||||
var mockDataProtector = Substitute.For<IDataProtector>();
|
||||
|
||||
// Given any byte array, just return the decryptedProviderInviteTokenByteArray (sidestepping any actual encryption)
|
||||
mockDataProtector.Unprotect(Arg.Any<byte[]>()).Returns(decryptedProviderInviteTokenByteArray);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectionProvider>()
|
||||
.CreateProtector("ProviderServiceDataProtector")
|
||||
.Returns(mockDataProtector);
|
||||
|
||||
sutProvider.GetDependency<IGlobalSettings>()
|
||||
.OrganizationInviteExpirationHours.Returns(120); // 5 days
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blocked-domain.com")
|
||||
.Returns(true);
|
||||
|
||||
// Using sutProvider in the parameters of the function means that the constructor has already run for the
|
||||
// command so we have to recreate it in order for our mock overrides to be used.
|
||||
sutProvider.Create();
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaProviderInviteToken(user, masterPasswordHash, base64EncodedProviderInvToken, providerUserId));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
// Invalid email format tests
|
||||
// -----------------------------------------------------------------------------------------------
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUser_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUser(user));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task RegisterUserViaEmailVerificationToken_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash,
|
||||
string emailVerificationToken, bool receiveMarketingMaterials)
|
||||
{
|
||||
// Arrange
|
||||
user.Email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.TryUnprotect(emailVerificationToken, out Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(callInfo =>
|
||||
{
|
||||
callInfo[1] = new RegistrationEmailVerificationTokenable(user.Email, user.Name, receiveMarketingMaterials);
|
||||
return true;
|
||||
});
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendWelcomeEmail_OrganizationNull_SendsIndividualWelcomeEmail(
|
||||
|
||||
@@ -21,9 +21,11 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsNewUserAndEnableEmailVerificationTrue_SendsEmailAndReturnsNull(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
@@ -34,6 +36,10 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
sutProvider.GetDependency<IMailService>()
|
||||
.SendRegistrationVerificationEmailAsync(email, Arg.Any<string>())
|
||||
.Returns(Task.CompletedTask);
|
||||
@@ -56,9 +62,11 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationTrue_ReturnsNull(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(new User());
|
||||
@@ -69,6 +77,10 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
@@ -87,9 +99,11 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsNewUserAndEnableEmailVerificationFalse_ReturnsToken(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
@@ -100,6 +114,10 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
@@ -128,9 +146,11 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationFalse_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string email, string name, bool receiveMarketingEmails)
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@example.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.Returns(new User());
|
||||
@@ -138,6 +158,13 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.EnableEmailVerification = false;
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(Arg.Any<string>())
|
||||
.Returns(false);
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||
}
|
||||
@@ -162,4 +189,88 @@ public class SendVerificationEmailForRegistrationCommandTests
|
||||
.DisableUserRegistration = false;
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenBlockedDomain_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@blockedcompany.com";
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("blockedcompany.com")
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||
Assert.Equal("This email address is claimed by an organization using Bitwarden.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_WhenAllowedDomain_Succeeds(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = $"test+{Guid.NewGuid()}@allowedcompany.com";
|
||||
|
||||
sutProvider.GetDependency<IUserRepository>()
|
||||
.GetByEmailAsync(email)
|
||||
.ReturnsNull();
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.EnableEmailVerification = false;
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationDomainRepository>()
|
||||
.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync("allowedcompany.com")
|
||||
.Returns(false);
|
||||
|
||||
var mockedToken = "token";
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||
.Returns(mockedToken);
|
||||
|
||||
// Act
|
||||
var result = await sutProvider.Sut.Run(email, name, receiveMarketingEmails);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(mockedToken, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendVerificationEmailForRegistrationCommand_InvalidEmailFormat_ThrowsBadRequestException(
|
||||
SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||
string name, bool receiveMarketingEmails)
|
||||
{
|
||||
// Arrange
|
||||
var email = "invalid-email-format";
|
||||
|
||||
sutProvider.GetDependency<GlobalSettings>()
|
||||
.DisableUserRegistration = false;
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>()
|
||||
.IsEnabled(FeatureFlagKeys.BlockClaimedDomainAccountCreation)
|
||||
.Returns(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
51
test/Core.Test/Utilities/EmailValidationTests.cs
Normal file
51
test/Core.Test/Utilities/EmailValidationTests.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Utilities;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Utilities;
|
||||
|
||||
public class EmailValidationTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("user@Example.COM", "example.com")]
|
||||
[InlineData("user@EXAMPLE.COM", "example.com")]
|
||||
[InlineData("user@example.com", "example.com")]
|
||||
[InlineData("user@Example.Com", "example.com")]
|
||||
[InlineData("User@DOMAIN.CO.UK", "domain.co.uk")]
|
||||
public void GetDomain_WithMixedCaseEmail_ReturnsLowercaseDomain(string email, string expectedDomain)
|
||||
{
|
||||
// Act
|
||||
var result = EmailValidation.GetDomain(email);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDomain, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("hello@world.com", "world.com")] // regular email address
|
||||
[InlineData("hello@world.planet.com", "world.planet.com")] // subdomain
|
||||
[InlineData("hello+1@world.com", "world.com")] // alias
|
||||
[InlineData("hello.there@world.com", "world.com")] // period in local-part
|
||||
[InlineData("hello@wörldé.com", "wörldé.com")] // unicode domain
|
||||
[InlineData("hello@world.cömé", "world.cömé")] // unicode top-level domain
|
||||
public void GetDomain_WithValidEmail_ReturnsLowercaseDomain(string email, string expectedDomain)
|
||||
{
|
||||
// Act
|
||||
var result = EmailValidation.GetDomain(email);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(expectedDomain, result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid-email")]
|
||||
[InlineData("@example.com")]
|
||||
[InlineData("user@")]
|
||||
[InlineData("")]
|
||||
public void GetDomain_WithInvalidEmail_ThrowsBadRequestException(string email)
|
||||
{
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<BadRequestException>(() => EmailValidation.GetDomain(email));
|
||||
Assert.Equal("Invalid email address format.", exception.Message);
|
||||
}
|
||||
}
|
||||
@@ -242,7 +242,7 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
var orgInviteToken = "BwOrgUserInviteToken_CfDJ8HOzu6wr6nVLouuDxgOHsMwPcj9Guuip5k_XLD1bBGpwQS1f66c9kB6X4rvKGxNdywhgimzgvG9SgLwwJU70O8P879XyP94W6kSoT4N25a73kgW3nU3vl3fAtGSS52xdBjNU8o4sxmomRvhOZIQ0jwtVjdMC2IdybTbxwCZhvN0hKIFs265k6wFRSym1eu4NjjZ8pmnMneG0PlKnNZL93tDe8FMcqStJXoddIEgbA99VJp8z1LQmOMfEdoMEM7Zs8W5bZ34N4YEGu8XCrVau59kGtWQk7N4rPV5okzQbTpeoY_4FeywgLFGm-tDtTPEdSEBJkRjexANri7CGdg3dpnMifQc_bTmjZd32gOjw8N8v";
|
||||
var orgUserId = new Guid("5e45fbdc-a080-4a77-93ff-b19c0161e81e");
|
||||
|
||||
var orgUser = new OrganizationUser { Id = orgUserId, Email = email };
|
||||
var orgUser = new OrganizationUser { Id = orgUserId, Email = email, OrganizationId = Guid.NewGuid() };
|
||||
|
||||
var orgInviteTokenable = new OrgUserInviteTokenable(orgUser)
|
||||
{
|
||||
@@ -259,6 +259,12 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
});
|
||||
});
|
||||
|
||||
localFactory.SubstituteService<IOrganizationUserRepository>(orgUserRepository =>
|
||||
{
|
||||
orgUserRepository.GetByIdAsync(orgUserId)
|
||||
.Returns(orgUser);
|
||||
});
|
||||
|
||||
var registerFinishReqModel = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
@@ -7,7 +9,7 @@ namespace Bit.Infrastructure.IntegrationTest.AdminConsole.Repositories;
|
||||
|
||||
public class OrganizationDomainRepositoryTests
|
||||
{
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetExpiredOrganizationDomainsAsync_ShouldReturn3DaysOldUnverifiedDomains(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -74,7 +76,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.NotNull(expectedDomain2);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetExpiredOrganizationDomainsAsync_ShouldNotReturnDomainsUnder3DaysOld(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -120,7 +122,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.Null(expectedDomain2);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetExpiredOrganizationDomainsAsync_ShouldNotReturnVerifiedDomains(
|
||||
IUserRepository userRepository,
|
||||
IOrganizationRepository organizationRepository,
|
||||
@@ -189,7 +191,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.Null(expectedDomain2);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetManyByNextRunDateAsync_ShouldReturnUnverifiedDomains(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
@@ -228,7 +230,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.NotNull(expectedDomain);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetManyByNextRunDateAsync_ShouldNotReturnUnverifiedDomains_WhenNextRunDateIsOutside36hoursWindow(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
@@ -267,7 +269,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.Null(expectedDomain);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetManyByNextRunDateAsync_ShouldNotReturnVerifiedDomains(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
@@ -307,7 +309,7 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.Null(expectedDomain);
|
||||
}
|
||||
|
||||
[DatabaseTheory, DatabaseData]
|
||||
[Theory, DatabaseData]
|
||||
public async Task GetVerifiedDomainsByOrganizationIdsAsync_ShouldVerifiedDomainsMatchesOrganizationIds(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
@@ -383,4 +385,437 @@ public class OrganizationDomainRepositoryTests
|
||||
Assert.Null(otherOrganizationDomain);
|
||||
Assert.Null(unverifiedDomain);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithVerifiedDomainAndBlockPolicy_ReturnsTrue(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithUnverifiedDomain_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
// Do not verify the domain
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithDisabledPolicy_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = false
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithDisabledOrganization_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = false,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithUsePoliciesFalse_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = false, // Organization doesn't have policies feature
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithUseOrganizationDomainsFalse_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = false // Organization doesn't have organization domains feature
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithNoPolicyOfType_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
// No policy created
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_WithNonExistentDomain_ReturnsFalse(
|
||||
IOrganizationDomainRepository organizationDomainRepository)
|
||||
{
|
||||
// Arrange
|
||||
var domainName = $"nonexistent-{Guid.NewGuid()}.example.com";
|
||||
|
||||
// Act
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_ExcludeOrganization_WhenSameOrg_ReturnsFalse(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org {id}",
|
||||
BillingEmail = $"test+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain.SetNextRunDate(1);
|
||||
organizationDomain.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain);
|
||||
|
||||
var policy = new Policy
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy);
|
||||
|
||||
// Act - Exclude the same organization that has the domain
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName, organization.Id);
|
||||
|
||||
// Assert - Should return false because we're excluding the only org with this domain
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Theory, DatabaseData]
|
||||
public async Task HasVerifiedDomainWithBlockClaimedDomainPolicyAsync_ExcludeOrganization_WhenDifferentOrg_ReturnsTrue(
|
||||
IOrganizationRepository organizationRepository,
|
||||
IOrganizationDomainRepository organizationDomainRepository,
|
||||
IPolicyRepository policyRepository)
|
||||
{
|
||||
// Arrange
|
||||
var id = Guid.NewGuid();
|
||||
var domainName = $"test-{id}.example.com";
|
||||
|
||||
var organization1 = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org 1 {id}",
|
||||
BillingEmail = $"test1+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
var organizationDomain1 = new OrganizationDomain
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
DomainName = domainName,
|
||||
Txt = "btw+12345"
|
||||
};
|
||||
organizationDomain1.SetNextRunDate(1);
|
||||
organizationDomain1.SetVerifiedDate();
|
||||
await organizationDomainRepository.CreateAsync(organizationDomain1);
|
||||
|
||||
var policy1 = new Policy
|
||||
{
|
||||
OrganizationId = organization1.Id,
|
||||
Type = PolicyType.BlockClaimedDomainAccountCreation,
|
||||
Enabled = true
|
||||
};
|
||||
await policyRepository.CreateAsync(policy1);
|
||||
|
||||
// Create a second organization (the one we'll exclude)
|
||||
var organization2 = await organizationRepository.CreateAsync(new Organization
|
||||
{
|
||||
Name = $"Test Org 2 {id}",
|
||||
BillingEmail = $"test2+{id}@example.com",
|
||||
Plan = "Test",
|
||||
PrivateKey = "privatekey",
|
||||
Enabled = true,
|
||||
UsePolicies = true,
|
||||
UseOrganizationDomains = true
|
||||
});
|
||||
|
||||
// Act - Exclude organization2 (but organization1 still has the domain blocked)
|
||||
var result = await organizationDomainRepository.HasVerifiedDomainWithBlockClaimedDomainPolicyAsync(domainName, organization2.Id);
|
||||
|
||||
// Assert - Should return true because organization1 (not excluded) has the domain blocked
|
||||
Assert.True(result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user