From c7c6528faaa2fb6f20b9e3e9186404405986abe0 Mon Sep 17 00:00:00 2001 From: Brandon Treston Date: Fri, 21 Mar 2025 10:07:55 -0400 Subject: [PATCH] Ac/pm 18240 implement policy requirement for reset password policy (#5521) * wip * fix test * fix test * refactor * fix factory method and tests * cleanup * refactor * update copy * cleanup --- .../OrganizationUsersController.cs | 11 ++- .../Controllers/OrganizationsController.cs | 15 ++- .../ResetPasswordPolicyRequirement.cs | 46 ++++++++++ .../PolicyServiceCollectionExtensions.cs | 1 + .../Implementations/OrganizationService.cs | 29 ++++-- .../OrganizationUsersControllerTests.cs | 91 +++++++++++++++++++ .../OrganizationsControllerTests.cs | 57 ++++++++++++ ...etPasswordPolicyRequirementFactoryTests.cs | 37 ++++++++ 8 files changed, 277 insertions(+), 10 deletions(-) create mode 100644 src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirement.cs create mode 100644 test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirementFactoryTests.cs diff --git a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs index 5a73e57204..cc7f2314fd 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationUsersController.cs @@ -8,6 +8,8 @@ using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; @@ -55,6 +57,7 @@ public class OrganizationUsersController : Controller private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly IDeleteManagedOrganizationUserAccountCommand _deleteManagedOrganizationUserAccountCommand; private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery; + private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IFeatureService _featureService; private readonly IPricingClient _pricingClient; @@ -79,6 +82,7 @@ public class OrganizationUsersController : Controller IRemoveOrganizationUserCommand removeOrganizationUserCommand, IDeleteManagedOrganizationUserAccountCommand deleteManagedOrganizationUserAccountCommand, IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery, + IPolicyRequirementQuery policyRequirementQuery, IFeatureService featureService, IPricingClient pricingClient) { @@ -102,6 +106,7 @@ public class OrganizationUsersController : Controller _removeOrganizationUserCommand = removeOrganizationUserCommand; _deleteManagedOrganizationUserAccountCommand = deleteManagedOrganizationUserAccountCommand; _getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery; + _policyRequirementQuery = policyRequirementQuery; _featureService = featureService; _pricingClient = pricingClient; } @@ -315,11 +320,13 @@ public class OrganizationUsersController : Controller throw new UnauthorizedAccessException(); } - var useMasterPasswordPolicy = await ShouldHandleResetPasswordAsync(orgId); + var useMasterPasswordPolicy = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements) + ? (await _policyRequirementQuery.GetAsync(user.Id)).AutoEnrollEnabled(orgId) + : await ShouldHandleResetPasswordAsync(orgId); if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey)) { - throw new BadRequestException(string.Empty, "Master Password reset is required, but not provided."); + throw new BadRequestException("Master Password reset is required, but not provided."); } await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService); diff --git a/src/Api/AdminConsole/Controllers/OrganizationsController.cs b/src/Api/AdminConsole/Controllers/OrganizationsController.cs index 34da3de10c..9fa9cb6672 100644 --- a/src/Api/AdminConsole/Controllers/OrganizationsController.cs +++ b/src/Api/AdminConsole/Controllers/OrganizationsController.cs @@ -16,6 +16,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Enums; using Bit.Core.Auth.Repositories; @@ -61,6 +63,7 @@ public class OrganizationsController : Controller private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand; private readonly IOrganizationDeleteCommand _organizationDeleteCommand; + private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPricingClient _pricingClient; public OrganizationsController( @@ -84,6 +87,7 @@ public class OrganizationsController : Controller IRemoveOrganizationUserCommand removeOrganizationUserCommand, ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand, IOrganizationDeleteCommand organizationDeleteCommand, + IPolicyRequirementQuery policyRequirementQuery, IPricingClient pricingClient) { _organizationRepository = organizationRepository; @@ -106,6 +110,7 @@ public class OrganizationsController : Controller _removeOrganizationUserCommand = removeOrganizationUserCommand; _cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand; _organizationDeleteCommand = organizationDeleteCommand; + _policyRequirementQuery = policyRequirementQuery; _pricingClient = pricingClient; } @@ -163,8 +168,13 @@ public class OrganizationsController : Controller throw new NotFoundException(); } - var resetPasswordPolicy = - await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); + if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) + { + var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync(user.Id); + return new OrganizationAutoEnrollStatusResponseModel(organization.Id, resetPasswordPolicyRequirement.AutoEnrollEnabled(organization.Id)); + } + + var resetPasswordPolicy = await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled || resetPasswordPolicy.Data == null) { return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false); @@ -172,6 +182,7 @@ public class OrganizationsController : Controller var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false); + } [HttpPost("")] diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirement.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirement.cs new file mode 100644 index 0000000000..4feef1b088 --- /dev/null +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirement.cs @@ -0,0 +1,46 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.Enums; + +namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +/// +/// Policy requirements for the Account recovery administration policy. +/// +public class ResetPasswordPolicyRequirement : IPolicyRequirement +{ + /// + /// List of Organization Ids that require automatic enrollment in password recovery. + /// + private IEnumerable _autoEnrollOrganizations; + public IEnumerable AutoEnrollOrganizations { init => _autoEnrollOrganizations = value; } + + /// + /// Returns true if provided organizationId requires automatic enrollment in password recovery. + /// + public bool AutoEnrollEnabled(Guid organizationId) + { + return _autoEnrollOrganizations.Contains(organizationId); + } + + +} + +public class ResetPasswordPolicyRequirementFactory : BasePolicyRequirementFactory +{ + public override PolicyType PolicyType => PolicyType.ResetPassword; + + protected override bool ExemptProviders => false; + + protected override IEnumerable ExemptRoles => []; + + public override ResetPasswordPolicyRequirement Create(IEnumerable policyDetails) + { + var result = policyDetails + .Where(p => p.GetDataModel().AutoEnrollEnabled) + .Select(p => p.OrganizationId) + .ToHashSet(); + + return new ResetPasswordPolicyRequirement() { AutoEnrollOrganizations = result }; + } +} diff --git a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs index 6c698f9ffc..d386006ad2 100644 --- a/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs +++ b/src/Core/AdminConsole/OrganizationFeatures/Policies/PolicyServiceCollectionExtensions.cs @@ -33,5 +33,6 @@ public static class PolicyServiceCollectionExtensions { services.AddScoped, DisableSendPolicyRequirementFactory>(); services.AddScoped, SendOptionsPolicyRequirementFactory>(); + services.AddScoped, ResetPasswordPolicyRequirementFactory>(); } } diff --git a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs index 1b44eea496..772b407951 100644 --- a/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs +++ b/src/Core/AdminConsole/Services/Implementations/OrganizationService.cs @@ -6,6 +6,8 @@ using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Business; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; using Bit.Core.AdminConsole.Services; using Bit.Core.Auth.Enums; @@ -76,6 +78,7 @@ public class OrganizationService : IOrganizationService private readonly IOrganizationBillingService _organizationBillingService; private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery; private readonly IPricingClient _pricingClient; + private readonly IPolicyRequirementQuery _policyRequirementQuery; public OrganizationService( IOrganizationRepository organizationRepository, @@ -111,7 +114,8 @@ public class OrganizationService : IOrganizationService ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery, IOrganizationBillingService organizationBillingService, IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery, - IPricingClient pricingClient) + IPricingClient pricingClient, + IPolicyRequirementQuery policyRequirementQuery) { _organizationRepository = organizationRepository; _organizationUserRepository = organizationUserRepository; @@ -147,6 +151,7 @@ public class OrganizationService : IOrganizationService _organizationBillingService = organizationBillingService; _hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery; _pricingClient = pricingClient; + _policyRequirementQuery = policyRequirementQuery; } public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken, @@ -1353,13 +1358,25 @@ public class OrganizationService : IOrganizationService } // Block the user from withdrawal if auto enrollment is enabled - if (resetPasswordKey == null && resetPasswordPolicy.Data != null) + if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)) { - var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); - - if (data?.AutoEnrollEnabled ?? false) + var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync(userId); + if (resetPasswordKey == null && resetPasswordPolicyRequirement.AutoEnrollEnabled(organizationId)) { - throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from Password Reset."); + throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); + } + + } + else + { + if (resetPasswordKey == null && resetPasswordPolicy.Data != null) + { + var data = JsonSerializer.Deserialize(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase); + + if (data?.AutoEnrollEnabled ?? false) + { + throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery."); + } } } diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs index e3071bd227..a19560ecee 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationUsersControllerTests.cs @@ -7,6 +7,8 @@ using Bit.Core.AdminConsole.Entities; using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Repositories; @@ -424,4 +426,93 @@ public class OrganizationUsersControllerTests .GetManyDetailsByOrganizationAsync(organizationAbility.Id, Arg.Any(), Arg.Any()) .Returns(organizationUsers); } + + [Theory] + [BitAutoData] + public async Task Accept_WhenOrganizationUsePoliciesIsEnabledAndResetPolicyIsEnabled_WithPolicyRequirementsEnabled_ShouldHandleResetPassword(Guid orgId, Guid orgUserId, + OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) + { + // Arrange + var applicationCacheService = sutProvider.GetDependency(); + applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true }); + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + + var policy = new Policy + { + Enabled = true, + Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }), + }; + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); + + var policyRequirementQuery = sutProvider.GetDependency(); + + var policyRepository = sutProvider.GetDependency(); + + var policyRequirement = new ResetPasswordPolicyRequirement { AutoEnrollOrganizations = [orgId] }; + + policyRequirementQuery.GetAsync(user.Id).Returns(policyRequirement); + + // Act + await sutProvider.Sut.Accept(orgId, orgUserId, model); + + // Assert + await sutProvider.GetDependency().Received(1) + .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService); + await sutProvider.GetDependency().Received(1) + .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); + + await userService.Received(1).GetUserByPrincipalAsync(default); + await applicationCacheService.Received(0).GetOrganizationAbilityAsync(orgId); + await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); + await policyRequirementQuery.Received(1).GetAsync(user.Id); + Assert.True(policyRequirement.AutoEnrollEnabled(orgId)); + } + + [Theory] + [BitAutoData] + public async Task Accept_WithInvalidModelResetPasswordKey_WithPolicyRequirementsEnabled_ThrowsBadRequestException(Guid orgId, Guid orgUserId, + OrganizationUserAcceptRequestModel model, User user, SutProvider sutProvider) + { + // Arrange + model.ResetPasswordKey = " "; + var applicationCacheService = sutProvider.GetDependency(); + applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true }); + + sutProvider.GetDependency().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + + var policy = new Policy + { + Enabled = true, + Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }), + }; + var userService = sutProvider.GetDependency(); + userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user); + + var policyRepository = sutProvider.GetDependency(); + + var policyRequirementQuery = sutProvider.GetDependency(); + + var policyRequirement = new ResetPasswordPolicyRequirement { AutoEnrollOrganizations = [orgId] }; + + policyRequirementQuery.GetAsync(user.Id).Returns(policyRequirement); + + // Act + var exception = await Assert.ThrowsAsync(() => + sutProvider.Sut.Accept(orgId, orgUserId, model)); + + // Assert + await sutProvider.GetDependency().Received(0) + .AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService); + await sutProvider.GetDependency().Received(0) + .UpdateUserResetPasswordEnrollmentAsync(orgId, user.Id, model.ResetPasswordKey, user.Id); + + await userService.Received(1).GetUserByPrincipalAsync(default); + await applicationCacheService.Received(0).GetOrganizationAbilityAsync(orgId); + await policyRepository.Received(0).GetByOrganizationIdTypeAsync(orgId, PolicyType.ResetPassword); + await policyRequirementQuery.Received(1).GetAsync(user.Id); + + Assert.Equal("Master Password reset is required, but not provided.", exception.Message); + } } diff --git a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs index b0906ddc43..8e6d2ce27b 100644 --- a/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs +++ b/test/Api.Test/AdminConsole/Controllers/OrganizationsControllerTests.cs @@ -4,12 +4,15 @@ using Bit.Api.AdminConsole.Controllers; using Bit.Api.Auth.Models.Request.Accounts; using Bit.Core; using Bit.Core.AdminConsole.Entities; +using Bit.Core.AdminConsole.Enums; using Bit.Core.AdminConsole.Enums.Provider; using Bit.Core.AdminConsole.Models.Business.Tokenables; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations; using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces; using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; using Bit.Core.AdminConsole.Repositories; using Bit.Core.Auth.Entities; using Bit.Core.Auth.Enums; @@ -55,6 +58,7 @@ public class OrganizationsControllerTests : IDisposable private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand; private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand; private readonly IOrganizationDeleteCommand _organizationDeleteCommand; + private readonly IPolicyRequirementQuery _policyRequirementQuery; private readonly IPricingClient _pricingClient; private readonly OrganizationsController _sut; @@ -80,6 +84,7 @@ public class OrganizationsControllerTests : IDisposable _removeOrganizationUserCommand = Substitute.For(); _cloudOrganizationSignUpCommand = Substitute.For(); _organizationDeleteCommand = Substitute.For(); + _policyRequirementQuery = Substitute.For(); _pricingClient = Substitute.For(); _sut = new OrganizationsController( @@ -103,6 +108,7 @@ public class OrganizationsControllerTests : IDisposable _removeOrganizationUserCommand, _cloudOrganizationSignUpCommand, _organizationDeleteCommand, + _policyRequirementQuery, _pricingClient); } @@ -236,4 +242,55 @@ public class OrganizationsControllerTests : IDisposable await _organizationDeleteCommand.Received(1).DeleteAsync(organization); } + + [Theory, AutoData] + public async Task GetAutoEnrollStatus_WithPolicyRequirementsEnabled_ReturnsOrganizationAutoEnrollStatus_WithResetPasswordEnabledTrue( + User user, + Organization organization, + OrganizationUser organizationUser + ) + { + var policyRequirement = new ResetPasswordPolicyRequirement() { AutoEnrollOrganizations = [organization.Id] }; + + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(user); + _organizationRepository.GetByIdentifierAsync(organization.Id.ToString()).Returns(organization); + _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true); + _organizationUserRepository.GetByOrganizationAsync(organization.Id, user.Id).Returns(organizationUser); + _policyRequirementQuery.GetAsync(user.Id).Returns(policyRequirement); + + var result = await _sut.GetAutoEnrollStatus(organization.Id.ToString()); + + await _userService.Received(1).GetUserByPrincipalAsync(Arg.Any()); + await _organizationRepository.Received(1).GetByIdentifierAsync(organization.Id.ToString()); + await _policyRequirementQuery.Received(1).GetAsync(user.Id); + + Assert.True(result.ResetPasswordEnabled); + Assert.Equal(result.Id, organization.Id); + } + + [Theory, AutoData] + public async Task GetAutoEnrollStatus_WithPolicyRequirementsDisabled_ReturnsOrganizationAutoEnrollStatus_WithResetPasswordEnabledTrue( + User user, + Organization organization, + OrganizationUser organizationUser +) + { + + var policy = new Policy() { Type = PolicyType.ResetPassword, Enabled = true, Data = "{\"AutoEnrollEnabled\": true}", OrganizationId = organization.Id }; + + _userService.GetUserByPrincipalAsync(Arg.Any()).Returns(user); + _organizationRepository.GetByIdentifierAsync(organization.Id.ToString()).Returns(organization); + _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(false); + _organizationUserRepository.GetByOrganizationAsync(organization.Id, user.Id).Returns(organizationUser); + _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword).Returns(policy); + + var result = await _sut.GetAutoEnrollStatus(organization.Id.ToString()); + + await _userService.Received(1).GetUserByPrincipalAsync(Arg.Any()); + await _organizationRepository.Received(1).GetByIdentifierAsync(organization.Id.ToString()); + await _policyRequirementQuery.Received(0).GetAsync(user.Id); + await _policyRepository.Received(1).GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword); + + Assert.True(result.ResetPasswordEnabled); + } } diff --git a/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirementFactoryTests.cs b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirementFactoryTests.cs new file mode 100644 index 0000000000..181f4f170e --- /dev/null +++ b/test/Core.Test/AdminConsole/OrganizationFeatures/Policies/PolicyRequirements/ResetPasswordPolicyRequirementFactoryTests.cs @@ -0,0 +1,37 @@ +using Bit.Core.AdminConsole.Enums; +using Bit.Core.AdminConsole.Models.Data.Organizations.Policies; +using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; +using Bit.Core.Test.AdminConsole.AutoFixture; +using Bit.Test.Common.AutoFixture; +using Bit.Test.Common.AutoFixture.Attributes; +using Xunit; + +namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements; + +[SutProviderCustomize] +public class ResetPasswordPolicyRequirementFactoryTests +{ + [Theory, BitAutoData] + public void AutoEnroll_WithNoPolicies_IsEmpty(SutProvider sutProvider, Guid orgId) + { + var actual = sutProvider.Sut.Create([]); + + Assert.False(actual.AutoEnrollEnabled(orgId)); + } + + [Theory, BitAutoData] + public void AutoEnrollAdministration_WithAnyResetPasswordPolices_ReturnsEnabledOrganizationIds( + [PolicyDetails(PolicyType.ResetPassword)] PolicyDetails[] policies, + SutProvider sutProvider) + { + policies[0].SetDataModel(new ResetPasswordDataModel { AutoEnrollEnabled = true }); + policies[1].SetDataModel(new ResetPasswordDataModel { AutoEnrollEnabled = false }); + policies[2].SetDataModel(new ResetPasswordDataModel { AutoEnrollEnabled = true }); + + var actual = sutProvider.Sut.Create(policies); + + Assert.True(actual.AutoEnrollEnabled(policies[0].OrganizationId)); + Assert.False(actual.AutoEnrollEnabled(policies[1].OrganizationId)); + Assert.True(actual.AutoEnrollEnabled(policies[2].OrganizationId)); + } +}