mirror of
https://github.com/bitwarden/mobile.git
synced 2024-12-02 13:13:31 +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;
|
||||||
using Android.Content.PM;
|
using Android.Content.PM;
|
||||||
using Android.OS;
|
using Android.OS;
|
||||||
|
using Android.Runtime;
|
||||||
|
using AndroidX.Activity.Result;
|
||||||
|
using AndroidX.Activity.Result.Contract;
|
||||||
using AndroidX.Credentials;
|
using AndroidX.Credentials;
|
||||||
|
using AndroidX.Credentials.Exceptions;
|
||||||
using AndroidX.Credentials.Provider;
|
using AndroidX.Credentials.Provider;
|
||||||
using AndroidX.Credentials.WebAuthn;
|
using AndroidX.Credentials.WebAuthn;
|
||||||
using Bit.App.Droid.Utilities;
|
|
||||||
using Bit.App.Abstractions;
|
using Bit.App.Abstractions;
|
||||||
using Bit.Core.Abstractions;
|
using Bit.App.Droid.Utilities;
|
||||||
using Bit.Core.Utilities;
|
|
||||||
using Bit.Core.Resources.Localization;
|
|
||||||
using Bit.Core.Utilities.Fido2;
|
|
||||||
using Bit.Core.Services;
|
|
||||||
using Bit.App.Platforms.Android.Autofill;
|
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;
|
using Org.Json;
|
||||||
|
|
||||||
namespace Bit.Droid.Autofill
|
namespace Bit.Droid.Autofill
|
||||||
{
|
{
|
||||||
[Activity(
|
[Activity(
|
||||||
NoHistory = true,
|
NoHistory = false,
|
||||||
LaunchMode = LaunchMode.SingleTop)]
|
LaunchMode = LaunchMode.SingleInstance)]
|
||||||
|
[Register("com.x8bit.bitwarden.CredentialProviderSelectionActivity")]
|
||||||
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
public class CredentialProviderSelectionActivity : MauiAppCompatActivity
|
||||||
{
|
{
|
||||||
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
|
private LazyResolve<IFido2MediatorService> _fido2MediatorService = new LazyResolve<IFido2MediatorService>();
|
||||||
@ -31,6 +36,8 @@ namespace Bit.Droid.Autofill
|
|||||||
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
|
private LazyResolve<IUserVerificationMediatorService> _userVerificationMediatorService = new LazyResolve<IUserVerificationMediatorService>();
|
||||||
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
|
private LazyResolve<IDeviceActionService> _deviceActionService = new LazyResolve<IDeviceActionService>();
|
||||||
|
|
||||||
|
private ActivityResultLauncher _activityResultLauncher;
|
||||||
|
|
||||||
protected override void OnCreate(Bundle bundle)
|
protected override void OnCreate(Bundle bundle)
|
||||||
{
|
{
|
||||||
Intent?.Validate();
|
Intent?.Validate();
|
||||||
@ -100,8 +107,14 @@ namespace Bit.Droid.Autofill
|
|||||||
cipherId,
|
cipherId,
|
||||||
false,
|
false,
|
||||||
() => hasVaultBeenUnlockedInThisTransaction,
|
() => hasVaultBeenUnlockedInThisTransaction,
|
||||||
RpId
|
RpId
|
||||||
);
|
);
|
||||||
|
|
||||||
|
_activityResultLauncher = RegisterForActivityResult(new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
new ActivityResultCallback(result =>
|
||||||
|
{
|
||||||
|
_fido2GetAssertionUserInterface.Value.ConfirmVaultUnlocked(result.ResultCode == (int)Result.Ok);
|
||||||
|
}));
|
||||||
|
|
||||||
var clientAssertParams = new Fido2ClientAssertCredentialParams
|
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()
|
private void FailAndFinish()
|
||||||
{
|
{
|
||||||
var result = new Intent();
|
var result = new Intent();
|
||||||
@ -180,4 +206,12 @@ namespace Bit.Droid.Autofill
|
|||||||
Finish();
|
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.Abstractions;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities.Fido2;
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
using Bit.Droid.Autofill;
|
||||||
|
|
||||||
namespace Bit.App.Platforms.Android.Autofill
|
namespace Bit.App.Platforms.Android.Autofill
|
||||||
{
|
{
|
||||||
@ -10,6 +11,11 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||||||
bool userVerified,
|
bool userVerified,
|
||||||
Func<bool> hasVaultBeenUnlockedInThisTransaction,
|
Func<bool> hasVaultBeenUnlockedInThisTransaction,
|
||||||
string rpId);
|
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
|
public class Fido2GetAssertionUserInterface : Core.Utilities.Fido2.Fido2GetAssertionUserInterface, IFido2AndroidGetAssertionUserInterface
|
||||||
@ -19,6 +25,8 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||||||
private readonly ICipherService _cipherService;
|
private readonly ICipherService _cipherService;
|
||||||
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
private readonly IUserVerificationMediatorService _userVerificationMediatorService;
|
||||||
|
|
||||||
|
private TaskCompletionSource<bool> _unlockVaultTcs;
|
||||||
|
|
||||||
public Fido2GetAssertionUserInterface(IStateService stateService,
|
public Fido2GetAssertionUserInterface(IStateService stateService,
|
||||||
IVaultTimeoutService vaultTimeoutService,
|
IVaultTimeoutService vaultTimeoutService,
|
||||||
ICipherService cipherService,
|
ICipherService cipherService,
|
||||||
@ -46,11 +54,38 @@ namespace Bit.App.Platforms.Android.Autofill
|
|||||||
{
|
{
|
||||||
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
|
if (!await _stateService.IsAuthenticatedAsync() || await _vaultTimeoutService.IsLockedAsync())
|
||||||
{
|
{
|
||||||
// this should never happen but just in case.
|
if (await _stateService.GetVaultTimeoutAsync() != 0)
|
||||||
throw new InvalidOperationException("Not authed or vault locked");
|
{
|
||||||
|
// 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)
|
private async Task<bool> VerifyUserAsync(string selectedCipherId, Fido2UserVerificationPreference userVerificationPreference, string rpId, bool vaultUnlockedDuringThisTransaction)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -23,11 +23,14 @@ using Resource = Bit.Core.Resource;
|
|||||||
using Application = Android.App.Application;
|
using Application = Android.App.Application;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Utilities.Fido2;
|
using Bit.Core.Utilities.Fido2;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
|
||||||
namespace Bit.Droid.Services
|
namespace Bit.Droid.Services
|
||||||
{
|
{
|
||||||
public class DeviceActionService : IDeviceActionService
|
public class DeviceActionService : IDeviceActionService
|
||||||
{
|
{
|
||||||
|
public const int DELAY_LOCK_LOGOUT_FOR_FIDO2_AUTOFILL_ON_IMMEDIATE_TIMEOUT_MS = 15000;
|
||||||
|
|
||||||
private readonly IStateService _stateService;
|
private readonly IStateService _stateService;
|
||||||
private readonly IMessagingService _messagingService;
|
private readonly IMessagingService _messagingService;
|
||||||
private AlertDialog _progressDialog;
|
private AlertDialog _progressDialog;
|
||||||
@ -578,6 +581,15 @@ namespace Bit.Droid.Services
|
|||||||
{
|
{
|
||||||
await ExecuteFido2GetCredentialAsync(appOptions);
|
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)
|
else if (appOptions.Fido2CredentialAction == CredentialProviderConstants.Fido2CredentialCreate)
|
||||||
{
|
{
|
||||||
await ExecuteFido2CreateCredentialAsync();
|
await ExecuteFido2CreateCredentialAsync();
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
public const string Fido2CredentialCreate = "fido2CredentialCreate";
|
public const string Fido2CredentialCreate = "fido2CredentialCreate";
|
||||||
public const string Fido2CredentialGet = "fido2CredentialGet";
|
public const string Fido2CredentialGet = "fido2CredentialGet";
|
||||||
public const string Fido2CredentialAction = "fido2CredentialAction";
|
public const string Fido2CredentialAction = "fido2CredentialAction";
|
||||||
|
public const string Fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout = "fido2CredentialNeedsUnlockingAgainBecauseImmediateTimeout";
|
||||||
public const string CredentialProviderCipherId = "credentialProviderCipherId";
|
public const string CredentialProviderCipherId = "credentialProviderCipherId";
|
||||||
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
|
public const string CredentialDataIntentExtra = "CREDENTIAL_DATA";
|
||||||
public const string CredentialIdIntentExtra = "credId";
|
public const string CredentialIdIntentExtra = "credId";
|
||||||
|
Loading…
Reference in New Issue
Block a user