You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
457 lines
18 KiB
457 lines
18 KiB
#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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
|
|
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); |
|
} |
|
}
|
|
|