mirror of
https://github.com/bitwarden/mobile
synced 2025-12-28 22:23:35 +00:00
* PM-6441 Implement passkeys User Verification * PM-6441 Reorganized UserVerificationMediatorService so everything is not in the same file * PM-6441 Fix Unit tests * PM-6441 Refactor UserVerification on Fido2Authenticator and Client services to be of an enum type so we can see which specific preference the RP sent and to be passed into the user verification mediator service to perform the correct flow depending on that. Also updated Unit tests. * PM-6441 Changed user verification logic a bit so if preference is Preferred and the app has the ability to verify the user then enforce required UV and fix issue on on Discouraged to take into account MP reprompt
127 lines
5.5 KiB
C#
127 lines
5.5 KiB
C#
using System;
|
|
using System.Threading.Tasks;
|
|
using Bit.Core.Abstractions;
|
|
using Bit.Core.Services;
|
|
using Bit.Core.Models.View;
|
|
using Bit.Core.Enums;
|
|
using Bit.Test.Common.AutoFixture;
|
|
using Bit.Test.Common.AutoFixture.Attributes;
|
|
using NSubstitute;
|
|
using Xunit;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using Bit.Core.Utilities;
|
|
|
|
namespace Bit.Core.Test.Services
|
|
{
|
|
public class Fido2AuthenticatorSilentCredentialDiscoveryTests
|
|
{
|
|
[Theory]
|
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
|
|
public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_NoCredentialsExist(SutProvider<Fido2AuthenticatorService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<ICipherService>().GetAllDecryptedAsync().Returns(Task.FromResult(new List<CipherView>()));
|
|
|
|
var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com");
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
|
|
public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_OnlyNonDiscoverableCredentialsExist(SutProvider<Fido2AuthenticatorService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<ICipherService>().GetAllDecryptedAsync().Returns(Task.FromResult(new List<CipherView>
|
|
{
|
|
CreateCipherView("bitwarden.com", false),
|
|
CreateCipherView("bitwarden.com", false),
|
|
CreateCipherView("bitwarden.com", false)
|
|
}));
|
|
|
|
var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com");
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
|
|
public async Task SilentCredentialDiscoveryAsync_ReturnsEmptyArray_NoCredentialsWithMatchingRpIdExist(SutProvider<Fido2AuthenticatorService> sutProvider)
|
|
{
|
|
sutProvider.GetDependency<ICipherService>().GetAllDecryptedAsync().Returns(Task.FromResult(new List<CipherView>
|
|
{
|
|
CreateCipherView("a.bitwarden.com", true),
|
|
CreateCipherView("example.com", true)
|
|
}));
|
|
|
|
var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com");
|
|
|
|
Assert.Empty(result);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineCustomAutoData(new[] { typeof(SutProviderCustomization) })]
|
|
public async Task SilentCredentialDiscoveryAsync_ReturnsCredentials_DiscoverableCredentialsWithMatchingRpIdExist(SutProvider<Fido2AuthenticatorService> sutProvider)
|
|
{
|
|
var matchingCredentials = new List<CipherView> {
|
|
CreateCipherView("bitwarden.com", true),
|
|
CreateCipherView("bitwarden.com", true)
|
|
};
|
|
var nonMatchingCredentials = new List<CipherView> {
|
|
CreateCipherView("example.com", true)
|
|
};
|
|
sutProvider.GetDependency<ICipherService>().GetAllDecryptedAsync().Returns(
|
|
matchingCredentials.Concat(nonMatchingCredentials).ToList()
|
|
);
|
|
|
|
var result = await sutProvider.Sut.SilentCredentialDiscoveryAsync("bitwarden.com");
|
|
|
|
Assert.True(
|
|
result.SequenceEqual(matchingCredentials.Select(c => new Fido2AuthenticatorDiscoverableCredentialMetadata {
|
|
Type = Constants.DefaultFido2CredentialType,
|
|
Id = c.Login.MainFido2Credential.CredentialId.GuidToRawFormat(),
|
|
RpId = "bitwarden.com",
|
|
UserHandle = c.Login.MainFido2Credential.UserHandleValue,
|
|
UserName = c.Login.MainFido2Credential.UserName
|
|
}), new MetadataComparer())
|
|
);
|
|
}
|
|
|
|
private byte[] RandomBytes(int length)
|
|
{
|
|
var bytes = new byte[length];
|
|
new Random().NextBytes(bytes);
|
|
return bytes;
|
|
}
|
|
|
|
#nullable enable
|
|
private CipherView CreateCipherView(string rpId, bool discoverable)
|
|
{
|
|
return new CipherView {
|
|
Type = CipherType.Login,
|
|
Id = Guid.NewGuid().ToString(),
|
|
Reprompt = CipherRepromptType.None,
|
|
Login = new LoginView {
|
|
Fido2Credentials = new List<Fido2CredentialView> {
|
|
new Fido2CredentialView {
|
|
CredentialId = Guid.NewGuid().ToString(),
|
|
RpId = rpId ?? "null.com",
|
|
DiscoverableValue = discoverable,
|
|
UserHandleValue = RandomBytes(32),
|
|
KeyValue = "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgO4wC7AlY4eJP7uedRUJGYsAIJAd6gN1Vp7uJh6xXAp6hRANCAARGvr56F_t27DEG1Tzl-qJRhrTUtC7jOEbasAEEZcE3TiMqoWCan0sxKDPylhRYk-1qyrBC_feN1UtGWH57sROa"
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private class MetadataComparer : IEqualityComparer<Fido2AuthenticatorDiscoverableCredentialMetadata>
|
|
{
|
|
public int GetHashCode([DisallowNull] Fido2AuthenticatorDiscoverableCredentialMetadata obj) => throw new NotImplementedException();
|
|
|
|
public bool Equals(Fido2AuthenticatorDiscoverableCredentialMetadata? a, Fido2AuthenticatorDiscoverableCredentialMetadata? b) =>
|
|
a != null && b != null && a.Type == b.Type && a.RpId == b.RpId && a.UserName == b.UserName && a.Id.SequenceEqual(b.Id) && a.UserHandle.SequenceEqual(b.UserHandle);
|
|
}
|
|
}
|
|
}
|