1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -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:
Ike
2025-05-09 11:39:57 -04:00
committed by GitHub
parent 80e7a0afd6
commit 3f95513d11
31 changed files with 372 additions and 259 deletions

View File

@ -14,6 +14,7 @@ using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Api.Request.Accounts;
using Bit.Core.Auth.Models.Data;
using Bit.Core.Auth.UserFeatures.TdeOffboardingPassword.Interfaces;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Auth.UserFeatures.UserMasterPassword.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
@ -40,6 +41,7 @@ public class AccountsControllerTests : IDisposable
private readonly IPolicyService _policyService;
private readonly ISetInitialMasterPasswordCommand _setInitialMasterPasswordCommand;
private readonly IRotateUserKeyCommand _rotateUserKeyCommand;
private readonly ITwoFactorIsEnabledQuery _twoFactorIsEnabledQuery;
private readonly ITdeOffboardingPasswordCommand _tdeOffboardingPasswordCommand;
private readonly IFeatureService _featureService;
@ -64,6 +66,7 @@ public class AccountsControllerTests : IDisposable
_policyService = Substitute.For<IPolicyService>();
_setInitialMasterPasswordCommand = Substitute.For<ISetInitialMasterPasswordCommand>();
_rotateUserKeyCommand = Substitute.For<IRotateUserKeyCommand>();
_twoFactorIsEnabledQuery = Substitute.For<ITwoFactorIsEnabledQuery>();
_tdeOffboardingPasswordCommand = Substitute.For<ITdeOffboardingPasswordCommand>();
_featureService = Substitute.For<IFeatureService>();
_cipherValidator =
@ -87,6 +90,7 @@ public class AccountsControllerTests : IDisposable
_setInitialMasterPasswordCommand,
_tdeOffboardingPasswordCommand,
_rotateUserKeyCommand,
_twoFactorIsEnabledQuery,
_featureService,
_cipherValidator,
_folderValidator,

View File

@ -8,6 +8,7 @@ using Bit.Core.AdminConsole.Enums.Provider;
using Bit.Core.AdminConsole.Models.Data.Provider;
using Bit.Core.AdminConsole.Repositories;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.UserFeatures.TwoFactorAuth.Interfaces;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
@ -64,6 +65,7 @@ public class SyncControllerTests
{
// Get dependencies
var userService = sutProvider.GetDependency<IUserService>();
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
@ -119,7 +121,7 @@ public class SyncControllerTests
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
// Back to standard test setup
userService.TwoFactorIsEnabledAsync(user).Returns(false);
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
userService.HasPremiumFromOrganization(user).Returns(false);
// Execute GET
@ -129,7 +131,7 @@ public class SyncControllerTests
// Asserts
// Assert that methods are called
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);
Assert.IsType<SyncResponseModel>(result);
@ -155,6 +157,7 @@ public class SyncControllerTests
{
// Get dependencies
var userService = sutProvider.GetDependency<IUserService>();
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
@ -205,7 +208,7 @@ public class SyncControllerTests
policyRepository.GetManyByUserIdAsync(user.Id).Returns(policies);
userService.TwoFactorIsEnabledAsync(user).Returns(false);
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
userService.HasPremiumFromOrganization(user).Returns(false);
// Execute GET
@ -216,7 +219,7 @@ public class SyncControllerTests
// Assert that methods are called
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);
Assert.IsType<SyncResponseModel>(result);
@ -244,6 +247,7 @@ public class SyncControllerTests
{
// Get dependencies
var userService = sutProvider.GetDependency<IUserService>();
var twoFactorIsEnabledQuery = sutProvider.GetDependency<ITwoFactorIsEnabledQuery>();
var organizationUserRepository = sutProvider.GetDependency<IOrganizationUserRepository>();
var providerUserRepository = sutProvider.GetDependency<IProviderUserRepository>();
var folderRepository = sutProvider.GetDependency<IFolderRepository>();
@ -283,7 +287,7 @@ public class SyncControllerTests
collectionRepository.GetManyByUserIdAsync(user.Id).Returns(collections);
collectionCipherRepository.GetManyByUserIdAsync(user.Id).Returns(new List<CollectionCipher>());
// Back to standard test setup
userService.TwoFactorIsEnabledAsync(user).Returns(false);
twoFactorIsEnabledQuery.TwoFactorIsEnabledAsync(user).Returns(false);
userService.HasPremiumFromOrganization(user).Returns(false);
// Execute GET
@ -293,7 +297,7 @@ public class SyncControllerTests
// Assert that methods are called
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);
Assert.IsType<SyncResponseModel>(result);
@ -315,6 +319,7 @@ public class SyncControllerTests
private async Task AssertMethodsCalledAsync(IUserService userService,
ITwoFactorIsEnabledQuery twoFactorIsEnabledQuery,
IOrganizationUserRepository organizationUserRepository,
IProviderUserRepository providerUserRepository, IFolderRepository folderRepository,
ICipherRepository cipherRepository, ISendRepository sendRepository,
@ -356,7 +361,7 @@ public class SyncControllerTests
.GetManyByUserIdAsync(default);
}
await userService.ReceivedWithAnyArgs(1)
await twoFactorIsEnabledQuery.ReceivedWithAnyArgs(1)
.TwoFactorIsEnabledAsync(default(ITwoFactorProvidersUser));
await userService.ReceivedWithAnyArgs(1)
.HasPremiumFromOrganization(default);