mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 15:42:48 -05:00
[PM-18238] Add RequireTwoFactorPolicyRequirement (#5840)
* Add RequireTwoFactorPolicyRequirement and its factory with unit tests * Implemented RequireTwoFactorPolicyRequirement to enforce two-factor authentication policies. * Created RequireTwoFactorPolicyRequirementFactory to generate policy requirements based on user status. * Added unit tests for the factory to validate behavior with various user statuses and policy details. * Enhance AcceptOrgUserCommand to use IPolicyRequirementQuery for two-factor authentication validation * Update ConfirmOrganizationUserCommand to use RequireTwoFactorPolicyRequirement to check for 2FA requirement * Implement CanAcceptInvitation and CanBeConfirmed methods in RequireTwoFactorPolicyRequirement; update tests to reflect new logic for two-factor authentication policy handling. * Refactor AcceptOrgUserCommand to enforce two-factor authentication policy based on feature flag; update validation logic and tests accordingly. * Enhance ConfirmOrganizationUserCommand to validate two-factor authentication policy based on feature flag; refactor validation logic and update related tests for improved policy handling. * Remove unused method and its dependencies from OrganizationService. * Implement CanBeRestored method in RequireTwoFactorPolicyRequirement to determine user restoration eligibility based on two-factor authentication status; add corresponding unit tests for various scenarios. * Update RestoreOrganizationUserCommand to use IPolicyRequirementQuery for two-factor authentication policies checks * Remove redundant vNext tests * Add TwoFactorPoliciesForActiveMemberships property to RequireTwoFactorPolicyRequirement and corresponding unit tests for policy retrieval based on user status * Refactor UserService to integrate IPolicyRequirementQuery for two-factor authentication policy checks * Add XML documentation for TwoFactorPoliciesForActiveMemberships property in RequireTwoFactorPolicyRequirement to clarify its purpose and return value. * Add exception documentation for ValidateTwoFactorAuthenticationPolicyAsync method in ConfirmOrganizationUserCommand to clarify error handling for users without two-step login enabled. * Update comments in AcceptOrgUserCommand and ConfirmOrganizationUserCommand to clarify handling of two-step login and 2FA policy checks. * Add RequireTwoFactorPolicyRequirementFactory to PolicyServiceCollectionExtensions * Refactor two-factor authentication policy checks in AcceptOrgUserCommand and ConfirmOrganizationUserCommand to streamline validation logic and improve clarity. Update RequireTwoFactorPolicyRequirement to provide a method for checking if two-factor authentication is required for an organization. Adjust related unit tests accordingly. * Add PolicyRequirements namespace * Update comments in AcceptOrgUserCommand and ConfirmOrganizationUserCommand to clarify two-factor authentication policy requirements and exception handling. * Refactor RequireTwoFactorPolicyRequirement to return tuples of (OrganizationId, OrganizationUserId) for active memberships requiring two-factor authentication. Update UserService and related tests to reflect this change. * Refactor AcceptOrgUserCommand: delegate feature flag check to the ValidateTwoFactorAuthenticationPolicyAsync method * Skip policy check if two-step login is enabled for the user * Refactor ConfirmOrganizationUserCommand to streamline two-factor authentication policy validation logic * Refactor AcceptOrgUserCommand to simplify two-factor authentication check by removing intermediate variable * Update documentation in RequireTwoFactorPolicyRequirement to clarify the purpose of the IsTwoFactorRequiredForOrganization * Refactor AcceptOrgUserCommandTests to remove redundant two-factor authentication checks and simplify test setup * Refactor AcceptOrgUserCommand and ConfirmOrganizationUserCommand to streamline two-factor authentication checks by removing redundant conditions and simplifying logic flow. * Rename removeOrgUserTasks variable in UserService * Refactor RestoreOrganizationUserCommand to simplify two-factor authentication compliance checks by consolidating logic into a new method, IsTwoFactorRequiredForOrganizationAsync. * Remove outdated two-factor authentication validation documentation from AcceptOrgUserCommand * Invert two-factor compliance check in RestoreOrganizationUserCommand to ensure correct validation of organization user policies. * Refactor UserService to enhance two-factor compliance checks by optimizing organization retrieval and logging when no organizations require two-factor authentication.
This commit is contained in:
@ -1,4 +1,6 @@
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
@ -27,6 +29,8 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
|
|
||||||
public AcceptOrgUserCommand(
|
public AcceptOrgUserCommand(
|
||||||
IDataProtectionProvider dataProtectionProvider,
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
@ -37,9 +41,10 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory)
|
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory,
|
||||||
|
IFeatureService featureService,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery)
|
||||||
{
|
{
|
||||||
|
|
||||||
// TODO: remove data protector when old token validation removed
|
// TODO: remove data protector when old token validation removed
|
||||||
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
|
_dataProtector = dataProtectionProvider.CreateProtector(OrgUserInviteTokenable.DataProtectorPurpose);
|
||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
@ -50,6 +55,8 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||||
|
_featureService = featureService;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OrganizationUser> AcceptOrgUserByEmailTokenAsync(Guid organizationUserId, User user, string emailToken,
|
public async Task<OrganizationUser> AcceptOrgUserByEmailTokenAsync(Guid organizationUserId, User user, string emailToken,
|
||||||
@ -196,15 +203,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
||||||
if (!await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user))
|
await ValidateTwoFactorAuthenticationPolicyAsync(user, orgUser.OrganizationId);
|
||||||
{
|
|
||||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
|
||||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
|
||||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
|
||||||
{
|
|
||||||
throw new BadRequestException("You cannot join this organization until you enable two-step login on your user account.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
orgUser.Status = OrganizationUserStatusType.Accepted;
|
orgUser.Status = OrganizationUserStatusType.Accepted;
|
||||||
orgUser.UserId = user.Id;
|
orgUser.UserId = user.Id;
|
||||||
@ -224,4 +223,33 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
return orgUser;
|
return orgUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ValidateTwoFactorAuthenticationPolicyAsync(User user, Guid organizationId)
|
||||||
|
{
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
|
{
|
||||||
|
if (await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user))
|
||||||
|
{
|
||||||
|
// If the user has two-step login enabled, we skip checking the 2FA policy
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var twoFactorPolicyRequirement = await _policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id);
|
||||||
|
if (twoFactorPolicyRequirement.IsTwoFactorRequiredForOrganization(organizationId))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You cannot join this organization until you enable two-step login on your user account.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user))
|
||||||
|
{
|
||||||
|
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||||
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||||
|
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == organizationId))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("You cannot join this organization until you enable two-step login on your user account.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
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.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
@ -24,6 +26,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
private readonly IPushRegistrationService _pushRegistrationService;
|
private readonly IPushRegistrationService _pushRegistrationService;
|
||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IDeviceRepository _deviceRepository;
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
public ConfirmOrganizationUserCommand(
|
public ConfirmOrganizationUserCommand(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
@ -35,7 +39,9 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
IPushNotificationService pushNotificationService,
|
IPushNotificationService pushNotificationService,
|
||||||
IPushRegistrationService pushRegistrationService,
|
IPushRegistrationService pushRegistrationService,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IDeviceRepository deviceRepository)
|
IDeviceRepository deviceRepository,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery,
|
||||||
|
IFeatureService featureService)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -47,6 +53,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
_pushRegistrationService = pushRegistrationService;
|
_pushRegistrationService = pushRegistrationService;
|
||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_deviceRepository = deviceRepository;
|
_deviceRepository = deviceRepository;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
|
_featureService = featureService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
public async Task<OrganizationUser> ConfirmUserAsync(Guid organizationId, Guid organizationUserId, string key,
|
||||||
@ -118,8 +126,8 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var twoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled;
|
var userTwoFactorEnabled = usersTwoFactorEnabled.FirstOrDefault(tuple => tuple.userId == user.Id).twoFactorIsEnabled;
|
||||||
await CheckPoliciesAsync(organizationId, user, orgUsers, twoFactorEnabled);
|
await CheckPoliciesAsync(organizationId, user, orgUsers, userTwoFactorEnabled);
|
||||||
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
orgUser.Status = OrganizationUserStatusType.Confirmed;
|
||||||
orgUser.Key = keys[orgUser.Id];
|
orgUser.Key = keys[orgUser.Id];
|
||||||
orgUser.Email = null;
|
orgUser.Email = null;
|
||||||
@ -142,15 +150,10 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckPoliciesAsync(Guid organizationId, User user,
|
private async Task CheckPoliciesAsync(Guid organizationId, User user,
|
||||||
ICollection<OrganizationUser> userOrgs, bool twoFactorEnabled)
|
ICollection<OrganizationUser> userOrgs, bool userTwoFactorEnabled)
|
||||||
{
|
{
|
||||||
// Enforce Two Factor Authentication Policy for this organization
|
// Enforce Two Factor Authentication Policy for this organization
|
||||||
var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication))
|
await ValidateTwoFactorAuthenticationPolicyAsync(user, organizationId, userTwoFactorEnabled);
|
||||||
.Any(p => p.OrganizationId == organizationId);
|
|
||||||
if (orgRequiresTwoFactor && !twoFactorEnabled)
|
|
||||||
{
|
|
||||||
throw new BadRequestException("User does not have two-step login enabled.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId);
|
var hasOtherOrgs = userOrgs.Any(ou => ou.OrganizationId != organizationId);
|
||||||
var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
|
var singleOrgPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg);
|
||||||
@ -168,6 +171,33 @@ public class ConfirmOrganizationUserCommand : IConfirmOrganizationUserCommand
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ValidateTwoFactorAuthenticationPolicyAsync(User user, Guid organizationId, bool userTwoFactorEnabled)
|
||||||
|
{
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
|
{
|
||||||
|
if (userTwoFactorEnabled)
|
||||||
|
{
|
||||||
|
// If the user has two-step login enabled, we skip checking the 2FA policy
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var twoFactorPolicyRequirement = await _policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id);
|
||||||
|
if (twoFactorPolicyRequirement.IsTwoFactorRequiredForOrganization(organizationId))
|
||||||
|
{
|
||||||
|
throw new BadRequestException("User does not have two-step login enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var orgRequiresTwoFactor = (await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication))
|
||||||
|
.Any(p => p.OrganizationId == organizationId);
|
||||||
|
if (orgRequiresTwoFactor && !userTwoFactorEnabled)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("User does not have two-step login enabled.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId)
|
private async Task DeleteAndPushUserRegistrationAsync(Guid organizationId, Guid userId)
|
||||||
{
|
{
|
||||||
var devices = await GetUserDeviceIdsAsync(userId);
|
var devices = await GetUserDeviceIdsAsync(userId);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
@ -22,7 +24,9 @@ public class RestoreOrganizationUserCommand(
|
|||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
IOrganizationService organizationService) : IRestoreOrganizationUserCommand
|
IOrganizationService organizationService,
|
||||||
|
IFeatureService featureService,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery) : IRestoreOrganizationUserCommand
|
||||||
{
|
{
|
||||||
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId)
|
public async Task RestoreUserAsync(OrganizationUser organizationUser, Guid? restoringUserId)
|
||||||
{
|
{
|
||||||
@ -270,12 +274,7 @@ public class RestoreOrganizationUserCommand(
|
|||||||
// Enforce 2FA Policy of organization user is trying to join
|
// Enforce 2FA Policy of organization user is trying to join
|
||||||
if (!userHasTwoFactorEnabled)
|
if (!userHasTwoFactorEnabled)
|
||||||
{
|
{
|
||||||
var invitedTwoFactorPolicies = await policyService.GetPoliciesApplicableToUserAsync(userId,
|
twoFactorCompliant = !await IsTwoFactorRequiredForOrganizationAsync(userId, orgUser.OrganizationId);
|
||||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Revoked);
|
|
||||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
|
||||||
{
|
|
||||||
twoFactorCompliant = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var user = await userRepository.GetByIdAsync(userId);
|
var user = await userRepository.GetByIdAsync(userId);
|
||||||
@ -299,4 +298,17 @@ public class RestoreOrganizationUserCommand(
|
|||||||
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
|
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IsTwoFactorRequiredForOrganizationAsync(Guid userId, Guid organizationId)
|
||||||
|
{
|
||||||
|
if (featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
|
{
|
||||||
|
var requirement = await policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(userId);
|
||||||
|
return requirement.IsTwoFactorRequiredForOrganization(organizationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
var invitedTwoFactorPolicies = await policyService.GetPoliciesApplicableToUserAsync(userId,
|
||||||
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Revoked);
|
||||||
|
return invitedTwoFactorPolicies.Any(p => p.OrganizationId == organizationId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
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 Require Two-Factor Authentication policy.
|
||||||
|
/// </summary>
|
||||||
|
public class RequireTwoFactorPolicyRequirement : IPolicyRequirement
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<PolicyDetails> _policyDetails;
|
||||||
|
|
||||||
|
public RequireTwoFactorPolicyRequirement(IEnumerable<PolicyDetails> policyDetails)
|
||||||
|
{
|
||||||
|
_policyDetails = policyDetails;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if two-factor authentication is required for the organization due to an active policy.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="organizationId">The ID of the organization to check.</param>
|
||||||
|
/// <returns>True if two-factor authentication is required for the organization, false otherwise.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should be used to check whether the member needs to have 2FA enabled before being
|
||||||
|
/// accepted, confirmed, or restored to the organization.
|
||||||
|
/// </remarks>
|
||||||
|
public bool IsTwoFactorRequiredForOrganization(Guid organizationId) =>
|
||||||
|
_policyDetails.Any(p => p.OrganizationId == organizationId);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns tuples of (OrganizationId, OrganizationUserId) for active memberships where two-factor authentication is required.
|
||||||
|
/// Users should be revoked from these organizations if they disable all 2FA methods.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<(Guid OrganizationId, Guid OrganizationUserId)> OrganizationsRequiringTwoFactor =>
|
||||||
|
_policyDetails
|
||||||
|
.Where(p => p.OrganizationUserStatus is
|
||||||
|
OrganizationUserStatusType.Accepted or
|
||||||
|
OrganizationUserStatusType.Confirmed)
|
||||||
|
.Select(p => (p.OrganizationId, p.OrganizationUserId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RequireTwoFactorPolicyRequirementFactory : BasePolicyRequirementFactory<RequireTwoFactorPolicyRequirement>
|
||||||
|
{
|
||||||
|
public override PolicyType PolicyType => PolicyType.TwoFactorAuthentication;
|
||||||
|
protected override IEnumerable<OrganizationUserStatusType> ExemptStatuses => [];
|
||||||
|
|
||||||
|
public override RequireTwoFactorPolicyRequirement Create(IEnumerable<PolicyDetails> policyDetails)
|
||||||
|
{
|
||||||
|
return new RequireTwoFactorPolicyRequirement(policyDetails);
|
||||||
|
}
|
||||||
|
}
|
@ -36,5 +36,6 @@ public static class PolicyServiceCollectionExtensions
|
|||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, ResetPasswordPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, ResetPasswordPolicyRequirementFactory>();
|
||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, PersonalOwnershipPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, PersonalOwnershipPolicyRequirementFactory>();
|
||||||
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireSsoPolicyRequirementFactory>();
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireSsoPolicyRequirementFactory>();
|
||||||
|
services.AddScoped<IPolicyRequirementFactory<IPolicyRequirement>, RequireTwoFactorPolicyRequirementFactory>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ 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;
|
||||||
using Bit.Core.Auth.Repositories;
|
using Bit.Core.Auth.Repositories;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Billing.Extensions;
|
using Bit.Core.Billing.Extensions;
|
||||||
@ -45,7 +44,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly ICollectionRepository _collectionRepository;
|
private readonly ICollectionRepository _collectionRepository;
|
||||||
private readonly IUserRepository _userRepository;
|
|
||||||
private readonly IGroupRepository _groupRepository;
|
private readonly IGroupRepository _groupRepository;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IPushNotificationService _pushNotificationService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
@ -69,7 +67,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
private readonly IUpdateSecretsManagerSubscriptionCommand _updateSecretsManagerSubscriptionCommand;
|
||||||
private readonly IProviderRepository _providerRepository;
|
private readonly IProviderRepository _providerRepository;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
|
||||||
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
private readonly IHasConfirmedOwnersExceptQuery _hasConfirmedOwnersExceptQuery;
|
||||||
private readonly IPricingClient _pricingClient;
|
private readonly IPricingClient _pricingClient;
|
||||||
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
@ -79,7 +76,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
ICollectionRepository collectionRepository,
|
ICollectionRepository collectionRepository,
|
||||||
IUserRepository userRepository,
|
|
||||||
IGroupRepository groupRepository,
|
IGroupRepository groupRepository,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IPushNotificationService pushNotificationService,
|
IPushNotificationService pushNotificationService,
|
||||||
@ -103,7 +99,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
IUpdateSecretsManagerSubscriptionCommand updateSecretsManagerSubscriptionCommand,
|
||||||
IProviderRepository providerRepository,
|
IProviderRepository providerRepository,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
|
||||||
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
IHasConfirmedOwnersExceptQuery hasConfirmedOwnersExceptQuery,
|
||||||
IPricingClient pricingClient,
|
IPricingClient pricingClient,
|
||||||
IPolicyRequirementQuery policyRequirementQuery,
|
IPolicyRequirementQuery policyRequirementQuery,
|
||||||
@ -113,7 +108,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
_collectionRepository = collectionRepository;
|
_collectionRepository = collectionRepository;
|
||||||
_userRepository = userRepository;
|
|
||||||
_groupRepository = groupRepository;
|
_groupRepository = groupRepository;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_pushNotificationService = pushNotificationService;
|
_pushNotificationService = pushNotificationService;
|
||||||
@ -137,7 +131,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
_updateSecretsManagerSubscriptionCommand = updateSecretsManagerSubscriptionCommand;
|
||||||
_providerRepository = providerRepository;
|
_providerRepository = providerRepository;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
|
||||||
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
_hasConfirmedOwnersExceptQuery = hasConfirmedOwnersExceptQuery;
|
||||||
_pricingClient = pricingClient;
|
_pricingClient = pricingClient;
|
||||||
_policyRequirementQuery = policyRequirementQuery;
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
@ -1722,72 +1715,6 @@ public class OrganizationService : IOrganizationService
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CheckPoliciesBeforeRestoreAsync(OrganizationUser orgUser, bool userHasTwoFactorEnabled)
|
|
||||||
{
|
|
||||||
// An invited OrganizationUser isn't linked with a user account yet, so these checks are irrelevant
|
|
||||||
// The user will be subject to the same checks when they try to accept the invite
|
|
||||||
if (GetPriorActiveOrganizationUserStatusType(orgUser) == OrganizationUserStatusType.Invited)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userId = orgUser.UserId.Value;
|
|
||||||
|
|
||||||
// Enforce Single Organization Policy of organization user is being restored to
|
|
||||||
var allOrgUsers = await _organizationUserRepository.GetManyByUserAsync(userId);
|
|
||||||
var hasOtherOrgs = allOrgUsers.Any(ou => ou.OrganizationId != orgUser.OrganizationId);
|
|
||||||
var singleOrgPoliciesApplyingToRevokedUsers = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
|
||||||
PolicyType.SingleOrg, OrganizationUserStatusType.Revoked);
|
|
||||||
var singleOrgPolicyApplies = singleOrgPoliciesApplyingToRevokedUsers.Any(p => p.OrganizationId == orgUser.OrganizationId);
|
|
||||||
|
|
||||||
var singleOrgCompliant = true;
|
|
||||||
var belongsToOtherOrgCompliant = true;
|
|
||||||
var twoFactorCompliant = true;
|
|
||||||
|
|
||||||
if (hasOtherOrgs && singleOrgPolicyApplies)
|
|
||||||
{
|
|
||||||
singleOrgCompliant = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce Single Organization Policy of other organizations user is a member of
|
|
||||||
var anySingleOrgPolicies = await _policyService.AnyPoliciesApplicableToUserAsync(userId,
|
|
||||||
PolicyType.SingleOrg);
|
|
||||||
if (anySingleOrgPolicies)
|
|
||||||
{
|
|
||||||
belongsToOtherOrgCompliant = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enforce Two Factor Authentication Policy of organization user is trying to join
|
|
||||||
if (!userHasTwoFactorEnabled)
|
|
||||||
{
|
|
||||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(userId,
|
|
||||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Revoked);
|
|
||||||
if (invitedTwoFactorPolicies.Any(p => p.OrganizationId == orgUser.OrganizationId))
|
|
||||||
{
|
|
||||||
twoFactorCompliant = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = await _userRepository.GetByIdAsync(userId);
|
|
||||||
|
|
||||||
if (!singleOrgCompliant && !twoFactorCompliant)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(user.Email + " is not compliant with the single organization and two-step login polciy");
|
|
||||||
}
|
|
||||||
else if (!singleOrgCompliant)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(user.Email + " is not compliant with the single organization policy");
|
|
||||||
}
|
|
||||||
else if (!belongsToOtherOrgCompliant)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(user.Email + " belongs to an organization that doesn't allow them to join multiple organizations");
|
|
||||||
}
|
|
||||||
else if (!twoFactorCompliant)
|
|
||||||
{
|
|
||||||
throw new BadRequestException(user.Email + " is not compliant with the two-step login policy");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusType(OrganizationUser organizationUser)
|
public static OrganizationUserStatusType GetPriorActiveOrganizationUserStatusType(OrganizationUser organizationUser)
|
||||||
{
|
{
|
||||||
// Determine status to revert back to
|
// Determine status to revert back to
|
||||||
|
@ -6,6 +6,8 @@ using Bit.Core.AdminConsole.Enums;
|
|||||||
using Bit.Core.AdminConsole.Models.Data;
|
using Bit.Core.AdminConsole.Models.Data;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
|
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;
|
||||||
@ -81,6 +83,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
||||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IDistributedCache _distributedCache;
|
private readonly IDistributedCache _distributedCache;
|
||||||
|
private readonly IPolicyRequirementQuery _policyRequirementQuery;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
@ -119,7 +122,8 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||||
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand,
|
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand,
|
||||||
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IDistributedCache distributedCache)
|
IDistributedCache distributedCache,
|
||||||
|
IPolicyRequirementQuery policyRequirementQuery)
|
||||||
: base(
|
: base(
|
||||||
store,
|
store,
|
||||||
optionsAccessor,
|
optionsAccessor,
|
||||||
@ -164,6 +168,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
||||||
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_distributedCache = distributedCache;
|
_distributedCache = distributedCache;
|
||||||
|
_policyRequirementQuery = policyRequirementQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Guid? GetProperUserId(ClaimsPrincipal principal)
|
public Guid? GetProperUserId(ClaimsPrincipal principal)
|
||||||
@ -1394,9 +1399,40 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
|
|
||||||
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user)
|
private async Task CheckPoliciesOnTwoFactorRemovalAsync(User user)
|
||||||
{
|
{
|
||||||
|
if (_featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements))
|
||||||
|
{
|
||||||
|
var requirement = await _policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id);
|
||||||
|
if (!requirement.OrganizationsRequiringTwoFactor.Any())
|
||||||
|
{
|
||||||
|
Logger.LogInformation("No organizations requiring two factor for user {userId}.", user.Id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var organizationIds = requirement.OrganizationsRequiringTwoFactor.Select(o => o.OrganizationId).ToList();
|
||||||
|
var organizations = await _organizationRepository.GetManyByIdsAsync(organizationIds);
|
||||||
|
var organizationLookup = organizations.ToDictionary(org => org.Id);
|
||||||
|
|
||||||
|
var revokeOrgUserTasks = requirement.OrganizationsRequiringTwoFactor
|
||||||
|
.Where(o => organizationLookup.ContainsKey(o.OrganizationId))
|
||||||
|
.Select(async o =>
|
||||||
|
{
|
||||||
|
var organization = organizationLookup[o.OrganizationId];
|
||||||
|
await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
|
new RevokeOrganizationUsersRequest(
|
||||||
|
o.OrganizationId,
|
||||||
|
[new OrganizationUserUserDetails { Id = o.OrganizationUserId, OrganizationId = o.OrganizationId }],
|
||||||
|
new SystemUser(EventSystemUser.TwoFactorDisabled)));
|
||||||
|
await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
await Task.WhenAll(revokeOrgUserTasks);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
|
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
|
||||||
|
|
||||||
var removeOrgUserTasks = twoFactorPolicies.Select(async p =>
|
var legacyRevokeOrgUserTasks = twoFactorPolicies.Select(async p =>
|
||||||
{
|
{
|
||||||
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
|
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
|
||||||
await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
|
await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
@ -1407,7 +1443,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
|
await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
await Task.WhenAll(removeOrgUserTasks);
|
await Task.WhenAll(legacyRevokeOrgUserTasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
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.OrganizationFeatures.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
@ -29,7 +32,6 @@ namespace Bit.Core.Test.OrganizationFeatures.OrganizationUsers;
|
|||||||
public class AcceptOrgUserCommandTests
|
public class AcceptOrgUserCommandTests
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService = Substitute.For<IUserService>();
|
private readonly IUserService _userService = Substitute.For<IUserService>();
|
||||||
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
|
|
||||||
private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory = Substitute.For<IOrgUserInviteTokenableFactory>();
|
private readonly IOrgUserInviteTokenableFactory _orgUserInviteTokenableFactory = Substitute.For<IOrgUserInviteTokenableFactory>();
|
||||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
|
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory = new FakeDataProtectorTokenFactory<OrgUserInviteTokenable>();
|
||||||
|
|
||||||
@ -166,9 +168,6 @@ public class AcceptOrgUserCommandTests
|
|||||||
// Arrange
|
// Arrange
|
||||||
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
||||||
|
|
||||||
// User doesn't have 2FA enabled
|
|
||||||
_twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
|
|
||||||
|
|
||||||
// Organization they are trying to join requires 2FA
|
// Organization they are trying to join requires 2FA
|
||||||
var twoFactorPolicy = new OrganizationUserPolicyDetails { OrganizationId = orgUser.OrganizationId };
|
var twoFactorPolicy = new OrganizationUserPolicyDetails { OrganizationId = orgUser.OrganizationId };
|
||||||
sutProvider.GetDependency<IPolicyService>()
|
sutProvider.GetDependency<IPolicyService>()
|
||||||
@ -185,6 +184,107 @@ public class AcceptOrgUserCommandTests
|
|||||||
exception.Message);
|
exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task AcceptOrgUserAsync_WithPolicyRequirementsEnabled_UserWithout2FAJoining2FARequiredOrg_ThrowsBadRequest(
|
||||||
|
SutProvider<AcceptOrgUserCommand> sutProvider,
|
||||||
|
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
|
||||||
|
{
|
||||||
|
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Organization they are trying to join requires 2FA
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgUser.OrganizationId,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Invited,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||||
|
sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService));
|
||||||
|
|
||||||
|
Assert.Equal("You cannot join this organization until you enable two-step login on your user account.",
|
||||||
|
exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task AcceptOrgUserAsync_WithPolicyRequirementsEnabled_UserWith2FAJoining2FARequiredOrg_Succeeds(
|
||||||
|
SutProvider<AcceptOrgUserCommand> sutProvider,
|
||||||
|
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
|
||||||
|
{
|
||||||
|
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// User has 2FA enabled
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(user)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Organization they are trying to join requires 2FA
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgUser.OrganizationId,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Invited,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.ReplaceAsync(Arg.Is<OrganizationUser>(ou => ou.Status == OrganizationUserStatusType.Accepted));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task AcceptOrgUserAsync_WithPolicyRequirementsEnabled_UserJoiningOrgWithout2FARequirement_Succeeds(
|
||||||
|
SutProvider<AcceptOrgUserCommand> sutProvider,
|
||||||
|
User user, Organization org, OrganizationUser orgUser, OrganizationUserUserDetails adminUserDetails)
|
||||||
|
{
|
||||||
|
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
// Organization they are trying to join doesn't require 2FA
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = Guid.NewGuid(),
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Invited,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
await sutProvider.Sut.AcceptOrgUserAsync(orgUser, user, _userService);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.ReplaceAsync(Arg.Is<OrganizationUser>(ou => ou.Status == OrganizationUserStatusType.Accepted));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(OrganizationUserType.Admin)]
|
[BitAutoData(OrganizationUserType.Admin)]
|
||||||
[BitAutoData(OrganizationUserType.Owner)]
|
[BitAutoData(OrganizationUserType.Owner)]
|
||||||
@ -647,9 +747,6 @@ public class AcceptOrgUserCommandTests
|
|||||||
.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg)
|
.AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.SingleOrg)
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
// User doesn't have 2FA enabled
|
|
||||||
_twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
|
|
||||||
|
|
||||||
// Org does not require 2FA
|
// Org does not require 2FA
|
||||||
sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(user.Id,
|
sutProvider.GetDependency<IPolicyService>().GetPoliciesApplicableToUserAsync(user.Id,
|
||||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited)
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited)
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
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.OrganizationFeatures.OrganizationUsers;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
@ -321,4 +324,122 @@ public class ConfirmOrganizationUserCommandTests
|
|||||||
Assert.Contains("User does not have two-step login enabled.", result[1].Item2);
|
Assert.Contains("User does not have two-step login enabled.", result[1].Item2);
|
||||||
Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", result[2].Item2);
|
Assert.Contains("Cannot confirm this member to the organization until they leave or remove all other organizations.", result[2].Item2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ConfirmUserAsync_WithPolicyRequirementsEnabled_AndTwoFactorRequired_ThrowsBadRequestException(
|
||||||
|
Organization org, OrganizationUser confirmingUser,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||||
|
SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||||
|
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||||
|
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||||
|
var policyRequirementQuery = sutProvider.GetDependency<IPolicyRequirementQuery>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
|
|
||||||
|
org.PlanType = PlanType.EnterpriseAnnually;
|
||||||
|
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
|
||||||
|
orgUser.UserId = user.Id;
|
||||||
|
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||||
|
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||||
|
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||||
|
featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
|
policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = org.Id,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Accepted,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, false) });
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, "key", confirmingUser.Id));
|
||||||
|
Assert.Contains("User does not have two-step login enabled.", exception.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ConfirmUserAsync_WithPolicyRequirementsEnabled_AndTwoFactorNotRequired_Succeeds(
|
||||||
|
Organization org, OrganizationUser confirmingUser,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||||
|
SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||||
|
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||||
|
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||||
|
var policyRequirementQuery = sutProvider.GetDependency<IPolicyRequirementQuery>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
|
|
||||||
|
org.PlanType = PlanType.EnterpriseAnnually;
|
||||||
|
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
|
||||||
|
orgUser.UserId = user.Id;
|
||||||
|
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||||
|
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||||
|
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||||
|
featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
|
policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = Guid.NewGuid(),
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Invited,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, false) });
|
||||||
|
|
||||||
|
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, "key", confirmingUser.Id);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
|
||||||
|
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager);
|
||||||
|
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task ConfirmUserAsync_WithPolicyRequirementsEnabled_AndTwoFactorEnabled_Succeeds(
|
||||||
|
Organization org, OrganizationUser confirmingUser,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||||
|
SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var organizationRepository = sutProvider.GetDependency<IOrganizationRepository>();
|
||||||
|
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||||
|
var featureService = sutProvider.GetDependency<IFeatureService>();
|
||||||
|
var policyRequirementQuery = sutProvider.GetDependency<IPolicyRequirementQuery>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
|
|
||||||
|
org.PlanType = PlanType.EnterpriseAnnually;
|
||||||
|
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
|
||||||
|
orgUser.UserId = user.Id;
|
||||||
|
organizationUserRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||||
|
organizationRepository.GetByIdAsync(org.Id).Returns(org);
|
||||||
|
userRepository.GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||||
|
featureService.IsEnabled(FeatureFlagKeys.PolicyRequirements).Returns(true);
|
||||||
|
policyRequirementQuery.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = org.Id,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Accepted,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(user.Id)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (user.Id, true) });
|
||||||
|
|
||||||
|
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, "key", confirmingUser.Id);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IEventService>().Received(1).LogOrganizationUserEventAsync(orgUser, EventType.OrganizationUser_Confirmed);
|
||||||
|
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager);
|
||||||
|
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
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.OrganizationFeatures.OrganizationUsers.RestoreUser.v1;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.RestoreUser.v1;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
using Bit.Core.AdminConsole.Services;
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
@ -208,6 +211,57 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RestoreUser_WithPolicyRequirementsEnabled_With2FAPolicyEnabled_WithoutUser2FAConfigured_Fails(
|
||||||
|
Organization organization,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
||||||
|
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
organizationUser.Email = null;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.UserId.Value)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, false) });
|
||||||
|
|
||||||
|
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(organizationUser.UserId.Value)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organizationUser.OrganizationId,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Revoked,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
var user = new User();
|
||||||
|
user.Email = "test@bitwarden.com";
|
||||||
|
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||||
|
|
||||||
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
|
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||||
|
|
||||||
|
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<EventSystemUser>());
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUser_With2FAPolicyEnabled_WithUser2FAConfigured_Success(
|
public async Task RestoreUser_With2FAPolicyEnabled_WithUser2FAConfigured_Success(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
@ -235,6 +289,46 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RestoreUser_WithPolicyRequirementsEnabled_With2FAPolicyEnabled_WithUser2FAConfigured_Success(
|
||||||
|
Organization organization,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
||||||
|
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.UserId.Value)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>() { (organizationUser.UserId.Value, true) });
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(organizationUser.UserId.Value)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organizationUser.OrganizationId,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Revoked,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed);
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUser_WithSingleOrgPolicyEnabled_Fails(
|
public async Task RestoreUser_WithSingleOrgPolicyEnabled_Fails(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
@ -277,45 +371,6 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task RestoreUser_vNext_WithOtherOrganizationSingleOrgPolicyEnabled_Fails(
|
|
||||||
Organization organization,
|
|
||||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
|
||||||
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
|
||||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser,
|
|
||||||
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
|
||||||
{
|
|
||||||
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
|
||||||
secondOrganizationUser.UserId = organizationUser.UserId;
|
|
||||||
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
|
||||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.UserId.Value)))
|
|
||||||
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)> { (organizationUser.UserId.Value, true) });
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPolicyService>()
|
|
||||||
.AnyPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
var user = new User { Email = "test@bitwarden.com" };
|
|
||||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
|
||||||
|
|
||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
|
||||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
|
||||||
|
|
||||||
Assert.Contains("test@bitwarden.com belongs to an organization that doesn't allow them to join multiple organizations", exception.Message.ToLowerInvariant());
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.RestoreAsync(Arg.Any<Guid>(), Arg.Any<OrganizationUserStatusType>());
|
|
||||||
await sutProvider.GetDependency<IEventService>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.LogOrganizationUserEventAsync(Arg.Any<OrganizationUser>(), Arg.Any<EventType>(), Arg.Any<EventSystemUser>());
|
|
||||||
await sutProvider.GetDependency<IPushNotificationService>()
|
|
||||||
.DidNotReceiveWithAnyArgs()
|
|
||||||
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUser_WithSingleOrgPolicyEnabled_And_2FA_Policy_Fails(
|
public async Task RestoreUser_WithSingleOrgPolicyEnabled_And_2FA_Policy_Fails(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
@ -364,20 +419,42 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUser_vNext_With2FAPolicyEnabled_WithoutUser2FAConfigured_Fails(
|
public async Task RestoreUser_WithPolicyRequirementsEnabled_WithSingleOrgPolicyEnabled_And_2FA_Policy_Fails(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser secondOrganizationUser,
|
||||||
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
||||||
{
|
{
|
||||||
organizationUser.Email = null;
|
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
||||||
|
secondOrganizationUser.UserId = organizationUser.UserId;
|
||||||
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
|
.GetManyByUserAsync(organizationUser.UserId.Value)
|
||||||
|
.Returns(new[] { organizationUser, secondOrganizationUser });
|
||||||
sutProvider.GetDependency<IPolicyService>()
|
sutProvider.GetDependency<IPolicyService>()
|
||||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.SingleOrg, Arg.Any<OrganizationUserStatusType>())
|
||||||
.Returns([new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication }
|
.Returns(new[]
|
||||||
]);
|
{
|
||||||
|
new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.SingleOrg, OrganizationUserStatus = OrganizationUserStatusType.Revoked }
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(organizationUser.UserId.Value)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organizationUser.OrganizationId,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Revoked,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
var user = new User { Email = "test@bitwarden.com" };
|
var user = new User { Email = "test@bitwarden.com" };
|
||||||
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(organizationUser.UserId.Value).Returns(user);
|
||||||
@ -385,7 +462,7 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||||
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
() => sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id));
|
||||||
|
|
||||||
Assert.Contains("test@bitwarden.com is not compliant with the two-step login policy", exception.Message.ToLowerInvariant());
|
Assert.Contains("test@bitwarden.com is not compliant with the single organization and two-step login policy", exception.Message.ToLowerInvariant());
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||||
.DidNotReceiveWithAnyArgs()
|
.DidNotReceiveWithAnyArgs()
|
||||||
@ -398,35 +475,6 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
.PushSyncOrgKeysAsync(Arg.Any<Guid>());
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
|
||||||
public async Task RestoreUser_vNext_With2FAPolicyEnabled_WithUser2FAConfigured_Success(
|
|
||||||
Organization organization,
|
|
||||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
|
||||||
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser organizationUser,
|
|
||||||
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
|
||||||
{
|
|
||||||
organizationUser.Email = null; // this is required to mock that the user as had already been confirmed before the revoke
|
|
||||||
RestoreUser_Setup(organization, owner, organizationUser, sutProvider);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<IPolicyService>()
|
|
||||||
.GetPoliciesApplicableToUserAsync(organizationUser.UserId.Value, PolicyType.TwoFactorAuthentication, Arg.Any<OrganizationUserStatusType>())
|
|
||||||
.Returns([new OrganizationUserPolicyDetails { OrganizationId = organizationUser.OrganizationId, PolicyType = PolicyType.TwoFactorAuthentication }
|
|
||||||
]);
|
|
||||||
|
|
||||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
|
||||||
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(organizationUser.UserId.Value)))
|
|
||||||
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)> { (organizationUser.UserId.Value, true) });
|
|
||||||
|
|
||||||
await sutProvider.Sut.RestoreUserAsync(organizationUser, owner.Id);
|
|
||||||
|
|
||||||
await sutProvider.GetDependency<IOrganizationUserRepository>()
|
|
||||||
.Received(1)
|
|
||||||
.RestoreAsync(organizationUser.Id, OrganizationUserStatusType.Confirmed);
|
|
||||||
await sutProvider.GetDependency<IEventService>()
|
|
||||||
.Received(1)
|
|
||||||
.LogOrganizationUserEventAsync(organizationUser, EventType.OrganizationUser_Restored);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUser_WhenUserOwningAnotherFreeOrganization_ThenRestoreUserFails(
|
public async Task RestoreUser_WhenUserOwningAnotherFreeOrganization_ThenRestoreUserFails(
|
||||||
Organization organization,
|
Organization organization,
|
||||||
@ -672,6 +720,77 @@ public class RestoreOrganizationUserCommandTests
|
|||||||
.RestoreAsync(orgUser3.Id, OrganizationUserStatusType.Invited);
|
.RestoreAsync(orgUser3.Id, OrganizationUserStatusType.Invited);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RestoreUsers_WithPolicyRequirementsEnabled_With2FAPolicy_BlocksNonCompliantUser(Organization organization,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser1,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser2,
|
||||||
|
[OrganizationUser(OrganizationUserStatusType.Revoked)] OrganizationUser orgUser3,
|
||||||
|
SutProvider<RestoreOrganizationUserCommand> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
RestoreUser_Setup(organization, owner, orgUser1, sutProvider);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
|
||||||
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
|
var userRepository = sutProvider.GetDependency<IUserRepository>();
|
||||||
|
var policyService = sutProvider.GetDependency<IPolicyService>();
|
||||||
|
var userService = Substitute.For<IUserService>();
|
||||||
|
|
||||||
|
orgUser1.Email = orgUser2.Email = null;
|
||||||
|
orgUser3.UserId = null;
|
||||||
|
orgUser3.Key = null;
|
||||||
|
orgUser1.OrganizationId = orgUser2.OrganizationId = orgUser3.OrganizationId = organization.Id;
|
||||||
|
organizationUserRepository
|
||||||
|
.GetManyAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUser1.Id) && ids.Contains(orgUser2.Id) && ids.Contains(orgUser3.Id)))
|
||||||
|
.Returns(new[] { orgUser1, orgUser2, orgUser3 });
|
||||||
|
|
||||||
|
userRepository.GetByIdAsync(orgUser2.UserId!.Value).Returns(new User { Email = "test@example.com" });
|
||||||
|
|
||||||
|
// Setup 2FA policy
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(Arg.Any<Guid>())
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Revoked,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
|
||||||
|
// User1 has 2FA, User2 doesn't
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(orgUser1.UserId!.Value) && ids.Contains(orgUser2.UserId!.Value)))
|
||||||
|
.Returns(new List<(Guid userId, bool twoFactorIsEnabled)>
|
||||||
|
{
|
||||||
|
(orgUser1.UserId!.Value, true),
|
||||||
|
(orgUser2.UserId!.Value, false)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.RestoreUsersAsync(organization.Id, [orgUser1.Id, orgUser2.Id, orgUser3.Id], owner.Id, userService);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal(3, result.Count);
|
||||||
|
Assert.Empty(result[0].Item2); // First user should succeed
|
||||||
|
Assert.Contains("two-step login", result[1].Item2); // Second user should fail
|
||||||
|
Assert.Empty(result[2].Item2); // Third user should succeed
|
||||||
|
await organizationUserRepository
|
||||||
|
.Received(1)
|
||||||
|
.RestoreAsync(orgUser1.Id, OrganizationUserStatusType.Confirmed);
|
||||||
|
await organizationUserRepository
|
||||||
|
.DidNotReceive()
|
||||||
|
.RestoreAsync(orgUser2.Id, Arg.Any<OrganizationUserStatusType>());
|
||||||
|
await organizationUserRepository
|
||||||
|
.Received(1)
|
||||||
|
.RestoreAsync(orgUser3.Id, OrganizationUserStatusType.Invited);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RestoreUsers_UserOwnsAnotherFreeOrganization_BlocksOwnerUserFromBeingRestored(Organization organization,
|
public async Task RestoreUsers_UserOwnsAnotherFreeOrganization_BlocksOwnerUserFromBeingRestored(Organization organization,
|
||||||
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
[OrganizationUser(OrganizationUserStatusType.Confirmed, OrganizationUserType.Owner)] OrganizationUser owner,
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
using Bit.Core.AdminConsole.Enums;
|
||||||
|
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||||
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class RequireTwoFactorPolicyRequirementFactoryTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public void IsTwoFactorRequiredForOrganization_WithNoPolicies_ReturnsFalse(
|
||||||
|
Guid organizationId,
|
||||||
|
SutProvider<RequireTwoFactorPolicyRequirementFactory> sutProvider)
|
||||||
|
{
|
||||||
|
var actual = sutProvider.Sut.Create([]);
|
||||||
|
|
||||||
|
Assert.False(actual.IsTwoFactorRequiredForOrganization(organizationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public void IsTwoFactorRequiredForOrganization_WithOrganizationPolicy_ReturnsTrue(
|
||||||
|
Guid organizationId,
|
||||||
|
SutProvider<RequireTwoFactorPolicyRequirementFactory> sutProvider)
|
||||||
|
{
|
||||||
|
var actual = sutProvider.Sut.Create(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organizationId,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.True(actual.IsTwoFactorRequiredForOrganization(organizationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public void IsTwoFactorRequiredForOrganization_WithOtherOrganizationPolicy_ReturnsFalse(
|
||||||
|
Guid organizationId,
|
||||||
|
SutProvider<RequireTwoFactorPolicyRequirementFactory> sutProvider)
|
||||||
|
{
|
||||||
|
var actual = sutProvider.Sut.Create(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = Guid.NewGuid(),
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
Assert.False(actual.IsTwoFactorRequiredForOrganization(organizationId));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public void OrganizationsRequiringTwoFactor_WithNoPolicies_ReturnsEmptyCollection(
|
||||||
|
SutProvider<RequireTwoFactorPolicyRequirementFactory> sutProvider)
|
||||||
|
{
|
||||||
|
var actual = sutProvider.Sut.Create([]);
|
||||||
|
|
||||||
|
Assert.Empty(actual.OrganizationsRequiringTwoFactor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public void OrganizationsRequiringTwoFactor_WithMultiplePolicies_ReturnsActiveMemberships(
|
||||||
|
Guid orgId1, Guid orgUserId1, Guid orgId2, Guid orgUserId2,
|
||||||
|
Guid orgId3, Guid orgUserId3, Guid orgId4, Guid orgUserId4,
|
||||||
|
SutProvider<RequireTwoFactorPolicyRequirementFactory> sutProvider)
|
||||||
|
{
|
||||||
|
var policies = new[]
|
||||||
|
{
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgId1,
|
||||||
|
OrganizationUserId = orgUserId1,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Accepted
|
||||||
|
},
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgId2,
|
||||||
|
OrganizationUserId = orgUserId2,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Confirmed
|
||||||
|
},
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgId3,
|
||||||
|
OrganizationUserId = orgUserId3,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Invited
|
||||||
|
},
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = orgId4,
|
||||||
|
OrganizationUserId = orgUserId4,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Revoked
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var actual = sutProvider.Sut.Create(policies);
|
||||||
|
|
||||||
|
var result = actual.OrganizationsRequiringTwoFactor.ToList();
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Contains(result, p => p.OrganizationId == orgId1 && p.OrganizationUserId == orgUserId1);
|
||||||
|
Assert.Contains(result, p => p.OrganizationId == orgId2 && p.OrganizationUserId == orgUserId2);
|
||||||
|
Assert.DoesNotContain(result, p => p.OrganizationId == orgId3 && p.OrganizationUserId == orgUserId3);
|
||||||
|
Assert.DoesNotContain(result, p => p.OrganizationId == orgId4 && p.OrganizationUserId == orgUserId4);
|
||||||
|
}
|
||||||
|
}
|
@ -2,8 +2,11 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
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.OrganizationFeatures.OrganizationUsers.Interfaces;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||||
|
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;
|
||||||
@ -326,7 +329,8 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||||
sutProvider.GetDependency<IDistributedCache>()
|
sutProvider.GetDependency<IDistributedCache>(),
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
);
|
);
|
||||||
|
|
||||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||||
@ -462,6 +466,78 @@ public class UserServiceTests
|
|||||||
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization2.DisplayName(), user.Email);
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization2.DisplayName(), user.Email);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task DisableTwoFactorProviderAsync_WithPolicyRequirementsEnabled_WhenOrganizationHas2FAPolicyEnabled_DisablingAllProviders_RevokesUserAndSendsEmail(
|
||||||
|
SutProvider<UserService> sutProvider, User user,
|
||||||
|
Organization organization1, Guid organizationUserId1,
|
||||||
|
Organization organization2, Guid organizationUserId2)
|
||||||
|
{
|
||||||
|
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
|
{
|
||||||
|
[TwoFactorProviderType.Email] = new() { Enabled = true }
|
||||||
|
});
|
||||||
|
organization1.Enabled = organization2.Enabled = true;
|
||||||
|
organization1.UseSso = organization2.UseSso = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organization1.Id,
|
||||||
|
OrganizationUserId = organizationUserId1,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Accepted,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
},
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organization2.Id,
|
||||||
|
OrganizationUserId = organizationUserId2,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Confirmed,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetManyByIdsAsync(Arg.Is<IEnumerable<Guid>>(ids => ids.Contains(organization1.Id) && ids.Contains(organization2.Id)))
|
||||||
|
.Returns(new[] { organization1, organization2 });
|
||||||
|
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>(), JsonHelpers.LegacyEnumKeyResolver);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||||
|
await sutProvider.GetDependency<IEventService>()
|
||||||
|
.Received(1)
|
||||||
|
.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||||
|
|
||||||
|
// Revoke the user from the first organization
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
|
Arg.Is<RevokeOrganizationUsersRequest>(r => r.OrganizationId == organization1.Id &&
|
||||||
|
r.OrganizationUsers.First().Id == organizationUserId1 &&
|
||||||
|
r.OrganizationUsers.First().OrganizationId == organization1.Id));
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization1.DisplayName(), user.Email);
|
||||||
|
|
||||||
|
// Remove the user from the second organization
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.Received(1)
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
|
Arg.Is<RevokeOrganizationUsersRequest>(r => r.OrganizationId == organization2.Id &&
|
||||||
|
r.OrganizationUsers.First().Id == organizationUserId2 &&
|
||||||
|
r.OrganizationUsers.First().OrganizationId == organization2.Id));
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.Received(1)
|
||||||
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization2.DisplayName(), user.Email);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task DisableTwoFactorProviderAsync_UserHasOneProviderEnabled_DoesNotRevokeUserFromOrganization(
|
public async Task DisableTwoFactorProviderAsync_UserHasOneProviderEnabled_DoesNotRevokeUserFromOrganization(
|
||||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||||
@ -509,6 +585,53 @@ public class UserServiceTests
|
|||||||
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(default, default);
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(default, default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task DisableTwoFactorProviderAsync_WithPolicyRequirementsEnabled_UserHasOneProviderEnabled_DoesNotRevokeUserFromOrganization(
|
||||||
|
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||||
|
{
|
||||||
|
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
|
{
|
||||||
|
[TwoFactorProviderType.Email] = new() { Enabled = true },
|
||||||
|
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||||
|
});
|
||||||
|
sutProvider.GetDependency<IFeatureService>()
|
||||||
|
.IsEnabled(FeatureFlagKeys.PolicyRequirements)
|
||||||
|
.Returns(true);
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
|
.GetAsync<RequireTwoFactorPolicyRequirement>(user.Id)
|
||||||
|
.Returns(new RequireTwoFactorPolicyRequirement(
|
||||||
|
[
|
||||||
|
new PolicyDetails
|
||||||
|
{
|
||||||
|
OrganizationId = organization.Id,
|
||||||
|
OrganizationUserStatus = OrganizationUserStatusType.Accepted,
|
||||||
|
PolicyType = PolicyType.TwoFactorAuthentication
|
||||||
|
}
|
||||||
|
]));
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetByIdAsync(organization.Id)
|
||||||
|
.Returns(organization);
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(user)
|
||||||
|
.Returns(true);
|
||||||
|
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
|
{
|
||||||
|
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||||
|
}, JsonHelpers.LegacyEnumKeyResolver);
|
||||||
|
|
||||||
|
await sutProvider.Sut.DisableTwoFactorProviderAsync(user, TwoFactorProviderType.Email);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.Received(1)
|
||||||
|
.ReplaceAsync(Arg.Is<User>(u => u.Id == user.Id && u.TwoFactorProviders == expectedSavedProviders));
|
||||||
|
await sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.RevokeNonCompliantOrganizationUsersAsync(default);
|
||||||
|
await sutProvider.GetDependency<IMailService>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(default, default);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task ResendNewDeviceVerificationEmail_UserNull_SendTwoFactorEmailAsyncNotCalled(
|
public async Task ResendNewDeviceVerificationEmail_UserNull_SendTwoFactorEmailAsyncNotCalled(
|
||||||
SutProvider<UserService> sutProvider, string email, string secret)
|
SutProvider<UserService> sutProvider, string email, string secret)
|
||||||
@ -800,7 +923,8 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||||
sutProvider.GetDependency<IDistributedCache>()
|
sutProvider.GetDependency<IDistributedCache>(),
|
||||||
|
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user