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:
committed by
GitHub
parent
310d8b363f
commit
8fd9e0203d
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|||||||
@@ -34,4 +34,11 @@ namespace Bit.Core.Utilities.Fido2
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class AccountSwitchedException : Fido2AuthenticatorException
|
||||||
|
{
|
||||||
|
public AccountSwitchedException() : base(nameof(AccountSwitchedException))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user