mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
[PM-13322] [BEEEP] Add PolicyValidators and refactor policy save logic (#4877)
This commit is contained in:
@ -127,7 +127,6 @@ public class SutProvider<TSut> : ISutProvider
|
||||
return _sutProvider.GetDependency(parameterInfo.ParameterType, "");
|
||||
}
|
||||
|
||||
|
||||
// This is the equivalent of _fixture.Create<parameterInfo.ParameterType>, but no overload for
|
||||
// Create(Type type) exists.
|
||||
var dependency = new SpecimenContext(_fixture).Resolve(new SeededRequest(parameterInfo.ParameterType,
|
||||
|
@ -1,6 +1,7 @@
|
||||
using AutoFixture;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using RichardSzalay.MockHttp;
|
||||
|
||||
@ -47,4 +48,19 @@ public static class SutProviderExtensions
|
||||
.SetDependency(mockHttpClientFactory)
|
||||
.Create();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures SutProvider to use FakeTimeProvider.
|
||||
/// It is registered under both the TimeProvider type and the FakeTimeProvider type
|
||||
/// so that it can be retrieved in a type-safe manner with GetDependency.
|
||||
/// This can be chained with other builder methods; make sure to call
|
||||
/// <see cref="ISutProvider.Create"/> before use.
|
||||
/// </summary>
|
||||
public static SutProvider<T> WithFakeTimeProvider<T>(this SutProvider<T> sutProvider)
|
||||
{
|
||||
var fakeTimeProvider = new FakeTimeProvider();
|
||||
return sutProvider
|
||||
.SetDependency((TimeProvider)fakeTimeProvider)
|
||||
.SetDependency(fakeTimeProvider);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" Version="8.10.0" />
|
||||
<PackageReference Include="NSubstitute" Version="$(NSubstituteVersion)" />
|
||||
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="$(XUnitRunnerVisualStudioVersion)">
|
||||
|
@ -9,10 +9,12 @@ namespace Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
internal class PolicyCustomization : ICustomization
|
||||
{
|
||||
public PolicyType Type { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
|
||||
public PolicyCustomization(PolicyType type)
|
||||
public PolicyCustomization(PolicyType type, bool enabled)
|
||||
{
|
||||
Type = type;
|
||||
Enabled = enabled;
|
||||
}
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
@ -20,21 +22,23 @@ internal class PolicyCustomization : ICustomization
|
||||
fixture.Customize<Policy>(composer => composer
|
||||
.With(o => o.OrganizationId, Guid.NewGuid())
|
||||
.With(o => o.Type, Type)
|
||||
.With(o => o.Enabled, true));
|
||||
.With(o => o.Enabled, Enabled));
|
||||
}
|
||||
}
|
||||
|
||||
public class PolicyAttribute : CustomizeAttribute
|
||||
{
|
||||
private readonly PolicyType _type;
|
||||
private readonly bool _enabled;
|
||||
|
||||
public PolicyAttribute(PolicyType type)
|
||||
public PolicyAttribute(PolicyType type, bool enabled = true)
|
||||
{
|
||||
_type = type;
|
||||
_enabled = enabled;
|
||||
}
|
||||
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new PolicyCustomization(_type);
|
||||
return new PolicyCustomization(_type, _enabled);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,25 @@
|
||||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
|
||||
internal class PolicyUpdateCustomization(PolicyType type, bool enabled) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<PolicyUpdate>(composer => composer
|
||||
.With(o => o.Type, type)
|
||||
.With(o => o.Enabled, enabled));
|
||||
}
|
||||
}
|
||||
|
||||
public class PolicyUpdateAttribute(PolicyType type, bool enabled = true) : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new PolicyUpdateCustomization(type, enabled);
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using NSubstitute;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies;
|
||||
|
||||
public class FakeSingleOrgPolicyValidator : IPolicyValidator
|
||||
{
|
||||
public PolicyType Type => PolicyType.SingleOrg;
|
||||
public IEnumerable<PolicyType> RequiredPolicies => Array.Empty<PolicyType>();
|
||||
|
||||
public readonly Func<PolicyUpdate, Policy?, Task<string>> ValidateAsyncMock = Substitute.For<Func<PolicyUpdate, Policy?, Task<string>>>();
|
||||
public readonly Action<PolicyUpdate, Policy?> OnSaveSideEffectsAsyncMock = Substitute.For<Action<PolicyUpdate, Policy?>>();
|
||||
|
||||
public Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||
{
|
||||
return ValidateAsyncMock(policyUpdate, currentPolicy);
|
||||
}
|
||||
|
||||
public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy)
|
||||
{
|
||||
OnSaveSideEffectsAsyncMock(policyUpdate, currentPolicy);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
public class FakeRequireSsoPolicyValidator : IPolicyValidator
|
||||
{
|
||||
public PolicyType Type => PolicyType.RequireSso;
|
||||
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
||||
public Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult("");
|
||||
public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0);
|
||||
}
|
||||
public class FakeVaultTimeoutPolicyValidator : IPolicyValidator
|
||||
{
|
||||
public PolicyType Type => PolicyType.MaximumVaultTimeout;
|
||||
public IEnumerable<PolicyType> RequiredPolicies => [PolicyType.SingleOrg];
|
||||
public Task<string> ValidateAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult("");
|
||||
public Task OnSaveSideEffectsAsync(PolicyUpdate policyUpdate, Policy? currentPolicy) => Task.FromResult(0);
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies;
|
||||
|
||||
public class PolicyValidatorHelpersTests
|
||||
{
|
||||
[Fact]
|
||||
public void ValidateDecryptionOptionsNotEnabled_RequiredByKeyConnector_ValidationError()
|
||||
{
|
||||
var ssoConfig = new SsoConfig();
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
|
||||
var result = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]);
|
||||
|
||||
Assert.Contains("Key Connector is enabled", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateDecryptionOptionsNotEnabled_RequiredByTDE_ValidationError()
|
||||
{
|
||||
var ssoConfig = new SsoConfig();
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
var result = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.TrustedDeviceEncryption]);
|
||||
|
||||
Assert.Contains("Trusted device encryption is on", result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateDecryptionOptionsNotEnabled_NullSsoConfig_NoValidationError()
|
||||
{
|
||||
var ssoConfig = new SsoConfig();
|
||||
var result = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]);
|
||||
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateDecryptionOptionsNotEnabled_RequiredOptionNotEnabled_NoValidationError()
|
||||
{
|
||||
var ssoConfig = new SsoConfig();
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
|
||||
var result = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.TrustedDeviceEncryption]);
|
||||
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateDecryptionOptionsNotEnabled_SsoConfigDisabled_NoValidationError()
|
||||
{
|
||||
var ssoConfig = new SsoConfig();
|
||||
ssoConfig.Enabled = false;
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
|
||||
var result = ssoConfig.ValidateDecryptionOptionsNotEnabled([MemberDecryptionType.KeyConnector]);
|
||||
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class RequireSsoPolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationError(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy policy,
|
||||
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy policy,
|
||||
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.Contains("Trusted device encryption is on", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_DecryptionOptionsNotEnabled_Success(
|
||||
[PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<RequireSsoPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = false };
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class ResetPasswordPolicyValidatorTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(false, false)]
|
||||
public async Task ValidateAsync_DisablingPolicy_TdeEnabled_ValidationError(
|
||||
bool policyEnabled,
|
||||
bool autoEnrollEnabled,
|
||||
[PolicyUpdate(PolicyType.ResetPassword)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<ResetPasswordPolicyValidator> sutProvider)
|
||||
{
|
||||
policyUpdate.Enabled = policyEnabled;
|
||||
policyUpdate.SetDataModel(new ResetPasswordDataModel
|
||||
{
|
||||
AutoEnrollEnabled = autoEnrollEnabled
|
||||
});
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.Contains("Trusted device encryption is on and requires this policy.", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_TdeNotEnabled_Success(
|
||||
[PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<ResetPasswordPolicyValidator> sutProvider)
|
||||
{
|
||||
policyUpdate.SetDataModel(new ResetPasswordDataModel
|
||||
{
|
||||
AutoEnrollEnabled = false
|
||||
});
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = false };
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
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;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class SingleOrgPolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_KeyConnectorEnabled_ValidationError(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy policy,
|
||||
SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = true };
|
||||
ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.KeyConnector });
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.Contains("Key Connector is enabled", result, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_DisablingPolicy_KeyConnectorNotEnabled_Success(
|
||||
[PolicyUpdate(PolicyType.ResetPassword, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.ResetPassword)] Policy policy,
|
||||
SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = policyUpdate.OrganizationId;
|
||||
|
||||
var ssoConfig = new SsoConfig { Enabled = false };
|
||||
|
||||
sutProvider.GetDependency<ISsoConfigRepository>()
|
||||
.GetByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns(ssoConfig);
|
||||
|
||||
var result = await sutProvider.Sut.ValidateAsync(policyUpdate, policy);
|
||||
Assert.True(string.IsNullOrEmpty(result));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RemovesNonCompliantUsers(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy policy,
|
||||
Guid savingUserId,
|
||||
Guid nonCompliantUserId,
|
||||
Organization organization, SutProvider<SingleOrgPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var compliantUser1 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user1@example.com"
|
||||
};
|
||||
|
||||
var compliantUser2 = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = new Guid(),
|
||||
Email = "user2@example.com"
|
||||
};
|
||||
|
||||
var nonCompliantUser = new OrganizationUserUserDetails
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
Type = OrganizationUserType.User,
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
UserId = nonCompliantUserId,
|
||||
Email = "user3@example.com"
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns([compliantUser1, compliantUser2, nonCompliantUser]);
|
||||
|
||||
var otherOrganizationUser = new OrganizationUser
|
||||
{
|
||||
OrganizationId = new Guid(),
|
||||
UserId = nonCompliantUserId,
|
||||
Status = OrganizationUserStatusType.Confirmed
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyByManyUsersAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(nonCompliantUserId)))
|
||||
.Returns([otherOrganizationUser]);
|
||||
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(policyUpdate.OrganizationId).Returns(organization);
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>()
|
||||
.Received(1)
|
||||
.RemoveUserAsync(policyUpdate.OrganizationId, nonCompliantUser.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>()
|
||||
.Received(1)
|
||||
.SendOrganizationUserRemovedForPolicySingleOrgEmailAsync(organization.DisplayName(),
|
||||
"user3@example.com");
|
||||
}
|
||||
}
|
@ -0,0 +1,209 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyValidators;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class TwoFactorAuthenticationPolicyValidatorTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_RemovesNonCompliantUsers(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
|
||||
var orgUserDetailUserInvited = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Invited,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserAcceptedWith2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserAcceptedWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Accepted,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policyUpdate.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserInvited,
|
||||
orgUserDetailUserAcceptedWith2FA,
|
||||
orgUserDetailUserAcceptedWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Any<IEnumerable<OrganizationUserUserDetails>>())
|
||||
.Returns(new List<(OrganizationUserUserDetails user, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserInvited, false),
|
||||
(orgUserDetailUserAcceptedWith2FA, true),
|
||||
(orgUserDetailUserAcceptedWithout2FA, false),
|
||||
(orgUserDetailAdmin, false),
|
||||
});
|
||||
|
||||
var savingUserId = Guid.NewGuid();
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(savingUserId);
|
||||
|
||||
await sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy);
|
||||
|
||||
var removeOrganizationUserCommand = sutProvider.GetDependency<IRemoveOrganizationUserCommand>();
|
||||
|
||||
await removeOrganizationUserCommand.Received()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWithout2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().Received()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWithout2FA.Email);
|
||||
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserInvited.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserInvited.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailUserAcceptedWith2FA.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailUserAcceptedWith2FA.Email);
|
||||
await removeOrganizationUserCommand.DidNotReceive()
|
||||
.RemoveUserAsync(policy.OrganizationId, orgUserDetailAdmin.Id, savingUserId);
|
||||
await sutProvider.GetDependency<IMailService>().DidNotReceive()
|
||||
.SendOrganizationUserRemovedForPolicyTwoStepEmailAsync(organization.DisplayName(), orgUserDetailAdmin.Email);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task OnSaveSideEffectsAsync_UsersToBeRemovedDontHaveMasterPasswords_Throws(
|
||||
Organization organization,
|
||||
[PolicyUpdate(PolicyType.TwoFactorAuthentication)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.TwoFactorAuthentication, false)] Policy policy,
|
||||
SutProvider<TwoFactorAuthenticationPolicyValidator> sutProvider)
|
||||
{
|
||||
policy.OrganizationId = organization.Id = policyUpdate.OrganizationId;
|
||||
|
||||
var orgUserDetailUserWith2FAAndMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user1@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = true
|
||||
};
|
||||
var orgUserDetailUserWith2FANoMP = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user2@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailUserWithout2FA = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.User,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "user3@test.com",
|
||||
Name = "TEST",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
var orgUserDetailAdmin = new OrganizationUserUserDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Status = OrganizationUserStatusType.Confirmed,
|
||||
Type = OrganizationUserType.Admin,
|
||||
// Needs to be different from what is passed in as the savingUserId to Sut.SaveAsync
|
||||
Email = "admin@test.com",
|
||||
Name = "ADMIN",
|
||||
UserId = Guid.NewGuid(),
|
||||
HasMasterPassword = false
|
||||
};
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetManyDetailsByOrganizationAsync(policy.OrganizationId)
|
||||
.Returns(new List<OrganizationUserUserDetails>
|
||||
{
|
||||
orgUserDetailUserWith2FAAndMP,
|
||||
orgUserDetailUserWith2FANoMP,
|
||||
orgUserDetailUserWithout2FA,
|
||||
orgUserDetailAdmin
|
||||
});
|
||||
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids =>
|
||||
ids.Contains(orgUserDetailUserWith2FANoMP.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailUserWithout2FA.UserId.Value)
|
||||
&& ids.Contains(orgUserDetailAdmin.UserId.Value)))
|
||||
.Returns(new List<(Guid userId, bool hasTwoFactor)>()
|
||||
{
|
||||
(orgUserDetailUserWith2FANoMP.UserId.Value, true),
|
||||
(orgUserDetailUserWithout2FA.UserId.Value, false),
|
||||
(orgUserDetailAdmin.UserId.Value, false),
|
||||
});
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.OnSaveSideEffectsAsync(policyUpdate, policy));
|
||||
|
||||
Assert.Contains("Policy could not be enabled. Non-compliant members will lose access to their accounts. Identify members without two-step login from the policies column in the members page.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
await sutProvider.GetDependency<IRemoveOrganizationUserCommand>().DidNotReceiveWithAnyArgs()
|
||||
.RemoveUserAsync(organizationId: default, organizationUserId: default, deletingUserId: default);
|
||||
}
|
||||
}
|
@ -0,0 +1,330 @@
|
||||
#nullable enable
|
||||
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data.Organizations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using EventType = Bit.Core.Enums.EventType;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies;
|
||||
|
||||
public class SavePolicyCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_NewPolicy_Success([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]);
|
||||
|
||||
var creationDate = sutProvider.GetDependency<FakeTimeProvider>().Start;
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
|
||||
await fakePolicyValidator.ValidateAsyncMock.Received(1).Invoke(policyUpdate, null);
|
||||
fakePolicyValidator.OnSaveSideEffectsAsyncMock.Received(1).Invoke(policyUpdate, null);
|
||||
|
||||
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received(1).UpsertAsync(Arg.Is<Policy>(p =>
|
||||
p.CreationDate == creationDate &&
|
||||
p.RevisionDate == creationDate));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ExistingPolicy_Success(
|
||||
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy currentPolicy)
|
||||
{
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
|
||||
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)
|
||||
.Returns(currentPolicy);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy]);
|
||||
|
||||
// Store mutable properties separately to assert later
|
||||
var id = currentPolicy.Id;
|
||||
var organizationId = currentPolicy.OrganizationId;
|
||||
var type = currentPolicy.Type;
|
||||
var creationDate = currentPolicy.CreationDate;
|
||||
var revisionDate = sutProvider.GetDependency<FakeTimeProvider>().Start;
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
|
||||
await fakePolicyValidator.ValidateAsyncMock.Received(1).Invoke(policyUpdate, currentPolicy);
|
||||
fakePolicyValidator.OnSaveSideEffectsAsyncMock.Received(1).Invoke(policyUpdate, currentPolicy);
|
||||
|
||||
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
||||
// Additional assertions to ensure certain properties have or have not been updated
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received(1).UpsertAsync(Arg.Is<Policy>(p =>
|
||||
p.Id == id &&
|
||||
p.OrganizationId == organizationId &&
|
||||
p.Type == type &&
|
||||
p.CreationDate == creationDate &&
|
||||
p.RevisionDate == revisionDate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DuplicatePolicyValidators_Throws()
|
||||
{
|
||||
var exception = Assert.Throws<Exception>(() =>
|
||||
new SavePolicyCommand(
|
||||
Substitute.For<IApplicationCacheService>(),
|
||||
Substitute.For<IEventService>(),
|
||||
Substitute.For<IPolicyRepository>(),
|
||||
[new FakeSingleOrgPolicyValidator(), new FakeSingleOrgPolicyValidator()],
|
||||
Substitute.For<TimeProvider>()
|
||||
));
|
||||
Assert.Contains("Duplicate PolicyValidator for SingleOrg policy", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
||||
.Returns(Task.FromResult<OrganizationAbility?>(null));
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest(PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory();
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
||||
.Returns(new OrganizationAbility
|
||||
{
|
||||
Id = policyUpdate.OrganizationId,
|
||||
UsePolicies = false
|
||||
});
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("cannot use policies", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequiredPolicyIsNull_Throws(
|
||||
[PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([]);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequiredPolicyNotEnabled_Throws(
|
||||
[PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg, false)] Policy singleOrgPolicy)
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([singleOrgPolicy]);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Turn on the Single organization policy because it is required for the Require single sign-on authentication policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_RequiredPolicyEnabled_Success(
|
||||
[PolicyUpdate(PolicyType.RequireSso)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy singleOrgPolicy)
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([singleOrgPolicy]);
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_DependentPolicyIsEnabled_Throws(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy currentPolicy,
|
||||
[Policy(PolicyType.RequireSso)] Policy requireSsoPolicy) // depends on Single Org
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy, requireSsoPolicy]);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Turn off the Require single sign-on authentication policy because it requires the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_MultipleDependentPoliciesAreEnabled_Throws(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy currentPolicy,
|
||||
[Policy(PolicyType.RequireSso)] Policy requireSsoPolicy, // depends on Single Org
|
||||
[Policy(PolicyType.MaximumVaultTimeout)] Policy vaultTimeoutPolicy) // depends on Single Org
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator(),
|
||||
new FakeVaultTimeoutPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy, requireSsoPolicy, vaultTimeoutPolicy]);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Turn off all of the policies that require the Single organization policy", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_DependentPolicyNotEnabled_Success(
|
||||
[PolicyUpdate(PolicyType.SingleOrg, false)] PolicyUpdate policyUpdate,
|
||||
[Policy(PolicyType.SingleOrg)] Policy currentPolicy,
|
||||
[Policy(PolicyType.RequireSso, false)] Policy requireSsoPolicy) // depends on Single Org but is not enabled
|
||||
{
|
||||
var sutProvider = SutProviderFactory([
|
||||
new FakeRequireSsoPolicyValidator(),
|
||||
new FakeSingleOrgPolicyValidator()
|
||||
]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>()
|
||||
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||
.Returns([currentPolicy, requireSsoPolicy]);
|
||||
|
||||
await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||
|
||||
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task SaveAsync_ThrowsOnValidationError([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
||||
{
|
||||
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("Validation error!");
|
||||
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||
|
||||
ArrangeOrganization(sutProvider, policyUpdate);
|
||||
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]);
|
||||
|
||||
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveAsync(policyUpdate));
|
||||
|
||||
Assert.Contains("Validation error!", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
||||
await AssertPolicyNotSavedAsync(sutProvider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new SutProvider with the PolicyValidators registered in the Sut.
|
||||
/// </summary>
|
||||
private static SutProvider<SavePolicyCommand> SutProviderFactory(IEnumerable<IPolicyValidator>? policyValidators = null)
|
||||
{
|
||||
return new SutProvider<SavePolicyCommand>()
|
||||
.WithFakeTimeProvider()
|
||||
.SetDependency(typeof(IEnumerable<IPolicyValidator>), policyValidators ?? [])
|
||||
.Create();
|
||||
}
|
||||
|
||||
private static void ArrangeOrganization(SutProvider<SavePolicyCommand> sutProvider, PolicyUpdate policyUpdate)
|
||||
{
|
||||
sutProvider.GetDependency<IApplicationCacheService>()
|
||||
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
||||
.Returns(new OrganizationAbility
|
||||
{
|
||||
Id = policyUpdate.OrganizationId,
|
||||
UsePolicies = true
|
||||
});
|
||||
}
|
||||
|
||||
private static async Task AssertPolicyNotSavedAsync(SutProvider<SavePolicyCommand> sutProvider)
|
||||
{
|
||||
await sutProvider.GetDependency<IPolicyRepository>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.UpsertAsync(default!);
|
||||
|
||||
await sutProvider.GetDependency<IEventService>()
|
||||
.DidNotReceiveWithAnyArgs()
|
||||
.LogPolicyEventAsync(default, default);
|
||||
}
|
||||
|
||||
private static async Task AssertPolicySavedAsync(SutProvider<SavePolicyCommand> sutProvider, PolicyUpdate policyUpdate)
|
||||
{
|
||||
var expectedPolicy = () => Arg.Is<Policy>(p =>
|
||||
p.Type == policyUpdate.Type &&
|
||||
p.OrganizationId == policyUpdate.OrganizationId &&
|
||||
p.Enabled == policyUpdate.Enabled &&
|
||||
p.Data == policyUpdate.Data);
|
||||
|
||||
await sutProvider.GetDependency<IPolicyRepository>().Received(1).UpsertAsync(expectedPolicy());
|
||||
|
||||
await sutProvider.GetDependency<IEventService>().Received(1)
|
||||
.LogPolicyEventAsync(expectedPolicy(), EventType.Policy_Updated);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user