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

PM-6798 Fix account switch on iOS Autofill extension and also changed to Try... actions for TaskCompletionSource to avoid exceptions on some occasions. (#3121)

Co-authored-by: Álison Fernandes <vvolkgang@users.noreply.github.com>
This commit is contained in:
Federico Maccaroni
2024-03-27 19:11:26 -03:00
committed by GitHub
parent 310d8b363f
commit 8fd9e0203d
8 changed files with 152 additions and 69 deletions

View File

@@ -36,6 +36,16 @@ namespace Bit.Core.Services
throw new NotSupportedError(); throw new NotSupportedError();
} }
string cipherId = null;
var userVerified = false;
var accountSwitched = false;
do
{
try
{
accountSwitched = false;
await userInterface.EnsureUnlockedVaultAsync(); await userInterface.EnsureUnlockedVaultAsync();
await _syncService.FullSyncAsync(false); await _syncService.FullSyncAsync(false);
@@ -48,16 +58,20 @@ namespace Bit.Core.Services
throw new NotAllowedError(); throw new NotAllowedError();
} }
var response = await userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams (cipherId, userVerified) = await userInterface.ConfirmNewCredentialAsync(new Fido2ConfirmNewCredentialParams
{ {
CredentialName = makeCredentialParams.RpEntity.Name, CredentialName = makeCredentialParams.RpEntity.Name,
UserName = makeCredentialParams.UserEntity.Name, UserName = makeCredentialParams.UserEntity.Name,
UserVerificationPreference = makeCredentialParams.UserVerificationPreference, UserVerificationPreference = makeCredentialParams.UserVerificationPreference,
RpId = makeCredentialParams.RpEntity.Id RpId = makeCredentialParams.RpEntity.Id
}); });
}
catch (AccountSwitchedException)
{
accountSwitched = true;
}
} while (accountSwitched);
var cipherId = response.CipherId;
var userVerified = response.UserVerified;
string credentialId; string credentialId;
if (cipherId == null) if (cipherId == null)
{ {
@@ -118,7 +132,17 @@ namespace Bit.Core.Services
public async Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface) public async Task<Fido2AuthenticatorGetAssertionResult> GetAssertionAsync(Fido2AuthenticatorGetAssertionParams assertionParams, IFido2GetAssertionUserInterface userInterface)
{ {
List<CipherView> cipherOptions; List<CipherView> cipherOptions = new List<CipherView>();
string selectedCipherId = null;
var userVerified = false;
var accountSwitched = false;
do
{
try
{
accountSwitched = false;
await userInterface.EnsureUnlockedVaultAsync(); await userInterface.EnsureUnlockedVaultAsync();
await _syncService.FullSyncAsync(false); await _syncService.FullSyncAsync(false);
@@ -140,15 +164,20 @@ namespace Bit.Core.Services
throw new NotAllowedError(); throw new NotAllowedError();
} }
var response = await userInterface.PickCredentialAsync( (selectedCipherId, userVerified) = await userInterface.PickCredentialAsync(
cipherOptions.Select((cipher) => new Fido2GetAssertionUserInterfaceCredential cipherOptions.Select((cipher) => new Fido2GetAssertionUserInterfaceCredential
{ {
CipherId = cipher.Id, CipherId = cipher.Id,
UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.GetUserVerificationPreferenceFrom(assertionParams.UserVerificationPreference, cipher.Reprompt) UserVerificationPreference = Fido2UserVerificationPreferenceExtensions.GetUserVerificationPreferenceFrom(assertionParams.UserVerificationPreference, cipher.Reprompt)
}).ToArray() }).ToArray()
); );
var selectedCipherId = response.CipherId;
var userVerified = response.UserVerified; }
catch (AccountSwitchedException)
{
accountSwitched = true;
}
} while (accountSwitched);
var selectedCipher = cipherOptions.FirstOrDefault((c) => c.Id == selectedCipherId); var selectedCipher = cipherOptions.FirstOrDefault((c) => c.Id == selectedCipherId);
if (selectedCipher == null) if (selectedCipher == null)

View File

@@ -38,7 +38,7 @@
// { // {
// _currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}"); // _currentBreadcrumbs.AppendLine($"{DateTime.Now.ToShortTimeString()}: {breadcrumb}");
//#if IOS //#if IOS
// UIPasteboard.General.String = _currentBreadcrumbs.ToString(); // MainThread.BeginInvokeOnMainThread(() => UIPasteboard.General.String = _currentBreadcrumbs.ToString());
//#endif //#endif
// } // }

View File

@@ -34,4 +34,11 @@ namespace Bit.Core.Utilities.Fido2
{ {
} }
} }
public class AccountSwitchedException : Fido2AuthenticatorException
{
public AccountSwitchedException() : base(nameof(AccountSwitchedException))
{
}
}
} }

View File

@@ -339,7 +339,7 @@ namespace Bit.iOS.Autofill
{ {
try try
{ {
PerformSegue(SegueConstants.LOGIN_LIST, this); DismissViewController(false, () => PerformSegue(SegueConstants.LOGIN_LIST, this));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -352,26 +352,22 @@ namespace Bit.iOS.Autofill
{ {
if (_context.IsCreatingPasskey) if (_context.IsCreatingPasskey)
{ {
if (!await IsAuthed()
||
await _vaultTimeoutService.Value.IsLoggedOutByTimeoutAsync()
||
await _vaultTimeoutService.Value.ShouldLogOutByTimeoutAsync())
{
await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.HomeLogin);
return;
}
if (!await IsLocked()) if (!await IsLocked())
{ {
return; return;
} }
_context.UnlockVaultTcs?.SetCanceled(); await NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget.Lock);
_context.UnlockVaultTcs = new TaskCompletionSource<bool>();
MainThread.BeginInvokeOnMainThread(() =>
{
try
{
PerformSegue(SegueConstants.LOCK, this);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
await _context.UnlockVaultTcs.Task;
return; return;
} }
@@ -381,5 +377,17 @@ namespace Bit.iOS.Autofill
throw new InvalidOperationNeedsUIException("Not authed or locked"); throw new InvalidOperationNeedsUIException("Not authed or locked");
} }
} }
private async Task NavigateAndWaitForUnlockAsync(Bit.Core.Enums.NavigationTarget navTarget)
{
_context.UnlockVaultTcs?.TrySetCanceled();
_context.UnlockVaultTcs = new TaskCompletionSource<bool>();
await MainThread.InvokeOnMainThreadAsync(() =>
{
DoNavigate(navTarget);
});
await _context.UnlockVaultTcs.Task;
}
} }
} }

View File

@@ -8,6 +8,7 @@ using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement; using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions; using Bit.Core.Abstractions;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models.Domain;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Core.Utilities.Fido2; using Bit.Core.Utilities.Fido2;
@@ -34,6 +35,8 @@ namespace Bit.iOS.Autofill
private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>(); private readonly LazyResolve<IStateService> _stateService = new LazyResolve<IStateService>();
private readonly LazyResolve<IConditionedAwaiterManager> _conditionedAwaiterManager = new LazyResolve<IConditionedAwaiterManager>(); private readonly LazyResolve<IConditionedAwaiterManager> _conditionedAwaiterManager = new LazyResolve<IConditionedAwaiterManager>();
private readonly LazyResolve<IBroadcasterService> _broadcasterService = new LazyResolve<IBroadcasterService>();
private readonly LazyResolve<IVaultTimeoutService> _vaultTimeoutService = new LazyResolve<IVaultTimeoutService>();
public CredentialProviderViewController(IntPtr handle) public CredentialProviderViewController(IntPtr handle)
: base(handle) : base(handle)
@@ -377,7 +380,7 @@ namespace Bit.iOS.Autofill
if (_context.IsCreatingPasskey) if (_context.IsCreatingPasskey)
{ {
_context.UnlockVaultTcs.SetResult(true); _context.UnlockVaultTcs.TrySetResult(true);
return; return;
} }
@@ -509,8 +512,7 @@ namespace Bit.iOS.Autofill
private Task<bool> IsLocked() private Task<bool> IsLocked()
{ {
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"); return _vaultTimeoutService.Value.IsLockedAsync();
return vaultTimeoutService.IsLockedAsync();
} }
private Task<bool> IsAuthed() private Task<bool> IsAuthed()
@@ -544,6 +546,8 @@ namespace Bit.iOS.Autofill
{ {
iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey, iOSCoreHelpers.InitApp(this, Bit.Core.Constants.iOSAutoFillClearCiphersCacheKey,
_nfcSession, out _nfcDelegate, out _accountsManager); _nfcSession, out _nfcDelegate, out _accountsManager);
_broadcasterService.Value.Subscribe(nameof(CredentialProviderViewController), OnMessageReceived);
} }
private async Task InitAppIfNeededAsync() private async Task InitAppIfNeededAsync()
@@ -556,6 +560,18 @@ namespace Bit.iOS.Autofill
await _stateService.Value.ReloadStateAsync(); await _stateService.Value.ReloadStateAsync();
} }
private void OnMessageReceived(Message message)
{
if (message?.Command == AccountsManagerMessageCommands.ACCOUNT_SWITCH_COMPLETED
&&
_context != null)
{
_context.VaultUnlockedDuringThisSession = false;
_context.PickCredentialForFido2CreationTcs?.TrySetException(new AccountSwitchedException());
_context.PickCredentialForFido2GetAssertionFromListTcs?.TrySetException(new AccountSwitchedException());
}
}
private void LaunchHomePage() private void LaunchHomePage()
{ {
var appOptions = new AppOptions { IosExtension = true }; var appOptions = new AppOptions { IosExtension = true };
@@ -751,6 +767,24 @@ namespace Bit.iOS.Autofill
public Task UpdateThemeAsync() => Task.CompletedTask; public Task UpdateThemeAsync() => Task.CompletedTask;
public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null) public void Navigate(NavigationTarget navTarget, INavigationParams navParams = null)
{
if (_context?.IsCreatingPasskey == true
&&
_context.PickCredentialForFido2CreationTcs != null
&&
!_context.PickCredentialForFido2CreationTcs.Task.IsCompleted)
{
// if it's creating passkey
// and we have an active pending TaskCompletionSource
// then we let the Fido2 Authenticator flow manage the navigation to avoid issues
// like duplicated navigation.
return;
}
DoNavigate(navTarget, navParams);
}
internal void DoNavigate(NavigationTarget navTarget, INavigationParams navParams = null)
{ {
switch (navTarget) switch (navTarget)
{ {

View File

@@ -31,7 +31,7 @@ namespace Bit.iOS.Autofill
public async Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams) public async Task<(string CipherId, bool UserVerified)> ConfirmNewCredentialAsync(Fido2ConfirmNewCredentialParams confirmNewCredentialParams)
{ {
_context.PickCredentialForFido2CreationTcs?.SetCanceled(); _context.PickCredentialForFido2CreationTcs?.TrySetCanceled();
_context.PickCredentialForFido2CreationTcs = new TaskCompletionSource<(string, bool?)>(); _context.PickCredentialForFido2CreationTcs = new TaskCompletionSource<(string, bool?)>();
_context.PasskeyCreationParams = confirmNewCredentialParams; _context.PasskeyCreationParams = confirmNewCredentialParams;

View File

@@ -285,7 +285,7 @@ namespace Bit.iOS.Autofill.Utilities
if (Context.IsPreparingListForPasskey && item.IsFido2ListItem) if (Context.IsPreparingListForPasskey && item.IsFido2ListItem)
{ {
Context.PickCredentialForFido2GetAssertionFromListTcs.SetResult(item.Id); Context.PickCredentialForFido2GetAssertionFromListTcs.TrySetResult(item.Id);
return; return;
} }
@@ -317,7 +317,12 @@ namespace Bit.iOS.Autofill.Utilities
return; return;
} }
Context.PickCredentialForFido2CreationTcs.SetResult((item.Id, null)); if (!await _passwordRepromptService.PromptAndCheckPasswordIfNeededAsync(item.Reprompt))
{
return;
}
Context.PickCredentialForFido2CreationTcs.TrySetResult((item.Id, null));
} }
private async Task<CipherViewModel> DeselectRowAndGetItemAsync(UITableView tableView, NSIndexPath indexPath) private async Task<CipherViewModel> DeselectRowAndGetItemAsync(UITableView tableView, NSIndexPath indexPath)

View File

@@ -51,7 +51,7 @@ namespace Bit.iOS.Autofill.Utilities
_onAllowedFido2Credentials?.Invoke(credentials.Select(c => c.CipherId).ToList() ?? new List<string>()); _onAllowedFido2Credentials?.Invoke(credentials.Select(c => c.CipherId).ToList() ?? new List<string>());
_context.PickCredentialForFido2GetAssertionFromListTcs?.SetCanceled(); _context.PickCredentialForFido2GetAssertionFromListTcs?.TrySetCanceled();
_context.PickCredentialForFido2GetAssertionFromListTcs = new TaskCompletionSource<string>(); _context.PickCredentialForFido2GetAssertionFromListTcs = new TaskCompletionSource<string>();
var cipherId = await _context.PickCredentialForFido2GetAssertionFromListTcs.Task; var cipherId = await _context.PickCredentialForFido2GetAssertionFromListTcs.Task;