1
0
mirror of https://github.com/bitwarden/server.git synced 2025-02-19 02:21:21 +01:00
bitwarden-server/test/Core.Test/Auth/Identity/DuoUniversalTwoFactorTokenProviderTests.cs
Ike ab5d4738d6
[PM-8107] Remove Duo v2 from server (#4934)
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
2024-11-18 15:58:05 -08:00

263 lines
9.3 KiB
C#

using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity.TokenProviders;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Entities;
using Bit.Core.Tokens;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using NSubstitute;
using Xunit;
using Duo = DuoUniversal;
namespace Bit.Core.Test.Auth.Identity;
public class DuoUniversalTwoFactorTokenProviderTests : BaseTokenProviderTests<DuoUniversalTokenProvider>
{
private readonly IDuoUniversalTokenService _duoUniversalTokenService = Substitute.For<IDuoUniversalTokenService>();
public override TwoFactorProviderType TwoFactorProviderType => TwoFactorProviderType.Duo;
public static IEnumerable<object[]> CanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
( // correct data
new Dictionary<string, object>
{
["ClientId"] = new string('c', 20),
["ClientSecret"] = new string('s', 40),
["Host"] = "https://api-abcd1234.duosecurity.com",
},
true
),
( // correct data duo federal
new Dictionary<string, object>
{
["ClientId"] = new string('c', 20),
["ClientSecret"] = new string('s', 40),
["Host"] = "https://api-abcd1234.duofederal.com",
},
true
),
( // correct data duo federal
new Dictionary<string, object>
{
["ClientId"] = new string('c', 20),
["ClientSecret"] = new string('s', 40),
["Host"] = "https://api-abcd1234.duofederal.com",
},
true
),
( // invalid host
new Dictionary<string, object>
{
["ClientId"] = new string('c', 20),
["ClientSecret"] = new string('s', 40),
["Host"] = "",
},
false
),
( // clientId missing
new Dictionary<string, object>
{
["ClientSecret"] = new string('s', 40),
["Host"] = "https://api-abcd1234.duofederal.com",
},
false
)
);
public static IEnumerable<object[]> NonPremiumCanGenerateTwoFactorTokenAsyncData
=> SetupCanGenerateData(
( // correct data
new Dictionary<string, object>
{
["ClientId"] = new string('c', 20),
["ClientSecret"] = new string('s', 40),
["Host"] = "https://api-abcd1234.duosecurity.com",
},
false
)
);
[Theory, BitMemberAutoData(nameof(CanGenerateTwoFactorTokenAsyncData))]
public override async Task RunCanGenerateTwoFactorTokenAsync(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
{
// Arrange
user.Premium = true;
user.PremiumExpirationDate = DateTime.UtcNow.AddDays(1);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
.Returns(expectedResponse);
// Act
// Assert
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
}
[Theory, BitMemberAutoData(nameof(NonPremiumCanGenerateTwoFactorTokenAsyncData))]
public async Task CanGenerateTwoFactorTokenAsync_UserCanNotAccessPremium_ReturnsNull(Dictionary<string, object> metaData, bool expectedResponse,
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
{
// Arrange
user.Premium = false;
sutProvider.GetDependency<IDuoUniversalTokenService>()
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
.Returns(expectedResponse);
// Act
// Assert
await base.RunCanGenerateTwoFactorTokenAsync(metaData, expectedResponse, user, sutProvider);
}
[Theory]
[BitAutoData]
public async Task GenerateToken_Success_ReturnsAuthUrl(
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string authUrl)
{
// Arrange
SetUpProperDuoUniversalTokenService(user, sutProvider);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.GenerateAuthUrl(
Arg.Any<Duo.Client>(),
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
user)
.Returns(authUrl);
// Act
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
// Assert
Assert.NotNull(token);
Assert.Equal(token, authUrl);
}
[Theory]
[BitAutoData]
public async Task GenerateToken_DuoClientNull_ReturnsNull(
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
{
// Arrange
user.Premium = true;
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
AdditionalSetup(sutProvider, user);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
.Returns(true);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
.Returns(null as Duo.Client);
// Act
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
// Assert
Assert.Null(token);
}
[Theory]
[BitAutoData]
public async Task GenerateToken_UserCanNotAccessPremium_ReturnsNull(
User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
{
// Arrange
user.Premium = false;
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
AdditionalSetup(sutProvider, user);
// Act
var token = await sutProvider.Sut.GenerateAsync("purpose", SubstituteUserManager(), user);
// Assert
Assert.Null(token);
}
[Theory]
[BitAutoData]
public async Task ValidateToken_ValidToken_ReturnsTrue(
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
{
// Arrange
SetUpProperDuoUniversalTokenService(user, sutProvider);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.RequestDuoValidationAsync(
Arg.Any<Duo.Client>(),
Arg.Any<IDataProtectorTokenFactory<DuoUserStateTokenable>>(),
user,
token)
.Returns(true);
// Act
var response = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
// Assert
Assert.True(response);
}
[Theory]
[BitAutoData]
public async Task ValidateToken_DuoClientNull_ReturnsFalse(
User user, SutProvider<DuoUniversalTokenProvider> sutProvider, string token)
{
user.Premium = true;
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
AdditionalSetup(sutProvider, user);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
.Returns(true);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
.Returns(null as Duo.Client);
// Act
var result = await sutProvider.Sut.ValidateAsync("purpose", token, SubstituteUserManager(), user);
// Assert
Assert.False(result);
}
/// <summary>
/// Ensures that the IDuoUniversalTokenService is properly setup for the test.
/// This ensures that the private GetDuoClientAsync, and GetDuoTwoFactorProvider
/// methods will return true enabling the test to execute on the correct path.
/// </summary>
/// <param name="user">user from calling test</param>
/// <param name="sutProvider">self</param>
private void SetUpProperDuoUniversalTokenService(User user, SutProvider<DuoUniversalTokenProvider> sutProvider)
{
user.Premium = true;
user.TwoFactorProviders = GetTwoFactorDuoProvidersJson();
var client = BuildDuoClient();
AdditionalSetup(sutProvider, user);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.HasProperDuoMetadata(Arg.Any<TwoFactorProvider>())
.Returns(true);
sutProvider.GetDependency<IDuoUniversalTokenService>()
.BuildDuoTwoFactorClientAsync(Arg.Any<TwoFactorProvider>())
.Returns(client);
}
private Duo.Client BuildDuoClient()
{
var clientId = new string('c', 20);
var clientSecret = new string('s', 40);
return new Duo.ClientBuilder(clientId, clientSecret, "api-abcd1234.duosecurity.com", "redirectUrl").Build();
}
private string GetTwoFactorDuoProvidersJson()
{
return
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
}