mirror of
https://github.com/bitwarden/server.git
synced 2025-05-12 15:12:16 -05:00
[PM-19029][PM-19203] Addressing UserService
tech debt around ITwoFactorIsEnabledQuery
(#5754)
* fix : split out the interface from the TwoFactorAuthenticationValidator into separate file. * fix: replacing IUserService.TwoFactorEnabled with ITwoFactorEnabledQuery * fix: combined logic for both bulk and single user look ups for TwoFactorIsEnabledQuery. * fix: return two factor provider enabled on CanGenerate() method. * tech debt: modfifying MFA providers to call the database less to validate if two factor is enabled. * tech debt: removed unused service from AuthenticatorTokenProvider * doc: added documentation to ITwoFactorProviderUsers * doc: updated comments for TwoFactorIsEnabled impl * test: fixing tests for ITwoFactorIsEnabledQuery * test: updating tests to have correct DI and removing test for automatic email of TOTP. * test: adding better test coverage
This commit is contained in:
parent
80e7a0afd6
commit
3f95513d11
@ -76,7 +76,7 @@ public class MembersController : Controller
|
|||||||
{
|
{
|
||||||
return new NotFoundResult();
|
return new NotFoundResult();
|
||||||
}
|
}
|
||||||
var response = new MemberResponseModel(orgUser, await _userService.TwoFactorIsEnabledAsync(orgUser),
|
var response = new MemberResponseModel(orgUser, await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(orgUser),
|
||||||
collections);
|
collections);
|
||||||
return new JsonResult(response);
|
return new JsonResult(response);
|
||||||
}
|
}
|
||||||
@ -185,7 +185,7 @@ public class MembersController : Controller
|
|||||||
{
|
{
|
||||||
var existingUserDetails = await _organizationUserRepository.GetDetailsByIdAsync(id);
|
var existingUserDetails = await _organizationUserRepository.GetDetailsByIdAsync(id);
|
||||||
response = new MemberResponseModel(existingUserDetails,
|
response = new MemberResponseModel(existingUserDetails,
|
||||||
await _userService.TwoFactorIsEnabledAsync(existingUserDetails), associations);
|
await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(existingUserDetails), associations);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -16,6 +16,7 @@ using Bit.Core.Auth.Entities;
|
|||||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -45,6 +46,7 @@ public class AccountsController : Controller
|
|||||||
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
||||||
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
||||||
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
private readonly IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> _cipherValidator;
|
||||||
@ -68,6 +70,7 @@ public class AccountsController : Controller
|
|||||||
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
|
ISetInitialMasterPasswordCommand setInitialMasterPasswordCommand,
|
||||||
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
|
ITdeOffboardingPasswordCommand tdeOffboardingPasswordCommand,
|
||||||
IRotateUserKeyCommand rotateUserKeyCommand,
|
IRotateUserKeyCommand rotateUserKeyCommand,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator,
|
IRotationValidator<IEnumerable<CipherWithIdRequestModel>, IEnumerable<Cipher>> cipherValidator,
|
||||||
IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator,
|
IRotationValidator<IEnumerable<FolderWithIdRequestModel>, IEnumerable<Folder>> folderValidator,
|
||||||
@ -87,6 +90,7 @@ public class AccountsController : Controller
|
|||||||
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
|
_setInitialMasterPasswordCommand = setInitialMasterPasswordCommand;
|
||||||
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
|
_tdeOffboardingPasswordCommand = tdeOffboardingPasswordCommand;
|
||||||
_rotateUserKeyCommand = rotateUserKeyCommand;
|
_rotateUserKeyCommand = rotateUserKeyCommand;
|
||||||
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_cipherValidator = cipherValidator;
|
_cipherValidator = cipherValidator;
|
||||||
_folderValidator = folderValidator;
|
_folderValidator = folderValidator;
|
||||||
@ -389,7 +393,7 @@ public class AccountsController : Controller
|
|||||||
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
|
await _providerUserRepository.GetManyOrganizationDetailsByUserAsync(user.Id,
|
||||||
ProviderUserStatusType.Confirmed);
|
ProviderUserStatusType.Confirmed);
|
||||||
|
|
||||||
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
var twoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
||||||
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
||||||
|
|
||||||
@ -423,7 +427,7 @@ public class AccountsController : Controller
|
|||||||
|
|
||||||
await _userService.SaveUserAsync(model.ToUser(user));
|
await _userService.SaveUserAsync(model.ToUser(user));
|
||||||
|
|
||||||
var twoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
var twoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
var hasPremiumFromOrg = await _userService.HasPremiumFromOrganization(user);
|
||||||
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
||||||
|
|
||||||
@ -442,7 +446,7 @@ public class AccountsController : Controller
|
|||||||
}
|
}
|
||||||
await _userService.SaveUserAsync(model.ToUser(user), true);
|
await _userService.SaveUserAsync(model.ToUser(user), true);
|
||||||
|
|
||||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
var userTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
||||||
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Api.Models.Request;
|
|||||||
using Bit.Api.Models.Request.Accounts;
|
using Bit.Api.Models.Request.Accounts;
|
||||||
using Bit.Api.Models.Response;
|
using Bit.Api.Models.Response;
|
||||||
using Bit.Api.Utilities;
|
using Bit.Api.Utilities;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
@ -22,7 +23,8 @@ namespace Bit.Api.Billing.Controllers;
|
|||||||
[Route("accounts")]
|
[Route("accounts")]
|
||||||
[Authorize("Application")]
|
[Authorize("Application")]
|
||||||
public class AccountsController(
|
public class AccountsController(
|
||||||
IUserService userService) : Controller
|
IUserService userService,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery) : Controller
|
||||||
{
|
{
|
||||||
[HttpPost("premium")]
|
[HttpPost("premium")]
|
||||||
public async Task<PaymentResponseModel> PostPremiumAsync(
|
public async Task<PaymentResponseModel> PostPremiumAsync(
|
||||||
@ -56,7 +58,7 @@ public class AccountsController(
|
|||||||
model.PaymentMethodType!.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license,
|
model.PaymentMethodType!.Value, model.AdditionalStorageGb.GetValueOrDefault(0), license,
|
||||||
new TaxInfo { BillingAddressCountry = model.Country, BillingAddressPostalCode = model.PostalCode });
|
new TaxInfo { BillingAddressCountry = model.Country, BillingAddressPostalCode = model.PostalCode });
|
||||||
|
|
||||||
var userTwoFactorEnabled = await userService.TwoFactorIsEnabledAsync(user);
|
var userTwoFactorEnabled = await twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var userHasPremiumFromOrganization = await userService.HasPremiumFromOrganization(user);
|
var userHasPremiumFromOrganization = await userService.HasPremiumFromOrganization(user);
|
||||||
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
var organizationIdsClaimingActiveUser = await GetOrganizationIdsClaimingUserAsync(user.Id);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using Bit.Core;
|
|||||||
using Bit.Core.AdminConsole.Entities;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Enums.Provider;
|
using Bit.Core.AdminConsole.Enums.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -37,6 +38,7 @@ public class SyncController : Controller
|
|||||||
private readonly Version _sshKeyCipherMinimumVersion = new(Constants.SSHKeyCipherMinimumVersion);
|
private readonly Version _sshKeyCipherMinimumVersion = new(Constants.SSHKeyCipherMinimumVersion);
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
|
|
||||||
public SyncController(
|
public SyncController(
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
@ -51,7 +53,8 @@ public class SyncController : Controller
|
|||||||
GlobalSettings globalSettings,
|
GlobalSettings globalSettings,
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IFeatureService featureService,
|
IFeatureService featureService,
|
||||||
IApplicationCacheService applicationCacheService)
|
IApplicationCacheService applicationCacheService,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery)
|
||||||
{
|
{
|
||||||
_userService = userService;
|
_userService = userService;
|
||||||
_folderRepository = folderRepository;
|
_folderRepository = folderRepository;
|
||||||
@ -66,6 +69,7 @@ public class SyncController : Controller
|
|||||||
_currentContext = currentContext;
|
_currentContext = currentContext;
|
||||||
_featureService = featureService;
|
_featureService = featureService;
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
@ -102,7 +106,7 @@ public class SyncController : Controller
|
|||||||
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
collectionCiphersGroupDict = collectionCiphers.GroupBy(c => c.CipherId).ToDictionary(s => s.Key);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userTwoFactorEnabled = await _userService.TwoFactorIsEnabledAsync(user);
|
var userTwoFactorEnabled = await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user);
|
||||||
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
var userHasPremiumFromOrganization = await _userService.HasPremiumFromOrganization(user);
|
||||||
var organizationClaimingActiveUser = await _userService.GetOrganizationsClaimingUserAsync(user.Id);
|
var organizationClaimingActiveUser = await _userService.GetOrganizationsClaimingUserAsync(user.Id);
|
||||||
var organizationIdsClaimingActiveUser = organizationClaimingActiveUser.Select(o => o.Id);
|
var organizationIdsClaimingActiveUser = organizationClaimingActiveUser.Select(o => o.Id);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
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.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -24,6 +25,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
private readonly IDataProtectorTokenFactory<OrgUserInviteTokenable> _orgUserInviteTokenDataFactory;
|
||||||
|
|
||||||
public AcceptOrgUserCommand(
|
public AcceptOrgUserCommand(
|
||||||
@ -34,6 +36,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
IPolicyService policyService,
|
IPolicyService policyService,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IUserRepository userRepository,
|
IUserRepository userRepository,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory)
|
IDataProtectorTokenFactory<OrgUserInviteTokenable> orgUserInviteTokenDataFactory)
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -45,6 +48,7 @@ public class AcceptOrgUserCommand : IAcceptOrgUserCommand
|
|||||||
_policyService = policyService;
|
_policyService = policyService;
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_userRepository = userRepository;
|
_userRepository = userRepository;
|
||||||
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
_orgUserInviteTokenDataFactory = orgUserInviteTokenDataFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +196,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 userService.TwoFactorIsEnabledAsync(user))
|
if (!await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user))
|
||||||
{
|
{
|
||||||
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
var invitedTwoFactorPolicies = await _policyService.GetPoliciesApplicableToUserAsync(user.Id,
|
||||||
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
PolicyType.TwoFactorAuthentication, OrganizationUserStatusType.Invited);
|
||||||
|
@ -6,7 +6,8 @@ public enum TwoFactorProviderType : byte
|
|||||||
Email = 1,
|
Email = 1,
|
||||||
Duo = 2,
|
Duo = 2,
|
||||||
YubiKey = 3,
|
YubiKey = 3,
|
||||||
U2f = 4, // Deprecated
|
[Obsolete("Deprecated in favor of WebAuthn.")]
|
||||||
|
U2f = 4,
|
||||||
Remember = 5,
|
Remember = 5,
|
||||||
OrganizationDuo = 6,
|
OrganizationDuo = 6,
|
||||||
WebAuthn = 7,
|
WebAuthn = 7,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -12,16 +11,13 @@ public class AuthenticatorTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
{
|
{
|
||||||
private const string CacheKeyFormat = "Authenticator_TOTP_{0}_{1}";
|
private const string CacheKeyFormat = "Authenticator_TOTP_{0}_{1}";
|
||||||
|
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
private readonly IDistributedCache _distributedCache;
|
private readonly IDistributedCache _distributedCache;
|
||||||
private readonly DistributedCacheEntryOptions _distributedCacheEntryOptions;
|
private readonly DistributedCacheEntryOptions _distributedCacheEntryOptions;
|
||||||
|
|
||||||
public AuthenticatorTokenProvider(
|
public AuthenticatorTokenProvider(
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
[FromKeyedServices("persistent")]
|
[FromKeyedServices("persistent")]
|
||||||
IDistributedCache distributedCache)
|
IDistributedCache distributedCache)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
_distributedCache = distributedCache;
|
_distributedCache = distributedCache;
|
||||||
_distributedCacheEntryOptions = new DistributedCacheEntryOptions
|
_distributedCacheEntryOptions = new DistributedCacheEntryOptions
|
||||||
{
|
{
|
||||||
@ -29,15 +25,14 @@ public class AuthenticatorTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
var authenticatorProvider = user.GetTwoFactorProvider(TwoFactorProviderType.Authenticator);
|
||||||
if (string.IsNullOrWhiteSpace((string)provider?.MetaData["Key"]))
|
if (string.IsNullOrWhiteSpace((string)authenticatorProvider?.MetaData["Key"]))
|
||||||
{
|
{
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
return await _serviceProvider.GetRequiredService<IUserService>()
|
return Task.FromResult(authenticatorProvider.Enabled);
|
||||||
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Authenticator, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
|
@ -29,12 +29,13 @@ public class DuoUniversalTokenProvider(
|
|||||||
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
||||||
var provider = await GetDuoTwoFactorProvider(user, userService);
|
var duoUniversalTokenProvider = await GetDuoTwoFactorProvider(user, userService);
|
||||||
if (provider == null)
|
if (duoUniversalTokenProvider == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Duo, user);
|
|
||||||
|
return duoUniversalTokenProvider.Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
@ -58,7 +59,7 @@ public class DuoUniversalTokenProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get the Duo Two Factor Provider for the user if they have access to Duo
|
/// Get the Duo Two Factor Provider for the user if they have premium access to Duo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">Active User</param>
|
/// <param name="user">Active User</param>
|
||||||
/// <returns>null or Duo TwoFactorProvider</returns>
|
/// <returns>null or Duo TwoFactorProvider</returns>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.Caching.Distributed;
|
using Microsoft.Extensions.Caching.Distributed;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@ -10,31 +9,25 @@ namespace Bit.Core.Auth.Identity.TokenProviders;
|
|||||||
|
|
||||||
public class EmailTwoFactorTokenProvider : EmailTokenProvider
|
public class EmailTwoFactorTokenProvider : EmailTokenProvider
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
|
||||||
|
|
||||||
public EmailTwoFactorTokenProvider(
|
public EmailTwoFactorTokenProvider(
|
||||||
IServiceProvider serviceProvider,
|
|
||||||
[FromKeyedServices("persistent")]
|
[FromKeyedServices("persistent")]
|
||||||
IDistributedCache distributedCache) :
|
IDistributedCache distributedCache) :
|
||||||
base(distributedCache)
|
base(distributedCache)
|
||||||
{
|
{
|
||||||
_serviceProvider = serviceProvider;
|
|
||||||
|
|
||||||
TokenAlpha = false;
|
TokenAlpha = false;
|
||||||
TokenNumeric = true;
|
TokenNumeric = true;
|
||||||
TokenLength = 6;
|
TokenLength = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public override Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
var emailTokenProvider = user.GetTwoFactorProvider(TwoFactorProviderType.Email);
|
||||||
if (!HasProperMetaData(provider))
|
if (!HasProperMetaData(emailTokenProvider))
|
||||||
{
|
{
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _serviceProvider.GetRequiredService<IUserService>().
|
return Task.FromResult(emailTokenProvider.Enabled);
|
||||||
TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.Email, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public override Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
|
@ -25,17 +25,16 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
_globalSettings = globalSettings;
|
_globalSettings = globalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
|
||||||
|
|
||||||
var webAuthnProvider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
var webAuthnProvider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
||||||
|
// null check happens in this method
|
||||||
if (!HasProperMetaData(webAuthnProvider))
|
if (!HasProperMetaData(webAuthnProvider))
|
||||||
{
|
{
|
||||||
return false;
|
return Task.FromResult(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.WebAuthn, user);
|
return Task.FromResult(webAuthnProvider.Enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public async Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
@ -81,7 +80,7 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.WebAuthn);
|
||||||
var keys = LoadKeys(provider);
|
var keys = LoadKeys(provider);
|
||||||
|
|
||||||
if (!provider.MetaData.ContainsKey("login"))
|
if (!provider.MetaData.TryGetValue("login", out var value))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -89,7 +88,7 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
|
var clientResponse = JsonSerializer.Deserialize<AuthenticatorAssertionRawResponse>(token,
|
||||||
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
|
||||||
|
|
||||||
var jsonOptions = provider.MetaData["login"].ToString();
|
var jsonOptions = value.ToString();
|
||||||
var options = AssertionOptions.FromJson(jsonOptions);
|
var options = AssertionOptions.FromJson(jsonOptions);
|
||||||
|
|
||||||
var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));
|
var webAuthCred = keys.Find(k => k.Item2.Descriptor.Id.SequenceEqual(clientResponse.Id));
|
||||||
@ -126,6 +125,12 @@ public class WebAuthnTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the provider has proper metadata.
|
||||||
|
/// This is used to determine if the provider has been properly configured.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="provider"></param>
|
||||||
|
/// <returns>true if metadata is present; false if empty or null</returns>
|
||||||
private bool HasProperMetaData(TwoFactorProvider provider)
|
private bool HasProperMetaData(TwoFactorProvider provider)
|
||||||
{
|
{
|
||||||
return provider?.MetaData?.Any() ?? false;
|
return provider?.MetaData?.Any() ?? false;
|
||||||
|
@ -23,19 +23,21 @@ public class YubicoOtpTokenProvider : IUserTwoFactorTokenProvider<User>
|
|||||||
|
|
||||||
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
public async Task<bool> CanGenerateTwoFactorTokenAsync(UserManager<User> manager, User user)
|
||||||
{
|
{
|
||||||
|
// Ensure the user has access to premium
|
||||||
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
var userService = _serviceProvider.GetRequiredService<IUserService>();
|
||||||
if (!await userService.CanAccessPremium(user))
|
if (!await userService.CanAccessPremium(user))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var provider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey);
|
// Check if the user has a YubiKey provider configured
|
||||||
if (!provider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? true)
|
var yubicoProvider = user.GetTwoFactorProvider(TwoFactorProviderType.YubiKey);
|
||||||
|
if (!yubicoProvider?.MetaData.Values.Any(v => !string.IsNullOrWhiteSpace((string)v)) ?? true)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await userService.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType.YubiKey, user);
|
return yubicoProvider.Enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
public Task<string> GenerateAsync(string purpose, UserManager<User> manager, User user)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
using Bit.Core.Context;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
@ -167,7 +167,7 @@ public class UserStore :
|
|||||||
|
|
||||||
public async Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
|
public async Task<bool> GetTwoFactorEnabledAsync(User user, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
return await _serviceProvider.GetRequiredService<IUserService>().TwoFactorIsEnabledAsync(user);
|
return await _serviceProvider.GetRequiredService<ITwoFactorIsEnabledQuery>().TwoFactorIsEnabledAsync(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)
|
public Task SetSecurityStampAsync(User user, string stamp, CancellationToken cancellationToken)
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.Auth.Models;
|
namespace Bit.Core.Auth.Models;
|
||||||
|
|
||||||
public interface ITwoFactorProvidersUser
|
public interface ITwoFactorProvidersUser
|
||||||
{
|
{
|
||||||
string TwoFactorProviders { get; }
|
string TwoFactorProviders { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Get the two factor providers for the user. Currently it can be assumed providers are enabled
|
||||||
|
/// if they exists in the dictionary. When two factor providers are disabled they are removed
|
||||||
|
/// from the dictionary. <see cref="IUserService.DisableTwoFactorProviderAsync"/>
|
||||||
|
/// <see cref="IOrganizationService.DisableTwoFactorProviderAsync"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Dictionary of providers with the type enum as the key</returns>
|
||||||
Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders();
|
Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders();
|
||||||
Guid? GetUserId();
|
Guid? GetUserId();
|
||||||
bool GetPremium();
|
bool GetPremium();
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
|
|
||||||
|
|
||||||
public interface ITwoFactorIsEnabledQuery
|
public interface ITwoFactorIsEnabledQuery
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -16,7 +17,8 @@ public interface ITwoFactorIsEnabledQuery
|
|||||||
/// <typeparam name="T">The type of user in the list. Must implement <see cref="ITwoFactorProvidersUser"/>.</typeparam>
|
/// <typeparam name="T">The type of user in the list. Must implement <see cref="ITwoFactorProvidersUser"/>.</typeparam>
|
||||||
Task<IEnumerable<(T user, bool twoFactorIsEnabled)>> TwoFactorIsEnabledAsync<T>(IEnumerable<T> users) where T : ITwoFactorProvidersUser;
|
Task<IEnumerable<(T user, bool twoFactorIsEnabled)>> TwoFactorIsEnabledAsync<T>(IEnumerable<T> users) where T : ITwoFactorProvidersUser;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns whether two factor is enabled for the user.
|
/// Returns whether two factor is enabled for the user. A user is able to have a TwoFactorProvider that is enabled but requires Premium.
|
||||||
|
/// If the user does not have premium then the TwoFactorProvider is considered _not_ enabled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">The user to check.</param>
|
/// <param name="user">The user to check.</param>
|
||||||
Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user);
|
Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user);
|
||||||
|
@ -1,17 +1,13 @@
|
|||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Enums;
|
||||||
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth;
|
namespace Bit.Core.Auth.UserFeatures.TwoFactorAuth;
|
||||||
|
|
||||||
public class TwoFactorIsEnabledQuery : ITwoFactorIsEnabledQuery
|
public class TwoFactorIsEnabledQuery(IUserRepository userRepository) : ITwoFactorIsEnabledQuery
|
||||||
{
|
{
|
||||||
private readonly IUserRepository _userRepository;
|
private readonly IUserRepository _userRepository = userRepository;
|
||||||
|
|
||||||
public TwoFactorIsEnabledQuery(IUserRepository userRepository)
|
|
||||||
{
|
|
||||||
_userRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<(Guid userId, bool twoFactorIsEnabled)>> TwoFactorIsEnabledAsync(IEnumerable<Guid> userIds)
|
public async Task<IEnumerable<(Guid userId, bool twoFactorIsEnabled)>> TwoFactorIsEnabledAsync(IEnumerable<Guid> userIds)
|
||||||
{
|
{
|
||||||
@ -21,26 +17,15 @@ public class TwoFactorIsEnabledQuery : ITwoFactorIsEnabledQuery
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
var userDetails = await _userRepository.GetManyWithCalculatedPremiumAsync(userIds.ToList());
|
var userDetails = await _userRepository.GetManyWithCalculatedPremiumAsync([.. userIds]);
|
||||||
|
|
||||||
foreach (var userDetail in userDetails)
|
foreach (var userDetail in userDetails)
|
||||||
{
|
{
|
||||||
var hasTwoFactor = false;
|
result.Add(
|
||||||
var providers = userDetail.GetTwoFactorProviders();
|
(userDetail.Id,
|
||||||
if (providers != null)
|
await TwoFactorEnabledAsync(userDetail.GetTwoFactorProviders(),
|
||||||
{
|
() => Task.FromResult(userDetail.HasPremiumAccess))
|
||||||
// Get all enabled providers
|
)
|
||||||
var enabledProviderKeys = from provider in providers
|
);
|
||||||
where provider.Value?.Enabled ?? false
|
|
||||||
select provider.Key;
|
|
||||||
|
|
||||||
// Find the first provider that is enabled and passes the premium check
|
|
||||||
hasTwoFactor = enabledProviderKeys
|
|
||||||
.Select(type => userDetail.HasPremiumAccess || !TwoFactorProvider.RequiresPremium(type))
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
result.Add((userDetail.Id, hasTwoFactor));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -83,41 +68,56 @@ public class TwoFactorIsEnabledQuery : ITwoFactorIsEnabledQuery
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var providers = user.GetTwoFactorProviders();
|
return await TwoFactorEnabledAsync(
|
||||||
if (providers == null || !providers.Any())
|
user.GetTwoFactorProviders(),
|
||||||
|
async () =>
|
||||||
|
{
|
||||||
|
var calcUser = await _userRepository.GetCalculatedPremiumAsync(userId.Value);
|
||||||
|
return calcUser?.HasPremiumAccess ?? false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks to see what kind of two-factor is enabled.
|
||||||
|
/// We use a delegate to check if the user has premium access, since there are multiple ways to
|
||||||
|
/// determine if a user has premium access.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="providers">dictionary of two factor providers</param>
|
||||||
|
/// <param name="hasPremiumAccessDelegate">function to check if the user has premium access</param>
|
||||||
|
/// <returns> true if the user has two factor enabled; false otherwise;</returns>
|
||||||
|
private async static Task<bool> TwoFactorEnabledAsync(
|
||||||
|
Dictionary<TwoFactorProviderType, TwoFactorProvider> providers,
|
||||||
|
Func<Task<bool>> hasPremiumAccessDelegate)
|
||||||
|
{
|
||||||
|
// If there are no providers, then two factor is not enabled
|
||||||
|
if (providers == null || providers.Count == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all enabled providers
|
// Get all enabled providers
|
||||||
var enabledProviderKeys = providers
|
// TODO: PM-21210: In practice we don't save disabled providers to the database, worth looking into.
|
||||||
.Where(provider => provider.Value?.Enabled ?? false)
|
var enabledProviderKeys = from provider in providers
|
||||||
.Select(provider => provider.Key);
|
where provider.Value?.Enabled ?? false
|
||||||
|
select provider.Key;
|
||||||
|
|
||||||
|
// If no providers are enabled then two factor is not enabled
|
||||||
if (!enabledProviderKeys.Any())
|
if (!enabledProviderKeys.Any())
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if any enabled provider passes the premium check
|
// If there are only premium two factor options then standard two factor is not enabled
|
||||||
var hasTwoFactor = enabledProviderKeys
|
var onlyHasPremiumTwoFactor = enabledProviderKeys.All(TwoFactorProvider.RequiresPremium);
|
||||||
.Select(type => user.GetPremium() || !TwoFactorProvider.RequiresPremium(type))
|
if (onlyHasPremiumTwoFactor)
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
// If no enabled provider passes the check, check the repository for organization premium access
|
|
||||||
if (!hasTwoFactor)
|
|
||||||
{
|
{
|
||||||
var userDetails = await _userRepository.GetManyWithCalculatedPremiumAsync(new List<Guid> { userId.Value });
|
// There are no Standard two factor options, check if the user has premium access
|
||||||
var userDetail = userDetails.FirstOrDefault();
|
// If the user has premium access, then two factor is enabled
|
||||||
|
var premiumAccess = await hasPremiumAccessDelegate();
|
||||||
if (userDetail != null)
|
return premiumAccess;
|
||||||
{
|
|
||||||
hasTwoFactor = enabledProviderKeys
|
|
||||||
.Select(type => userDetail.HasPremiumAccess || !TwoFactorProvider.RequiresPremium(type))
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return hasTwoFactor;
|
// The user has at least one non-premium two factor option
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,6 +128,10 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
|
|
||||||
public bool IsExpired() => PremiumExpirationDate.HasValue && PremiumExpirationDate.Value <= DateTime.UtcNow;
|
public bool IsExpired() => PremiumExpirationDate.HasValue && PremiumExpirationDate.Value <= DateTime.UtcNow;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserializes the User.TwoFactorProviders property from JSON to the appropriate C# dictionary.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Dictionary of TwoFactor providers</returns>
|
||||||
public Dictionary<TwoFactorProviderType, TwoFactorProvider>? GetTwoFactorProviders()
|
public Dictionary<TwoFactorProviderType, TwoFactorProvider>? GetTwoFactorProviders()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(TwoFactorProviders))
|
if (string.IsNullOrWhiteSpace(TwoFactorProviders))
|
||||||
@ -137,19 +141,17 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_twoFactorProviders == null)
|
_twoFactorProviders ??=
|
||||||
{
|
|
||||||
_twoFactorProviders =
|
|
||||||
JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
|
JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(
|
||||||
TwoFactorProviders);
|
TwoFactorProviders);
|
||||||
}
|
|
||||||
|
|
||||||
// U2F is no longer supported, and all users keys should have been migrated to WebAuthn.
|
/*
|
||||||
// To prevent issues with accounts being prompted for unsupported U2F we remove them
|
U2F is no longer supported, and all users keys should have been migrated to WebAuthn.
|
||||||
if (_twoFactorProviders.ContainsKey(TwoFactorProviderType.U2f))
|
To prevent issues with accounts being prompted for unsupported U2F we remove them.
|
||||||
{
|
This will probably exist in perpetuity since there is no way to know for sure if any
|
||||||
_twoFactorProviders.Remove(TwoFactorProviderType.U2f);
|
given user does or doesn't have this enabled. It is a non-zero chance.
|
||||||
}
|
*/
|
||||||
|
_twoFactorProviders?.Remove(TwoFactorProviderType.U2f);
|
||||||
|
|
||||||
return _twoFactorProviders;
|
return _twoFactorProviders;
|
||||||
}
|
}
|
||||||
@ -169,6 +171,10 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
return Premium;
|
return Premium;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serializes the C# object to the User.TwoFactorProviders property in JSON format.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="providers">Dictionary of Two Factor providers</param>
|
||||||
public void SetTwoFactorProviders(Dictionary<TwoFactorProviderType, TwoFactorProvider> providers)
|
public void SetTwoFactorProviders(Dictionary<TwoFactorProviderType, TwoFactorProvider> providers)
|
||||||
{
|
{
|
||||||
// When replacing with system.text remember to remove the extra serialization in WebAuthnTokenProvider.
|
// When replacing with system.text remember to remove the extra serialization in WebAuthnTokenProvider.
|
||||||
@ -176,20 +182,21 @@ public class User : ITableObject<Guid>, IStorableSubscriber, IRevisable, ITwoFac
|
|||||||
_twoFactorProviders = providers;
|
_twoFactorProviders = providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearTwoFactorProviders()
|
/// <summary>
|
||||||
{
|
/// Checks if the user has a specific TwoFactorProvider configured. If a user has a premium TwoFactor
|
||||||
SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
|
/// configured it will still be found, even if the user's premium subscription has ended.
|
||||||
}
|
/// </summary>
|
||||||
|
/// <param name="provider">TwoFactor provider being searched for</param>
|
||||||
|
/// <returns>TwoFactorProvider if found; null otherwise.</returns>
|
||||||
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
public TwoFactorProvider? GetTwoFactorProvider(TwoFactorProviderType provider)
|
||||||
{
|
{
|
||||||
var providers = GetTwoFactorProviders();
|
var providers = GetTwoFactorProviders();
|
||||||
if (providers == null || !providers.ContainsKey(provider))
|
if (providers == null || !providers.TryGetValue(provider, out var value))
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return providers[provider];
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long StorageBytesRemaining()
|
public long StorageBytesRemaining()
|
||||||
|
@ -25,6 +25,16 @@ public interface IUserRepository : IRepository<User, Guid>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
Task<IEnumerable<UserWithCalculatedPremium>> GetManyWithCalculatedPremiumAsync(IEnumerable<Guid> ids);
|
Task<IEnumerable<UserWithCalculatedPremium>> GetManyWithCalculatedPremiumAsync(IEnumerable<Guid> ids);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
/// Retrieves the data for the requested user ID and includes additional property indicating
|
||||||
|
/// whether the user has premium access directly or through an organization.
|
||||||
|
///
|
||||||
|
/// Calls the same stored procedure as GetManyWithCalculatedPremiumAsync but handles the query
|
||||||
|
/// for a single user.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userId">The user ID to retrieve data for.</param>
|
||||||
|
/// <returns>User data with calculated premium access; null if nothing is found</returns>
|
||||||
|
Task<UserWithCalculatedPremium?> GetCalculatedPremiumAsync(Guid userId);
|
||||||
|
/// <summary>
|
||||||
/// Sets a new user key and updates all encrypted data.
|
/// Sets a new user key and updates all encrypted data.
|
||||||
/// <para>Warning: Any user key encrypted data not included will be lost.</para>
|
/// <para>Warning: Any user key encrypted data not included will be lost.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -71,11 +71,13 @@ public interface IUserService
|
|||||||
Task<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
|
Task<UserLicense> GenerateLicenseAsync(User user, SubscriptionInfo subscriptionInfo = null,
|
||||||
int? version = null);
|
int? version = null);
|
||||||
Task<bool> CheckPasswordAsync(User user, string password);
|
Task<bool> CheckPasswordAsync(User user, string password);
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the user has access to premium features, either through a personal subscription or through an organization.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">user being acted on</param>
|
||||||
|
/// <returns>true if they can access premium; false otherwise.</returns>
|
||||||
Task<bool> CanAccessPremium(ITwoFactorProvidersUser user);
|
Task<bool> CanAccessPremium(ITwoFactorProvidersUser user);
|
||||||
Task<bool> HasPremiumFromOrganization(ITwoFactorProvidersUser user);
|
Task<bool> HasPremiumFromOrganization(ITwoFactorProvidersUser user);
|
||||||
[Obsolete("Use ITwoFactorIsEnabledQuery instead.")]
|
|
||||||
Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user);
|
|
||||||
Task<bool> TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user);
|
|
||||||
Task<string> GenerateSignInTokenAsync(User user, string purpose);
|
Task<string> GenerateSignInTokenAsync(User user, string purpose);
|
||||||
|
|
||||||
Task<IdentityResult> UpdatePasswordHash(User user, string newPassword,
|
Task<IdentityResult> UpdatePasswordHash(User user, string newPassword,
|
||||||
|
@ -11,6 +11,7 @@ using Bit.Core.AdminConsole.Services;
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Constants;
|
using Bit.Core.Billing.Constants;
|
||||||
using Bit.Core.Billing.Models;
|
using Bit.Core.Billing.Models;
|
||||||
using Bit.Core.Billing.Models.Sales;
|
using Bit.Core.Billing.Models.Sales;
|
||||||
@ -77,6 +78,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
private readonly IPremiumUserBillingService _premiumUserBillingService;
|
private readonly IPremiumUserBillingService _premiumUserBillingService;
|
||||||
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
private readonly IRemoveOrganizationUserCommand _removeOrganizationUserCommand;
|
||||||
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
private readonly IRevokeNonCompliantOrganizationUserCommand _revokeNonCompliantOrganizationUserCommand;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly IDistributedCache _distributedCache;
|
private readonly IDistributedCache _distributedCache;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
@ -115,6 +117,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
IPremiumUserBillingService premiumUserBillingService,
|
IPremiumUserBillingService premiumUserBillingService,
|
||||||
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
IRemoveOrganizationUserCommand removeOrganizationUserCommand,
|
||||||
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand,
|
IRevokeNonCompliantOrganizationUserCommand revokeNonCompliantOrganizationUserCommand,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IDistributedCache distributedCache)
|
IDistributedCache distributedCache)
|
||||||
: base(
|
: base(
|
||||||
store,
|
store,
|
||||||
@ -158,6 +161,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
_premiumUserBillingService = premiumUserBillingService;
|
_premiumUserBillingService = premiumUserBillingService;
|
||||||
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
_removeOrganizationUserCommand = removeOrganizationUserCommand;
|
||||||
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
_revokeNonCompliantOrganizationUserCommand = revokeNonCompliantOrganizationUserCommand;
|
||||||
|
_twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
_distributedCache = distributedCache;
|
_distributedCache = distributedCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -918,7 +922,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
await SaveUserAsync(user);
|
await SaveUserAsync(user);
|
||||||
await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
await _eventService.LogUserEventAsync(user.Id, EventType.User_Disabled2fa);
|
||||||
|
|
||||||
if (!await TwoFactorIsEnabledAsync(user))
|
if (!await _twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user))
|
||||||
{
|
{
|
||||||
await CheckPoliciesOnTwoFactorRemovalAsync(user);
|
await CheckPoliciesOnTwoFactorRemovalAsync(user);
|
||||||
}
|
}
|
||||||
@ -1280,48 +1284,6 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
orgAbility.UsersGetPremium &&
|
orgAbility.UsersGetPremium &&
|
||||||
orgAbility.Enabled);
|
orgAbility.Enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> TwoFactorIsEnabledAsync(ITwoFactorProvidersUser user)
|
|
||||||
{
|
|
||||||
var providers = user.GetTwoFactorProviders();
|
|
||||||
if (providers == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var p in providers)
|
|
||||||
{
|
|
||||||
if (p.Value?.Enabled ?? false)
|
|
||||||
{
|
|
||||||
if (!TwoFactorProvider.RequiresPremium(p.Key))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (await CanAccessPremium(user))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> TwoFactorProviderIsEnabledAsync(TwoFactorProviderType provider, ITwoFactorProvidersUser user)
|
|
||||||
{
|
|
||||||
var providers = user.GetTwoFactorProviders();
|
|
||||||
if (providers == null || !providers.ContainsKey(provider) || !providers[provider].Enabled)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TwoFactorProvider.RequiresPremium(provider))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await CanAccessPremium(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> GenerateSignInTokenAsync(User user, string purpose)
|
public async Task<string> GenerateSignInTokenAsync(User user, string purpose)
|
||||||
{
|
{
|
||||||
var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
|
var token = await GenerateUserTokenAsync(user, Options.Tokens.PasswordResetTokenProvider,
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
|
||||||
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.Auth.Enums;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
using Duende.IdentityServer.Validation;
|
||||||
|
|
||||||
|
namespace Bit.Identity.IdentityServer.RequestValidators;
|
||||||
|
|
||||||
|
public interface ITwoFactorAuthenticationValidator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Check if the user is required to use two-factor authentication to login. This is based on the user's
|
||||||
|
/// enabled two-factor providers, the user's organizations enabled two-factor providers, and the grant type.
|
||||||
|
/// Client credentials and webauthn grant types do not require two-factor authentication.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">the active user for the request</param>
|
||||||
|
/// <param name="request">the request that contains the grant types</param>
|
||||||
|
/// <returns>boolean</returns>
|
||||||
|
Task<Tuple<bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request);
|
||||||
|
/// <summary>
|
||||||
|
/// Builds the two-factor authentication result for the user based on the available two-factor providers
|
||||||
|
/// from either their user account or Organization.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">user trying to login</param>
|
||||||
|
/// <param name="organization">organization associated with the user; Can be null</param>
|
||||||
|
/// <returns>Dictionary with the TwoFactorProviderType as the Key and the Provider Metadata as the Value</returns>
|
||||||
|
Task<Dictionary<string, object>> BuildTwoFactorResultAsync(User user, Organization organization);
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the built in userManager methods to verify the two-factor token for the user. If the organization uses
|
||||||
|
/// organization duo, it will use the organization duo token provider to verify the token.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="user">the active User</param>
|
||||||
|
/// <param name="organization">organization of user; can be null</param>
|
||||||
|
/// <param name="twoFactorProviderType">Two Factor Provider to use to verify the token</param>
|
||||||
|
/// <param name="token">secret passed from the user and consumed by the two-factor provider's verify method</param>
|
||||||
|
/// <returns>boolean</returns>
|
||||||
|
Task<bool> VerifyTwoFactorAsync(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token);
|
||||||
|
}
|
@ -4,6 +4,7 @@ using Bit.Core.Auth.Enums;
|
|||||||
using Bit.Core.Auth.Identity.TokenProviders;
|
using Bit.Core.Auth.Identity.TokenProviders;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Models.Data.Organizations;
|
using Bit.Core.Models.Data.Organizations;
|
||||||
@ -16,56 +17,25 @@ using Microsoft.AspNetCore.Identity;
|
|||||||
|
|
||||||
namespace Bit.Identity.IdentityServer.RequestValidators;
|
namespace Bit.Identity.IdentityServer.RequestValidators;
|
||||||
|
|
||||||
public interface ITwoFactorAuthenticationValidator
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Check if the user is required to use two-factor authentication to login. This is based on the user's
|
|
||||||
/// enabled two-factor providers, the user's organizations enabled two-factor providers, and the grant type.
|
|
||||||
/// Client credentials and webauthn grant types do not require two-factor authentication.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">the active user for the request</param>
|
|
||||||
/// <param name="request">the request that contains the grant types</param>
|
|
||||||
/// <returns>boolean</returns>
|
|
||||||
Task<Tuple<bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request);
|
|
||||||
/// <summary>
|
|
||||||
/// Builds the two-factor authentication result for the user based on the available two-factor providers
|
|
||||||
/// from either their user account or Organization.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">user trying to login</param>
|
|
||||||
/// <param name="organization">organization associated with the user; Can be null</param>
|
|
||||||
/// <returns>Dictionary with the TwoFactorProviderType as the Key and the Provider Metadata as the Value</returns>
|
|
||||||
Task<Dictionary<string, object>> BuildTwoFactorResultAsync(User user, Organization organization);
|
|
||||||
/// <summary>
|
|
||||||
/// Uses the built in userManager methods to verify the two-factor token for the user. If the organization uses
|
|
||||||
/// organization duo, it will use the organization duo token provider to verify the token.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="user">the active User</param>
|
|
||||||
/// <param name="organization">organization of user; can be null</param>
|
|
||||||
/// <param name="twoFactorProviderType">Two Factor Provider to use to verify the token</param>
|
|
||||||
/// <param name="token">secret passed from the user and consumed by the two-factor provider's verify method</param>
|
|
||||||
/// <returns>boolean</returns>
|
|
||||||
Task<bool> VerifyTwoFactorAsync(User user, Organization organization, TwoFactorProviderType twoFactorProviderType, string token);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TwoFactorAuthenticationValidator(
|
public class TwoFactorAuthenticationValidator(
|
||||||
IUserService userService,
|
IUserService userService,
|
||||||
UserManager<User> userManager,
|
UserManager<User> userManager,
|
||||||
IOrganizationDuoUniversalTokenProvider organizationDuoWebTokenProvider,
|
IOrganizationDuoUniversalTokenProvider organizationDuoWebTokenProvider,
|
||||||
IFeatureService featureService,
|
|
||||||
IApplicationCacheService applicationCacheService,
|
IApplicationCacheService applicationCacheService,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> ssoEmail2faSessionTokeFactory,
|
IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> ssoEmail2faSessionTokeFactory,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
ICurrentContext currentContext) : ITwoFactorAuthenticationValidator
|
ICurrentContext currentContext) : ITwoFactorAuthenticationValidator
|
||||||
{
|
{
|
||||||
private readonly IUserService _userService = userService;
|
private readonly IUserService _userService = userService;
|
||||||
private readonly UserManager<User> _userManager = userManager;
|
private readonly UserManager<User> _userManager = userManager;
|
||||||
private readonly IOrganizationDuoUniversalTokenProvider _organizationDuoUniversalTokenProvider = organizationDuoWebTokenProvider;
|
private readonly IOrganizationDuoUniversalTokenProvider _organizationDuoUniversalTokenProvider = organizationDuoWebTokenProvider;
|
||||||
private readonly IFeatureService _featureService = featureService;
|
|
||||||
private readonly IApplicationCacheService _applicationCacheService = applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService = applicationCacheService;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository = organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository = organizationUserRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository = organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository = organizationRepository;
|
||||||
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _ssoEmail2faSessionTokeFactory = ssoEmail2faSessionTokeFactory;
|
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _ssoEmail2faSessionTokeFactory = ssoEmail2faSessionTokeFactory;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery = twoFactorIsEnabledQuery;
|
||||||
private readonly ICurrentContext _currentContext = currentContext;
|
private readonly ICurrentContext _currentContext = currentContext;
|
||||||
|
|
||||||
public async Task<Tuple<bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request)
|
public async Task<Tuple<bool, Organization>> RequiresTwoFactorAsync(User user, ValidatedTokenRequest request)
|
||||||
@ -161,7 +131,7 @@ public class TwoFactorAuthenticationValidator(
|
|||||||
|
|
||||||
// These cases we want to always return false, U2f is deprecated and OrganizationDuo
|
// These cases we want to always return false, U2f is deprecated and OrganizationDuo
|
||||||
// uses a different flow than the other two factor providers, it follows the same
|
// uses a different flow than the other two factor providers, it follows the same
|
||||||
// structure of a UserTokenProvider but has it's logic ran outside the usual token
|
// structure of a UserTokenProvider but has it's logic runs outside the usual token
|
||||||
// provider flow. See IOrganizationDuoUniversalTokenProvider.cs
|
// provider flow. See IOrganizationDuoUniversalTokenProvider.cs
|
||||||
if (type is TwoFactorProviderType.U2f or TwoFactorProviderType.OrganizationDuo)
|
if (type is TwoFactorProviderType.U2f or TwoFactorProviderType.OrganizationDuo)
|
||||||
{
|
{
|
||||||
@ -171,12 +141,12 @@ public class TwoFactorAuthenticationValidator(
|
|||||||
// Now we are concerning the rest of the Two Factor Provider Types
|
// Now we are concerning the rest of the Two Factor Provider Types
|
||||||
|
|
||||||
// The intent of this check is to make sure that the user is using a 2FA provider that
|
// The intent of this check is to make sure that the user is using a 2FA provider that
|
||||||
// is enabled and allowed by their premium status. The exception for Remember
|
// is enabled and allowed by their premium status.
|
||||||
// is because it is a "special" 2FA type that isn't ever explicitly
|
// The exception for Remember is because it is a "special" 2FA type that isn't ever explicitly
|
||||||
// enabled by a user, so we can't check the user's 2FA providers to see if they're
|
// enabled by a user, so we can't check the user's 2FA providers to see if they're
|
||||||
// enabled. We just have to check if the token is valid.
|
// enabled. We just have to check if the token is valid.
|
||||||
if (type != TwoFactorProviderType.Remember &&
|
if (type != TwoFactorProviderType.Remember &&
|
||||||
!await _userService.TwoFactorProviderIsEnabledAsync(type, user))
|
user.GetTwoFactorProvider(type) == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +253,6 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task UpdateUserKeyAndEncryptedDataV2Async(
|
public async Task UpdateUserKeyAndEncryptedDataV2Async(
|
||||||
User user,
|
User user,
|
||||||
IEnumerable<UpdateEncryptedDataForKeyRotation> updateDataActions)
|
IEnumerable<UpdateEncryptedDataForKeyRotation> updateDataActions)
|
||||||
@ -289,7 +288,6 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
|
|||||||
UnprotectData(user);
|
UnprotectData(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids)
|
public async Task<IEnumerable<User>> GetManyAsync(IEnumerable<Guid> ids)
|
||||||
{
|
{
|
||||||
using (var connection = new SqlConnection(ReadOnlyConnectionString))
|
using (var connection = new SqlConnection(ReadOnlyConnectionString))
|
||||||
@ -318,6 +316,14 @@ public class UserRepository : Repository<User, Guid>, IUserRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<UserWithCalculatedPremium?> GetCalculatedPremiumAsync(Guid userId)
|
||||||
|
{
|
||||||
|
var result = await GetManyWithCalculatedPremiumAsync([userId]);
|
||||||
|
|
||||||
|
UnprotectData(result);
|
||||||
|
return result.SingleOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ProtectDataAndSaveAsync(User user, Func<Task> saveTask)
|
private async Task ProtectDataAndSaveAsync(User user, Func<Task> saveTask)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using Bit.Core.KeyManagement.UserKey;
|
using Bit.Core.KeyManagement.UserKey;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Infrastructure.EntityFramework.Models;
|
using Bit.Infrastructure.EntityFramework.Models;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using DataModel = Bit.Core.Models.Data;
|
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -38,13 +38,13 @@ public class UserRepository : Repository<Core.Entities.User, User, Guid>, IUserR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DataModel.UserKdfInformation?> GetKdfInformationByEmailAsync(string email)
|
public async Task<UserKdfInformation?> GetKdfInformationByEmailAsync(string email)
|
||||||
{
|
{
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
return await GetDbSet(dbContext).Where(e => e.Email == email)
|
return await GetDbSet(dbContext).Where(e => e.Email == email)
|
||||||
.Select(e => new DataModel.UserKdfInformation
|
.Select(e => new UserKdfInformation
|
||||||
{
|
{
|
||||||
Kdf = e.Kdf,
|
Kdf = e.Kdf,
|
||||||
KdfIterations = e.KdfIterations,
|
KdfIterations = e.KdfIterations,
|
||||||
@ -251,13 +251,13 @@ public class UserRepository : Repository<Core.Entities.User, User, Guid>, IUserR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<DataModel.UserWithCalculatedPremium>> GetManyWithCalculatedPremiumAsync(IEnumerable<Guid> ids)
|
public async Task<IEnumerable<UserWithCalculatedPremium>> GetManyWithCalculatedPremiumAsync(IEnumerable<Guid> ids)
|
||||||
{
|
{
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
{
|
{
|
||||||
var dbContext = GetDatabaseContext(scope);
|
var dbContext = GetDatabaseContext(scope);
|
||||||
var users = dbContext.Users.Where(x => ids.Contains(x.Id));
|
var users = dbContext.Users.Where(x => ids.Contains(x.Id));
|
||||||
return await users.Select(e => new DataModel.UserWithCalculatedPremium(e)
|
return await users.Select(e => new UserWithCalculatedPremium(e)
|
||||||
{
|
{
|
||||||
HasPremiumAccess = e.Premium || dbContext.OrganizationUsers
|
HasPremiumAccess = e.Premium || dbContext.OrganizationUsers
|
||||||
.Any(ou => ou.UserId == e.Id &&
|
.Any(ou => ou.UserId == e.Id &&
|
||||||
@ -269,6 +269,12 @@ public class UserRepository : Repository<Core.Entities.User, User, Guid>, IUserR
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<UserWithCalculatedPremium?> GetCalculatedPremiumAsync(Guid id)
|
||||||
|
{
|
||||||
|
var result = await GetManyWithCalculatedPremiumAsync([id]);
|
||||||
|
return result.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
public override async Task DeleteAsync(Core.Entities.User user)
|
public override async Task DeleteAsync(Core.Entities.User user)
|
||||||
{
|
{
|
||||||
using (var scope = ServiceScopeFactory.CreateScope())
|
using (var scope = ServiceScopeFactory.CreateScope())
|
||||||
|
@ -14,6 +14,7 @@ using Bit.Core.Auth.Entities;
|
|||||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||||
using Bit.Core.Auth.Models.Data;
|
using Bit.Core.Auth.Models.Data;
|
||||||
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -40,6 +41,7 @@ public class AccountsControllerTests : IDisposable
|
|||||||
private readonly IPolicyService _policyService;
|
private readonly IPolicyService _policyService;
|
||||||
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
|
||||||
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
|
||||||
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
|
||||||
private readonly IFeatureService _featureService;
|
private readonly IFeatureService _featureService;
|
||||||
|
|
||||||
@ -64,6 +66,7 @@ public class AccountsControllerTests : IDisposable
|
|||||||
_policyService = Substitute.For<IPolicyService>();
|
_policyService = Substitute.For<IPolicyService>();
|
||||||
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
|
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
|
||||||
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
|
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
|
||||||
|
_twoFactorIsEnabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
|
||||||
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
|
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
_featureService = Substitute.For<IFeatureService>();
|
||||||
_cipherValidator =
|
_cipherValidator =
|
||||||
@ -87,6 +90,7 @@ public class AccountsControllerTests : IDisposable
|
|||||||
_setInitialMasterPasswordCommand,
|
_setInitialMasterPasswordCommand,
|
||||||
_tdeOffboardingPasswordCommand,
|
_tdeOffboardingPasswordCommand,
|
||||||
_rotateUserKeyCommand,
|
_rotateUserKeyCommand,
|
||||||
|
_twoFactorIsEnabledQuery,
|
||||||
_featureService,
|
_featureService,
|
||||||
_cipherValidator,
|
_cipherValidator,
|
||||||
_folderValidator,
|
_folderValidator,
|
||||||
|
@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
|
|||||||
using Bit.Core.AdminConsole.Models.Data.Provider;
|
using Bit.Core.AdminConsole.Models.Data.Provider;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
@ -64,6 +65,7 @@ public class SyncControllerTests
|
|||||||
{
|
{
|
||||||
// Get dependencies
|
// Get dependencies
|
||||||
var userService = sutProvider.GetDependency<IUserService>();
|
var userService = sutProvider.GetDependency<IUserService>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||||
@ -119,7 +121,7 @@ public class SyncControllerTests
|
|||||||
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
||||||
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
||||||
// Back to standard test setup
|
// Back to standard test setup
|
||||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||||
|
|
||||||
// Execute GET
|
// Execute GET
|
||||||
@ -129,7 +131,7 @@ public class SyncControllerTests
|
|||||||
// Asserts
|
// Asserts
|
||||||
// Assert that methods are called
|
// Assert that methods are called
|
||||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||||
await this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
await this.AssertMethodsCalledAsync(userService, twoFactorIsEnabledQuery, organizationUserRepository, providerUserRepository, folderRepository,
|
||||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||||
|
|
||||||
Assert.IsType<SyncResponseModel>(result);
|
Assert.IsType<SyncResponseModel>(result);
|
||||||
@ -155,6 +157,7 @@ public class SyncControllerTests
|
|||||||
{
|
{
|
||||||
// Get dependencies
|
// Get dependencies
|
||||||
var userService = sutProvider.GetDependency<IUserService>();
|
var userService = sutProvider.GetDependency<IUserService>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||||
@ -205,7 +208,7 @@ public class SyncControllerTests
|
|||||||
|
|
||||||
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
|
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
|
||||||
|
|
||||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||||
|
|
||||||
// Execute GET
|
// Execute GET
|
||||||
@ -216,7 +219,7 @@ public class SyncControllerTests
|
|||||||
// Assert that methods are called
|
// Assert that methods are called
|
||||||
|
|
||||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||||
await this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
await this.AssertMethodsCalledAsync(userService, twoFactorIsEnabledQuery, organizationUserRepository, providerUserRepository, folderRepository,
|
||||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||||
|
|
||||||
Assert.IsType<SyncResponseModel>(result);
|
Assert.IsType<SyncResponseModel>(result);
|
||||||
@ -244,6 +247,7 @@ public class SyncControllerTests
|
|||||||
{
|
{
|
||||||
// Get dependencies
|
// Get dependencies
|
||||||
var userService = sutProvider.GetDependency<IUserService>();
|
var userService = sutProvider.GetDependency<IUserService>();
|
||||||
|
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
|
||||||
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
|
||||||
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
|
||||||
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
|
||||||
@ -283,7 +287,7 @@ public class SyncControllerTests
|
|||||||
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
|
||||||
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
|
||||||
// Back to standard test setup
|
// Back to standard test setup
|
||||||
userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
|
||||||
userService.HasPremiumFromOrganization(user).Returns(false);
|
userService.HasPremiumFromOrganization(user).Returns(false);
|
||||||
|
|
||||||
// Execute GET
|
// Execute GET
|
||||||
@ -293,7 +297,7 @@ public class SyncControllerTests
|
|||||||
// Assert that methods are called
|
// Assert that methods are called
|
||||||
|
|
||||||
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
var hasEnabledOrgs = organizationUserDetails.Any(o => o.Enabled);
|
||||||
await this.AssertMethodsCalledAsync(userService, organizationUserRepository, providerUserRepository, folderRepository,
|
await this.AssertMethodsCalledAsync(userService, twoFactorIsEnabledQuery, organizationUserRepository, providerUserRepository, folderRepository,
|
||||||
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
cipherRepository, sendRepository, collectionRepository, collectionCipherRepository, hasEnabledOrgs);
|
||||||
|
|
||||||
Assert.IsType<SyncResponseModel>(result);
|
Assert.IsType<SyncResponseModel>(result);
|
||||||
@ -315,6 +319,7 @@ public class SyncControllerTests
|
|||||||
|
|
||||||
|
|
||||||
private async Task AssertMethodsCalledAsync(IUserService userService,
|
private async Task AssertMethodsCalledAsync(IUserService userService,
|
||||||
|
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
IProviderUserRepository providerUserRepository, IFolderRepository folderRepository,
|
IProviderUserRepository providerUserRepository, IFolderRepository folderRepository,
|
||||||
ICipherRepository cipherRepository, ISendRepository sendRepository,
|
ICipherRepository cipherRepository, ISendRepository sendRepository,
|
||||||
@ -356,7 +361,7 @@ public class SyncControllerTests
|
|||||||
.GetManyByUserIdAsync(default);
|
.GetManyByUserIdAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
await userService.ReceivedWithAnyArgs(1)
|
await twoFactorIsEnabledQuery.ReceivedWithAnyArgs(1)
|
||||||
.TwoFactorIsEnabledAsync(default(ITwoFactorProvidersUser));
|
.TwoFactorIsEnabledAsync(default(ITwoFactorProvidersUser));
|
||||||
await userService.ReceivedWithAnyArgs(1)
|
await userService.ReceivedWithAnyArgs(1)
|
||||||
.HasPremiumFromOrganization(default);
|
.HasPremiumFromOrganization(default);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Bit.Core.AdminConsole.Enums;
|
using Bit.Core.AdminConsole.Enums;
|
||||||
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.Billing.Enums;
|
using Bit.Core.Billing.Enums;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
@ -28,6 +29,7 @@ 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>();
|
||||||
|
|
||||||
@ -165,7 +167,7 @@ public class AcceptOrgUserCommandTests
|
|||||||
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
SetupCommonAcceptOrgUserMocks(sutProvider, user, org, orgUser, adminUserDetails);
|
||||||
|
|
||||||
// User doesn't have 2FA enabled
|
// User doesn't have 2FA enabled
|
||||||
_userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
_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 };
|
||||||
@ -646,7 +648,7 @@ public class AcceptOrgUserCommandTests
|
|||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
// User doesn't have 2FA enabled
|
// User doesn't have 2FA enabled
|
||||||
_userService.TwoFactorIsEnabledAsync(user).Returns(false);
|
_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,
|
||||||
|
@ -44,9 +44,6 @@ public abstract class BaseTokenProviderTests<T>
|
|||||||
|
|
||||||
protected virtual void SetupUserService(IUserService userService, User user)
|
protected virtual void SetupUserService(IUserService userService, User user)
|
||||||
{
|
{
|
||||||
userService
|
|
||||||
.TwoFactorProviderIsEnabledAsync(TwoFactorProviderType, user)
|
|
||||||
.Returns(true);
|
|
||||||
userService
|
userService
|
||||||
.CanAccessPremium(user)
|
.CanAccessPremium(user)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
@ -85,8 +82,6 @@ public abstract class BaseTokenProviderTests<T>
|
|||||||
var userManager = SubstituteUserManager();
|
var userManager = SubstituteUserManager();
|
||||||
MockDatabase(user, metaData);
|
MockDatabase(user, metaData);
|
||||||
|
|
||||||
AdditionalSetup(sutProvider, user);
|
|
||||||
|
|
||||||
var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user);
|
var response = await sutProvider.Sut.CanGenerateTwoFactorTokenAsync(userManager, user);
|
||||||
Assert.Equal(expectedResponse, response);
|
Assert.Equal(expectedResponse, response);
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ public class DuoUniversalTwoFactorTokenProviderTests : BaseTokenProviderTests<Du
|
|||||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
AdditionalSetup(sutProvider, user);
|
||||||
user.Premium = true;
|
user.Premium = true;
|
||||||
user.PremiumExpirationDate = DateTime.UtcNow.AddDays(1);
|
user.PremiumExpirationDate = DateTime.UtcNow.AddDays(1);
|
||||||
|
|
||||||
@ -100,6 +101,8 @@ public class DuoUniversalTwoFactorTokenProviderTests : BaseTokenProviderTests<Du
|
|||||||
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
|
AdditionalSetup(sutProvider, user);
|
||||||
|
|
||||||
user.Premium = false;
|
user.Premium = false;
|
||||||
|
|
||||||
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
||||||
|
@ -5,6 +5,7 @@ using Bit.Core.Entities;
|
|||||||
using Bit.Core.Models.Data;
|
using Bit.Core.Models.Data;
|
||||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
@ -53,6 +54,39 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task TwoFactorIsEnabledQuery_DatabaseReturnsEmpty_ResultEmpty(
|
||||||
|
SutProvider<TwoFactorIsEnabledQuery> sutProvider,
|
||||||
|
List<UserWithCalculatedPremium> usersWithCalculatedPremium)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var userIds = usersWithCalculatedPremium.Select(u => u.Id).ToList();
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.GetManyWithCalculatedPremiumAsync(Arg.Any<IEnumerable<Guid>>())
|
||||||
|
.Returns([]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData((IEnumerable<Guid>)null)]
|
||||||
|
[BitAutoData([])]
|
||||||
|
public async Task TwoFactorIsEnabledQuery_UserIdsNullorEmpty_ResultEmpty(
|
||||||
|
IEnumerable<Guid> userIds,
|
||||||
|
SutProvider<TwoFactorIsEnabledQuery> sutProvider)
|
||||||
|
{
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(userIds);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task TwoFactorIsEnabledQuery_WithNoTwoFactorEnabled_ReturnsAllTwoFactorDisabled(
|
public async Task TwoFactorIsEnabledQuery_WithNoTwoFactorEnabled_ReturnsAllTwoFactorDisabled(
|
||||||
@ -122,8 +156,11 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData("")]
|
||||||
public async Task TwoFactorIsEnabledQuery_WithNullTwoFactorProviders_ReturnsAllTwoFactorDisabled(
|
[BitAutoData("{}")]
|
||||||
|
[BitAutoData((string)null)]
|
||||||
|
public async Task TwoFactorIsEnabledQuery_WithNullOrEmptyTwoFactorProviders_ReturnsAllTwoFactorDisabled(
|
||||||
|
string twoFactorProviders,
|
||||||
SutProvider<TwoFactorIsEnabledQuery> sutProvider,
|
SutProvider<TwoFactorIsEnabledQuery> sutProvider,
|
||||||
List<UserWithCalculatedPremium> usersWithCalculatedPremium)
|
List<UserWithCalculatedPremium> usersWithCalculatedPremium)
|
||||||
{
|
{
|
||||||
@ -132,7 +169,7 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
|
|
||||||
foreach (var user in usersWithCalculatedPremium)
|
foreach (var user in usersWithCalculatedPremium)
|
||||||
{
|
{
|
||||||
user.TwoFactorProviders = null; // No two-factor providers configured
|
user.TwoFactorProviders = twoFactorProviders; // No two-factor providers configured
|
||||||
}
|
}
|
||||||
|
|
||||||
sutProvider.GetDependency<IUserRepository>()
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
@ -176,6 +213,24 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
.GetManyWithCalculatedPremiumAsync(default);
|
.GetManyWithCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task TwoFactorIsEnabledQuery_UserIdNull_ReturnsFalse(
|
||||||
|
SutProvider<TwoFactorIsEnabledQuery> sutProvider)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var user = new TestTwoFactorProviderUser
|
||||||
|
{
|
||||||
|
Id = null
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(result);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(TwoFactorProviderType.Authenticator)]
|
[BitAutoData(TwoFactorProviderType.Authenticator)]
|
||||||
[BitAutoData(TwoFactorProviderType.Email)]
|
[BitAutoData(TwoFactorProviderType.Email)]
|
||||||
@ -193,10 +248,8 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
{ freeProviderType, new TwoFactorProvider { Enabled = true } }
|
{ freeProviderType, new TwoFactorProvider { Enabled = true } }
|
||||||
};
|
};
|
||||||
|
|
||||||
user.Premium = false;
|
|
||||||
user.SetTwoFactorProviders(twoFactorProviders);
|
user.SetTwoFactorProviders(twoFactorProviders);
|
||||||
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
||||||
|
|
||||||
@ -205,7 +258,7 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IUserRepository>()
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
.DidNotReceiveWithAnyArgs()
|
.DidNotReceiveWithAnyArgs()
|
||||||
.GetManyWithCalculatedPremiumAsync(default);
|
.GetCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -230,7 +283,7 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
|
|
||||||
await sutProvider.GetDependency<IUserRepository>()
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
.DidNotReceiveWithAnyArgs()
|
.DidNotReceiveWithAnyArgs()
|
||||||
.GetManyWithCalculatedPremiumAsync(default);
|
.GetCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -252,14 +305,18 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
user.SetTwoFactorProviders(twoFactorProviders);
|
user.SetTwoFactorProviders(twoFactorProviders);
|
||||||
|
|
||||||
sutProvider.GetDependency<IUserRepository>()
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
.GetManyWithCalculatedPremiumAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(user.Id)))
|
.GetCalculatedPremiumAsync(user.Id)
|
||||||
.Returns(new List<UserWithCalculatedPremium> { user });
|
.Returns(user);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.ReceivedWithAnyArgs(1)
|
||||||
|
.GetCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -268,7 +325,7 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_WithUserPremium_ReturnsTrue(
|
public async Task TwoFactorIsEnabledQuery_WithProviderTypeRequiringPremium_WithUserPremium_ReturnsTrue(
|
||||||
TwoFactorProviderType premiumProviderType,
|
TwoFactorProviderType premiumProviderType,
|
||||||
SutProvider<TwoFactorIsEnabledQuery> sutProvider,
|
SutProvider<TwoFactorIsEnabledQuery> sutProvider,
|
||||||
User user)
|
UserWithCalculatedPremium user)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var twoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
var twoFactorProviders = new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
@ -276,9 +333,14 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
{ premiumProviderType, new TwoFactorProvider { Enabled = true } }
|
{ premiumProviderType, new TwoFactorProvider { Enabled = true } }
|
||||||
};
|
};
|
||||||
|
|
||||||
user.Premium = true;
|
user.Premium = false;
|
||||||
|
user.HasPremiumAccess = true;
|
||||||
user.SetTwoFactorProviders(twoFactorProviders);
|
user.SetTwoFactorProviders(twoFactorProviders);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.GetCalculatedPremiumAsync(user.Id)
|
||||||
|
.Returns(user);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
||||||
|
|
||||||
@ -286,8 +348,8 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
||||||
await sutProvider.GetDependency<IUserRepository>()
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
.DidNotReceiveWithAnyArgs()
|
.ReceivedWithAnyArgs(1)
|
||||||
.GetManyWithCalculatedPremiumAsync(default);
|
.GetCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -309,14 +371,18 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
user.SetTwoFactorProviders(twoFactorProviders);
|
user.SetTwoFactorProviders(twoFactorProviders);
|
||||||
|
|
||||||
sutProvider.GetDependency<IUserRepository>()
|
sutProvider.GetDependency<IUserRepository>()
|
||||||
.GetManyWithCalculatedPremiumAsync(Arg.Is<IEnumerable<Guid>>(i => i.Contains(user.Id)))
|
.GetCalculatedPremiumAsync(user.Id)
|
||||||
.Returns(new List<UserWithCalculatedPremium> { user });
|
.Returns(user);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
var result = await sutProvider.Sut.TwoFactorIsEnabledAsync(user);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.True(result);
|
Assert.True(result);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.ReceivedWithAnyArgs(1)
|
||||||
|
.GetCalculatedPremiumAsync(default);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -333,5 +399,29 @@ public class TwoFactorIsEnabledQueryTests
|
|||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.False(result);
|
Assert.False(result);
|
||||||
|
await sutProvider.GetDependency<IUserRepository>()
|
||||||
|
.DidNotReceiveWithAnyArgs()
|
||||||
|
.GetCalculatedPremiumAsync(default);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestTwoFactorProviderUser : ITwoFactorProvidersUser
|
||||||
|
{
|
||||||
|
public Guid? Id { get; set; }
|
||||||
|
public string TwoFactorProviders { get; set; }
|
||||||
|
public bool Premium { get; set; }
|
||||||
|
public Dictionary<TwoFactorProviderType, TwoFactorProvider> GetTwoFactorProviders()
|
||||||
|
{
|
||||||
|
return JsonHelpers.LegacyDeserialize<Dictionary<TwoFactorProviderType, TwoFactorProvider>>(TwoFactorProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guid? GetUserId()
|
||||||
|
{
|
||||||
|
return Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetPremium()
|
||||||
|
{
|
||||||
|
return Premium;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ using Bit.Core.AdminConsole.Services;
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Models;
|
using Bit.Core.Auth.Models;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Billing.Services;
|
using Bit.Core.Billing.Services;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
@ -324,6 +325,7 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||||
sutProvider.GetDependency<IDistributedCache>()
|
sutProvider.GetDependency<IDistributedCache>()
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -476,6 +478,9 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IOrganizationRepository>()
|
sutProvider.GetDependency<IOrganizationRepository>()
|
||||||
.GetByIdAsync(organization.Id)
|
.GetByIdAsync(organization.Id)
|
||||||
.Returns(organization);
|
.Returns(organization);
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>()
|
||||||
|
.TwoFactorIsEnabledAsync(user)
|
||||||
|
.Returns(true);
|
||||||
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
var expectedSavedProviders = JsonHelpers.LegacySerialize(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
{
|
{
|
||||||
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
[TwoFactorProviderType.Remember] = new() { Enabled = true }
|
||||||
@ -911,6 +916,7 @@ public class UserServiceTests
|
|||||||
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
sutProvider.GetDependency<IPremiumUserBillingService>(),
|
||||||
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRemoveOrganizationUserCommand>(),
|
||||||
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
sutProvider.GetDependency<IRevokeNonCompliantOrganizationUserCommand>(),
|
||||||
|
sutProvider.GetDependency<ITwoFactorIsEnabledQuery>(),
|
||||||
sutProvider.GetDependency<IDistributedCache>()
|
sutProvider.GetDependency<IDistributedCache>()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using Bit.Core.Auth.Enums;
|
using Bit.Core.Auth.Enums;
|
||||||
using Bit.Core.Auth.Identity.TokenProviders;
|
using Bit.Core.Auth.Identity.TokenProviders;
|
||||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||||
|
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
using Bit.Core.Models.Data.Organizations;
|
using Bit.Core.Models.Data.Organizations;
|
||||||
@ -27,11 +28,11 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly UserManagerTestWrapper<User> _userManager;
|
private readonly UserManagerTestWrapper<User> _userManager;
|
||||||
private readonly IOrganizationDuoUniversalTokenProvider _organizationDuoUniversalTokenProvider;
|
private readonly IOrganizationDuoUniversalTokenProvider _organizationDuoUniversalTokenProvider;
|
||||||
private readonly IFeatureService _featureService;
|
|
||||||
private readonly IApplicationCacheService _applicationCacheService;
|
private readonly IApplicationCacheService _applicationCacheService;
|
||||||
private readonly IOrganizationUserRepository _organizationUserRepository;
|
private readonly IOrganizationUserRepository _organizationUserRepository;
|
||||||
private readonly IOrganizationRepository _organizationRepository;
|
private readonly IOrganizationRepository _organizationRepository;
|
||||||
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _ssoEmail2faSessionTokenable;
|
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _ssoEmail2faSessionTokenable;
|
||||||
|
private readonly ITwoFactorIsEnabledQuery _twoFactorenabledQuery;
|
||||||
private readonly ICurrentContext _currentContext;
|
private readonly ICurrentContext _currentContext;
|
||||||
private readonly TwoFactorAuthenticationValidator _sut;
|
private readonly TwoFactorAuthenticationValidator _sut;
|
||||||
|
|
||||||
@ -40,22 +41,22 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
_userService = Substitute.For<IUserService>();
|
_userService = Substitute.For<IUserService>();
|
||||||
_userManager = SubstituteUserManager();
|
_userManager = SubstituteUserManager();
|
||||||
_organizationDuoUniversalTokenProvider = Substitute.For<IOrganizationDuoUniversalTokenProvider>();
|
_organizationDuoUniversalTokenProvider = Substitute.For<IOrganizationDuoUniversalTokenProvider>();
|
||||||
_featureService = Substitute.For<IFeatureService>();
|
|
||||||
_applicationCacheService = Substitute.For<IApplicationCacheService>();
|
_applicationCacheService = Substitute.For<IApplicationCacheService>();
|
||||||
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
|
||||||
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
_organizationRepository = Substitute.For<IOrganizationRepository>();
|
||||||
_ssoEmail2faSessionTokenable = Substitute.For<IDataProtectorTokenFactory<SsoEmail2faSessionTokenable>>();
|
_ssoEmail2faSessionTokenable = Substitute.For<IDataProtectorTokenFactory<SsoEmail2faSessionTokenable>>();
|
||||||
|
_twoFactorenabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
|
||||||
_currentContext = Substitute.For<ICurrentContext>();
|
_currentContext = Substitute.For<ICurrentContext>();
|
||||||
|
|
||||||
_sut = new TwoFactorAuthenticationValidator(
|
_sut = new TwoFactorAuthenticationValidator(
|
||||||
_userService,
|
_userService,
|
||||||
_userManager,
|
_userManager,
|
||||||
_organizationDuoUniversalTokenProvider,
|
_organizationDuoUniversalTokenProvider,
|
||||||
_featureService,
|
|
||||||
_applicationCacheService,
|
_applicationCacheService,
|
||||||
_organizationUserRepository,
|
_organizationUserRepository,
|
||||||
_organizationRepository,
|
_organizationRepository,
|
||||||
_ssoEmail2faSessionTokenable,
|
_ssoEmail2faSessionTokenable,
|
||||||
|
_twoFactorenabledQuery,
|
||||||
_currentContext);
|
_currentContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,9 +264,6 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
_userManager.SUPPORTS_TWO_FACTOR = true;
|
_userManager.SUPPORTS_TWO_FACTOR = true;
|
||||||
_userManager.TWO_FACTOR_PROVIDERS = [providerType.ToString()];
|
_userManager.TWO_FACTOR_PROVIDERS = [providerType.ToString()];
|
||||||
|
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(Arg.Any<TwoFactorProviderType>(), user)
|
|
||||||
.Returns(true);
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _sut.BuildTwoFactorResultAsync(user, null);
|
var result = await _sut.BuildTwoFactorResultAsync(user, null);
|
||||||
|
|
||||||
@ -322,9 +320,6 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(
|
|
||||||
TwoFactorProviderType.Email, user).Returns(true);
|
|
||||||
|
|
||||||
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
|
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@ -342,10 +337,8 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(
|
|
||||||
TwoFactorProviderType.Email, user).Returns(false);
|
|
||||||
|
|
||||||
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
|
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
|
||||||
|
user.TwoFactorProviders = "";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _sut.VerifyTwoFactorAsync(
|
var result = await _sut.VerifyTwoFactorAsync(
|
||||||
@ -362,9 +355,6 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(
|
|
||||||
TwoFactorProviderType.OrganizationDuo, user).Returns(false);
|
|
||||||
|
|
||||||
_userManager.TWO_FACTOR_PROVIDERS = ["OrganizationDuo"];
|
_userManager.TWO_FACTOR_PROVIDERS = ["OrganizationDuo"];
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@ -387,11 +377,9 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(
|
|
||||||
providerType, user).Returns(true);
|
|
||||||
|
|
||||||
_userManager.TWO_FACTOR_ENABLED = true;
|
_userManager.TWO_FACTOR_ENABLED = true;
|
||||||
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
|
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
|
||||||
|
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
|
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
|
||||||
@ -412,11 +400,9 @@ public class TwoFactorAuthenticationValidatorTests
|
|||||||
string token)
|
string token)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
_userService.TwoFactorProviderIsEnabledAsync(
|
|
||||||
providerType, user).Returns(true);
|
|
||||||
|
|
||||||
_userManager.TWO_FACTOR_ENABLED = true;
|
_userManager.TWO_FACTOR_ENABLED = true;
|
||||||
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
|
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
|
||||||
|
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
|
var result = await _sut.VerifyTwoFactorAsync(user, null, providerType, token);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user