Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AC-1435] Single Organization policy prerequisite for Account Recovery policy #3082

Merged
merged 7 commits into from
Jul 18, 2023
9 changes: 8 additions & 1 deletion src/Core/Auth/Services/Implementations/SsoConfigService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,16 @@ public async Task SaveAsync(SsoConfig config, Organization organization)
throw new BadRequestException("Key Connector cannot be disabled at this moment.");
}

// Automatically enable reset password policy if trusted device encryption is selected
// Automatically enable account recovery and single org policies if trusted device encryption is selected
if (config.GetData().MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption)
{
var singleOrgPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.SingleOrg) ??
new Policy { OrganizationId = config.OrganizationId, Type = PolicyType.SingleOrg };

singleOrgPolicy.Enabled = true;

await _policyService.SaveAsync(singleOrgPolicy, _userService, _organizationService, null);

var resetPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(config.OrganizationId, PolicyType.ResetPassword) ??
new Policy { OrganizationId = config.OrganizationId, Type = PolicyType.ResetPassword, };

Expand Down
15 changes: 15 additions & 0 deletions src/Core/Services/Implementations/PolicyService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public async Task SaveAsync(Policy policy, IUserService userService, IOrganizati
await RequiredBySsoAsync(org);
await RequiredByVaultTimeoutAsync(org);
await RequiredByKeyConnectorAsync(org);
await RequiredByAccountRecoveryAsync(org);
}
break;

Expand All @@ -80,6 +81,11 @@ public async Task SaveAsync(Policy policy, IUserService userService, IOrganizati
{
await RequiredBySsoTrustedDeviceEncryptionAsync(org);
}

if (policy.Enabled)
{
await DependsOnSingleOrgAsync(org);
}
break;

case PolicyType.MaximumVaultTimeout:
Expand Down Expand Up @@ -244,6 +250,15 @@ private async Task RequiredByKeyConnectorAsync(Organization org)
}
}

private async Task RequiredByAccountRecoveryAsync(Organization org)
{
var requireSso = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.ResetPassword);
if (requireSso?.Enabled == true)
{
throw new BadRequestException("Account recovery policy is enabled.");
}
}

private async Task RequiredByVaultTimeoutAsync(Organization org)
{
var vaultTimeout = await _policyRepository.GetByOrganizationIdTypeAsync(org.Id, PolicyType.MaximumVaultTimeout);
Expand Down
38 changes: 38 additions & 0 deletions test/Core.Test/Auth/Services/SsoConfigServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Models.Data.Organizations.Policies;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
Expand Down Expand Up @@ -317,4 +319,40 @@ public async Task SaveAsync_KeyConnector_Success(SutProvider<SsoConfigService> s
await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
.UpsertAsync(default);
}

[Theory, BitAutoData]
public async Task SaveAsync_Tde_Enable_Required_Policies(SutProvider<SsoConfigService> sutProvider, Organization organization)
{
var ssoConfig = new SsoConfig
{
Id = default,
Data = new SsoConfigurationData
{
MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption,
}.Serialize(),
Enabled = true,
OrganizationId = organization.Id,
};

await sutProvider.Sut.SaveAsync(ssoConfig, organization);

await sutProvider.GetDependency<IPolicyService>().Received(1)
.SaveAsync(
Arg.Is<Policy>(t => t.Type == Enums.PolicyType.SingleOrg),
Arg.Any<IUserService>(),
Arg.Any<IOrganizationService>(),
null
);

await sutProvider.GetDependency<IPolicyService>().Received(1)
.SaveAsync(
Arg.Is<Policy>(t => t.Type == Enums.PolicyType.ResetPassword && t.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled),
Arg.Any<IUserService>(),
Arg.Any<IOrganizationService>(),
null
);

await sutProvider.GetDependency<ISsoConfigRepository>().ReceivedWithAnyArgs()
.UpsertAsync(default);
}
}
68 changes: 68 additions & 0 deletions test/Core.Test/Services/PolicyServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ public async Task SaveAsync_NewPolicy_Created(
UsePolicies = true,
});

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policy.OrganizationId, Enums.PolicyType.SingleOrg)
.Returns(Task.FromResult(new Policy { Enabled = true }));

var utcNow = DateTime.UtcNow;

await sutProvider.Sut.SaveAsync(policy, Substitute.For<IUserService>(), Substitute.For<IOrganizationService>(), Guid.NewGuid());
Expand Down Expand Up @@ -444,6 +448,70 @@ await sutProvider.GetDependency<IEventService>()
.LogPolicyEventAsync(default, default, default);
}

[Theory, BitAutoData]
public async Task SaveAsync_PolicyRequiredForAccountRecovery_NotEnabled_ThrowsBadRequestAsync(
[PolicyFixtures.Policy(Enums.PolicyType.ResetPassword)] Policy policy, SutProvider<PolicyService> sutProvider)
{
policy.Enabled = true;
policy.SetDataModel(new ResetPasswordDataModel());

SetupOrg(sutProvider, policy.OrganizationId, new Organization
{
Id = policy.OrganizationId,
UsePolicies = true,
});

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policy.OrganizationId, PolicyType.SingleOrg)
.Returns(Task.FromResult(new Policy { Enabled = false }));

var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(policy,
Substitute.For<IUserService>(),
Substitute.For<IOrganizationService>(),
Guid.NewGuid()));

Assert.Contains("Single Organization policy not enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);

await sutProvider.GetDependency<IPolicyRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);

await sutProvider.GetDependency<IEventService>()
.DidNotReceiveWithAnyArgs()
.LogPolicyEventAsync(default, default, default);
}


[Theory, BitAutoData]
public async Task SaveAsync_SingleOrg_AccountRecoveryEnabled_ThrowsBadRequest(
[PolicyFixtures.Policy(Enums.PolicyType.SingleOrg)] Policy policy, SutProvider<PolicyService> sutProvider)
{
policy.Enabled = false;

SetupOrg(sutProvider, policy.OrganizationId, new Organization
{
Id = policy.OrganizationId,
UsePolicies = true,
});

sutProvider.GetDependency<IPolicyRepository>()
.GetByOrganizationIdTypeAsync(policy.OrganizationId, Enums.PolicyType.ResetPassword)
.Returns(new Policy { Enabled = true });

var badRequestException = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.SaveAsync(policy,
Substitute.For<IUserService>(),
Substitute.For<IOrganizationService>(),
Guid.NewGuid()));

Assert.Contains("Account recovery policy is enabled.", badRequestException.Message, StringComparison.OrdinalIgnoreCase);

await sutProvider.GetDependency<IPolicyRepository>()
.DidNotReceiveWithAnyArgs()
.UpsertAsync(default);
}

[Theory, BitAutoData]
public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider<PolicyService> sutProvider)
{
Expand Down