1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 15:42:48 -05:00

[PM-10742] Pull Device verification into testable service (#4851)

* initial device removal

* Unit Testing

* Added unit tests fixed validator null checks

* Finalized tests

* formatting

* fixed test

* lint

* addressing review notes

* comments
This commit is contained in:
Ike
2024-10-10 17:26:17 -07:00
committed by GitHub
parent 96f58dc309
commit 22dd957543
11 changed files with 446 additions and 139 deletions

View File

@ -4,11 +4,15 @@ using Bit.Core.Auth.Enums;
using Bit.Core.Entities;
using Bit.Core.Enums;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Identity.IdentityServer;
using Bit.Identity.Models.Request.Accounts;
using Bit.IntegrationTestCommon.Factories;
using Bit.Test.Common.AutoFixture.Attributes;
using Bit.Test.Common.Helpers;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.Identity;
using NSubstitute;
using Xunit;
namespace Bit.Identity.IntegrationTest.RequestValidation;
@ -21,6 +25,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
private readonly IdentityApplicationFactory _factory;
private readonly UserManager<User> _userManager;
private readonly IAuthRequestRepository _authRequestRepository;
private readonly IDeviceService _deviceService;
public ResourceOwnerPasswordValidatorTests(IdentityApplicationFactory factory)
{
@ -28,13 +33,13 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
_userManager = _factory.GetService<UserManager<User>>();
_authRequestRepository = _factory.GetService<IAuthRequestRepository>();
_deviceService = _factory.GetService<IDeviceService>();
}
[Fact]
public async Task ValidateAsync_Success()
{
// Arrange
// Arrange
await EnsureUserCreatedAsync();
// Act
@ -53,7 +58,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
[Fact]
public async Task ValidateAsync_AuthEmailHeaderInvalid_InvalidGrantResponse()
{
// Arrange
// Arrange
await EnsureUserCreatedAsync();
// Act
@ -88,12 +93,12 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
}
/// <summary>
/// I would have liked to spy into the IUserService but by spying into the IUserService it
/// creates a Singleton that is not available to the UserManager<User> thus causing the
/// I would have liked to spy into the IUserService but by spying into the IUserService it
/// creates a Singleton that is not available to the UserManager<User> thus causing the
/// RegisterAsync() to create a the user in a different UserStore than the one the
/// UserManager<User> has access to. This is an assumption made from observing the behavior while
/// writing theses tests. I could be wrong.
///
/// UserManager<User> has access to (This is an assumption made from observing the behavior while
/// writing theses tests, I could be wrong).
///
/// For the time being, verifying that the user is not null confirms that the failure is due to
/// a bad password.
/// </summary>
@ -102,10 +107,11 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
[Theory, BitAutoData]
public async Task ValidateAsync_BadPassword_Failure(string badPassword)
{
// Arrange
// Arrange
await EnsureUserCreatedAsync();
// Verify the User is not null to ensure the failure is due to bad password
Assert.NotNull(await _userManager.FindByEmailAsync(DefaultUsername));
// Act
var context = await _factory.Server.PostAsync("/connect/token",
@ -113,8 +119,6 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
context => context.SetAuthEmail(DefaultUsername));
// Assert
Assert.NotNull(await _userManager.FindByEmailAsync(DefaultUsername));
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
@ -200,8 +204,8 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
// Assert
/*
An improvement on the current failure flow would be to document which part of
the flow failed since all of the failures are basically the same.
An improvement on the current failure flow would be to document which part of
the flow failed since all of the failures are basically the same.
This doesn't build confidence in the tests.
*/
@ -213,6 +217,43 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
}
[Fact]
public async Task ValidateAsync_DeviceSaveAsync_ReturnsNullDevice_ErrorResult()
{
// Arrange
var factory = new IdentityApplicationFactory();
// Stub DeviceValidator
factory.SubstituteService<IDeviceValidator>(sub =>
{
sub.SaveDeviceAsync(Arg.Any<User>(), Arg.Any<ValidatedTokenRequest>())
.Returns(null as Device);
});
// Add User
await factory.RegisterAsync(new RegisterRequestModel
{
Email = DefaultUsername,
MasterPasswordHash = DefaultPassword
});
var userManager = factory.GetService<UserManager<User>>();
var user = await userManager.FindByEmailAsync(DefaultUsername);
Assert.NotNull(user);
// Act
var context = await factory.Server.PostAsync("/connect/token",
GetFormUrlEncodedContent(),
context => context.SetAuthEmail(DefaultUsername));
// Assert
var body = await AssertHelper.AssertResponseTypeIs<JsonDocument>(context);
var root = body.RootElement;
var errorModel = AssertHelper.AssertJsonProperty(root, "ErrorModel", JsonValueKind.Object);
var errorMessage = AssertHelper.AssertJsonProperty(errorModel, "Message", JsonValueKind.String).GetString();
Assert.Equal("No device information provided.", errorMessage);
}
private async Task EnsureUserCreatedAsync(IdentityApplicationFactory factory = null)
{
factory ??= _factory;