diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index 35f778cb3..558d75dbf 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -17,9 +17,7 @@ using Bit.Core.Utilities; using Fido2NetLib; using Fido2NetLib.Objects; using Microsoft.AspNetCore.DataProtection; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using File = System.IO.File; @@ -50,11 +48,10 @@ namespace Bit.Core.Services private readonly IReferenceEventService _referenceEventService; private readonly IFido2 _fido2; private readonly ICurrentContext _currentContext; - private readonly GlobalSettings _globalSettings; + private readonly IGlobalSettings _globalSettings; private readonly IOrganizationService _organizationService; private readonly IProviderUserRepository _providerUserRepository; private readonly IDeviceRepository _deviceRepository; - private readonly IWebHostEnvironment _environment; public UserService( IUserRepository userRepository, @@ -81,11 +78,10 @@ namespace Bit.Core.Services IReferenceEventService referenceEventService, IFido2 fido2, ICurrentContext currentContext, - GlobalSettings globalSettings, + IGlobalSettings globalSettings, IOrganizationService organizationService, IProviderUserRepository providerUserRepository, - IDeviceRepository deviceRepository, - IWebHostEnvironment environment) + IDeviceRepository deviceRepository) : base( store, optionsAccessor, @@ -121,7 +117,6 @@ namespace Bit.Core.Services _organizationService = organizationService; _providerUserRepository = providerUserRepository; _deviceRepository = deviceRepository; - _environment = environment; } public Guid? GetProperUserId(ClaimsPrincipal principal) @@ -1422,9 +1417,9 @@ namespace Bit.Core.Services public async Task Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType) { - return user.EmailVerified + return _globalSettings.TwoFactorAuth.EmailOnNewDeviceLogin + && user.EmailVerified && grantType != "authorization_code" - && !_environment.IsDevelopment() && await IsNewDeviceAndNotTheFirstOneAsync(user, deviceIdentifier); } diff --git a/src/Core/Settings/GlobalSettings.cs b/src/Core/Settings/GlobalSettings.cs index 0778ed88f..8cf3a99c2 100644 --- a/src/Core/Settings/GlobalSettings.cs +++ b/src/Core/Settings/GlobalSettings.cs @@ -69,6 +69,7 @@ namespace Bit.Core.Settings public virtual AppleIapSettings AppleIap { get; set; } = new AppleIapSettings(); public virtual SsoSettings Sso { get; set; } = new SsoSettings(); public virtual StripeSettings Stripe { get; set; } = new StripeSettings(); + public virtual ITwoFactorAuthSettings TwoFactorAuth { get; set; } = new TwoFactorAuthSettings(); public string BuildExternalUri(string explicitValue, string name) { @@ -480,5 +481,10 @@ namespace Bit.Core.Settings public string ApiKey { get; set; } public int MaxNetworkRetries { get; set; } = 2; } + + public class TwoFactorAuthSettings : ITwoFactorAuthSettings + { + public bool EmailOnNewDeviceLogin { get; set; } = true; + } } } diff --git a/src/Core/Settings/IGlobalSettings.cs b/src/Core/Settings/IGlobalSettings.cs index 0d5710b10..8ccafdc1b 100644 --- a/src/Core/Settings/IGlobalSettings.cs +++ b/src/Core/Settings/IGlobalSettings.cs @@ -1,6 +1,4 @@ -using static Bit.Core.Settings.GlobalSettings; - -namespace Bit.Core.Settings +namespace Bit.Core.Settings { public interface IGlobalSettings { @@ -10,9 +8,11 @@ namespace Bit.Core.Settings string LicenseDirectory { get; set; } string LicenseCertificatePassword { get; set; } int OrganizationInviteExpirationHours { get; set; } + bool DisableUserRegistration { get; set; } IInstallationSettings Installation { get; set; } IFileStorageSettings Attachment { get; set; } IConnectionStringSettings Storage { get; set; } IBaseServiceUriSettings BaseServiceUri { get; set; } + ITwoFactorAuthSettings TwoFactorAuth { get; set; } } } diff --git a/src/Core/Settings/ITwoFactorAuthSettings.cs b/src/Core/Settings/ITwoFactorAuthSettings.cs new file mode 100644 index 000000000..06dced0f8 --- /dev/null +++ b/src/Core/Settings/ITwoFactorAuthSettings.cs @@ -0,0 +1,7 @@ +namespace Bit.Core.Settings +{ + public interface ITwoFactorAuthSettings + { + bool EmailOnNewDeviceLogin { get; set; } + } +} diff --git a/test/Core.Test/Services/UserServiceTests.cs b/test/Core.Test/Services/UserServiceTests.cs index 6b4bd3a03..85e19e880 100644 --- a/test/Core.Test/Services/UserServiceTests.cs +++ b/test/Core.Test/Services/UserServiceTests.cs @@ -12,9 +12,7 @@ using Bit.Core.Services; using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.Helpers; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Hosting; using NSubstitute; using NSubstitute.ReceivedExtensions; using Xunit; @@ -38,8 +36,8 @@ namespace Bit.Core.Test.Services user.EmailVerified = true; user.Email = userLicense.Email; - sutProvider.GetDependency().SelfHosted = true; - sutProvider.GetDependency().LicenseDirectory = tempDir.Directory; + sutProvider.GetDependency().SelfHosted = true; + sutProvider.GetDependency().LicenseDirectory = tempDir.Directory; sutProvider.GetDependency() .VerifyLicense(userLicense) .Returns(true); @@ -175,6 +173,9 @@ namespace Bit.Core.Test.Services new Device { Identifier = deviceIdInRepo } })); + + sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true); + Assert.True(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); } @@ -246,7 +247,7 @@ namespace Bit.Core.Test.Services } [Theory, CustomAutoData(typeof(SutProviderCustomization))] - public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Environment_Is_Development(SutProvider sutProvider, User user) + public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider sutProvider, User user) { user.Id = Guid.NewGuid(); user.EmailVerified = true; @@ -260,9 +261,7 @@ namespace Bit.Core.Test.Services new Device { Identifier = deviceIdInRepo } })); - sutProvider.GetDependency() - .EnvironmentName - .Returns(Environments.Development); + sutProvider.GetDependency().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false); Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password")); }