mirror of
https://github.com/bitwarden/server.git
synced 2025-12-10 15:55:10 -06:00
* Implement IOnPolicyPreUpdateEvent for FreeFamiliesForEnterprisePolicyValidator and add corresponding unit tests * Implement IEnforceDependentPoliciesEvent in MaximumVaultTimeoutPolicyValidator * Rename test methods in FreeFamiliesForEnterprisePolicyValidatorTests for consistency * Implement IPolicyValidationEvent and IEnforceDependentPoliciesEvent in RequireSsoPolicyValidator and enhance unit tests * Implement IPolicyValidationEvent and IEnforceDependentPoliciesEvent in ResetPasswordPolicyValidator and add unit tests * Implement IOnPolicyPreUpdateEvent in TwoFactorAuthenticationPolicyValidator and add unit tests * Implement IPolicyValidationEvent and IOnPolicyPreUpdateEvent in SingleOrgPolicyValidator with corresponding unit tests * Implement IOnPolicyPostUpdateEvent in OrganizationDataOwnershipPolicyValidator and add unit tests for ExecutePostUpsertSideEffectAsync * Refactor policy validation logic in VNextSavePolicyCommand to simplify enabling and disabling requirements checks * Refactor VNextSavePolicyCommand to replace IEnforceDependentPoliciesEvent with IPolicyUpdateEvent and update related tests * Add AddPolicyUpdateEvents method and update service registration for policy update events
458 lines
18 KiB
C#
458 lines
18 KiB
C#
#nullable enable
|
|
|
|
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.AdminConsole.Enums;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Interfaces;
|
|
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 OneOf.Types;
|
|
using Xunit;
|
|
using EventType = Bit.Core.Enums.EventType;
|
|
|
|
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies;
|
|
|
|
public class VNextSavePolicyCommandTests
|
|
{
|
|
[Theory, BitAutoData]
|
|
public async Task SaveAsync_NewPolicy_Success([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
|
{
|
|
// Arrange
|
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
|
var sutProvider = SutProviderFactory([
|
|
new FakeSingleOrgDependencyEvent(),
|
|
fakePolicyValidationEvent
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
var newPolicy = new Policy
|
|
{
|
|
Type = policyUpdate.Type,
|
|
OrganizationId = policyUpdate.OrganizationId,
|
|
Enabled = false
|
|
};
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([newPolicy]);
|
|
|
|
var creationDate = sutProvider.GetDependency<FakeTimeProvider>().Start;
|
|
|
|
// Act
|
|
await sutProvider.Sut.SaveAsync(savePolicyModel);
|
|
|
|
// Assert
|
|
await fakePolicyValidationEvent.ValidateAsyncMock
|
|
.Received(1)
|
|
.Invoke(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>());
|
|
|
|
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)
|
|
{
|
|
// Arrange
|
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("");
|
|
var sutProvider = SutProviderFactory([
|
|
new FakeSingleOrgDependencyEvent(),
|
|
fakePolicyValidationEvent
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
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]);
|
|
|
|
// Act
|
|
await sutProvider.Sut.SaveAsync(savePolicyModel);
|
|
|
|
// Assert
|
|
await fakePolicyValidationEvent.ValidateAsyncMock
|
|
.Received(1)
|
|
.Invoke(Arg.Any<SavePolicyModel>(), currentPolicy);
|
|
|
|
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
|
|
|
|
|
var revisionDate = sutProvider.GetDependency<FakeTimeProvider>().Start;
|
|
|
|
await sutProvider.GetDependency<IPolicyRepository>()
|
|
.Received(1)
|
|
.UpsertAsync(Arg.Is<Policy>(p =>
|
|
p.Id == currentPolicy.Id &&
|
|
p.OrganizationId == currentPolicy.OrganizationId &&
|
|
p.Type == currentPolicy.Type &&
|
|
p.CreationDate == currentPolicy.CreationDate &&
|
|
p.RevisionDate == revisionDate));
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task SaveAsync_OrganizationDoesNotExist_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory();
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
sutProvider.GetDependency<IApplicationCacheService>()
|
|
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
|
.Returns(Task.FromResult<OrganizationAbility?>(null));
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
Assert.Contains("Organization not found", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
|
await AssertPolicyNotSavedAsync(sutProvider);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task SaveAsync_OrganizationCannotUsePolicies_ThrowsBadRequest([PolicyUpdate(PolicyType.ActivateAutofill)] PolicyUpdate policyUpdate)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory();
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
sutProvider.GetDependency<IApplicationCacheService>()
|
|
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
|
.Returns(new OrganizationAbility
|
|
{
|
|
Id = policyUpdate.OrganizationId,
|
|
UsePolicies = false
|
|
});
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
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)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
var requireSsoPolicy = new Policy
|
|
{
|
|
Type = PolicyType.RequireSso,
|
|
OrganizationId = policyUpdate.OrganizationId,
|
|
Enabled = false
|
|
};
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([requireSsoPolicy]);
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
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)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
var requireSsoPolicy = new Policy
|
|
{
|
|
Type = PolicyType.RequireSso,
|
|
OrganizationId = policyUpdate.OrganizationId,
|
|
Enabled = false
|
|
};
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([singleOrgPolicy, requireSsoPolicy]);
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
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)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
var requireSsoPolicy = new Policy
|
|
{
|
|
Type = PolicyType.RequireSso,
|
|
OrganizationId = policyUpdate.OrganizationId,
|
|
Enabled = false
|
|
};
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([singleOrgPolicy, requireSsoPolicy]);
|
|
|
|
// Act
|
|
await sutProvider.Sut.SaveAsync(savePolicyModel);
|
|
|
|
// Assert
|
|
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)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([currentPolicy, requireSsoPolicy]);
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
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,
|
|
[Policy(PolicyType.MaximumVaultTimeout)] Policy vaultTimeoutPolicy)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent(),
|
|
new FakeVaultTimeoutDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([currentPolicy, requireSsoPolicy, vaultTimeoutPolicy]);
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
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)
|
|
{
|
|
// Arrange
|
|
var sutProvider = SutProviderFactory(
|
|
[
|
|
new FakeRequireSsoDependencyEvent(),
|
|
new FakeSingleOrgDependencyEvent()
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>()
|
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
|
.Returns([currentPolicy, requireSsoPolicy]);
|
|
|
|
// Act
|
|
await sutProvider.Sut.SaveAsync(savePolicyModel);
|
|
|
|
// Assert
|
|
await AssertPolicySavedAsync(sutProvider, policyUpdate);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task SaveAsync_ThrowsOnValidationError([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
|
{
|
|
// Arrange
|
|
var fakePolicyValidationEvent = new FakeSingleOrgValidationEvent();
|
|
fakePolicyValidationEvent.ValidateAsyncMock(Arg.Any<SavePolicyModel>(), Arg.Any<Policy>()).Returns("Validation error!");
|
|
var sutProvider = SutProviderFactory([
|
|
new FakeSingleOrgDependencyEvent(),
|
|
fakePolicyValidationEvent
|
|
]);
|
|
|
|
var savePolicyModel = new SavePolicyModel(policyUpdate, null, new EmptyMetadataModel());
|
|
|
|
var singleOrgPolicy = new Policy
|
|
{
|
|
Type = PolicyType.SingleOrg,
|
|
OrganizationId = policyUpdate.OrganizationId,
|
|
Enabled = false
|
|
};
|
|
|
|
ArrangeOrganization(sutProvider, policyUpdate);
|
|
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([singleOrgPolicy]);
|
|
|
|
// Act
|
|
var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
|
|
() => sutProvider.Sut.SaveAsync(savePolicyModel));
|
|
|
|
// Assert
|
|
Assert.Contains("Validation error!", badRequestException.Message, StringComparison.OrdinalIgnoreCase);
|
|
await AssertPolicyNotSavedAsync(sutProvider);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a new SutProvider with the PolicyUpdateEvents registered in the Sut.
|
|
/// </summary>
|
|
private static SutProvider<VNextSavePolicyCommand> SutProviderFactory(
|
|
IEnumerable<IPolicyUpdateEvent>? policyUpdateEvents = null)
|
|
{
|
|
var policyEventHandlerFactory = Substitute.For<IPolicyEventHandlerFactory>();
|
|
var handlers = policyUpdateEvents ?? [];
|
|
|
|
// Setup factory to return handlers based on type
|
|
policyEventHandlerFactory.GetHandler<IEnforceDependentPoliciesEvent>(Arg.Any<PolicyType>())
|
|
.Returns(callInfo =>
|
|
{
|
|
var policyType = callInfo.Arg<PolicyType>();
|
|
var handler = handlers.OfType<IEnforceDependentPoliciesEvent>().FirstOrDefault(e => e.Type == policyType);
|
|
return handler != null ? OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT0(handler) : OneOf.OneOf<IEnforceDependentPoliciesEvent, None>.FromT1(new None());
|
|
});
|
|
|
|
policyEventHandlerFactory.GetHandler<IPolicyValidationEvent>(Arg.Any<PolicyType>())
|
|
.Returns(callInfo =>
|
|
{
|
|
var policyType = callInfo.Arg<PolicyType>();
|
|
var handler = handlers.OfType<IPolicyValidationEvent>().FirstOrDefault(e => e.Type == policyType);
|
|
return handler != null ? OneOf.OneOf<IPolicyValidationEvent, None>.FromT0(handler) : OneOf.OneOf<IPolicyValidationEvent, None>.FromT1(new None());
|
|
});
|
|
|
|
policyEventHandlerFactory.GetHandler<IOnPolicyPreUpdateEvent>(Arg.Any<PolicyType>())
|
|
.Returns(new None());
|
|
|
|
policyEventHandlerFactory.GetHandler<IOnPolicyPostUpdateEvent>(Arg.Any<PolicyType>())
|
|
.Returns(new None());
|
|
|
|
return new SutProvider<VNextSavePolicyCommand>()
|
|
.WithFakeTimeProvider()
|
|
.SetDependency(handlers)
|
|
.SetDependency(policyEventHandlerFactory)
|
|
.Create();
|
|
}
|
|
|
|
private static void ArrangeOrganization(SutProvider<VNextSavePolicyCommand> sutProvider, PolicyUpdate policyUpdate)
|
|
{
|
|
sutProvider.GetDependency<IApplicationCacheService>()
|
|
.GetOrganizationAbilityAsync(policyUpdate.OrganizationId)
|
|
.Returns(new OrganizationAbility
|
|
{
|
|
Id = policyUpdate.OrganizationId,
|
|
UsePolicies = true
|
|
});
|
|
}
|
|
|
|
private static async Task AssertPolicyNotSavedAsync(SutProvider<VNextSavePolicyCommand> sutProvider)
|
|
{
|
|
await sutProvider.GetDependency<IPolicyRepository>()
|
|
.DidNotReceiveWithAnyArgs()
|
|
.UpsertAsync(default!);
|
|
|
|
await sutProvider.GetDependency<IEventService>()
|
|
.DidNotReceiveWithAnyArgs()
|
|
.LogPolicyEventAsync(default, default);
|
|
}
|
|
|
|
private static async Task AssertPolicySavedAsync(SutProvider<VNextSavePolicyCommand> sutProvider, PolicyUpdate policyUpdate)
|
|
{
|
|
await sutProvider.GetDependency<IPolicyRepository>().Received(1).UpsertAsync(ExpectedPolicy());
|
|
|
|
await sutProvider.GetDependency<IEventService>().Received(1)
|
|
.LogPolicyEventAsync(ExpectedPolicy(), EventType.Policy_Updated);
|
|
|
|
return;
|
|
|
|
Policy ExpectedPolicy() => Arg.Is<Policy>(
|
|
p =>
|
|
p.Type == policyUpdate.Type
|
|
&& p.OrganizationId == policyUpdate.OrganizationId
|
|
&& p.Enabled == policyUpdate.Enabled
|
|
&& p.Data == policyUpdate.Data);
|
|
}
|
|
}
|