1
0
mirror of https://github.com/bitwarden/server.git synced 2025-06-30 07:36:14 -05:00

[PM-4168] update keys for WebAuthnLoginCredential (#3506)

* allow update of webauthnlogincredential

* Added Tests

* fixed tests to use commands

* addressing various feedback items
This commit is contained in:
Ike
2023-12-15 13:38:34 -08:00
committed by GitHub
parent d488ebec0f
commit 767c58466c
8 changed files with 286 additions and 22 deletions

View File

@ -3,7 +3,10 @@ using Bit.Api.Auth.Models.Request.Accounts;
using Bit.Api.Auth.Models.Request.Webauthn;
using Bit.Core.AdminConsole.Enums;
using Bit.Core.AdminConsole.Services;
using Bit.Core.Auth.Entities;
using Bit.Core.Auth.Models.Api.Response.Accounts;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Auth.Repositories;
using Bit.Core.Auth.UserFeatures.WebAuthnLogin;
using Bit.Core.Entities;
using Bit.Core.Exceptions;
@ -36,34 +39,34 @@ public class WebAuthnControllerTests
}
[Theory, BitAutoData]
public async Task PostOptions_UserNotFound_ThrowsUnauthorizedAccessException(SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
public async Task AttestationOptions_UserNotFound_ThrowsUnauthorizedAccessException(SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
// Act
var result = () => sutProvider.Sut.PostOptions(requestModel);
var result = () => sutProvider.Sut.AttestationOptions(requestModel);
// Assert
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
}
[Theory, BitAutoData]
public async Task PostOptions_UserVerificationFailed_ThrowsBadRequestException(SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
public async Task AttestationOptions_UserVerificationFailed_ThrowsBadRequestException(SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, default).Returns(false);
// Act
var result = () => sutProvider.Sut.PostOptions(requestModel);
var result = () => sutProvider.Sut.AttestationOptions(requestModel);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
public async Task PostOptions_RequireSsoPolicyApplicable_ThrowsBadRequestException(
public async Task AttestationOptions_RequireSsoPolicyApplicable_ThrowsBadRequestException(
SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
@ -73,12 +76,58 @@ public class WebAuthnControllerTests
// Act & Assert
var exception = await Assert.ThrowsAsync<BadRequestException>(
() => sutProvider.Sut.PostOptions(requestModel));
() => sutProvider.Sut.AttestationOptions(requestModel));
Assert.Contains("Passkeys cannot be created for your account. SSO login is required", exception.Message);
}
#region Assertion Options
[Theory, BitAutoData]
public async Task Post_UserNotFound_ThrowsUnauthorizedAccessException(WebAuthnCredentialRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
public async Task AssertionOptions_UserNotFound_ThrowsUnauthorizedAccessException(SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
// Act
var result = () => sutProvider.Sut.AssertionOptions(requestModel);
// Assert
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
}
[Theory, BitAutoData]
public async Task AssertionOptions_UserVerificationFailed_ThrowsBadRequestException(SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, default).Returns(false);
// Act
var result = () => sutProvider.Sut.AssertionOptions(requestModel);
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
[Theory, BitAutoData]
public async Task AssertionOptions_UserVerificationSuccess_ReturnsAssertionOptions(SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, requestModel.Secret).Returns(true);
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Protect(Arg.Any<WebAuthnLoginAssertionOptionsTokenable>()).Returns("token");
// Act
var result = await sutProvider.Sut.AssertionOptions(requestModel);
// Assert
Assert.NotNull(result);
Assert.IsType<WebAuthnLoginAssertionOptionsResponseModel>(result);
}
#endregion
[Theory, BitAutoData]
public async Task Post_UserNotFound_ThrowsUnauthorizedAccessException(WebAuthnLoginCredentialCreateRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
@ -91,7 +140,7 @@ public class WebAuthnControllerTests
}
[Theory, BitAutoData]
public async Task Post_ExpiredToken_ThrowsBadRequestException(WebAuthnCredentialRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
public async Task Post_ExpiredToken_ThrowsBadRequestException(WebAuthnLoginCredentialCreateRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
@ -110,7 +159,7 @@ public class WebAuthnControllerTests
}
[Theory, BitAutoData]
public async Task Post_ValidInput_Returns(WebAuthnCredentialRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
public async Task Post_ValidInput_Returns(WebAuthnLoginCredentialCreateRequestModel requestModel, CredentialCreateOptions createOptions, User user, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
@ -128,12 +177,17 @@ public class WebAuthnControllerTests
await sutProvider.Sut.Post(requestModel);
// Assert
// Nothing to assert since return is void
await sutProvider.GetDependency<IUserService>()
.Received(1)
.GetUserByPrincipalAsync(default);
await sutProvider.GetDependency<ICreateWebAuthnLoginCredentialCommand>()
.Received(1)
.CreateWebAuthnLoginCredentialAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>(), requestModel.SupportsPrf, requestModel.EncryptedUserKey, requestModel.EncryptedPublicKey, requestModel.EncryptedPrivateKey);
}
[Theory, BitAutoData]
public async Task Post_RequireSsoPolicyApplicable_ThrowsBadRequestException(
WebAuthnCredentialRequestModel requestModel,
WebAuthnLoginCredentialCreateRequestModel requestModel,
CredentialCreateOptions createOptions,
User user,
SutProvider<WebAuthnController> sutProvider)
@ -183,5 +237,91 @@ public class WebAuthnControllerTests
// Assert
await Assert.ThrowsAsync<BadRequestException>(result);
}
}
#region Update Credential
[Theory, BitAutoData]
public async Task Put_TokenVerificationFailed_ThrowsBadRequestException(AssertionOptions assertionOptions, WebAuthnLoginCredentialUpdateRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var expectedMessage = "The token associated with your request is invalid or has expired. A valid token is required to continue.";
var token = new WebAuthnLoginAssertionOptionsTokenable(
Core.Auth.Enums.WebAuthnLoginAssertionOptionsScope.PrfRegistration, assertionOptions);
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Unprotect(requestModel.Token)
.Returns(token);
// Act
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateCredential(requestModel));
// Assert
Assert.Equal(expectedMessage, exception.Message);
}
[Theory, BitAutoData]
public async Task Put_CredentialNotFound_ThrowsBadRequestException(AssertionOptions assertionOptions, WebAuthnLoginCredentialUpdateRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var expectedMessage = "Unable to update credential.";
var token = new WebAuthnLoginAssertionOptionsTokenable(
Core.Auth.Enums.WebAuthnLoginAssertionOptionsScope.UpdateKeySet, assertionOptions);
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Unprotect(requestModel.Token)
.Returns(token);
// Act
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateCredential(requestModel));
// Assert
Assert.Equal(expectedMessage, exception.Message);
}
[Theory, BitAutoData]
public async Task Put_PrfNotSupported_ThrowsBadRequestException(User user, WebAuthnCredential credential, AssertionOptions assertionOptions, WebAuthnLoginCredentialUpdateRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var expectedMessage = "Unable to update credential.";
credential.SupportsPrf = false;
var token = new WebAuthnLoginAssertionOptionsTokenable(
Core.Auth.Enums.WebAuthnLoginAssertionOptionsScope.UpdateKeySet, assertionOptions);
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Unprotect(requestModel.Token)
.Returns(token);
sutProvider.GetDependency<IAssertWebAuthnLoginCredentialCommand>()
.AssertWebAuthnLoginCredential(assertionOptions, requestModel.DeviceResponse)
.Returns((user, credential));
// Act
var exception = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.UpdateCredential(requestModel));
// Assert
Assert.Equal(expectedMessage, exception.Message);
}
[Theory, BitAutoData]
public async Task Put_UpdateCredential_Success(User user, WebAuthnCredential credential, AssertionOptions assertionOptions, WebAuthnLoginCredentialUpdateRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
{
// Arrange
var token = new WebAuthnLoginAssertionOptionsTokenable(
Core.Auth.Enums.WebAuthnLoginAssertionOptionsScope.UpdateKeySet, assertionOptions);
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Unprotect(requestModel.Token)
.Returns(token);
sutProvider.GetDependency<IAssertWebAuthnLoginCredentialCommand>()
.AssertWebAuthnLoginCredential(assertionOptions, requestModel.DeviceResponse)
.Returns((user, credential));
// Act
await sutProvider.Sut.UpdateCredential(requestModel);
// Assert
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnLoginAssertionOptionsTokenable>>()
.Received(1)
.Unprotect(requestModel.Token);
await sutProvider.GetDependency<IAssertWebAuthnLoginCredentialCommand>()
.Received(1)
.AssertWebAuthnLoginCredential(assertionOptions, requestModel.DeviceResponse);
await sutProvider.GetDependency<IWebAuthnCredentialRepository>()
.Received(1)
.UpdateAsync(credential);
}
#endregion
}