diff --git a/src/Core/Abstractions/IConditionedAwaiterManager.cs b/src/Core/Abstractions/IConditionedAwaiterManager.cs index 6eb4df5dc..3a6a0ec0f 100644 --- a/src/Core/Abstractions/IConditionedAwaiterManager.cs +++ b/src/Core/Abstractions/IConditionedAwaiterManager.cs @@ -1,12 +1,10 @@ -using System; -using System.Threading.Tasks; - -namespace Bit.Core.Abstractions +namespace Bit.Core.Abstractions { public enum AwaiterPrecondition { EnvironmentUrlsInited, - AndroidWindowCreated + AndroidWindowCreated, + AutofillIOSExtensionViewDidAppear } public interface IConditionedAwaiterManager @@ -14,5 +12,6 @@ namespace Bit.Core.Abstractions Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition); void SetAsCompleted(AwaiterPrecondition awaiterPrecondition); void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex); + void Recreate(AwaiterPrecondition awaiterPrecondition); } } diff --git a/src/Core/Resources/Localization/AppResources.Designer.cs b/src/Core/Resources/Localization/AppResources.Designer.cs index 6ea8bcc87..cc9b7c6ab 100644 --- a/src/Core/Resources/Localization/AppResources.Designer.cs +++ b/src/Core/Resources/Localization/AppResources.Designer.cs @@ -7685,6 +7685,15 @@ namespace Bit.Core.Resources.Localization { } } + /// + /// Looks up a localized string similar to Verifying identity.... + /// + public static string VerifyingIdentityEllipsis { + get { + return ResourceManager.GetString("VerifyingIdentityEllipsis", resourceCulture); + } + } + /// /// Looks up a localized string similar to Verify master password. /// diff --git a/src/Core/Resources/Localization/AppResources.resx b/src/Core/Resources/Localization/AppResources.resx index bfe9be087..36ffeee11 100644 --- a/src/Core/Resources/Localization/AppResources.resx +++ b/src/Core/Resources/Localization/AppResources.resx @@ -2936,6 +2936,9 @@ Do you want to switch to this account? There was a problem reading your passkey for {0}. Try again later. The parameter is the RpId + + Verifying identity... + Passwords diff --git a/src/Core/Services/ConditionedAwaiterManager.cs b/src/Core/Services/ConditionedAwaiterManager.cs index 30b744923..62f959e2c 100644 --- a/src/Core/Services/ConditionedAwaiterManager.cs +++ b/src/Core/Services/ConditionedAwaiterManager.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Threading.Tasks; +using System.Collections.Concurrent; using Bit.Core.Abstractions; namespace Bit.Core.Services @@ -11,7 +8,8 @@ namespace Bit.Core.Services private readonly ConcurrentDictionary> _preconditionsTasks = new ConcurrentDictionary> { [AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource(), - [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource() + [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource(), + [AwaiterPrecondition.AutofillIOSExtensionViewDidAppear] = new TaskCompletionSource() }; public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition) @@ -39,5 +37,15 @@ namespace Bit.Core.Services tcs.TrySetException(ex); } } + + public void Recreate(AwaiterPrecondition awaiterPrecondition) + { + if (_preconditionsTasks.TryRemove(awaiterPrecondition, out var oldTcs)) + { + oldTcs.TrySetCanceled(); + + _preconditionsTasks.TryAdd(awaiterPrecondition, new TaskCompletionSource()); + } + } } } diff --git a/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs b/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs index 901ecbd2a..7f42aafa6 100644 --- a/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs +++ b/src/Core/Services/UserVerification/Fido2UserVerificationPreferredServiceStrategy.cs @@ -19,7 +19,10 @@ namespace Bit.Core.Services.UserVerification return true; } - options.OnNeedUI?.Invoke(); + if (options.OnNeedUITask != null) + { + await options.OnNeedUITask(); + } var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); if (canPerformOSUnlock) diff --git a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs index a918f6f58..592803045 100644 --- a/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs +++ b/src/Core/Services/UserVerification/Fido2UserVerificationRequiredServiceStrategy.cs @@ -23,7 +23,10 @@ namespace Bit.Core.Services.UserVerification return true; } - options.OnNeedUI?.Invoke(); + if (options.OnNeedUITask != null) + { + await options.OnNeedUITask(); + } var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); if (canPerformOSUnlock) diff --git a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs index 6aacff6f7..3fc13be1b 100644 --- a/src/Core/Services/UserVerification/UserVerificationMediatorService.cs +++ b/src/Core/Services/UserVerification/UserVerificationMediatorService.cs @@ -39,7 +39,11 @@ namespace Bit.Core.Services.UserVerification { if (await ShouldPerformMasterPasswordRepromptAsync(options)) { - options.OnNeedUI?.Invoke(); + if (options.OnNeedUITask != null) + { + await options.OnNeedUITask(); + } + return await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Enums.CipherRepromptType.Password); } diff --git a/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs b/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs index b0e9071d2..3304e64c8 100644 --- a/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs +++ b/src/Core/Utilities/Fido2/Fido2UserVerificationOptions.cs @@ -6,19 +6,19 @@ Fido2UserVerificationPreference userVerificationPreference, bool hasVaultBeenUnlockedInTransaction, string rpId = null, - Action onNeedUI = null) + Func onNeedUITask = null) { ShouldCheckMasterPasswordReprompt = shouldCheckMasterPasswordReprompt; UserVerificationPreference = userVerificationPreference; HasVaultBeenUnlockedInTransaction = hasVaultBeenUnlockedInTransaction; RpId = rpId; - OnNeedUI = onNeedUI; + OnNeedUITask = onNeedUITask; } public bool ShouldCheckMasterPasswordReprompt { get; } public Fido2UserVerificationPreference UserVerificationPreference { get; } public bool HasVaultBeenUnlockedInTransaction { get; } public string RpId { get; } - public Action OnNeedUI { get; } + public Func OnNeedUITask { get; } } } diff --git a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs index d5e5b02df..1b8fdf090 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.Passkeys.cs @@ -270,13 +270,21 @@ namespace Bit.iOS.Autofill userVerificationPreference, _context.VaultUnlockedDuringThisSession, _context.PasskeyCredentialIdentity?.RelyingPartyIdentifier, - () => + async () => { if (_context.IsExecutingWithoutUserInteraction) { CancelRequest(ASExtensionErrorCode.UserInteractionRequired); throw new InvalidOperationNeedsUIException(); } + + // HACK: [PM-6685] There are some devices that end up with a race condition when doing biometrics authentication + // that the check is trying to be done before the iOS extension UI is shown, which cause the bio check to fail. + // So a workaround is to show a toast which force the iOS extension UI to be shown and then awaiting for the + // precondition that the view did appear before continuing with the verification. + _platformUtilsService.Value.ShowToast(null, null, AppResources.VerifyingIdentityEllipsis); + + await _conditionedAwaiterManager.Value.GetAwaiterForPrecondition(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear); }) ); } diff --git a/src/iOS.Autofill/CredentialProviderViewController.cs b/src/iOS.Autofill/CredentialProviderViewController.cs index f2cf4d0eb..51b753769 100644 --- a/src/iOS.Autofill/CredentialProviderViewController.cs +++ b/src/iOS.Autofill/CredentialProviderViewController.cs @@ -32,6 +32,7 @@ namespace Bit.iOS.Autofill private IAccountsManager _accountsManager; private readonly LazyResolve _stateService = new LazyResolve(); + private readonly LazyResolve _conditionedAwaiterManager = new LazyResolve(); public CredentialProviderViewController(IntPtr handle) : base(handle) @@ -56,6 +57,8 @@ namespace Bit.iOS.Autofill { ExtContext = ExtensionContext }; + + _conditionedAwaiterManager.Value.Recreate(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear); } catch (Exception ex) { @@ -63,6 +66,13 @@ namespace Bit.iOS.Autofill } } + public override void ViewDidAppear(bool animated) + { + base.ViewDidAppear(animated); + + _conditionedAwaiterManager.Value.SetAsCompleted(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear); + } + public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers) { try