mirror of
https://github.com/bitwarden/server.git
synced 2024-11-25 12:45:18 +01:00
Auth/PM-3659 - Disable Passkey registration if Require SSO Policy Enabled (#3399)
* PM-3659 - WebAuthnController.cs - Passkey Creation - Add RequireSSO login policy validation to prevent users from creating passkeys if require SSO applies to them. * PM-3659 - per PR feedback, apply new require SSO validation to options call * PM-3659 - Remove unneeded comment * PM-3659 - Per PR feedback, add unit tests for new require SSO scenarios on both Post and Options endpoints on the WebAuthnController * Remove duplicated line * Remove extra whitespace
This commit is contained in:
parent
1fb5e49a05
commit
f5f64059c5
@ -5,6 +5,7 @@ using Bit.Api.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
@ -22,15 +23,18 @@ public class WebAuthnController : Controller
|
||||
private readonly IUserService _userService;
|
||||
private readonly IWebAuthnCredentialRepository _credentialRepository;
|
||||
private readonly IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable> _createOptionsDataProtector;
|
||||
private readonly IPolicyService _policyService;
|
||||
|
||||
public WebAuthnController(
|
||||
IUserService userService,
|
||||
IWebAuthnCredentialRepository credentialRepository,
|
||||
IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable> createOptionsDataProtector)
|
||||
IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable> createOptionsDataProtector,
|
||||
IPolicyService policyService)
|
||||
{
|
||||
_userService = userService;
|
||||
_credentialRepository = credentialRepository;
|
||||
_createOptionsDataProtector = createOptionsDataProtector;
|
||||
_policyService = policyService;
|
||||
}
|
||||
|
||||
[HttpGet("")]
|
||||
@ -46,6 +50,7 @@ public class WebAuthnController : Controller
|
||||
public async Task<WebAuthnCredentialCreateOptionsResponseModel> PostOptions([FromBody] SecretVerificationRequestModel model)
|
||||
{
|
||||
var user = await VerifyUserAsync(model);
|
||||
await ValidateRequireSsoPolicyDisabledOrNotApplicable(user.Id);
|
||||
var options = await _userService.StartWebAuthnLoginRegistrationAsync(user);
|
||||
|
||||
var tokenable = new WebAuthnCredentialCreateOptionsTokenable(user, options);
|
||||
@ -62,7 +67,9 @@ public class WebAuthnController : Controller
|
||||
public async Task Post([FromBody] WebAuthnCredentialRequestModel model)
|
||||
{
|
||||
var user = await GetUserAsync();
|
||||
await ValidateRequireSsoPolicyDisabledOrNotApplicable(user.Id);
|
||||
var tokenable = _createOptionsDataProtector.Unprotect(model.Token);
|
||||
|
||||
if (!tokenable.TokenIsValid(user))
|
||||
{
|
||||
throw new BadRequestException("The token associated with your request is expired. A valid token is required to continue.");
|
||||
@ -75,6 +82,16 @@ public class WebAuthnController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ValidateRequireSsoPolicyDisabledOrNotApplicable(Guid userId)
|
||||
{
|
||||
var requireSsoLogin = await _policyService.AnyPoliciesApplicableToUserAsync(userId, PolicyType.RequireSso);
|
||||
|
||||
if (requireSsoLogin)
|
||||
{
|
||||
throw new BadRequestException("Passkeys cannot be created for your account. SSO login is required.");
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("{id}/delete")]
|
||||
public async Task Delete(Guid id, [FromBody] SecretVerificationRequestModel model)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@ using Bit.Api.Auth.Models.Request.Accounts;
|
||||
using Bit.Api.Auth.Models.Request.Webauthn;
|
||||
using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
@ -22,7 +23,7 @@ public class WebAuthnControllerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task Get_UserNotFound_ThrowsUnauthorizedAccessException(SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
@ -35,7 +36,7 @@ public class WebAuthnControllerTests
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostOptions_UserNotFound_ThrowsUnauthorizedAccessException(SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
@ -59,10 +60,25 @@ public class WebAuthnControllerTests
|
||||
await Assert.ThrowsAsync<BadRequestException>(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostOptions_RequireSsoPolicyApplicable_ThrowsBadRequestException(
|
||||
SecretVerificationRequestModel requestModel, User user, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>().VerifySecretAsync(user, default).ReturnsForAnyArgs(true);
|
||||
sutProvider.GetDependency<IPolicyService>().AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.RequireSso).ReturnsForAnyArgs(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.PostOptions(requestModel));
|
||||
Assert.Contains("Passkeys cannot be created for your account. SSO login is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Post_UserNotFound_ThrowsUnauthorizedAccessException(WebAuthnCredentialRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
@ -113,10 +129,36 @@ public class WebAuthnControllerTests
|
||||
// Nothing to assert since return is void
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Post_RequireSsoPolicyApplicable_ThrowsBadRequestException(
|
||||
WebAuthnCredentialRequestModel requestModel,
|
||||
CredentialCreateOptions createOptions,
|
||||
User user,
|
||||
SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
var token = new WebAuthnCredentialCreateOptionsTokenable(user, createOptions);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.GetUserByPrincipalAsync(default)
|
||||
.ReturnsForAnyArgs(user);
|
||||
sutProvider.GetDependency<IUserService>()
|
||||
.CompleteWebAuthLoginRegistrationAsync(user, requestModel.Name, createOptions, Arg.Any<AuthenticatorAttestationRawResponse>())
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IDataProtectorTokenFactory<WebAuthnCredentialCreateOptionsTokenable>>()
|
||||
.Unprotect(requestModel.Token)
|
||||
.Returns(token);
|
||||
sutProvider.GetDependency<IPolicyService>().AnyPoliciesApplicableToUserAsync(user.Id, PolicyType.RequireSso).ReturnsForAnyArgs(true);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.Post(requestModel));
|
||||
Assert.Contains("Passkeys cannot be created for your account. SSO login is required", exception.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task Delete_UserNotFound_ThrowsUnauthorizedAccessException(Guid credentialId, SecretVerificationRequestModel requestModel, SutProvider<WebAuthnController> sutProvider)
|
||||
{
|
||||
// Arrange
|
||||
// Arrange
|
||||
sutProvider.GetDependency<IUserService>().GetUserByPrincipalAsync(default).ReturnsNullForAnyArgs();
|
||||
|
||||
// Act
|
||||
|
Loading…
Reference in New Issue
Block a user