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:
65
src/Core/Utilities/Fido2/Fido2DelegatedUserInterface.cs
Normal file
65
src/Core/Utilities/Fido2/Fido2DelegatedUserInterface.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user