1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-05 23:53:33 +00:00

Add example of delegated UI class

This commit is contained in:
Andreas Coroiu
2024-02-19 16:44:15 +01:00
parent c801b2fc3a
commit da051b8fc1
3 changed files with 87 additions and 7 deletions

View File

@@ -0,0 +1,65 @@
using Bit.Core.Abstractions;
namespace Bit.Core.Utilities.Fido2
{
/// <summary>
/// This implementation is used when all interactions are delegated to the operating system.
/// Most often these decisions have already been made by the time the Authenticator is called.
///
/// This is only supported for assertion operations. Attestation requires the user to interact
/// with the app directly.
/// </summary>
public class Fido2DelegatedUserInterface : IFido2UserInterface
{
private string _cipherId = null;
private bool _userVerified = false;
private Func<Task> _ensureUnlockedVaultAsyncCallback;
/// <summary>
/// Indicates that the user has already picked a credential from a list of existing credentials.
/// Picking a credential also assumes user presence.
/// </summary>
public Fido2DelegatedUserInterface UserPickedCredential(string cipherId)
{
_cipherId = cipherId;
return this;
}
/// <summary>
/// Indicates that the user was verified by the OS, e.g. by a fingerprint or face scan.
/// </summary>
public Fido2DelegatedUserInterface UserIsVerified()
{
_userVerified = true;
return this;
}
public Fido2DelegatedUserInterface WithEnsureUnlockedVaultAsyncCallback(Func<Task> callback)
{
_ensureUnlockedVaultAsyncCallback = callback;
return this;
}
public Task<Fido2PickCredentialResult> PickCredentialAsync(Fido2PickCredentialParams parameters)
{
return Task.FromResult(new Fido2PickCredentialResult
{
CipherId = _cipherId,
UserVerified = _userVerified
});
}
public Task EnsureUnlockedVaultAsync()
{
if (_ensureUnlockedVaultAsyncCallback != null)
{
return _ensureUnlockedVaultAsyncCallback();
}
throw new Exception("No callback provided to ensure the vault is unlocked");
}
public Task InformExcludedCredential(string[] existingCipherIds) => throw new NotImplementedException();
public Task<Fido2ConfirmNewCredentialResult> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams parameters) => throw new NotImplementedException();
}
}

View File

@@ -21,6 +21,8 @@ namespace Bit.iOS.Autofill
{
private readonly LazyResolve<ICipherService> _cipherService = new LazyResolve<ICipherService>();
private readonly Fido2DelegatedUserInterface _userInterface = new Fido2DelegatedUserInterface();
private IFido2AuthenticatorService _fido2AuthService;
private IFido2AuthenticatorService Fido2AuthService
{
@@ -29,7 +31,7 @@ namespace Bit.iOS.Autofill
if (_fido2AuthService is null)
{
_fido2AuthService = ServiceContainer.Resolve<IFido2AuthenticatorService>();
_fido2AuthService.Init(this);
_fido2AuthService.Init(_userInterface);
}
return _fido2AuthService;
}
@@ -159,18 +161,20 @@ namespace Bit.iOS.Autofill
}).ToArray();
}
public class UserInteractionRequiredException : Exception {}
private async Task ProvideCredentialWithoutUserInteractionAsync(ASPasskeyCredentialRequest passkeyCredentialRequest)
{
InitAppIfNeeded();
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
await _stateService.Value.SetPasswordVerifiedAutofillAsync(false);
if (!await IsAuthed() || await IsLocked())
{
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
return;
}
_context.PasskeyCredentialRequest = passkeyCredentialRequest;
await ProvideCredentialAsync(false);
try {
await ProvideCredentialAsync(false);
} catch (UserInteractionRequiredException) {
CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
}
}
public async Task CompleteAssertionRequestAsync(string rpId, NSData userHandleData, NSData credentialIdData, string cipherId)
@@ -209,6 +213,10 @@ namespace Bit.iOS.Autofill
}
});
_userInterface.UserPickedCredential(cipherId);
// if (os.PerformedFaceID) {
_userInterface.UserIsVerified();
//}
ClipLogger.Log("fido2AssertionResult:" + fido2AssertionResult);

View File

@@ -506,6 +506,13 @@ namespace Bit.iOS.Autofill
private async Task ProvideCredentialAsync(bool userInteraction = true)
{
_userInterface.WithEnsureUnlockedVaultAsyncCallback(async () => {
if (!userInteraction && (!await IsAuthed() || await IsLocked())) {
throw new UserInteractionRequiredException();
}
await EnsureUnlockedVaultAsync();
});
try
{
ClipLogger.Log("ProvideCredentialAsync");