1
0
mirror of https://github.com/bitwarden/server.git synced 2025-05-18 01:55:37 -05:00

Auth/pm 17111/add browser to list of approving clients (#5825)

* refactor(update-auth-approving-clients): [PM-17111] Add Browser to List of Approving Clients - Refactored how it works to fit different priorities.
This commit is contained in:
Patrick-Pimentel-Bitwarden 2025-05-16 09:50:32 -04:00 committed by GitHub
parent 67f745ebc4
commit 8d2629fe58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 103 additions and 23 deletions

View File

@ -115,6 +115,7 @@ public static class FeatureFlagKeys
public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence"; public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence";
public const string EmailVerification = "email-verification"; public const string EmailVerification = "email-verification";
public const string UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh"; public const string UnauthenticatedExtensionUIRefresh = "unauth-ui-refresh";
public const string BrowserExtensionLoginApproval = "pm-14938-browser-extension-login-approvals";
public const string SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor"; public const string SetInitialPasswordRefactor = "pm-16117-set-initial-password-refactor";
public const string ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor"; public const string ChangeExistingPasswordRefactor = "pm-16117-change-existing-password-refactor";
public const string RecoveryCodeLogin = "pm-17128-recovery-code-login"; public const string RecoveryCodeLogin = "pm-17128-recovery-code-login";

View File

@ -22,6 +22,7 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ILoginApprovingClientTypes _loginApprovingClientTypes;
private UserDecryptionOptions _options = new UserDecryptionOptions(); private UserDecryptionOptions _options = new UserDecryptionOptions();
private User? _user; private User? _user;
@ -31,12 +32,14 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder
public UserDecryptionOptionsBuilder( public UserDecryptionOptionsBuilder(
ICurrentContext currentContext, ICurrentContext currentContext,
IDeviceRepository deviceRepository, IDeviceRepository deviceRepository,
IOrganizationUserRepository organizationUserRepository IOrganizationUserRepository organizationUserRepository,
ILoginApprovingClientTypes loginApprovingClientTypes
) )
{ {
_currentContext = currentContext; _currentContext = currentContext;
_deviceRepository = deviceRepository; _deviceRepository = deviceRepository;
_organizationUserRepository = organizationUserRepository; _organizationUserRepository = organizationUserRepository;
_loginApprovingClientTypes = loginApprovingClientTypes;
} }
public IUserDecryptionOptionsBuilder ForUser(User user) public IUserDecryptionOptionsBuilder ForUser(User user)
@ -119,8 +122,7 @@ public class UserDecryptionOptionsBuilder : IUserDecryptionOptionsBuilder
// Checks if the current user has any devices that are capable of approving login with device requests except for // Checks if the current user has any devices that are capable of approving login with device requests except for
// their current device. // their current device.
// NOTE: this doesn't check for if the users have configured the devices to be capable of approving requests as that is a client side setting. // NOTE: this doesn't check for if the users have configured the devices to be capable of approving requests as that is a client side setting.
hasLoginApprovingDevice = allDevices hasLoginApprovingDevice = allDevices.Any(d => d.Identifier != _device.Identifier && _loginApprovingClientTypes.TypesThatCanApprove.Contains(DeviceTypes.ToClientType(d.Type)));
.Any(d => d.Identifier != _device.Identifier && LoginApprovingClientTypes.TypesThatCanApprove.Contains(DeviceTypes.ToClientType(d.Type)));
} }
// Determine if user has manage reset password permission as post sso logic requires it for forcing users with this permission to set a MP // Determine if user has manage reset password permission as post sso logic requires it for forcing users with this permission to set a MP

View File

@ -1,22 +1,39 @@
using Bit.Core.Enums; using Bit.Core;
using Bit.Core.Enums;
using Bit.Core.Services;
namespace Bit.Identity.Utilities; namespace Bit.Identity.Utilities;
public static class LoginApprovingClientTypes public interface ILoginApprovingClientTypes
{ {
private static readonly IReadOnlyCollection<ClientType> _clientTypesThatCanApprove; IReadOnlyCollection<ClientType> TypesThatCanApprove { get; }
}
static LoginApprovingClientTypes() public class LoginApprovingClientTypes : ILoginApprovingClientTypes
{
public LoginApprovingClientTypes(
IFeatureService featureService)
{ {
var clientTypes = new List<ClientType> if (featureService.IsEnabled(FeatureFlagKeys.BrowserExtensionLoginApproval))
{
TypesThatCanApprove = new List<ClientType>
{ {
ClientType.Desktop, ClientType.Desktop,
ClientType.Mobile, ClientType.Mobile,
ClientType.Web, ClientType.Web,
ClientType.Browser, ClientType.Browser,
}; };
_clientTypesThatCanApprove = clientTypes.AsReadOnly(); }
else
{
TypesThatCanApprove = new List<ClientType>
{
ClientType.Desktop,
ClientType.Mobile,
ClientType.Web,
};
}
} }
public static IReadOnlyCollection<ClientType> TypesThatCanApprove => _clientTypesThatCanApprove; public IReadOnlyCollection<ClientType> TypesThatCanApprove { get; }
} }

View File

@ -23,6 +23,7 @@ public static class ServiceCollectionExtensions
services.AddTransient<IUserDecryptionOptionsBuilder, UserDecryptionOptionsBuilder>(); services.AddTransient<IUserDecryptionOptionsBuilder, UserDecryptionOptionsBuilder>();
services.AddTransient<IDeviceValidator, DeviceValidator>(); services.AddTransient<IDeviceValidator, DeviceValidator>();
services.AddTransient<ITwoFactorAuthenticationValidator, TwoFactorAuthenticationValidator>(); services.AddTransient<ITwoFactorAuthenticationValidator, TwoFactorAuthenticationValidator>();
services.AddTransient<ILoginApprovingClientTypes, LoginApprovingClientTypes>();
var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity); var issuerUri = new Uri(globalSettings.BaseServiceUri.InternalIdentity);
var identityServerBuilder = services var identityServerBuilder = services

View File

@ -6,6 +6,7 @@ using Bit.Core.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Identity.IdentityServer; using Bit.Identity.IdentityServer;
using Bit.Identity.Utilities;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute; using NSubstitute;
using Xunit; using Xunit;
@ -17,6 +18,7 @@ public class UserDecryptionOptionsBuilderTests
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IDeviceRepository _deviceRepository; private readonly IDeviceRepository _deviceRepository;
private readonly IOrganizationUserRepository _organizationUserRepository; private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly ILoginApprovingClientTypes _loginApprovingClientTypes;
private readonly UserDecryptionOptionsBuilder _builder; private readonly UserDecryptionOptionsBuilder _builder;
public UserDecryptionOptionsBuilderTests() public UserDecryptionOptionsBuilderTests()
@ -24,7 +26,8 @@ public class UserDecryptionOptionsBuilderTests
_currentContext = Substitute.For<ICurrentContext>(); _currentContext = Substitute.For<ICurrentContext>();
_deviceRepository = Substitute.For<IDeviceRepository>(); _deviceRepository = Substitute.For<IDeviceRepository>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>(); _organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_builder = new UserDecryptionOptionsBuilder(_currentContext, _deviceRepository, _organizationUserRepository); _loginApprovingClientTypes = Substitute.For<ILoginApprovingClientTypes>();
_builder = new UserDecryptionOptionsBuilder(_currentContext, _deviceRepository, _organizationUserRepository, _loginApprovingClientTypes);
} }
[Theory] [Theory]
@ -120,17 +123,17 @@ public class UserDecryptionOptionsBuilderTests
[BitAutoData(DeviceType.SafariBrowser)] [BitAutoData(DeviceType.SafariBrowser)]
[BitAutoData(DeviceType.VivaldiBrowser)] [BitAutoData(DeviceType.VivaldiBrowser)]
[BitAutoData(DeviceType.UnknownBrowser)] [BitAutoData(DeviceType.UnknownBrowser)]
// Extension
[BitAutoData(DeviceType.ChromeExtension)]
[BitAutoData(DeviceType.FirefoxExtension)]
[BitAutoData(DeviceType.OperaExtension)]
[BitAutoData(DeviceType.EdgeExtension)]
[BitAutoData(DeviceType.VivaldiExtension)]
[BitAutoData(DeviceType.SafariExtension)]
public async Task Build_WhenHasLoginApprovingDevice_ShouldApprovingDeviceTrue( public async Task Build_WhenHasLoginApprovingDevice_ShouldApprovingDeviceTrue(
DeviceType deviceType, DeviceType deviceType,
SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice) SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice)
{ {
_loginApprovingClientTypes.TypesThatCanApprove.Returns(new List<ClientType>
{
ClientType.Desktop,
ClientType.Mobile,
ClientType.Web,
});
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption; configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
ssoConfig.Data = configurationData.Serialize(); ssoConfig.Data = configurationData.Serialize();
approvingDevice.Type = deviceType; approvingDevice.Type = deviceType;
@ -142,9 +145,65 @@ public class UserDecryptionOptionsBuilderTests
} }
[Theory] [Theory]
// Desktop
[BitAutoData(DeviceType.LinuxDesktop)]
[BitAutoData(DeviceType.MacOsDesktop)]
[BitAutoData(DeviceType.WindowsDesktop)]
[BitAutoData(DeviceType.UWP)]
// Mobile
[BitAutoData(DeviceType.Android)]
[BitAutoData(DeviceType.iOS)]
[BitAutoData(DeviceType.AndroidAmazon)]
// Web
[BitAutoData(DeviceType.ChromeBrowser)]
[BitAutoData(DeviceType.FirefoxBrowser)]
[BitAutoData(DeviceType.OperaBrowser)]
[BitAutoData(DeviceType.EdgeBrowser)]
[BitAutoData(DeviceType.IEBrowser)]
[BitAutoData(DeviceType.SafariBrowser)]
[BitAutoData(DeviceType.VivaldiBrowser)]
[BitAutoData(DeviceType.UnknownBrowser)]
// Extension
[BitAutoData(DeviceType.ChromeExtension)]
[BitAutoData(DeviceType.FirefoxExtension)]
[BitAutoData(DeviceType.OperaExtension)]
[BitAutoData(DeviceType.EdgeExtension)]
[BitAutoData(DeviceType.VivaldiExtension)]
[BitAutoData(DeviceType.SafariExtension)]
public async Task Build_WhenHasLoginApprovingDeviceFeatureFlag_ShouldApprovingDeviceTrue(
DeviceType deviceType,
SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice)
{
_loginApprovingClientTypes.TypesThatCanApprove.Returns(new List<ClientType>
{
ClientType.Desktop,
ClientType.Mobile,
ClientType.Web,
ClientType.Browser,
});
configurationData.MemberDecryptionType = MemberDecryptionType.TrustedDeviceEncryption;
ssoConfig.Data = configurationData.Serialize();
approvingDevice.Type = deviceType;
_deviceRepository.GetManyByUserIdAsync(user.Id).Returns(new Device[] { approvingDevice });
var result = await _builder.ForUser(user).WithSso(ssoConfig).WithDevice(device).BuildAsync();
Assert.True(result.TrustedDeviceOption?.HasLoginApprovingDevice);
}
[Theory]
// CLI
[BitAutoData(DeviceType.WindowsCLI)] [BitAutoData(DeviceType.WindowsCLI)]
[BitAutoData(DeviceType.MacOsCLI)] [BitAutoData(DeviceType.MacOsCLI)]
[BitAutoData(DeviceType.LinuxCLI)] [BitAutoData(DeviceType.LinuxCLI)]
// Extension
[BitAutoData(DeviceType.ChromeExtension)]
[BitAutoData(DeviceType.FirefoxExtension)]
[BitAutoData(DeviceType.OperaExtension)]
[BitAutoData(DeviceType.EdgeExtension)]
[BitAutoData(DeviceType.VivaldiExtension)]
[BitAutoData(DeviceType.SafariExtension)]
public async Task Build_WhenHasLoginApprovingDevice_ShouldApprovingDeviceFalse( public async Task Build_WhenHasLoginApprovingDevice_ShouldApprovingDeviceFalse(
DeviceType deviceType, DeviceType deviceType,
SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice) SsoConfig ssoConfig, SsoConfigurationData configurationData, User user, Device device, Device approvingDevice)