mirror of
https://github.com/bitwarden/server.git
synced 2025-04-04 20:50:21 -05:00
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
This commit is contained in:
parent
5d549402c7
commit
c7c6528faa
@ -8,6 +8,8 @@ using Bit.Core.AdminConsole.Enums;
|
|||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Authorization;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.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.OrganizationFeatures.Shared.Authorization;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Shared.Authorization;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -55,6 +57,7 @@ public class OrganizationUsersController : Controller
|
|||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
private readonly IDeleteManagedOrganizationUserAccountCommand _deleteManagedOrganizationUserAccountCommand;
|
private readonly IDeleteManagedOrganizationUserAccountCommand _deleteManagedOrganizationUserAccountCommand;
|
||||||
private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery;
|
private readonly IGetOrganizationUsersManagementStatusQuery _getOrganizationUsersManagementStatusQuery;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
|
|
||||||
@ -79,6 +82,7 @@ public class OrganizationUsersController : Controller
|
|||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||||
IDeleteManagedOrganizationUserAccountCommand deleteManagedOrganizationUserAccountCommand,
|
IDeleteManagedOrganizationUserAccountCommand deleteManagedOrganizationUserAccountCommand,
|
||||||
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
|
IGetOrganizationUsersManagementStatusQuery getOrganizationUsersManagementStatusQuery,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IPricingClient pricingClient)
|
IPricingClient pricingClient)
|
||||||
{
|
{
|
||||||
@ -102,6 +106,7 @@ public class OrganizationUsersController : Controller
|
|||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
_deleteManagedOrganizationUserAccountCommand = deleteManagedOrganizationUserAccountCommand;
|
_deleteManagedOrganizationUserAccountCommand = deleteManagedOrganizationUserAccountCommand;
|
||||||
_getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery;
|
_getOrganizationUsersManagementStatusQuery = getOrganizationUsersManagementStatusQuery;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
}
|
}
|
||||||
@ -315,11 +320,13 @@ public class OrganizationUsersController : Controller
|
|||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var useMasterPasswordPolicy = await ShouldHandleResetPasswordAsync(orgId);
|
var useMasterPasswordPolicy = _featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
? (await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(user.Id)).AutoEnrollEnabled(orgId)
|
||||||
|
: await ShouldHandleResetPasswordAsync(orgId);
|
||||||
|
|
||||||
if (useMasterPasswordPolicy && string.IsNullOrWhiteSpace(model.ResetPasswordKey))
|
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);
|
await _acceptOrgUserCommand.AcceptOrgUserByEmailTokenAsync(organizationUserId, user, model.Token, _userService);
|
||||||
|
@ -16,6 +16,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.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.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
@ -61,6 +63,7 @@ public class OrganizationsController : Controller
|
|||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
|
|
||||||
public OrganizationsController(
|
public OrganizationsController(
|
||||||
@ -84,6 +87,7 @@ public class OrganizationsController : Controller
|
|||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||||
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
ICloudOrganizationSignUpCommand cloudOrganizationSignUpCommand,
|
||||||
IOrganizationDeleteCommand organizationDeleteCommand,
|
IOrganizationDeleteCommand organizationDeleteCommand,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery,
|
||||||
IPricingClient pricingClient)
|
IPricingClient pricingClient)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
@ -106,6 +110,7 @@ public class OrganizationsController : Controller
|
|||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
_cloudOrganizationSignUpCommand = cloudOrganizationSignUpCommand;
|
||||||
_organizationDeleteCommand = organizationDeleteCommand;
|
_organizationDeleteCommand = organizationDeleteCommand;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,8 +168,13 @@ public class OrganizationsController : Controller
|
|||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
var resetPasswordPolicy =
|
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
await _policyRepository.GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
|
{
|
||||||
|
var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(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)
|
if (resetPasswordPolicy == null || !resetPasswordPolicy.Enabled || resetPasswordPolicy.Data == null)
|
||||||
{
|
{
|
||||||
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false);
|
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, false);
|
||||||
@ -172,6 +182,7 @@ public class OrganizationsController : Controller
|
|||||||
|
|
||||||
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
|
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
|
||||||
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false);
|
return new OrganizationAutoEnrollStatusResponseModel(organization.Id, data?.AutoEnrollEnabled ?? false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("")]
|
[HttpPost("")]
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Policy requirements for the Account recovery administration policy.
|
||||||
|
/// </summary>
|
||||||
|
public class ResetPasswordPolicyRequirement : IPolicyRequirement
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// List of Organization Ids that require automatic enrollment in password recovery.
|
||||||
|
/// </summary>
|
||||||
|
private IEnumerable<Guid> _autoEnrollOrganizations;
|
||||||
|
public IEnumerable<Guid> AutoEnrollOrganizations { init => _autoEnrollOrganizations = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if provided organizationId requires automatic enrollment in password recovery.
|
||||||
|
/// </summary>
|
||||||
|
public bool AutoEnrollEnabled(Guid organizationId)
|
||||||
|
{
|
||||||
|
return _autoEnrollOrganizations.Contains(organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ResetPasswordPolicyRequirementFactory : BasePolicyRequirementFactory<ResetPasswordPolicyRequirement>
|
||||||
|
{
|
||||||
|
public override PolicyType PolicyType => PolicyType.ResetPassword;
|
||||||
|
|
||||||
|
protected override bool ExemptProviders => false;
|
||||||
|
|
||||||
|
protected override IEnumerable<OrganizationUserType> ExemptRoles => [];
|
||||||
|
|
||||||
|
public override ResetPasswordPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails)
|
||||||
|
{
|
||||||
|
var result = policyDetails
|
||||||
|
.Where(p => p.GetDataModel<ResetPasswordDataModel>().AutoEnrollEnabled)
|
||||||
|
.Select(p => p.OrganizationId)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
|
return new ResetPasswordPolicyRequirement() { AutoEnrollOrganizations = result };
|
||||||
|
}
|
||||||
|
}
|
@ -33,5 +33,6 @@ public static class PolicyServiceCollectionExtensions
|
|||||||
{
|
{
|
||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, DisableSendPolicyRequirementFactory>();
|
||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SendOptionsPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, SendOptionsPolicyRequirementFactory>();
|
||||||
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, ResetPasswordPolicyRequirementFactory>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
|||||||
using Bit.Core.AdminConsole.Models.Business;
|
using Bit.Core.AdminConsole.Models.Business;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.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.AdminConsole.Repositories;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -76,6 +78,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
private readonly IOrganizationBillingService _organizationBillingService;
|
private readonly IOrganizationBillingService _organizationBillingService;
|
||||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
|
|
||||||
public OrganizationService(
|
public OrganizationService(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
@ -111,7 +114,8 @@ public class OrganizationService : IOrganizationService
|
|||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IOrganizationBillingService organizationBillingService,
|
IOrganizationBillingService organizationBillingService,
|
||||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||||
IPricingClient pricingClient)
|
IPricingClient pricingClient,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -147,6 +151,7 @@ public class OrganizationService : IOrganizationService
|
|||||||
_organizationBillingService = organizationBillingService;
|
_organizationBillingService = organizationBillingService;
|
||||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ReplacePaymentMethodAsync(Guid organizationId, string paymentToken,
|
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
|
// Block the user from withdrawal if auto enrollment is enabled
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
|
{
|
||||||
|
var resetPasswordPolicyRequirement = await _policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(userId);
|
||||||
|
if (resetPasswordKey == null && resetPasswordPolicyRequirement.AutoEnrollEnabled(organizationId))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Due to an Enterprise Policy, you are not allowed to withdraw from account recovery.");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
if (resetPasswordKey == null && resetPasswordPolicy.Data != null)
|
if (resetPasswordKey == null && resetPasswordPolicy.Data != null)
|
||||||
{
|
{
|
||||||
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
|
var data = JsonSerializer.Deserialize<ResetPasswordDataModel>(resetPasswordPolicy.Data, JsonHelpers.IgnoreCase);
|
||||||
|
|
||||||
if (data?.AutoEnrollEnabled ?? false)
|
if (data?.AutoEnrollEnabled ?? false)
|
||||||
{
|
{
|
||||||
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.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@ using Bit.Core.AdminConsole.Entities;
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.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.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
@ -424,4 +426,93 @@ public class OrganizationUsersControllerTests
|
|||||||
.GetManyDetailsByOrganizationAsync(organizationAbility.Id, Arg.Any<bool>(), Arg.Any<bool>())
|
.GetManyDetailsByOrganizationAsync(organizationAbility.Id, Arg.Any<bool>(), Arg.Any<bool>())
|
||||||
.Returns(organizationUsers);
|
.Returns(organizationUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task Accept_WhenOrganizationUsePoliciesIsEnabledAndResetPolicyIsEnabled_WithPolicyRequirementsEnabled_ShouldHandleResetPassword(Guid orgId, Guid orgUserId,
|
||||||
|
OrganizationUserAcceptRequestModel model, User user, SutProvider<OrganizationUsersController> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||||
|
applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
|
|
||||||
|
var policy = new Policy
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
|
||||||
|
};
|
||||||
|
var userService = sutProvider.GetDependency<IUserService>();
|
||||||
|
userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
|
||||||
|
|
||||||
|
var policyRequirementQuery = sutProvider.GetDependency<IPolicyRequirementQuery>();
|
||||||
|
|
||||||
|
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
|
||||||
|
|
||||||
|
var policyRequirement = new ResetPasswordPolicyRequirement { AutoEnrollOrganizations = [orgId] };
|
||||||
|
|
||||||
|
policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(user.Id).Returns(policyRequirement);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await sutProvider.Sut.Accept(orgId, orgUserId, model);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(1)
|
||||||
|
.AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService);
|
||||||
|
await sutProvider.GetDependency<IOrganizationService>().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<ResetPasswordPolicyRequirement>(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<OrganizationUsersController> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
model.ResetPasswordKey = " ";
|
||||||
|
var applicationCacheService = sutProvider.GetDependency<IApplicationCacheService>();
|
||||||
|
applicationCacheService.GetOrganizationAbilityAsync(orgId).Returns(new OrganizationAbility { UsePolicies = true });
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
|
|
||||||
|
var policy = new Policy
|
||||||
|
{
|
||||||
|
Enabled = true,
|
||||||
|
Data = CoreHelpers.ClassToJsonData(new ResetPasswordDataModel { AutoEnrollEnabled = true, }),
|
||||||
|
};
|
||||||
|
var userService = sutProvider.GetDependency<IUserService>();
|
||||||
|
userService.GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
|
||||||
|
|
||||||
|
var policyRepository = sutProvider.GetDependency<IPolicyRepository>();
|
||||||
|
|
||||||
|
var policyRequirementQuery = sutProvider.GetDependency<IPolicyRequirementQuery>();
|
||||||
|
|
||||||
|
var policyRequirement = new ResetPasswordPolicyRequirement { AutoEnrollOrganizations = [orgId] };
|
||||||
|
|
||||||
|
policyRequirementQuery.GetAsync<ResetPasswordPolicyRequirement>(user.Id).Returns(policyRequirement);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
|
sutProvider.Sut.Accept(orgId, orgUserId, model));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IAcceptOrgUserCommand>().Received(0)
|
||||||
|
.AcceptOrgUserByEmailTokenAsync(orgUserId, user, model.Token, userService);
|
||||||
|
await sutProvider.GetDependency<IOrganizationService>().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<ResetPasswordPolicyRequirement>(user.Id);
|
||||||
|
|
||||||
|
Assert.Equal("Master Password reset is required, but not provided.", exception.Message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,12 +4,15 @@ using Bit.Api.AdminConsole.Controllers;
|
|||||||
using Bit.Api.Auth.Models.Request.Accounts;
|
using Bit.Api.Auth.Models.Request.Accounts;
|
||||||
using Bit.Core;
|
using Bit.Core;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
using Bit.Core.AdminConsole.Models.Business.Tokenables;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationApiKeys.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Organizations.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.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.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Entities;
|
using Bit.Core.Auth.Entities;
|
||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
@ -55,6 +58,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
private readonly ICloudOrganizationSignUpCommand _cloudOrganizationSignUpCommand;
|
||||||
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
private readonly IOrganizationDeleteCommand _organizationDeleteCommand;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
private readonly OrganizationsController _sut;
|
private readonly OrganizationsController _sut;
|
||||||
|
|
||||||
@ -80,6 +84,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
_removeOrganizationUserCommand = Substitute.For<IRemoveOrganizationUserCommand>();
|
||||||
_cloudOrganizationSignUpCommand = Substitute.For<ICloudOrganizationSignUpCommand>();
|
_cloudOrganizationSignUpCommand = Substitute.For<ICloudOrganizationSignUpCommand>();
|
||||||
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
|
_organizationDeleteCommand = Substitute.For<IOrganizationDeleteCommand>();
|
||||||
|
_policyRequirementQuery = Substitute.For<IPolicyRequirementQuery>();
|
||||||
_pricingClient = Substitute.For<IPricingClient>();
|
_pricingClient = Substitute.For<IPricingClient>();
|
||||||
|
|
||||||
_sut = new OrganizationsController(
|
_sut = new OrganizationsController(
|
||||||
@ -103,6 +108,7 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
_removeOrganizationUserCommand,
|
_removeOrganizationUserCommand,
|
||||||
_cloudOrganizationSignUpCommand,
|
_cloudOrganizationSignUpCommand,
|
||||||
_organizationDeleteCommand,
|
_organizationDeleteCommand,
|
||||||
|
_policyRequirementQuery,
|
||||||
_pricingClient);
|
_pricingClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,4 +242,55 @@ public class OrganizationsControllerTests : IDisposable
|
|||||||
|
|
||||||
await _organizationDeleteCommand.Received(1).DeleteAsync(organization);
|
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<ClaimsPrincipal>()).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<ResetPasswordPolicyRequirement>(user.Id).Returns(policyRequirement);
|
||||||
|
|
||||||
|
var result = await _sut.GetAutoEnrollStatus(organization.Id.ToString());
|
||||||
|
|
||||||
|
await _userService.Received(1).GetUserByPrincipalAsync(Arg.Any<ClaimsPrincipal>());
|
||||||
|
await _organizationRepository.Received(1).GetByIdentifierAsync(organization.Id.ToString());
|
||||||
|
await _policyRequirementQuery.Received(1).GetAsync<ResetPasswordPolicyRequirement>(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<ClaimsPrincipal>()).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<ClaimsPrincipal>());
|
||||||
|
await _organizationRepository.Received(1).GetByIdentifierAsync(organization.Id.ToString());
|
||||||
|
await _policyRequirementQuery.Received(0).GetAsync<ResetPasswordPolicyRequirement>(user.Id);
|
||||||
|
await _policyRepository.Received(1).GetByOrganizationIdTypeAsync(organization.Id, PolicyType.ResetPassword);
|
||||||
|
|
||||||
|
Assert.True(result.ResetPasswordEnabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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<ResetPasswordPolicyRequirementFactory> 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<ResetPasswordPolicyRequirementFactory> 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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user