mirror of
https://github.com/bitwarden/mobile.git
synced 2024-11-24 11:55:38 +01:00
PM-7963 Fix vault timeout immediately on Android Fido2 autofill
This commit is contained in:
parent
477b1cca44
commit
2cdb5a9c74
@ -1,26 +1,31 @@
|
||||
using Android.App;
|
||||
using System.Diagnostics;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using AndroidX.Activity.Result;
|
||||
using AndroidX.Activity.Result.Contract;
|
||||
using AndroidX.Credentials;
|
||||
using AndroidX.Credentials.Exceptions;
|
||||
using AndroidX.Credentials.Provider;
|
||||
using AndroidX.Credentials.WebAuthn;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Bit.Core.Services;
|
||||
using Bit.App.Droid.Utilities;
|
||||
using Bit.App.Platforms.Android.Autofill;
|
||||
using AndroidX.Credentials.Exceptions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Resources.Localization;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Org.Json;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Activity(
|
||||
NoHistory = true,
|
||||
LaunchMode = LaunchMode.SingleTop)]
|
||||
NoHistory = false,
|
||||
LaunchMode = LaunchMode.SingleInstance)]
|
||||
[Register("com.x8bit.bitwarden.CredentialProviderSelectionActivity")]
|
||||
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
||||
{
|
||||
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
|
||||
@ -31,6 +36,8 @@ namespace Bit.Droid.Autofill
|
||||
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
|
||||
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
|
||||
|
||||
private ActivityResultLauncher _activityResultLauncher;
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
Intent?.Validate();
|
||||
@ -100,8 +107,14 @@ namespace Bit.Droid.Autofill
|
||||
cipherId,
|
||||
false,
|
||||
() => hasVaultBeenUnlockedInThisTransaction,
|
||||
RpId
|
||||
);
|
||||
RpId
|
||||
);
|
||||
|
||||
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.StartActivityForResult(),
|
||||
new ActivityResultCallback(result =>
|
||||
{
|
||||
_fido2GetAssertionUserInterface.Value.ConfirmVaultUnlocked(result.ResultCode == (int)Result.Ok);
|
||||
}));
|
||||
|
||||
var clientAssertParams = new Fido2ClientAssertCredentialParams
|
||||
{
|
||||
@ -171,6 +184,19 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
public void LaunchToUnlock()
|
||||
{
|
||||
if (_activityResultLauncher is null)
|
||||
{
|
||||
throw new InvalidOperationException("There is no activity result launcher available");
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.PutExtra(CredentialProviderConstants.Fido2CredentialAction, CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout);
|
||||
|
||||
_activityResultLauncher.Launch(intent);
|
||||
}
|
||||
|
||||
private void FailAndFinish()
|
||||
{
|
||||
var result = new Intent();
|
||||
@ -180,4 +206,12 @@ namespace Bit.Droid.Autofill
|
||||
Finish();
|
||||
}
|
||||
}
|
||||
|
||||
public class ActivityResultCallback : Java.Lang.Object, IActivityResultCallback
|
||||
{
|
||||
readonly Action<ActivityResult> _callback;
|
||||
public ActivityResultCallback(Action<ActivityResult> callback) => _callback = callback;
|
||||
public ActivityResultCallback(TaskCompletionSource<ActivityResult> tcs) => _callback = tcs.SetResult;
|
||||
public void OnActivityResult(Java.Lang.Object p0) => _callback((ActivityResult)p0);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Bit.Droid.Autofill;
|
||||
|
||||
namespace Bit.App.Platforms.Android.Autofill
|
||||
{
|
||||
@ -10,6 +11,11 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
bool userVerified,
|
||||
Func<bool> hasVaultBeenUnlockedInThisTransaction,
|
||||
string rpId);
|
||||
|
||||
/// <summary>
|
||||
/// Call this after the vault was unlocked so that Fido2 credential autofill can proceed.
|
||||
/// </summary>
|
||||
void ConfirmVaultUnlocked(bool unlocked);
|
||||
}
|
||||
|
||||
public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface
|
||||
@ -19,6 +25,8 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
private readonly ICipherService _cipherService;
|
||||
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
||||
|
||||
private TaskCompletionSource<bool> _unlockVaultTcs;
|
||||
|
||||
public Fido2GetAssertionUserInterface(IStateService stateService,
|
||||
IVaultTimeoutService vaultTimeoutService,
|
||||
ICipherService cipherService,
|
||||
@ -46,11 +54,38 @@ namespace Bit.App.Platforms.Android.Autofill
|
||||
{
|
||||
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
|
||||
{
|
||||
// this should never happen but just in case.
|
||||
throw new InvalidOperationException("Not authed or vault locked");
|
||||
if (await _stateService.GetVaultTimeoutAsync() != 0)
|
||||
{
|
||||
// this should never happen but just in case.
|
||||
throw new InvalidOperationException("Not authed or vault locked");
|
||||
}
|
||||
|
||||
// if vault timeout is immediate, then we need to unlock the vault
|
||||
if (!await NavigateAndWaitForUnlockAsync())
|
||||
{
|
||||
throw new InvalidOperationException("Couldn't unlock with immediate timeout");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ConfirmVaultUnlocked(bool unlocked) => _unlockVaultTcs?.TrySetResult(unlocked);
|
||||
|
||||
private async Task<bool> NavigateAndWaitForUnlockAsync()
|
||||
{
|
||||
var credentialProviderSelectionActivity = Platform.CurrentActivity as CredentialProviderSelectionActivity;
|
||||
if (credentialProviderSelectionActivity == null)
|
||||
{
|
||||
throw new InvalidOperationException("Can't get current activity");
|
||||
}
|
||||
|
||||
_unlockVaultTcs?.TrySetCanceled();
|
||||
_unlockVaultTcs = new TaskCompletionSource<bool>();
|
||||
|
||||
credentialProviderSelectionActivity.LaunchToUnlock();
|
||||
|
||||
return await _unlockVaultTcs.Task;
|
||||
}
|
||||
|
||||
private async Task<bool> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction)
|
||||
{
|
||||
try
|
||||
|
@ -23,11 +23,14 @@ using Resource = Bit.Core.Resource;
|
||||
using Application = Android.App.Application;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities.Fido2;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class DeviceActionService : IDeviceActionService
|
||||
{
|
||||
public const int DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS = 15000;
|
||||
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private AlertDialog _progressDialog;
|
||||
@ -578,6 +581,15 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
await ExecuteFido2GetCredentialAsync(appOptions);
|
||||
}
|
||||
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout
|
||||
&&
|
||||
ServiceContainer.TryResolve<IVaultTimeoutService>(out var vaultTimeoutService))
|
||||
{
|
||||
vaultTimeoutService.DelayLockAndLogoutMs = DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS;
|
||||
|
||||
activity.SetResult(Result.Ok);
|
||||
activity.Finish();
|
||||
}
|
||||
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialCreate)
|
||||
{
|
||||
await ExecuteFido2CreateCredentialAsync();
|
||||
|
@ -5,6 +5,7 @@
|
||||
public const string Fido2CredentialCreate = "fido2CredentialCreate";
|
||||
public const string Fido2CredentialGet = "fido2CredentialGet";
|
||||
public const string Fido2CredentialAction = "fido2CredentialAction";
|
||||
public const string Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout = "fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout";
|
||||
public const string CredentialProviderCipherId = "credentialProviderCipherId";
|
||||
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
|
||||
public const string CredentialIdIntentExtra = "credId";
|
||||
|
Loading…
Reference in New Issue
Block a user