1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 23:52:50 -05:00

[PM-4619] Rewrite UserService methods as commands (#3432)

* [PM-4619] feat: scaffold new create options command

* [PM-4169] feat: implement credential create options command

* [PM-4619] feat: create command for credential creation

* [PM-4619] feat: create assertion options command

* [PM-4619] chore: clean-up unused argument

* [PM-4619] feat: implement assertion command

* [PM-4619] feat: migrate to commands

* [PM-4619] fix: lint

* [PM-4169] fix: use constant

* [PM-4619] fix: lint

I have no idea what this commit acutally changes, but the file seems to have some character encoding issues. This fix was generated by `dotnet format`
This commit is contained in:
Andreas Coroiu
2023-12-14 09:35:52 +01:00
committed by GitHub
parent 27d7d823a7
commit d63c917c95
20 changed files with 500 additions and 245 deletions

View File

@ -0,0 +1,107 @@
using System.Text;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin.Implementations;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Fido2NetLib;
using Fido2NetLib.Objects;
using NSubstitute;
using NSubstitute.ReturnsExtensions;
using Xunit;
namespace Bit.Core.Test.Auth.UserFeatures.WebAuthnLogin;
[SutProviderCustomize]
public class AssertWebAuthnLoginCredentialCommandTests
{
[Theory, BitAutoData]
internal async void InvalidUserHandle_ThrowsBadRequestException(SutProvider<AssertWebAuthnLoginCredentialCommand> sutProvider, AssertionOptions options, AuthenticatorAssertionRawResponse response)
{
// Arrange
response.Response.UserHandle = Encoding.UTF8.GetBytes("invalid-user-handle");
// Act
var result = async () => await sutProvider.Sut.AssertWebAuthnLoginCredential(options, response);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
internal async void UserNotFound_ThrowsBadRequestException(SutProvider<AssertWebAuthnLoginCredentialCommand> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response)
{
// Arrange
response.Response.UserHandle = user.Id.ToByteArray();
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).ReturnsNull();
// Act
var result = async () => await sutProvider.Sut.AssertWebAuthnLoginCredential(options, response);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
internal async void NoMatchingCredentialExists_ThrowsBadRequestException(SutProvider<AssertWebAuthnLoginCredentialCommand> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response)
{
// Arrange
response.Response.UserHandle = user.Id.ToByteArray();
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { });
// Act
var result = async () => await sutProvider.Sut.AssertWebAuthnLoginCredential(options, response);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
internal async void AssertionFails_ThrowsBadRequestException(SutProvider<AssertWebAuthnLoginCredentialCommand> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response, WebAuthnCredential credential, AssertionVerificationResult assertionResult)
{
// Arrange
var credentialId = Guid.NewGuid().ToByteArray();
credential.CredentialId = CoreHelpers.Base64UrlEncode(credentialId);
response.Id = credentialId;
response.Response.UserHandle = user.Id.ToByteArray();
assertionResult.Status = "Not ok";
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { credential });
sutProvider.GetDependency<IFido2>().MakeAssertionAsync(response, options, Arg.Any<byte[]>(), Arg.Any<uint>(), Arg.Any<IsUserHandleOwnerOfCredentialIdAsync>())
.Returns(assertionResult);
// Act
var result = async () => await sutProvider.Sut.AssertWebAuthnLoginCredential(options, response);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
internal async void AssertionSucceeds_ReturnsUserAndCredential(SutProvider<AssertWebAuthnLoginCredentialCommand> sutProvider, User user, AssertionOptions options, AuthenticatorAssertionRawResponse response, WebAuthnCredential credential, AssertionVerificationResult assertionResult)
{
// Arrange
var credentialId = Guid.NewGuid().ToByteArray();
credential.CredentialId = CoreHelpers.Base64UrlEncode(credentialId);
response.Id = credentialId;
response.Response.UserHandle = user.Id.ToByteArray();
assertionResult.Status = "ok";
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(user.Id).Returns(user);
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(new WebAuthnCredential[] { credential });
sutProvider.GetDependency<IFido2>().MakeAssertionAsync(response, options, Arg.Any<byte[]>(), Arg.Any<uint>(), Arg.Any<IsUserHandleOwnerOfCredentialIdAsync>())
.Returns(assertionResult);
// Act
var result = await sutProvider.Sut.AssertWebAuthnLoginCredential(options, response);
// Assert
var (userResult, credentialResult) = result;
Assert.Equal(user, userResult);
Assert.Equal(credential, credentialResult);
}
}

View File

@ -0,0 +1,65 @@
using AutoFixture;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin.Implementations;
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Fido2NetLib;
using Fido2NetLib.Objects;
using NSubstitute;
using Xunit;
using static Fido2NetLib.Fido2;
namespace Bit.Core.Test.Auth.UserFeatures.WebAuthnLogin;
[SutProviderCustomize]
public class CreateWebAuthnLoginCredentialCommandTests
{
[Theory, BitAutoData]
internal async void ExceedsExistingCredentialsLimit_ReturnsFalse(SutProvider<CreateWebAuthnLoginCredentialCommand> sutProvider, User user, CredentialCreateOptions options, AuthenticatorAttestationRawResponse response, Generator<WebAuthnCredential> credentialGenerator)
{
// Arrange
var existingCredentials = credentialGenerator.Take(CreateWebAuthnLoginCredentialCommand.MaxCredentialsPerUser).ToList();
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(existingCredentials);
// Act
var result = await sutProvider.Sut.CreateWebAuthnLoginCredentialAsync(user, "name", options, response, false, null, null, null);
// Assert
Assert.False(result);
await sutProvider.GetDependency<IWebAuthnCredentialRepository>().DidNotReceive().CreateAsync(Arg.Any<WebAuthnCredential>());
}
[Theory, BitAutoData]
internal async void DoesNotExceedExistingCredentialsLimit_CreatesCredential(SutProvider<CreateWebAuthnLoginCredentialCommand> sutProvider, User user, CredentialCreateOptions options, AuthenticatorAttestationRawResponse response, Generator<WebAuthnCredential> credentialGenerator)
{
// Arrange
var existingCredentials = credentialGenerator.Take(CreateWebAuthnLoginCredentialCommand.MaxCredentialsPerUser - 1).ToList();
sutProvider.GetDependency<IWebAuthnCredentialRepository>().GetManyByUserIdAsync(user.Id).Returns(existingCredentials);
sutProvider.GetDependency<IFido2>().MakeNewCredentialAsync(
response, options, Arg.Any<IsCredentialIdUniqueToUserAsyncDelegate>(), Arg.Any<byte[]>(), Arg.Any<CancellationToken>()
).Returns(MakeCredentialResult());
// Act
var result = await sutProvider.Sut.CreateWebAuthnLoginCredentialAsync(user, "name", options, response, false, null, null, null);
// Assert
Assert.True(result);
await sutProvider.GetDependency<IWebAuthnCredentialRepository>().Received().CreateAsync(Arg.Any<WebAuthnCredential>());
}
private CredentialMakeResult MakeCredentialResult()
{
return new CredentialMakeResult("ok", "", new AttestationVerificationSuccess
{
Aaguid = new Guid(),
Counter = 0,
CredentialId = new Guid().ToByteArray(),
CredType = "public-key",
PublicKey = new byte[0],
Status = "ok",
User = new Fido2User(),
});
}
}

View File

@ -0,0 +1,60 @@
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin.Implementations;
using Bit.Core.Entities;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Fido2NetLib;
using Fido2NetLib.Objects;
using NSubstitute;
using Xunit;
namespace Bit.Core.Test.Auth.UserFeatures.WebAuthnLogin;
[SutProviderCustomize]
public class GetWebAuthnLoginCredentialCreateOptionsTests
{
[Theory, BitAutoData]
internal async Task NoExistingCredentials_ReturnsOptionsWithoutExcludedCredentials(SutProvider<GetWebAuthnLoginCredentialCreateOptionsCommand> sutProvider, User user)
{
// Arrange
sutProvider.GetDependency<IWebAuthnCredentialRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(new List<WebAuthnCredential>());
// Act
var result = await sutProvider.Sut.GetWebAuthnLoginCredentialCreateOptionsAsync(user);
// Assert
sutProvider.GetDependency<IFido2>()
.Received()
.RequestNewCredential(
Arg.Any<Fido2User>(),
Arg.Is<List<PublicKeyCredentialDescriptor>>(list => list.Count == 0),
Arg.Any<AuthenticatorSelection>(),
Arg.Any<AttestationConveyancePreference>(),
Arg.Any<AuthenticationExtensionsClientInputs>());
}
[Theory, BitAutoData]
internal async Task HasExistingCredential_ReturnsOptionsWithExcludedCredential(SutProvider<GetWebAuthnLoginCredentialCreateOptionsCommand> sutProvider, User user, WebAuthnCredential credential)
{
// Arrange
sutProvider.GetDependency<IWebAuthnCredentialRepository>()
.GetManyByUserIdAsync(user.Id)
.Returns(new List<WebAuthnCredential> { credential });
// Act
var result = await sutProvider.Sut.GetWebAuthnLoginCredentialCreateOptionsAsync(user);
// Assert
sutProvider.GetDependency<IFido2>()
.Received()
.RequestNewCredential(
Arg.Any<Fido2User>(),
Arg.Is<List<PublicKeyCredentialDescriptor>>(list => list.Count == 1),
Arg.Any<AuthenticatorSelection>(),
Arg.Any<AttestationConveyancePreference>(),
Arg.Any<AuthenticationExtensionsClientInputs>());
}
}