mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -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:
@ -2,8 +2,11 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Models.Data.Organizations.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Interfaces;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.OrganizationUsers.Requests;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyRequirements;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Auth.Enums;
|
||||
@ -326,7 +329,8 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||
sutProvider.GetDependency<IDistributedCache>()
|
||||
sutProvider.GetDependency<IDistributedCache>(),
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
);
|
||||
|
||||
var actualIsVerified = await sut.VerifySecretAsync(user, secret);
|
||||
@ -462,6 +466,78 @@ public class UserServiceTests
|
||||
.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]
|
||||
public async Task DisableTwoFactorProviderAsync_UserHasOneProviderEnabled_DoesNotRevokeUserFromOrganization(
|
||||
SutProvider<UserService> sutProvider, User user, Organization organization)
|
||||
@ -509,6 +585,53 @@ public class UserServiceTests
|
||||
.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]
|
||||
public async Task ResendNewDeviceVerificationEmail_UserNull_SendTwoFactorEmailAsyncNotCalled(
|
||||
SutProvider<UserService> sutProvider, string email, string secret)
|
||||
@ -800,7 +923,8 @@ public class UserServiceTests
|
||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||
sutProvider.GetDependency<IDistributedCache>()
|
||||
sutProvider.GetDependency<IDistributedCache>(),
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user