mirror of
https://github.com/bitwarden/server.git
synced 2025-05-23 04:21:05 -05:00
Refactor UserService to integrate IPolicyRequirementQuery for two-factor authentication policy checks
This commit is contained in:
parent
73bf74dac4
commit
102c8adf61
@ -6,6 +6,7 @@ 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.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 +82,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 +121,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 +167,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)
|
||||||
@ -1393,6 +1397,25 @@ 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);
|
||||||
|
|
||||||
|
var removeOrgUserTasks = requirement.TwoFactorPoliciesForActiveMemberships.Select(async p =>
|
||||||
|
{
|
||||||
|
var organization = await _organizationRepository.GetByIdAsync(p.OrganizationId);
|
||||||
|
await _revokeNonCompliantOrganizationUserCommand.RevokeNonCompliantOrganizationUsersAsync(
|
||||||
|
new RevokeOrganizationUsersRequest(
|
||||||
|
p.OrganizationId,
|
||||||
|
[new OrganizationUserUserDetails { Id = p.OrganizationUserId, OrganizationId = p.OrganizationId }],
|
||||||
|
new SystemUser(EventSystemUser.TwoFactorDisabled)));
|
||||||
|
await _mailService.SendOrganizationUserRevokedForTwoFactorPolicyEmailAsync(organization.DisplayName(), user.Email);
|
||||||
|
}).ToArray();
|
||||||
|
|
||||||
|
await Task.WhenAll(removeOrgUserTasks);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
|
var twoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id, PolicyType.TwoFactorAuthentication);
|
||||||
|
|
||||||
@ -1409,6 +1432,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
|
|
||||||
await Task.WhenAll(removeOrgUserTasks);
|
await Task.WhenAll(removeOrgUserTasks);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
public override async Task<IdentityResult> ConfirmEmailAsync(User user, string token)
|
||||||
{
|
{
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
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.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 +328,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 +465,81 @@ 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>()
|
||||||
|
.GetByIdAsync(organization1.Id)
|
||||||
|
.Returns(organization1);
|
||||||
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
|
.GetByIdAsync(organization2.Id)
|
||||||
|
.Returns(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 +587,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 +925,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>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user