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:
committed by
GitHub
parent
144fc7c727
commit
74085689d3
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user