diff --git a/src/Core/Services/Implementations/PolicyService.cs b/src/Core/Services/Implementations/PolicyService.cs index ac54ff65d56f..a8411e41982e 100644 --- a/src/Core/Services/Implementations/PolicyService.cs +++ b/src/Core/Services/Implementations/PolicyService.cs @@ -75,6 +75,13 @@ public async Task SaveAsync(Policy policy, IUserService userService, IOrganizati } break; + case PolicyType.ResetPassword: + if (!policy.Enabled || policy.GetDataModel()?.AutoEnrollEnabled == false) + { + await RequiredBySsoTrustedDeviceEncryptionAsync(org); + } + break; + case PolicyType.MaximumVaultTimeout: if (policy.Enabled) { @@ -230,7 +237,6 @@ private async Task RequiredBySsoAsync(Organization org) private async Task RequiredByKeyConnectorAsync(Organization org) { - var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.KeyConnector) { @@ -254,4 +260,13 @@ private void LockedTo2020Plan(Organization org) throw new BadRequestException("This policy is only available to 2020 Enterprise plans."); } } + + private async Task RequiredBySsoTrustedDeviceEncryptionAsync(Organization org) + { + var ssoConfig = await _ssoConfigRepository.GetByOrganizationIdAsync(org.Id); + if (ssoConfig?.GetData()?.MemberDecryptionType == MemberDecryptionType.TrustedDeviceEncryption) + { + throw new BadRequestException("Trusted device encryption is on and requires this policy."); + } + } } diff --git a/test/Core.Test/Services/PolicyServiceTests.cs b/test/Core.Test/Services/PolicyServiceTests.cs index 9f09e9262ff6..3b4d9b1b80c5 100644 --- a/test/Core.Test/Services/PolicyServiceTests.cs +++ b/test/Core.Test/Services/PolicyServiceTests.cs @@ -6,6 +6,7 @@ using Bit.Core.Enums; 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; @@ -208,6 +209,7 @@ public async Task SaveAsync_NewPolicy_Created( [PolicyFixtures.Policy(PolicyType.ResetPassword)] Policy policy, SutProvider sutProvider) { policy.Id = default; + policy.Data = null; SetupOrg(sutProvider, policy.OrganizationId, new Organization { @@ -396,6 +398,52 @@ await sutProvider.GetDependency().Received() Assert.True(policy.RevisionDate - utcNow < TimeSpan.FromSeconds(1)); } + [Theory] + [BitAutoData(true, false)] + [BitAutoData(false, true)] + [BitAutoData(false, false)] + public async Task SaveAsync_PolicyRequiredByTrustedDeviceEncryption_DisablePolicyOrDisableAutomaticEnrollment_ThrowsBadRequest( + bool policyEnabled, + bool autoEnrollEnabled, + [PolicyFixtures.Policy(PolicyType.ResetPassword)] Policy policy, + SutProvider sutProvider) + { + policy.Enabled = policyEnabled; + policy.SetDataModel(new ResetPasswordDataModel + { + AutoEnrollEnabled = autoEnrollEnabled + }); + + SetupOrg(sutProvider, policy.OrganizationId, new Organization + { + Id = policy.OrganizationId, + UsePolicies = true, + }); + + var ssoConfig = new SsoConfig { Enabled = true }; + ssoConfig.SetData(new SsoConfigurationData { MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption }); + + sutProvider.GetDependency() + .GetByOrganizationIdAsync(policy.OrganizationId) + .Returns(ssoConfig); + + var badRequestException = await Assert.ThrowsAsync( + () => sutProvider.Sut.SaveAsync(policy, + Substitute.For(), + Substitute.For(), + Guid.NewGuid())); + + Assert.Contains("Trusted device encryption is on and requires this policy.", badRequestException.Message, StringComparison.OrdinalIgnoreCase); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .UpsertAsync(default); + + await sutProvider.GetDependency() + .DidNotReceiveWithAnyArgs() + .LogPolicyEventAsync(default, default, default); + } + [Theory, BitAutoData] public async Task GetPoliciesApplicableToUserAsync_WithRequireSsoTypeFilter_WithDefaultOrganizationUserStatusFilter_ReturnsNoPolicies(Guid userId, SutProvider sutProvider) {