mirror of
https://github.com/bitwarden/server.git
synced 2024-11-22 12:15:36 +01:00
ab5d4738d6
refactor(TwoFactorAuthentication): Remove references to old Duo SDK version 2 code and replace them with the Duo SDK version 4 supported library DuoUniversal code. Increased unit test coverage in the Two Factor Authentication code space. We opted to use DI instead of Inheritance for the Duo and OrganizaitonDuo two factor tokens to increase testability, since creating a testing mock of the Duo.Client was non-trivial. Reviewed-by: @JaredSnider-Bitwarden
296 lines
10 KiB
C#
296 lines
10 KiB
C#
using Bit.Api.Auth.Controllers;
|
|
using Bit.Api.Auth.Models.Request;
|
|
using Bit.Api.Auth.Models.Request.Accounts;
|
|
using Bit.Api.Auth.Models.Response.TwoFactor;
|
|
using Bit.Core.AdminConsole.Entities;
|
|
using Bit.Core.Auth.Identity.TokenProviders;
|
|
using Bit.Core.Context;
|
|
using Bit.Core.Entities;
|
|
using Bit.Core.Exceptions;
|
|
using Bit.Core.Repositories;
|
|
using Bit.Core.Services;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
|
|
namespace Bit.Api.Test.Auth.Controllers;
|
|
|
|
[ControllerCustomize(typeof(TwoFactorController))]
|
|
[SutProviderCustomize]
|
|
public class TwoFactorControllerTests
|
|
{
|
|
[Theory, BitAutoData]
|
|
public async Task CheckAsync_UserNull_ThrowsUnauthorizedException(SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
sutProvider.GetDependency<IUserService>()
|
|
.GetUserByPrincipalAsync(default)
|
|
.ReturnsForAnyArgs(null as User);
|
|
|
|
// Act
|
|
var result = () => sutProvider.Sut.GetDuo(request);
|
|
|
|
// Assert
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task CheckAsync_BadSecret_ThrowsBadRequestException(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
sutProvider.GetDependency<IUserService>()
|
|
.GetUserByPrincipalAsync(default)
|
|
.ReturnsForAnyArgs(user);
|
|
|
|
sutProvider.GetDependency<IUserService>()
|
|
.VerifySecretAsync(default, default)
|
|
.ReturnsForAnyArgs(false);
|
|
|
|
// Act
|
|
try
|
|
{
|
|
await sutProvider.Sut.GetDuo(request);
|
|
}
|
|
catch (BadRequestException e)
|
|
{
|
|
// Assert
|
|
Assert.Equal("The model state is invalid.", e.Message);
|
|
}
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task CheckAsync_CannotAccessPremium_ThrowsBadRequestException(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
sutProvider.GetDependency<IUserService>()
|
|
.GetUserByPrincipalAsync(default)
|
|
.ReturnsForAnyArgs(user);
|
|
|
|
sutProvider.GetDependency<IUserService>()
|
|
.VerifySecretAsync(default, default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
sutProvider.GetDependency<IUserService>()
|
|
.CanAccessPremium(default)
|
|
.ReturnsForAnyArgs(false);
|
|
|
|
// Act
|
|
try
|
|
{
|
|
await sutProvider.Sut.GetDuo(request);
|
|
}
|
|
catch (BadRequestException e)
|
|
{
|
|
// Assert
|
|
Assert.Equal("Premium status is required.", e.Message);
|
|
}
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task GetDuo_Success(User user, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
user.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
|
|
// Act
|
|
var result = await sutProvider.Sut.GetDuo(request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task PutDuo_InvalidConfiguration_ThrowsBadRequestException(User user, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
|
.ValidateDuoConfiguration(default, default, default)
|
|
.Returns(false);
|
|
|
|
// Act
|
|
try
|
|
{
|
|
await sutProvider.Sut.PutDuo(request);
|
|
}
|
|
catch (BadRequestException e)
|
|
{
|
|
// Assert
|
|
Assert.Equal("Duo configuration settings are not valid. Please re-check the Duo Admin panel.", e.Message);
|
|
}
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task PutDuo_Success(User user, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
user.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
|
|
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
|
.ValidateDuoConfiguration(default, default, default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
// Act
|
|
var result = await sutProvider.Sut.PutDuo(request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
|
Assert.Equal(user.TwoFactorProviders, request.ToUser(user).TwoFactorProviders);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task CheckOrganizationAsync_ManagePolicies_ThrowsNotFoundException(
|
|
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
|
|
sutProvider.GetDependency<ICurrentContext>()
|
|
.ManagePolicies(default)
|
|
.ReturnsForAnyArgs(false);
|
|
|
|
// Act
|
|
var result = () => sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
|
|
|
// Assert
|
|
await Assert.ThrowsAsync<NotFoundException>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task CheckOrganizationAsync_GetByIdAsync_ThrowsNotFoundException(
|
|
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
|
|
sutProvider.GetDependency<ICurrentContext>()
|
|
.ManagePolicies(default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(default)
|
|
.ReturnsForAnyArgs(null as Organization);
|
|
|
|
// Act
|
|
var result = () => sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
|
|
|
// Assert
|
|
await Assert.ThrowsAsync<NotFoundException>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task GetOrganizationDuo_Success(
|
|
User user, Organization organization, SecretVerificationRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
organization.TwoFactorProviders = GetOrganizationTwoFactorDuoProvidersJson();
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
|
|
|
// Act
|
|
var result = await sutProvider.Sut.GetOrganizationDuo(organization.Id.ToString(), request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task PutOrganizationDuo_InvalidConfiguration_ThrowsBadRequestException(
|
|
User user, Organization organization, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
|
|
|
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
|
.ValidateDuoConfiguration(default, default, default)
|
|
.ReturnsForAnyArgs(false);
|
|
|
|
// Act
|
|
try
|
|
{
|
|
await sutProvider.Sut.PutOrganizationDuo(organization.Id.ToString(), request);
|
|
}
|
|
catch (BadRequestException e)
|
|
{
|
|
// Assert
|
|
Assert.Equal("Duo configuration settings are not valid. Please re-check the Duo Admin panel.", e.Message);
|
|
}
|
|
}
|
|
|
|
[Theory, BitAutoData]
|
|
public async Task PutOrganizationDuo_Success(
|
|
User user, Organization organization, UpdateTwoFactorDuoRequestModel request, SutProvider<TwoFactorController> sutProvider)
|
|
{
|
|
// Arrange
|
|
SetupCheckAsyncToPass(sutProvider, user);
|
|
SetupCheckOrganizationAsyncToPass(sutProvider, organization);
|
|
organization.TwoFactorProviders = GetUserTwoFactorDuoProvidersJson();
|
|
|
|
sutProvider.GetDependency<IDuoUniversalTokenService>()
|
|
.ValidateDuoConfiguration(default, default, default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
// Act
|
|
var result =
|
|
await sutProvider.Sut.PutOrganizationDuo(organization.Id.ToString(), request);
|
|
|
|
// Assert
|
|
Assert.NotNull(result);
|
|
Assert.IsType<TwoFactorDuoResponseModel>(result);
|
|
Assert.Equal(organization.TwoFactorProviders, request.ToOrganization(organization).TwoFactorProviders);
|
|
}
|
|
|
|
|
|
private string GetUserTwoFactorDuoProvidersJson()
|
|
{
|
|
return
|
|
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
|
}
|
|
|
|
private string GetOrganizationTwoFactorDuoProvidersJson()
|
|
{
|
|
return
|
|
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets up the CheckAsync method to pass.
|
|
/// </summary>
|
|
/// <param name="sutProvider">uses bit auto data</param>
|
|
/// <param name="user">uses bit auto data</param>
|
|
private void SetupCheckAsyncToPass(SutProvider<TwoFactorController> sutProvider, User user)
|
|
{
|
|
sutProvider.GetDependency<IUserService>()
|
|
.GetUserByPrincipalAsync(default)
|
|
.ReturnsForAnyArgs(user);
|
|
|
|
sutProvider.GetDependency<IUserService>()
|
|
.VerifySecretAsync(default, default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
sutProvider.GetDependency<IUserService>()
|
|
.CanAccessPremium(default)
|
|
.ReturnsForAnyArgs(true);
|
|
}
|
|
|
|
private void SetupCheckOrganizationAsyncToPass(SutProvider<TwoFactorController> sutProvider, Organization organization)
|
|
{
|
|
sutProvider.GetDependency<ICurrentContext>()
|
|
.ManagePolicies(default)
|
|
.ReturnsForAnyArgs(true);
|
|
|
|
sutProvider.GetDependency<IOrganizationRepository>()
|
|
.GetByIdAsync(default)
|
|
.ReturnsForAnyArgs(organization);
|
|
}
|
|
}
|