1
0
mirror of https://github.com/bitwarden/server.git synced 2024-11-28 13:15:12 +01:00
bitwarden-server/test/Identity.Test/IdentityServer/TwoFactorAuthenticationValidatorTests.cs
Ike c028c68d9c
[PM-6666] Two factor Validator refactor (#4894)
* initial device removal

* Unit Testing

* Finalized tests

* initial commit refactoring two factor

* initial tests

* Unit Tests

* initial device removal

* Unit Testing

* Finalized tests

* initial commit refactoring two factor

* initial tests

* Unit Tests

* Fixing some tests

* renaming and reorganizing

* refactored two factor flows

* fixed a possible issue with object mapping.

* Update TwoFactorAuthenticationValidator.cs

removed unused code
2024-10-24 10:41:25 -07:00

576 lines
21 KiB
C#

using Bit.Core;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.Auth.Enums;
using Bit.Core.Auth.Identity;
using Bit.Core.Auth.Models;
using Bit.Core.Auth.Models.Business.Tokenables;
using Bit.Core.Context;
using Bit.Core.Entities;
using Bit.Core.Models.Data.Organizations;
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Core.Tokens;
using Bit.Identity.IdentityServer.RequestValidators;
using Bit.Identity.Test.Wrappers;
using Bit.Test.Common.AutoFixture.Attributes;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NSubstitute;
using Xunit;
using AuthFixtures = Bit.Identity.Test.AutoFixture;
namespace Bit.Identity.Test.IdentityServer;
public class TwoFactorAuthenticationValidatorTests
{
private readonly IUserService _userService;
private readonly UserManagerTestWrapper<User> _userManager;
private readonly IOrganizationDuoWebTokenProvider _organizationDuoWebTokenProvider;
private readonly ITemporaryDuoWebV4SDKService _temporaryDuoWebV4SDKService;
private readonly IFeatureService _featureService;
private readonly IApplicationCacheService _applicationCacheService;
private readonly IOrganizationUserRepository _organizationUserRepository;
private readonly IOrganizationRepository _organizationRepository;
private readonly IDataProtectorTokenFactory<SsoEmail2faSessionTokenable> _ssoEmail2faSessionTokenable;
private readonly ICurrentContext _currentContext;
private readonly TwoFactorAuthenticationValidator _sut;
public TwoFactorAuthenticationValidatorTests()
{
_userService = Substitute.For<IUserService>();
_userManager = SubstituteUserManager();
_organizationDuoWebTokenProvider = Substitute.For<IOrganizationDuoWebTokenProvider>();
_temporaryDuoWebV4SDKService = Substitute.For<ITemporaryDuoWebV4SDKService>();
_featureService = Substitute.For<IFeatureService>();
_applicationCacheService = Substitute.For<IApplicationCacheService>();
_organizationUserRepository = Substitute.For<IOrganizationUserRepository>();
_organizationRepository = Substitute.For<IOrganizationRepository>();
_ssoEmail2faSessionTokenable = Substitute.For<IDataProtectorTokenFactory<SsoEmail2faSessionTokenable>>();
_currentContext = Substitute.For<ICurrentContext>();
_sut = new TwoFactorAuthenticationValidator(
_userService,
_userManager,
_organizationDuoWebTokenProvider,
_temporaryDuoWebV4SDKService,
_featureService,
_applicationCacheService,
_organizationUserRepository,
_organizationRepository,
_ssoEmail2faSessionTokenable,
_currentContext);
}
[Theory]
[BitAutoData("password")]
[BitAutoData("authorization_code")]
public async void RequiresTwoFactorAsync_IndividualOnly_Required_ReturnTrue(
string grantType,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
User user)
{
// Arrange
request.GrantType = grantType;
// All three of these must be true for the two factor authentication to be required
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.SUPPORTS_TWO_FACTOR = true;
// In order for the two factor authentication to be required, the user must have at least one two factor provider
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
// Act
var result = await _sut.RequiresTwoFactorAsync(user, request);
// Assert
Assert.True(result.Item1);
Assert.Null(result.Item2);
}
[Theory]
[BitAutoData("client_credentials")]
[BitAutoData("webauthn")]
public async void RequiresTwoFactorAsync_NotRequired_ReturnFalse(
string grantType,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
User user)
{
// Arrange
request.GrantType = grantType;
// Act
var result = await _sut.RequiresTwoFactorAsync(user, request);
// Assert
Assert.False(result.Item1);
Assert.Null(result.Item2);
}
[Theory]
[BitAutoData("password")]
[BitAutoData("authorization_code")]
public async void RequiresTwoFactorAsync_IndividualFalse_OrganizationRequired_ReturnTrue(
string grantType,
[AuthFixtures.ValidatedTokenRequest] ValidatedTokenRequest request,
User user,
OrganizationUserOrganizationDetails orgUser,
Organization organization,
ICollection<CurrentContextOrganization> organizationCollection)
{
// Arrange
request.GrantType = grantType;
// Link the orgUser to the User making the request
orgUser.UserId = user.Id;
// Link organization to the organization user
organization.Id = orgUser.OrganizationId;
// Set Organization 2FA to required
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
organization.Enabled = true;
// Make sure organization list is not empty
organizationCollection.Clear();
// Fix OrganizationUser Permissions field
orgUser.Permissions = "{}";
organizationCollection.Add(new CurrentContextOrganization(orgUser));
_currentContext.OrganizationMembershipAsync(Arg.Any<IOrganizationUserRepository>(), Arg.Any<Guid>())
.Returns(Task.FromResult(organizationCollection));
_applicationCacheService.GetOrganizationAbilitiesAsync()
.Returns(new Dictionary<Guid, OrganizationAbility>()
{
{ orgUser.OrganizationId, new OrganizationAbility(organization)}
});
_organizationRepository.GetManyByUserIdAsync(Arg.Any<Guid>()).Returns([organization]);
// Act
var result = await _sut.RequiresTwoFactorAsync(user, request);
// Assert
Assert.True(result.Item1);
Assert.NotNull(result.Item2);
Assert.IsType<Organization>(result.Item2);
}
[Theory]
[BitAutoData]
public async void BuildTwoFactorResultAsync_NoProviders_ReturnsNull(
User user,
Organization organization)
{
// Arrange
organization.Use2fa = true;
organization.TwoFactorProviders = "{}";
organization.Enabled = true;
user.TwoFactorProviders = "";
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, organization);
// Assert
Assert.Null(result);
}
[Theory]
[BitAutoData]
public async void BuildTwoFactorResultAsync_OrganizationProviders_NotEnabled_ReturnsNull(
User user,
Organization organization)
{
// Arrange
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationNotEnabledDuoProviderJson();
organization.Enabled = true;
user.TwoFactorProviders = null;
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, organization);
// Assert
Assert.Null(result);
}
[Theory]
[BitAutoData]
public async void BuildTwoFactorResultAsync_OrganizationProviders_ReturnsNotNull(
User user,
Organization organization)
{
// Arrange
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
organization.Enabled = true;
user.TwoFactorProviders = null;
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, organization);
// Assert
Assert.NotNull(result);
Assert.IsType<Dictionary<string, object>>(result);
Assert.NotEmpty(result);
Assert.True(result.ContainsKey("TwoFactorProviders2"));
}
[Theory]
[BitAutoData]
public async void BuildTwoFactorResultAsync_IndividualProviders_NotEnabled_ReturnsNull(
User user)
{
// Arrange
user.TwoFactorProviders = GetTwoFactorIndividualNotEnabledProviderJson(TwoFactorProviderType.Email);
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, null);
// Assert
Assert.Null(result);
}
[Theory]
[BitAutoData]
public async void BuildTwoFactorResultAsync_IndividualProviders_ReturnsNotNull(
User user)
{
// Arrange
_userService.CanAccessPremium(user).Returns(true);
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(TwoFactorProviderType.Duo);
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, null);
// Assert
Assert.NotNull(result);
Assert.IsType<Dictionary<string, object>>(result);
Assert.NotEmpty(result);
Assert.True(result.ContainsKey("TwoFactorProviders2"));
}
[Theory]
[BitAutoData(TwoFactorProviderType.Email)]
public async void BuildTwoFactorResultAsync_IndividualEmailProvider_SendsEmail_SetsSsoToken_ReturnsNotNull(
TwoFactorProviderType providerType,
User user)
{
// Arrange
var providerTypeInt = (int)providerType;
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.SUPPORTS_TWO_FACTOR = true;
_userManager.TWO_FACTOR_PROVIDERS = [providerType.ToString()];
_userService.TwoFactorProviderIsEnabledAsync(Arg.Any<TwoFactorProviderType>(), user)
.Returns(true);
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, null);
// Assert
Assert.NotNull(result);
Assert.IsType<Dictionary<string, object>>(result);
Assert.NotEmpty(result);
Assert.True(result.ContainsKey("TwoFactorProviders2"));
var providers = (Dictionary<string, Dictionary<string, object>>)result["TwoFactorProviders2"];
Assert.True(providers.ContainsKey(providerTypeInt.ToString()));
Assert.True(result.ContainsKey("SsoEmail2faSessionToken"));
Assert.True(result.ContainsKey("Email"));
await _userService.Received(1).SendTwoFactorEmailAsync(Arg.Any<User>());
}
[Theory]
[BitAutoData(TwoFactorProviderType.Duo)]
[BitAutoData(TwoFactorProviderType.WebAuthn)]
[BitAutoData(TwoFactorProviderType.Email)]
[BitAutoData(TwoFactorProviderType.YubiKey)]
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
public async void BuildTwoFactorResultAsync_IndividualProvider_ReturnMatchesType(
TwoFactorProviderType providerType,
User user)
{
// Arrange
var providerTypeInt = (int)providerType;
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.SUPPORTS_TWO_FACTOR = true;
_userManager.TWO_FACTOR_PROVIDERS = [providerType.ToString()];
_userManager.TWO_FACTOR_TOKEN = "{\"Key1\":\"WebauthnToken\"}";
_userService.CanAccessPremium(user).Returns(true);
// Act
var result = await _sut.BuildTwoFactorResultAsync(user, null);
// Assert
Assert.NotNull(result);
Assert.IsType<Dictionary<string, object>>(result);
Assert.NotEmpty(result);
Assert.True(result.ContainsKey("TwoFactorProviders2"));
var providers = (Dictionary<string, Dictionary<string, object>>)result["TwoFactorProviders2"];
Assert.True(providers.ContainsKey(providerTypeInt.ToString()));
}
[Theory]
[BitAutoData]
public async void VerifyTwoFactorAsync_Individual_TypeNull_ReturnsFalse(
User user,
string token)
{
// Arrange
_userService.TwoFactorProviderIsEnabledAsync(
TwoFactorProviderType.Email, user).Returns(true);
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
// Act
var result = await _sut.VerifyTwoFactor(
user, null, TwoFactorProviderType.U2f, token);
// Assert
Assert.False(result);
}
[Theory]
[BitAutoData]
public async void VerifyTwoFactorAsync_Individual_NotEnabled_ReturnsFalse(
User user,
string token)
{
// Arrange
_userService.TwoFactorProviderIsEnabledAsync(
TwoFactorProviderType.Email, user).Returns(false);
_userManager.TWO_FACTOR_PROVIDERS = ["email"];
// Act
var result = await _sut.VerifyTwoFactor(
user, null, TwoFactorProviderType.Email, token);
// Assert
Assert.False(result);
}
[Theory]
[BitAutoData]
public async void VerifyTwoFactorAsync_Organization_NotEnabled_ReturnsFalse(
User user,
string token)
{
// Arrange
_userService.TwoFactorProviderIsEnabledAsync(
TwoFactorProviderType.OrganizationDuo, user).Returns(false);
_userManager.TWO_FACTOR_PROVIDERS = ["OrganizationDuo"];
// Act
var result = await _sut.VerifyTwoFactor(
user, null, TwoFactorProviderType.OrganizationDuo, token);
// Assert
Assert.False(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.Duo)]
[BitAutoData(TwoFactorProviderType.WebAuthn)]
[BitAutoData(TwoFactorProviderType.Email)]
[BitAutoData(TwoFactorProviderType.YubiKey)]
[BitAutoData(TwoFactorProviderType.Remember)]
public async void VerifyTwoFactorAsync_Individual_ValidToken_ReturnsTrue(
TwoFactorProviderType providerType,
User user,
string token)
{
// Arrange
_userService.TwoFactorProviderIsEnabledAsync(
providerType, user).Returns(true);
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
// Act
var result = await _sut.VerifyTwoFactor(user, null, providerType, token);
// Assert
Assert.True(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.Duo)]
[BitAutoData(TwoFactorProviderType.WebAuthn)]
[BitAutoData(TwoFactorProviderType.Email)]
[BitAutoData(TwoFactorProviderType.YubiKey)]
[BitAutoData(TwoFactorProviderType.Remember)]
public async void VerifyTwoFactorAsync_Individual_InvalidToken_ReturnsFalse(
TwoFactorProviderType providerType,
User user,
string token)
{
// Arrange
_userService.TwoFactorProviderIsEnabledAsync(
providerType, user).Returns(true);
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
// Act
var result = await _sut.VerifyTwoFactor(user, null, providerType, token);
// Assert
Assert.False(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
public async void VerifyTwoFactorAsync_Organization_ValidToken_ReturnsTrue(
TwoFactorProviderType providerType,
User user,
Organization organization,
string token)
{
// Arrange
_organizationDuoWebTokenProvider.ValidateAsync(
token, organization, user).Returns(true);
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
organization.Enabled = true;
// Act
var result = await _sut.VerifyTwoFactor(
user, organization, providerType, token);
// Assert
Assert.True(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.Duo)]
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
public async void VerifyTwoFactorAsync_TemporaryDuoService_ValidToken_ReturnsTrue(
TwoFactorProviderType providerType,
User user,
Organization organization,
string token)
{
// Arrange
_featureService.IsEnabled(FeatureFlagKeys.DuoRedirect).Returns(true);
_userService.TwoFactorProviderIsEnabledAsync(providerType, user).Returns(true);
_temporaryDuoWebV4SDKService.ValidateAsync(
token, Arg.Any<TwoFactorProvider>(), user).Returns(true);
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
organization.Enabled = true;
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.TWO_FACTOR_TOKEN = token;
_userManager.TWO_FACTOR_TOKEN_VERIFIED = true;
// Act
var result = await _sut.VerifyTwoFactor(
user, organization, providerType, token);
// Assert
Assert.True(result);
}
[Theory]
[BitAutoData(TwoFactorProviderType.Duo)]
[BitAutoData(TwoFactorProviderType.OrganizationDuo)]
public async void VerifyTwoFactorAsync_TemporaryDuoService_InvalidToken_ReturnsFalse(
TwoFactorProviderType providerType,
User user,
Organization organization,
string token)
{
// Arrange
_featureService.IsEnabled(FeatureFlagKeys.DuoRedirect).Returns(true);
_userService.TwoFactorProviderIsEnabledAsync(providerType, user).Returns(true);
_temporaryDuoWebV4SDKService.ValidateAsync(
token, Arg.Any<TwoFactorProvider>(), user).Returns(true);
user.TwoFactorProviders = GetTwoFactorIndividualProviderJson(providerType);
organization.Use2fa = true;
organization.TwoFactorProviders = GetTwoFactorOrganizationDuoProviderJson();
organization.Enabled = true;
_userManager.TWO_FACTOR_ENABLED = true;
_userManager.TWO_FACTOR_TOKEN = token;
_userManager.TWO_FACTOR_TOKEN_VERIFIED = false;
// Act
var result = await _sut.VerifyTwoFactor(
user, organization, providerType, token);
// Assert
Assert.True(result);
}
private static UserManagerTestWrapper<User> SubstituteUserManager()
{
return new UserManagerTestWrapper<User>(
Substitute.For<IUserTwoFactorStore<User>>(),
Substitute.For<IOptions<IdentityOptions>>(),
Substitute.For<IPasswordHasher<User>>(),
Enumerable.Empty<IUserValidator<User>>(),
Enumerable.Empty<IPasswordValidator<User>>(),
Substitute.For<ILookupNormalizer>(),
Substitute.For<IdentityErrorDescriber>(),
Substitute.For<IServiceProvider>(),
Substitute.For<ILogger<UserManager<User>>>());
}
private static string GetTwoFactorOrganizationDuoProviderJson(bool enabled = true)
{
return
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private static string GetTwoFactorOrganizationNotEnabledDuoProviderJson(bool enabled = true)
{
return
"{\"6\":{\"Enabled\":false,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
}
private static string GetTwoFactorIndividualProviderJson(TwoFactorProviderType providerType)
{
return providerType switch
{
TwoFactorProviderType.Duo => "{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}",
TwoFactorProviderType.Email => "{\"1\":{\"Enabled\":true,\"MetaData\":{\"Email\":\"user@test.dev\"}}}",
TwoFactorProviderType.WebAuthn => "{\"7\":{\"Enabled\":true,\"MetaData\":{\"Key1\":{\"Name\":\"key1\",\"Descriptor\":{\"Type\":0,\"Id\":\"keyId\",\"Transports\":null},\"PublicKey\":\"key\",\"UserHandle\":\"handle\",\"SignatureCounter\":0,\"CredType\":\"none\",\"RegDate\":\"2022-01-01T00:00:00Z\",\"AaGuid\":\"00000000-0000-0000-0000-000000000000\",\"Migrated\":false}}}}",
TwoFactorProviderType.YubiKey => "{\"3\":{\"Enabled\":true,\"MetaData\":{\"Id\":\"yubikeyId\",\"Nfc\":true}}}",
TwoFactorProviderType.OrganizationDuo => "{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}",
_ => "{}",
};
}
private static string GetTwoFactorIndividualNotEnabledProviderJson(TwoFactorProviderType providerType)
{
return providerType switch
{
TwoFactorProviderType.Duo => "{\"2\":{\"Enabled\":false,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}",
TwoFactorProviderType.Email => "{\"1\":{\"Enabled\":false,\"MetaData\":{\"Email\":\"user@test.dev\"}}}",
TwoFactorProviderType.WebAuthn => "{\"7\":{\"Enabled\":false,\"MetaData\":{\"Key1\":{\"Name\":\"key1\",\"Descriptor\":{\"Type\":0,\"Id\":\"keyId\",\"Transports\":null},\"PublicKey\":\"key\",\"UserHandle\":\"handle\",\"SignatureCounter\":0,\"CredType\":\"none\",\"RegDate\":\"2022-01-01T00:00:00Z\",\"AaGuid\":\"00000000-0000-0000-0000-000000000000\",\"Migrated\":false}}}}",
TwoFactorProviderType.YubiKey => "{\"3\":{\"Enabled\":false,\"MetaData\":{\"Id\":\"yubikeyId\",\"Nfc\":true}}}",
TwoFactorProviderType.OrganizationDuo => "{\"6\":{\"Enabled\":false,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}",
_ => "{}",
};
}
}