EC-312 Fix crash on entering invalid credentials five times on Autofill (#1988)

This commit is contained in:
Federico Maccaroni 2022-07-14 19:17:04 -03:00 committed by GitHub
parent 2d2a883b96
commit d2fbf5bdea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 236 additions and 170 deletions

View file

@ -8,5 +8,6 @@ namespace Bit.App.Abstractions
{
void Init(Func<AppOptions> getOptionsFunc, IAccountsManagerHost accountsManagerHost);
Task NavigateOnAccountChangeAsync(bool? isAuthed = null);
Task LogOutAsync(string userId, bool userInitiated, bool expired);
}
}

View file

@ -301,7 +301,7 @@ namespace Bit.App
UpdateThemeAsync();
};
Current.MainPage = new NavigationPage(new HomePage(Options));
var mainPageTask = _accountsManager.NavigateOnAccountChangeAsync();
_accountsManager.NavigateOnAccountChangeAsync().FireAndForget();
ServiceContainer.Resolve<MobilePlatformUtilsService>("platformUtilsService").Init();
}

View file

@ -123,7 +123,11 @@ namespace Bit.App.Utilities.AccountManagement
await _vaultTimeoutService.LockAsync(true);
break;
case AccountsManagerMessageCommands.LOGOUT:
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(message.Data as Tuple<string, bool, bool>));
var extras = message.Data as Tuple<string, bool, bool>;
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
await Device.InvokeOnMainThreadAsync(() => LogOutAsync(userId, userInitiated, expired));
break;
case AccountsManagerMessageCommands.LOGGED_OUT:
// Clean up old migrated key if they ever log out.
@ -181,12 +185,8 @@ namespace Bit.App.Utilities.AccountManagement
});
}
private async Task LogOutAsync(Tuple<string, bool, bool> extras)
public async Task LogOutAsync(string userId, bool userInitiated, bool expired)
{
var userId = extras?.Item1;
var userInitiated = extras?.Item2 ?? true;
var expired = extras?.Item3 ?? false;
await AppHelpers.LogOutAsync(userId, userInitiated);
await NavigateOnAccountChangeAsync();
_authService.LogOut(() =>

View file

@ -6,6 +6,7 @@ using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Domain;
using Bit.Core.Models.Request;
using Bit.Core.Utilities;
namespace Bit.Core.Services
{
@ -173,7 +174,7 @@ namespace Bit.Core.Services
public void LogOut(Action callback)
{
callback.Invoke();
_messagingService.Send("loggedOut");
_messagingService.Send(AccountsManagerMessageCommands.LOGGED_OUT);
}
public List<TwoFactorProvider> GetSupportedTwoFactorProviders()

View file

@ -8,10 +8,12 @@ using Bit.App.Utilities;
using Bit.App.Utilities.AccountManagement;
using Bit.Core.Abstractions;
using Bit.Core.Enums;
using Bit.Core.Services;
using Bit.Core.Utilities;
using Bit.iOS.Autofill.Models;
using Bit.iOS.Core.Utilities;
using Bit.iOS.Core.Views;
using CoreFoundation;
using CoreNFC;
using Foundation;
using UIKit;
@ -35,6 +37,8 @@ namespace Bit.iOS.Autofill
}
public override void ViewDidLoad()
{
try
{
InitApp();
base.ViewDidLoad();
@ -45,8 +49,16 @@ namespace Bit.iOS.Autofill
ExtContext = ExtensionContext
};
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override async void PrepareCredentialList(ASCredentialServiceIdentifier[] serviceIdentifiers)
{
try
{
InitAppIfNeeded();
_context.ServiceIdentifiers = serviceIdentifiers;
@ -79,8 +91,16 @@ namespace Bit.iOS.Autofill
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override async void ProvideCredentialWithoutUserInteraction(ASPasswordCredentialIdentity credentialIdentity)
{
try
{
InitAppIfNeeded();
await _stateService.Value.SetPasswordRepromptAutofillAsync(false);
@ -95,8 +115,16 @@ namespace Bit.iOS.Autofill
_context.CredentialIdentity = credentialIdentity;
await ProvideCredentialAsync(false);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override async void PrepareInterfaceToProvideCredential(ASPasswordCredentialIdentity credentialIdentity)
{
try
{
InitAppIfNeeded();
if (!await IsAuthed())
@ -105,10 +133,18 @@ namespace Bit.iOS.Autofill
return;
}
_context.CredentialIdentity = credentialIdentity;
CheckLock(async () => await ProvideCredentialAsync());
await CheckLockAsync(async () => await ProvideCredentialAsync());
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public override async void PrepareInterfaceForExtensionConfiguration()
{
try
{
InitAppIfNeeded();
_context.Configuring = true;
@ -117,7 +153,13 @@ namespace Bit.iOS.Autofill
await _accountsManager.NavigateOnAccountChangeAsync(false);
return;
}
CheckLock(() => PerformSegue("setupSegue", this));
await CheckLockAsync(() => PerformSegue("setupSegue", this));
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public void CompleteRequest(string id = null, string username = null,
@ -158,6 +200,8 @@ namespace Bit.iOS.Autofill
}
public override void PrepareForSegue(UIStoryboardSegue segue, NSObject sender)
{
try
{
if (segue.DestinationViewController is UINavigationController navController)
{
@ -188,11 +232,20 @@ namespace Bit.iOS.Autofill
new CustomPresentationControllerDelegate(setupViewController.DismissModalAction);
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
public void DismissLockAndContinue()
{
DismissViewController(false, async () =>
{
try
{
if (_context.CredentialIdentity != null)
{
@ -212,10 +265,18 @@ namespace Bit.iOS.Autofill
{
PerformSegue("loginListSegue", this);
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
});
}
private async Task ProvideCredentialAsync(bool userInteraction = true)
{
try
{
var cipherService = ServiceContainer.Resolve<ICipherService>("cipherService", true);
Bit.Core.Models.Domain.Cipher cipher = null;
@ -275,12 +336,18 @@ namespace Bit.iOS.Autofill
CompleteRequest(decCipher.Id, decCipher.Login.Username, decCipher.Login.Password, totpCode);
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
throw;
}
}
private async void CheckLock(Action notLockedAction)
private async Task CheckLockAsync(Action notLockedAction)
{
if (await IsLocked() || await _stateService.Value.GetPasswordRepromptAutofillAsync())
{
PerformSegue("lockPasswordSegue", this);
DispatchQueue.MainQueue.DispatchAsync(() => PerformSegue("lockPasswordSegue", this));
}
else
{
@ -302,16 +369,22 @@ namespace Bit.iOS.Autofill
private void LogoutIfAuthed()
{
NSRunLoop.Main.BeginInvokeOnMainThread(async () =>
{
try
{
if (await IsAuthed())
{
await AppHelpers.LogOutAsync(await _stateService.Value.GetActiveUserIdAsync());
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
if (deviceActionService.SystemMajorVersion() >= 12)
if (UIDevice.CurrentDevice.CheckSystemVersion(12, 0))
{
await ASCredentialIdentityStore.SharedStore?.RemoveAllCredentialIdentitiesAsync();
}
}
}
catch (Exception ex)
{
LoggerHelper.LogEvenIfCantBeResolved(ex);
}
});
}

View file

@ -28,6 +28,7 @@ namespace Bit.iOS.Core.Controllers
private IPlatformUtilsService _platformUtilsService;
private IBiometricService _biometricService;
private IKeyConnectorService _keyConnectorService;
private IAccountsManager _accountManager;
private bool _isPinProtected;
private bool _isPinProtectedWithKey;
private bool _pinLock;
@ -95,6 +96,7 @@ namespace Bit.iOS.Core.Controllers
_platformUtilsService = ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService");
_biometricService = ServiceContainer.Resolve<IBiometricService>("biometricService");
_keyConnectorService = ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService");
_accountManager = ServiceContainer.Resolve<IAccountsManager>("accountsManager");
// We re-use the lock screen for autofill extension to verify master password
// when trying to access protected items.
@ -265,13 +267,7 @@ namespace Bit.iOS.Core.Controllers
}
if (failed)
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
return;
}
InvalidValue();
await HandleFailedCredentialsAsync();
}
}
else
@ -305,17 +301,22 @@ namespace Bit.iOS.Core.Controllers
await SetKeyAndContinueAsync(key2, true);
}
else
{
await HandleFailedCredentialsAsync();
}
}
}
private async Task HandleFailedCredentialsAsync()
{
var invalidUnlockAttempts = await AppHelpers.IncrementInvalidUnlockAttemptsAsync();
if (invalidUnlockAttempts >= 5)
{
await LogOutAsync();
await _accountManager.LogOutAsync(await _stateService.GetActiveUserIdAsync(), false, false);
return;
}
InvalidValue();
}
}
}
public async Task PromptBiometricAsync()
{
@ -395,16 +396,6 @@ namespace Bit.iOS.Core.Controllers
PresentViewController(alert, true, null);
}
private async Task LogOutAsync()
{
await AppHelpers.LogOutAsync(await _stateService.GetActiveUserIdAsync());
var authService = ServiceContainer.Resolve<IAuthService>("authService");
authService.LogOut(() =>
{
Cancel?.Invoke();
});
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);