2024-12-12 18:08:11 +01:00
|
|
|
|
using Bit.Core;
|
|
|
|
|
using Bit.Core.Context;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
using Bit.Core.Entities;
|
|
|
|
|
using Bit.Core.Enums;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
using Bit.Core.Models.Api;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
using Bit.Core.Repositories;
|
|
|
|
|
using Bit.Core.Services;
|
|
|
|
|
using Bit.Core.Settings;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
using Bit.Identity.IdentityServer;
|
2024-10-24 19:41:25 +02:00
|
|
|
|
using Bit.Identity.IdentityServer.RequestValidators;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
|
|
|
using Duende.IdentityServer.Validation;
|
2024-12-17 17:59:39 +01:00
|
|
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
using NSubstitute;
|
|
|
|
|
using Xunit;
|
|
|
|
|
using AuthFixtures = Bit.Identity.Test.AutoFixture;
|
|
|
|
|
|
|
|
|
|
namespace Bit.Identity.Test.IdentityServer;
|
|
|
|
|
|
|
|
|
|
public class DeviceValidatorTests
|
|
|
|
|
{
|
|
|
|
|
private readonly IDeviceService _deviceService;
|
|
|
|
|
private readonly IDeviceRepository _deviceRepository;
|
|
|
|
|
private readonly GlobalSettings _globalSettings;
|
|
|
|
|
private readonly IMailService _mailService;
|
|
|
|
|
private readonly ICurrentContext _currentContext;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
private readonly IUserService _userService;
|
2024-12-17 17:59:39 +01:00
|
|
|
|
private readonly IDistributedCache _distributedCache;
|
|
|
|
|
private readonly Logger<DeviceValidator> _logger;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
private readonly IFeatureService _featureService;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
private readonly DeviceValidator _sut;
|
|
|
|
|
|
|
|
|
|
public DeviceValidatorTests()
|
|
|
|
|
{
|
|
|
|
|
_deviceService = Substitute.For<IDeviceService>();
|
|
|
|
|
_deviceRepository = Substitute.For<IDeviceRepository>();
|
|
|
|
|
_globalSettings = new GlobalSettings();
|
|
|
|
|
_mailService = Substitute.For<IMailService>();
|
|
|
|
|
_currentContext = Substitute.For<ICurrentContext>();
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_userService = Substitute.For<IUserService>();
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache = Substitute.For<IDistributedCache>();
|
|
|
|
|
_logger = new Logger<DeviceValidator>(Substitute.For<ILoggerFactory>());
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_featureService = Substitute.For<IFeatureService>();
|
2024-10-11 02:26:17 +02:00
|
|
|
|
_sut = new DeviceValidator(
|
|
|
|
|
_deviceService,
|
|
|
|
|
_deviceRepository,
|
|
|
|
|
_globalSettings,
|
|
|
|
|
_mailService,
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_currentContext,
|
|
|
|
|
_userService,
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache,
|
|
|
|
|
_logger,
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_featureService);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void GetKnownDeviceAsync_UserNull_ReturnsFalse(
|
|
|
|
|
Device device)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-17 17:59:39 +01:00
|
|
|
|
// AutoData arranges
|
2024-12-12 18:08:11 +01:00
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.GetKnownDeviceAsync(null, device);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.Null(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void GetKnownDeviceAsync_DeviceNull_ReturnsFalse(
|
2024-10-11 02:26:17 +02:00
|
|
|
|
User user)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
// Device raw data is null which will cause the device to be null
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.GetKnownDeviceAsync(user, null);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Assert.Null(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void GetKnownDeviceAsync_DeviceNotInDatabase_ReturnsFalse(
|
|
|
|
|
User user,
|
|
|
|
|
Device device)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.GetKnownDeviceAsync(user, device);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.Null(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void GetKnownDeviceAsync_UserAndDeviceValid_ReturnsTrue(
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
|
|
|
|
|
User user,
|
|
|
|
|
Device device)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
|
|
|
|
.Returns(device);
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.GetKnownDeviceAsync(user, device);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.NotNull(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[BitAutoData("not null", "Android", "")]
|
|
|
|
|
[BitAutoData("not null", "", "not null")]
|
|
|
|
|
[BitAutoData("", "Android", "not null")]
|
|
|
|
|
public void GetDeviceFromRequest_RawDeviceInfoNull_ReturnsNull(
|
|
|
|
|
string deviceIdentifier,
|
|
|
|
|
string deviceType,
|
|
|
|
|
string deviceName,
|
2024-10-11 02:26:17 +02:00
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
request.Raw["DeviceIdentifier"] = deviceIdentifier;
|
|
|
|
|
request.Raw["DeviceType"] = deviceType;
|
|
|
|
|
request.Raw["DeviceName"] = deviceName;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = DeviceValidator.GetDeviceFromRequest(request);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Assert.Null(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public void GetDeviceFromRequest_RawDeviceInfoValid_ReturnsDevice(
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = DeviceValidator.GetDeviceFromRequest(request);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.NotNull(result);
|
|
|
|
|
Assert.Equal("DeviceIdentifier", result.Identifier);
|
|
|
|
|
Assert.Equal("DeviceName", result.Name);
|
|
|
|
|
Assert.Equal(DeviceType.Android, result.Type);
|
|
|
|
|
Assert.Equal("DevicePushToken", result.PushToken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_DeviceNull_ContextModified_ReturnsFalse(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
context.Device = null;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
// Act
|
|
|
|
|
Assert.NotNull(context.User);
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
|
|
|
|
Assert.False(result);
|
|
|
|
|
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
|
|
|
|
var expectedErrorModel = new ErrorResponseModel("no device information provided");
|
|
|
|
|
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
|
|
|
|
Assert.Equal(expectedErrorModel.Message, actualResponse.Message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_RequestDeviceKnown_ContextDeviceModified_ReturnsTrue(
|
|
|
|
|
Device device,
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
context.Device = null;
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
|
|
|
|
.Returns(device);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.NotNull(context.Device);
|
|
|
|
|
Assert.Equal(context.Device, device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_ContextDeviceKnown_ContextDeviceModified_ReturnsTrue(
|
|
|
|
|
Device databaseDevice,
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(Arg.Any<string>(), Arg.Any<Guid>())
|
|
|
|
|
.Returns(databaseDevice);
|
|
|
|
|
// we want to show that the context device is updated when the device is known
|
|
|
|
|
Assert.NotEqual(context.Device, databaseDevice);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.Equal(context.Device, databaseDevice);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
2025-01-31 16:46:09 +01:00
|
|
|
|
public async void ValidateRequestDeviceAsync_ExistingUserNewDeviceLogin_SendNewDeviceLoginEmail_ReturnsTrue(
|
2024-12-12 18:08:11 +01:00
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
AddValidDeviceToRequest(request);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
_globalSettings.DisableEmailNewDevice = false;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
// set user creation to more than 10 minutes ago
|
|
|
|
|
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(11);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
await _mailService.Received(1).SendNewDeviceLoggedInEmail(
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
|
|
|
|
Assert.True(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
2025-01-31 16:46:09 +01:00
|
|
|
|
public async void ValidateRequestDeviceAsync_NewUserNewDeviceLogin_DoesNotSendNewDeviceLoginEmail_ReturnsTrue(
|
2024-12-12 18:08:11 +01:00
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_globalSettings.DisableEmailNewDevice = false;
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
// set user creation to less than 10 minutes ago
|
|
|
|
|
context.User.CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(9);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
await _mailService.Received(0).SendNewDeviceLoggedInEmail(
|
|
|
|
|
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
}
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
2025-01-31 16:46:09 +01:00
|
|
|
|
public async void ValidateRequestDeviceAsynce_DisableNewDeviceLoginEmailTrue_DoesNotSendNewDeviceEmail_ReturnsTrue(
|
2024-12-12 18:08:11 +01:00
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
AddValidDeviceToRequest(request);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
_globalSettings.DisableEmailNewDevice = true;
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
await _mailService.Received(0).SendNewDeviceLoggedInEmail(
|
|
|
|
|
Arg.Any<string>(), Arg.Any<string>(), Arg.Any<DateTime>(), Arg.Any<string>());
|
|
|
|
|
Assert.True(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[BitAutoData("webauthn")]
|
|
|
|
|
[BitAutoData("refresh_token")]
|
|
|
|
|
[BitAutoData("authorization_code")]
|
|
|
|
|
[BitAutoData("client_credentials")]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_GrantTypeNotPassword_SavesDevice_ReturnsTrue(
|
|
|
|
|
string grantType,
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
|
|
|
|
.Returns(true);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
request.GrantType = grantType;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
Assert.True(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_IsAuthRequest_SavesDevice_ReturnsTrue(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
|
|
|
|
.Returns(true);
|
|
|
|
|
|
|
|
|
|
request.Raw.Add("AuthRequest", "authRequest");
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
Assert.True(result);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_TwoFactorRequired_SavesDevice_ReturnsTrue(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
|
|
|
|
.Returns(true);
|
|
|
|
|
|
|
|
|
|
context.TwoFactorRequired = true;
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void ValidateRequestDeviceAsync_SsoRequired_SavesDevice_ReturnsTrue(
|
|
|
|
|
CustomValidatorRequestContext context,
|
2024-10-11 02:26:17 +02:00
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
AddValidDeviceToRequest(request);
|
|
|
|
|
_deviceRepository.GetByIdentifierAsync(context.Device.Identifier, context.User.Id)
|
|
|
|
|
.Returns(null as Device);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification)
|
|
|
|
|
.Returns(true);
|
|
|
|
|
|
|
|
|
|
context.SsoRequired = true;
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_UserNull_ContextModified_ReturnsInvalidUser(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
|
|
|
|
|
|
|
|
|
context.User = null;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
2024-10-11 02:26:17 +02:00
|
|
|
|
Assert.False(result);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
|
|
|
|
// PM-13340: The error message should be "invalid user" instead of "no device information provided"
|
|
|
|
|
var expectedErrorMessage = "no device information provided";
|
|
|
|
|
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
|
|
|
|
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-08 16:31:24 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_VerifyDevicesFalse_ReturnsSuccess(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
|
|
|
|
context.User.VerifyDevices = false;
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _userService.Received(0).SendOTPAsync(context.User);
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.Equal(context.User.Id, context.Device.UserId);
|
|
|
|
|
Assert.NotNull(context.Device);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-17 17:59:39 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_UserHasCacheValue_ReturnsSuccess(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
|
|
|
|
_distributedCache.GetAsync(Arg.Any<string>()).Returns([1]);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _userService.Received(0).SendOTPAsync(context.User);
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.Equal(context.User.Id, context.Device.UserId);
|
|
|
|
|
Assert.NotNull(context.Device);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpValid_ReturnsSuccess(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
|
|
|
|
|
var newDeviceOtp = "123456";
|
|
|
|
|
request.Raw.Add("NewDeviceOtp", newDeviceOtp);
|
|
|
|
|
|
|
|
|
|
_userService.VerifyOTPAsync(context.User, newDeviceOtp).Returns(true);
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _userService.Received(0).SendOTPAsync(context.User);
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.Equal(context.User.Id, context.Device.UserId);
|
|
|
|
|
Assert.NotNull(context.Device);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory]
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[BitAutoData("")]
|
|
|
|
|
[BitAutoData("123456")]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpInvalid_ReturnsInvalidNewDeviceOtp(
|
|
|
|
|
string newDeviceOtp,
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
|
|
|
|
|
request.Raw.Add("NewDeviceOtp", newDeviceOtp);
|
|
|
|
|
|
|
|
|
|
_userService.VerifyOTPAsync(context.User, newDeviceOtp).Returns(false);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _userService.DidNotReceive().SendOTPAsync(Arg.Any<User>());
|
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
2024-10-11 02:26:17 +02:00
|
|
|
|
Assert.False(result);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
|
|
|
|
var expectedErrorMessage = "invalid new device otp";
|
|
|
|
|
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
|
|
|
|
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_UserHasNoDevices_ReturnsSuccess(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache.GetAsync(Arg.Any<string>()).Returns([1]);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
_deviceRepository.GetManyByUserIdAsync(context.User.Id).Returns([]);
|
|
|
|
|
|
2024-10-11 02:26:17 +02:00
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
2024-12-12 18:08:11 +01:00
|
|
|
|
await _userService.Received(0).VerifyOTPAsync(Arg.Any<User>(), Arg.Any<string>());
|
|
|
|
|
await _userService.Received(0).SendOTPAsync(Arg.Any<User>());
|
|
|
|
|
await _deviceService.Received(1).SaveAsync(context.Device);
|
|
|
|
|
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
Assert.False(context.CustomResponse.ContainsKey("ErrorModel"));
|
|
|
|
|
Assert.Equal(context.User.Id, context.Device.UserId);
|
|
|
|
|
Assert.NotNull(context.Device);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public async void HandleNewDeviceVerificationAsync_NewDeviceOtpEmpty_UserHasDevices_ReturnsNewDeviceVerificationRequired(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
ArrangeForHandleNewDeviceVerificationTest(context, request);
|
|
|
|
|
_featureService.IsEnabled(FeatureFlagKeys.NewDeviceVerification).Returns(true);
|
|
|
|
|
_globalSettings.EnableNewDeviceVerification = true;
|
|
|
|
|
_deviceRepository.GetManyByUserIdAsync(context.User.Id).Returns([new Device()]);
|
2024-12-17 17:59:39 +01:00
|
|
|
|
_distributedCache.GetAsync(Arg.Any<string>()).Returns(null as byte[]);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = await _sut.ValidateRequestDeviceAsync(request, context);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
await _userService.Received(1).SendOTPAsync(context.User);
|
|
|
|
|
await _deviceService.Received(0).SaveAsync(Arg.Any<Device>());
|
|
|
|
|
|
2024-10-11 02:26:17 +02:00
|
|
|
|
Assert.False(result);
|
2024-12-12 18:08:11 +01:00
|
|
|
|
Assert.NotNull(context.CustomResponse["ErrorModel"]);
|
|
|
|
|
var expectedErrorMessage = "new device verification required";
|
|
|
|
|
var actualResponse = (ErrorResponseModel)context.CustomResponse["ErrorModel"];
|
|
|
|
|
Assert.Equal(expectedErrorMessage, actualResponse.Message);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public void NewDeviceOtpRequest_NewDeviceOtpNull_ReturnsFalse(
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
// Arrange
|
2024-12-12 18:08:11 +01:00
|
|
|
|
// Autodata arranges
|
|
|
|
|
|
|
|
|
|
// Act
|
|
|
|
|
var result = DeviceValidator.NewDeviceOtpRequest(request);
|
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.False(result);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Theory, BitAutoData]
|
|
|
|
|
public void NewDeviceOtpRequest_NewDeviceOtpNotNull_ReturnsTrue(
|
|
|
|
|
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
// Arrange
|
|
|
|
|
request.Raw["NewDeviceOtp"] = "123456";
|
|
|
|
|
|
2024-10-11 02:26:17 +02:00
|
|
|
|
// Act
|
2024-12-12 18:08:11 +01:00
|
|
|
|
var result = DeviceValidator.NewDeviceOtpRequest(request);
|
2024-10-11 02:26:17 +02:00
|
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
|
Assert.True(result);
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-12 18:08:11 +01:00
|
|
|
|
private static void AddValidDeviceToRequest(ValidatedTokenRequest request)
|
2024-10-11 02:26:17 +02:00
|
|
|
|
{
|
|
|
|
|
request.Raw["DeviceIdentifier"] = "DeviceIdentifier";
|
2024-12-12 18:08:11 +01:00
|
|
|
|
request.Raw["DeviceType"] = "Android"; // must be valid device type
|
2024-10-11 02:26:17 +02:00
|
|
|
|
request.Raw["DeviceName"] = "DeviceName";
|
|
|
|
|
request.Raw["DevicePushToken"] = "DevicePushToken";
|
2024-12-12 18:08:11 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Configures the request context to facilitate testing the HandleNewDeviceVerificationAsync method.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="context">test context</param>
|
|
|
|
|
/// <param name="request">test request</param>
|
|
|
|
|
private static void ArrangeForHandleNewDeviceVerificationTest(
|
|
|
|
|
CustomValidatorRequestContext context,
|
|
|
|
|
ValidatedTokenRequest request)
|
|
|
|
|
{
|
|
|
|
|
context.KnownDevice = false;
|
|
|
|
|
request.GrantType = "password";
|
|
|
|
|
context.TwoFactorRequired = false;
|
|
|
|
|
context.SsoRequired = false;
|
2024-10-11 02:26:17 +02:00
|
|
|
|
}
|
|
|
|
|
}
|