1
0
mirror of https://github.com/bitwarden/mobile synced 2025-12-20 10:13:42 +00:00

PM-6685 Fix race condition issue where the biometrics check is being done before the iOS extension is being shown. So when we need the UI, we wait until ViewDidAppear happens. (#3078)

This commit is contained in:
Federico Maccaroni
2024-03-14 18:07:52 -03:00
committed by GitHub
parent 144fc7c727
commit 74085689d3
10 changed files with 64 additions and 17 deletions

View File

@@ -1,12 +1,10 @@
using System; namespace Bit.Core.Abstractions
using System.Threading.Tasks;
namespace Bit.Core.Abstractions
{ {
public enum AwaiterPrecondition public enum AwaiterPrecondition
{ {
EnvironmentUrlsInited, EnvironmentUrlsInited,
AndroidWindowCreated AndroidWindowCreated,
AutofillIOSExtensionViewDidAppear
} }
public interface IConditionedAwaiterManager public interface IConditionedAwaiterManager
@@ -14,5 +12,6 @@ namespace Bit.Core.Abstractions
Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition); Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition);
void SetAsCompleted(AwaiterPrecondition awaiterPrecondition); void SetAsCompleted(AwaiterPrecondition awaiterPrecondition);
void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex); void SetException(AwaiterPrecondition awaiterPrecondition, Exception ex);
void Recreate(AwaiterPrecondition awaiterPrecondition);
} }
} }

View File

@@ -7685,6 +7685,15 @@ namespace Bit.Core.Resources.Localization {
} }
} }
/// <summary>
/// Looks up a localized string similar to Verifying identity....
/// </summary>
public static string VerifyingIdentityEllipsis {
get {
return ResourceManager.GetString("VerifyingIdentityEllipsis", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Verify master password. /// Looks up a localized string similar to Verify master password.
/// </summary> /// </summary>

View File

@@ -2936,6 +2936,9 @@ Do you want to switch to this account?</value>
<value>There was a problem reading your passkey for {0}. Try again later.</value> <value>There was a problem reading your passkey for {0}. Try again later.</value>
<comment>The parameter is the RpId</comment> <comment>The parameter is the RpId</comment>
</data> </data>
<data name="VerifyingIdentityEllipsis" xml:space="preserve">
<value>Verifying identity...</value>
</data>
<data name="Passwords" xml:space="preserve"> <data name="Passwords" xml:space="preserve">
<value>Passwords</value> <value>Passwords</value>
</data> </data>

View File

@@ -1,7 +1,4 @@
using System; using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
namespace Bit.Core.Services namespace Bit.Core.Services
@@ -11,7 +8,8 @@ namespace Bit.Core.Services
private readonly ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>> _preconditionsTasks = new ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>> private readonly ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>> _preconditionsTasks = new ConcurrentDictionary<AwaiterPrecondition, TaskCompletionSource<bool>>
{ {
[AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource<bool>(), [AwaiterPrecondition.EnvironmentUrlsInited] = new TaskCompletionSource<bool>(),
[AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource<bool>() [AwaiterPrecondition.AndroidWindowCreated] = new TaskCompletionSource<bool>(),
[AwaiterPrecondition.AutofillIOSExtensionViewDidAppear] = new TaskCompletionSource<bool>()
}; };
public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition) public Task GetAwaiterForPrecondition(AwaiterPrecondition awaiterPrecondition)
@@ -39,5 +37,15 @@ namespace Bit.Core.Services
tcs.TrySetException(ex); tcs.TrySetException(ex);
} }
} }
public void Recreate(AwaiterPrecondition awaiterPrecondition)
{
if (_preconditionsTasks.TryRemove(awaiterPrecondition, out var oldTcs))
{
oldTcs.TrySetCanceled();
_preconditionsTasks.TryAdd(awaiterPrecondition, new TaskCompletionSource<bool>());
}
}
} }
} }

View File

@@ -19,7 +19,10 @@ namespace Bit.Core.Services.UserVerification
return true; return true;
} }
options.OnNeedUI?.Invoke(); if (options.OnNeedUITask != null)
{
await options.OnNeedUITask();
}
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock) if (canPerformOSUnlock)

View File

@@ -23,7 +23,10 @@ namespace Bit.Core.Services.UserVerification
return true; return true;
} }
options.OnNeedUI?.Invoke(); if (options.OnNeedUITask != null)
{
await options.OnNeedUITask();
}
var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync(); var (canPerformOSUnlock, isOSUnlocked) = await _userVerificationMediatorService.PerformOSUnlockAsync();
if (canPerformOSUnlock) if (canPerformOSUnlock)

View File

@@ -39,7 +39,11 @@ namespace Bit.Core.Services.UserVerification
{ {
if (await ShouldPerformMasterPasswordRepromptAsync(options)) if (await ShouldPerformMasterPasswordRepromptAsync(options))
{ {
options.OnNeedUI?.Invoke(); if (options.OnNeedUITask != null)
{
await options.OnNeedUITask();
}
return await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Enums.CipherRepromptType.Password); return await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(Enums.CipherRepromptType.Password);
} }

View File

@@ -6,19 +6,19 @@
Fido2UserVerificationPreference userVerificationPreference, Fido2UserVerificationPreference userVerificationPreference,
bool hasVaultBeenUnlockedInTransaction, bool hasVaultBeenUnlockedInTransaction,
string rpId = null, string rpId = null,
Action onNeedUI = null) Func<Task> onNeedUITask = null)
{ {
ShouldCheckMasterPasswordReprompt = shouldCheckMasterPasswordReprompt; ShouldCheckMasterPasswordReprompt = shouldCheckMasterPasswordReprompt;
UserVerificationPreference = userVerificationPreference; UserVerificationPreference = userVerificationPreference;
HasVaultBeenUnlockedInTransaction = hasVaultBeenUnlockedInTransaction; HasVaultBeenUnlockedInTransaction = hasVaultBeenUnlockedInTransaction;
RpId = rpId; RpId = rpId;
OnNeedUI = onNeedUI; OnNeedUITask = onNeedUITask;
} }
public bool ShouldCheckMasterPasswordReprompt { get; } public bool ShouldCheckMasterPasswordReprompt { get; }
public Fido2UserVerificationPreference UserVerificationPreference { get; } public Fido2UserVerificationPreference UserVerificationPreference { get; }
public bool HasVaultBeenUnlockedInTransaction { get; } public bool HasVaultBeenUnlockedInTransaction { get; }
public string RpId { get; } public string RpId { get; }
public Action OnNeedUI { get; } public Func<Task> OnNeedUITask { get; }
} }
} }

View File

@@ -270,13 +270,21 @@ namespace Bit.iOS.Autofill
userVerificationPreference, userVerificationPreference,
_context.VaultUnlockedDuringThisSession, _context.VaultUnlockedDuringThisSession,
_context.PasskeyCredentialIdentity?.RelyingPartyIdentifier, _context.PasskeyCredentialIdentity?.RelyingPartyIdentifier,
() => async () =>
{ {
if (_context.IsExecutingWithoutUserInteraction) if (_context.IsExecutingWithoutUserInteraction)
{ {
CancelRequest(ASExtensionErrorCode.UserInteractionRequired); CancelRequest(ASExtensionErrorCode.UserInteractionRequired);
throw new InvalidOperationNeedsUIException(); 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);
}) })
); );
} }

View File

@@ -32,6 +32,7 @@ namespace Bit.iOS.Autofill
private IAccountsManager _accountsManager; private IAccountsManager _accountsManager;
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>(); private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private readonly LazyResolve<IConditionedAwaiterManager> _conditionedAwaiterManager = new LazyResolve<IConditionedAwaiterManager>();
public CredentialProviderViewController(IntPtr handle) public CredentialProviderViewController(IntPtr handle)
: base(handle) : base(handle)
@@ -56,6 +57,8 @@ namespace Bit.iOS.Autofill
{ {
ExtContext = ExtensionContext ExtContext = ExtensionContext
}; };
_conditionedAwaiterManager.Value.Recreate(AwaiterPrecondition.AutofillIOSExtensionViewDidAppear);
} }
catch (Exception ex) 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) public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
{ {
try try