1
0
mirror of https://github.com/bitwarden/mobile.git synced 2024-12-28 17:18:23 +01:00

biometrics cleanup (#964)

This commit is contained in:
Matt Portune 2020-06-08 08:25:13 -04:00 committed by GitHub
parent ec7d87e757
commit 5da2f3279b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 117 additions and 257 deletions

View File

@ -8,12 +8,9 @@ using Android.App;
using Android.App.Assist; using Android.App.Assist;
using Android.Content; using Android.Content;
using Android.Content.PM; using Android.Content.PM;
using Android.Hardware.Biometrics;
using Android.Hardware.Fingerprints;
using Android.Nfc; using Android.Nfc;
using Android.OS; using Android.OS;
using Android.Provider; using Android.Provider;
using Android.Runtime;
using Android.Text; using Android.Text;
using Android.Text.Method; using Android.Text.Method;
using Android.Views.Autofill; using Android.Views.Autofill;
@ -31,7 +28,6 @@ using Bit.Core.Models.View;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Droid.Autofill; using Bit.Droid.Autofill;
using Plugin.CurrentActivity; using Plugin.CurrentActivity;
using Plugin.Fingerprint;
namespace Bit.Droid.Services namespace Bit.Droid.Services
{ {
@ -389,71 +385,16 @@ namespace Bit.Droid.Services
public bool SupportsFaceBiometric() public bool SupportsFaceBiometric()
{ {
// only used by iOS
return false; return false;
} }
public Task<bool> SupportsFaceBiometricAsync() public Task<bool> SupportsFaceBiometricAsync()
{ {
// only used by iOS
return Task.FromResult(SupportsFaceBiometric()); return Task.FromResult(SupportsFaceBiometric());
} }
public async Task<bool> BiometricAvailableAsync()
{
if (UseNativeBiometric())
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var manager = activity.GetSystemService(Context.BiometricService) as BiometricManager;
return manager.CanAuthenticate() == BiometricCode.Success;
}
else
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
}
}
public bool UseNativeBiometric()
{
return (int)Build.VERSION.SdkInt >= 29;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
if (string.IsNullOrWhiteSpace(text))
{
text = AppResources.BiometricsDirection;
}
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
using (var builder = new BiometricPrompt.Builder(activity))
{
builder.SetTitle(text);
builder.SetConfirmationRequired(false);
builder.SetNegativeButton(AppResources.Cancel, activity.MainExecutor,
new DialogInterfaceOnClickListener
{
Clicked = () => { }
});
var prompt = builder.Build();
var result = new TaskCompletionSource<bool>();
prompt.Authenticate(new CancellationSignal(), activity.MainExecutor,
new BiometricAuthenticationCallback
{
Success = authResult => result.TrySetResult(true),
Error = () => result.TrySetResult(false),
Failed = () => { },
Help = (helpCode, helpString) => { }
});
return result.Task;
}
}
public bool SupportsNfc() public bool SupportsNfc()
{ {
var activity = (MainActivity)CrossCurrentActivity.Current.Activity; var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
@ -870,48 +811,5 @@ namespace Bit.Droid.Services
Context.ClipboardService) as Android.Content.ClipboardManager; Context.ClipboardService) as Android.Content.ClipboardManager;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text); clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
} }
private class BiometricAuthenticationCallback : BiometricPrompt.AuthenticationCallback
{
public Action<BiometricPrompt.AuthenticationResult> Success { get; set; }
public Action Error { get; set; }
public Action Failed { get; set; }
public Action<BiometricAcquiredStatus, Java.Lang.ICharSequence> Help { get; set; }
public override void OnAuthenticationSucceeded(BiometricPrompt.AuthenticationResult authResult)
{
base.OnAuthenticationSucceeded(authResult);
Success?.Invoke(authResult);
}
public override void OnAuthenticationError([GeneratedEnum] BiometricErrorCode errorCode, Java.Lang.ICharSequence errString)
{
base.OnAuthenticationError(errorCode, errString);
Error?.Invoke();
}
public override void OnAuthenticationFailed()
{
base.OnAuthenticationFailed();
Failed?.Invoke();
}
public override void OnAuthenticationHelp([GeneratedEnum] BiometricAcquiredStatus helpCode,
Java.Lang.ICharSequence helpString)
{
base.OnAuthenticationHelp(helpCode, helpString);
Help?.Invoke(helpCode, helpString);
}
}
private class DialogInterfaceOnClickListener : Java.Lang.Object, IDialogInterfaceOnClickListener
{
public Action Clicked { get; set; }
public void OnClick(IDialogInterface dialog, int which)
{
Clicked?.Invoke();
}
}
} }
} }

View File

@ -23,9 +23,6 @@ namespace Bit.App.Abstractions
void RateApp(); void RateApp();
bool SupportsFaceBiometric(); bool SupportsFaceBiometric();
Task<bool> SupportsFaceBiometricAsync(); Task<bool> SupportsFaceBiometricAsync();
Task<bool> BiometricAvailableAsync();
bool UseNativeBiometric();
Task<bool> AuthenticateBiometricAsync(string text = null);
bool SupportsNfc(); bool SupportsNfc();
bool SupportsCamera(); bool SupportsCamera();
bool SupportsAutofillService(); bool SupportsAutofillService();

View File

@ -219,7 +219,7 @@ namespace Bit.App
SyncIfNeeded(); SyncIfNeeded();
if (Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage) if (Current.MainPage is NavigationPage navPage && navPage.CurrentPage is LockPage lockPage)
{ {
await lockPage.PromptFingerprintAfterResumeAsync(); await lockPage.PromptBiometricAfterResumeAsync();
} }
} }
@ -246,7 +246,7 @@ namespace Bit.App
_passwordGenerationService.ClearAsync(), _passwordGenerationService.ClearAsync(),
_vaultTimeoutService.ClearAsync(), _vaultTimeoutService.ClearAsync(),
_stateService.PurgeAsync()); _stateService.PurgeAsync());
_vaultTimeoutService.FingerprintLocked = true; _vaultTimeoutService.BiometricLocked = true;
_searchService.ClearIndex(); _searchService.ClearIndex();
_authService.LogOut(() => _authService.LogOut(() =>
{ {
@ -404,21 +404,20 @@ namespace Bit.App
} }
} }
private async Task LockedAsync(bool autoPromptFingerprint) private async Task LockedAsync(bool autoPromptBiometric)
{ {
await _stateService.PurgeAsync(); await _stateService.PurgeAsync();
if (autoPromptFingerprint && Device.RuntimePlatform == Device.iOS) if (autoPromptBiometric && Device.RuntimePlatform == Device.iOS)
{ {
var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey); var vaultTimeout = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
if (vaultTimeout == 0) if (vaultTimeout == 0)
{ {
autoPromptFingerprint = false; autoPromptBiometric = false;
} }
} }
else if (autoPromptFingerprint && Device.RuntimePlatform == Device.Android && else if (autoPromptBiometric && Device.RuntimePlatform == Device.Android)
_deviceActionService.UseNativeBiometric())
{ {
autoPromptFingerprint = false; autoPromptBiometric = false;
} }
PreviousPageInfo lastPageBeforeLock = null; PreviousPageInfo lastPageBeforeLock = null;
if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0) if (Current.MainPage is TabbedPage tabbedPage && tabbedPage.Navigation.ModalStack.Count > 0)
@ -445,7 +444,7 @@ namespace Bit.App
} }
} }
await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock); await _storageService.SaveAsync(Constants.PreviousPageKey, lastPageBeforeLock);
var lockPage = new LockPage(Options, autoPromptFingerprint); var lockPage = new LockPage(Options, autoPromptBiometric);
Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage)); Device.BeginInvokeOnMainThread(() => Current.MainPage = new NavigationPage(lockPage));
} }
} }

View File

@ -106,8 +106,8 @@
Margin="0, 10, 0, 0" /> Margin="0, 10, 0, 0" />
</StackLayout> </StackLayout>
<StackLayout Padding="10, 0"> <StackLayout Padding="10, 0">
<Button Text="{Binding FingerprintButtonText}" Clicked="Fingerprint_Clicked" <Button Text="{Binding BiometricButtonText}" Clicked="Biometric_Clicked"
IsVisible="{Binding FingerprintLock}"></Button> IsVisible="{Binding BiometricLock}"></Button>
<Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button> <Button Text="{u:I18n LogOut}" Clicked="LogOut_Clicked"></Button>
</StackLayout> </StackLayout>
</StackLayout> </StackLayout>

View File

@ -12,17 +12,17 @@ namespace Bit.App.Pages
{ {
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly AppOptions _appOptions; private readonly AppOptions _appOptions;
private readonly bool _autoPromptFingerprint; private readonly bool _autoPromptBiometric;
private readonly LockPageViewModel _vm; private readonly LockPageViewModel _vm;
private bool _promptedAfterResume; private bool _promptedAfterResume;
private bool _appeared; private bool _appeared;
public LockPage(AppOptions appOptions = null, bool autoPromptFingerprint = true) public LockPage(AppOptions appOptions = null, bool autoPromptBiometric = true)
{ {
_storageService = ServiceContainer.Resolve<IStorageService>("storageService"); _storageService = ServiceContainer.Resolve<IStorageService>("storageService");
_appOptions = appOptions; _appOptions = appOptions;
_autoPromptFingerprint = autoPromptFingerprint; _autoPromptBiometric = autoPromptBiometric;
InitializeComponent(); InitializeComponent();
_vm = BindingContext as LockPageViewModel; _vm = BindingContext as LockPageViewModel;
_vm.Page = this; _vm.Page = this;
@ -34,15 +34,15 @@ namespace Bit.App.Pages
public Entry MasterPasswordEntry { get; set; } public Entry MasterPasswordEntry { get; set; }
public Entry PinEntry { get; set; } public Entry PinEntry { get; set; }
public async Task PromptFingerprintAfterResumeAsync() public async Task PromptBiometricAfterResumeAsync()
{ {
if (_vm.FingerprintLock) if (_vm.BiometricLock)
{ {
await Task.Delay(500); await Task.Delay(500);
if (!_promptedAfterResume) if (!_promptedAfterResume)
{ {
_promptedAfterResume = true; _promptedAfterResume = true;
await _vm?.PromptFingerprintAsync(); await _vm?.PromptBiometricAsync();
} }
} }
} }
@ -55,8 +55,8 @@ namespace Bit.App.Pages
return; return;
} }
_appeared = true; _appeared = true;
await _vm.InitAsync(_autoPromptFingerprint); await _vm.InitAsync(_autoPromptBiometric);
if (!_vm.FingerprintLock) if (!_vm.BiometricLock)
{ {
if (_vm.PinLock) if (_vm.PinLock)
{ {
@ -89,11 +89,11 @@ namespace Bit.App.Pages
} }
} }
private async void Fingerprint_Clicked(object sender, EventArgs e) private async void Biometric_Clicked(object sender, EventArgs e)
{ {
if (DoOnce()) if (DoOnce())
{ {
await _vm.PromptFingerprintAsync(); await _vm.PromptBiometricAsync();
} }
} }

View File

@ -28,8 +28,8 @@ namespace Bit.App.Pages
private string _email; private string _email;
private bool _showPassword; private bool _showPassword;
private bool _pinLock; private bool _pinLock;
private bool _fingerprintLock; private bool _biometricLock;
private string _fingerprintButtonText; private string _biometricButtonText;
private string _loggedInAsText; private string _loggedInAsText;
private string _lockedVerifyText; private string _lockedVerifyText;
private int _invalidPinAttempts = 0; private int _invalidPinAttempts = 0;
@ -69,16 +69,16 @@ namespace Bit.App.Pages
set => SetProperty(ref _pinLock, value); set => SetProperty(ref _pinLock, value);
} }
public bool FingerprintLock public bool BiometricLock
{ {
get => _fingerprintLock; get => _biometricLock;
set => SetProperty(ref _fingerprintLock, value); set => SetProperty(ref _biometricLock, value);
} }
public string FingerprintButtonText public string BiometricButtonText
{ {
get => _fingerprintButtonText; get => _biometricButtonText;
set => SetProperty(ref _fingerprintButtonText, value); set => SetProperty(ref _biometricButtonText, value);
} }
public string LoggedInAsText public string LoggedInAsText
@ -100,11 +100,11 @@ namespace Bit.App.Pages
public string Pin { get; set; } public string Pin { get; set; }
public Action UnlockedAction { get; set; } public Action UnlockedAction { get; set; }
public async Task InitAsync(bool autoPromptFingerprint) public async Task InitAsync(bool autoPromptBiometric)
{ {
_pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); _pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; PinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
FingerprintLock = await _vaultTimeoutService.IsFingerprintLockSetAsync(); BiometricLock = await _vaultTimeoutService.IsBiometricLockSetAsync();
_email = await _userService.GetEmailAsync(); _email = await _userService.GetEmailAsync();
var webVault = _environmentService.GetWebVaultUrl(); var webVault = _environmentService.GetWebVaultUrl();
if (string.IsNullOrWhiteSpace(webVault)) if (string.IsNullOrWhiteSpace(webVault))
@ -124,27 +124,21 @@ namespace Bit.App.Pages
LockedVerifyText = AppResources.VaultLockedMasterPassword; LockedVerifyText = AppResources.VaultLockedMasterPassword;
} }
if (FingerprintLock) if (BiometricLock)
{
BiometricButtonText = AppResources.UseBiometricsToUnlock;
if (Device.RuntimePlatform == Device.iOS)
{ {
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync(); var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
if (Device.RuntimePlatform == Device.iOS && supportsFace) BiometricButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
{ AppResources.UseFingerprintToUnlock;
FingerprintButtonText = AppResources.UseFaceIDToUnlock;
} }
else if (Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric()) if (autoPromptBiometric)
{
FingerprintButtonText = AppResources.UseBiometricsToUnlock;
}
else
{
FingerprintButtonText = AppResources.UseFingerprintToUnlock;
}
if (autoPromptFingerprint)
{ {
var tasks = Task.Run(async () => var tasks = Task.Run(async () =>
{ {
await Task.Delay(500); await Task.Delay(500);
Device.BeginInvokeOnMainThread(async () => await PromptFingerprintAsync()); Device.BeginInvokeOnMainThread(async () => await PromptBiometricAsync());
}); });
} }
} }
@ -271,9 +265,9 @@ namespace Bit.App.Pages
entry.Focus(); entry.Focus();
} }
public async Task PromptFingerprintAsync() public async Task PromptBiometricAsync()
{ {
if (!FingerprintLock) if (!BiometricLock)
{ {
return; return;
} }
@ -290,7 +284,7 @@ namespace Bit.App.Pages
page.MasterPasswordEntry.Focus(); page.MasterPasswordEntry.Focus();
} }
}); });
_vaultTimeoutService.FingerprintLocked = !success; _vaultTimeoutService.BiometricLocked = !success;
if (success) if (success)
{ {
await DoContinueAsync(); await DoContinueAsync();
@ -309,7 +303,7 @@ namespace Bit.App.Pages
private async Task DoContinueAsync() private async Task DoContinueAsync()
{ {
_vaultTimeoutService.FingerprintLocked = false; _vaultTimeoutService.BiometricLocked = false;
var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey); var disableFavicon = await _storageService.GetAsync<bool?>(Constants.DisableFaviconKey);
await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault()); await _stateService.SaveAsync(Constants.DisableFaviconKey, disableFavicon.GetValueOrDefault());
_messagingService.Send("unlocked"); _messagingService.Send("unlocked");

View File

@ -143,19 +143,15 @@ namespace Bit.App.Pages
} }
else else
{ {
var fingerprintName = AppResources.Fingerprint; var biometricName = AppResources.Biometrics;
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync(); var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
fingerprintName = supportsFace ? AppResources.FaceID : AppResources.TouchID; biometricName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
} }
else if (Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric()) if (item.Name == string.Format(AppResources.UnlockWith, biometricName))
{ {
fingerprintName = AppResources.Biometrics; await _vm.UpdateBiometricAsync();
}
if (item.Name == string.Format(AppResources.UnlockWith, fingerprintName))
{
await _vm.UpdateFingerprintAsync();
} }
} }
} }

View File

@ -23,9 +23,9 @@ namespace Bit.App.Pages
private readonly IStorageService _storageService; private readonly IStorageService _storageService;
private readonly ISyncService _syncService; private readonly ISyncService _syncService;
private bool _supportsFingerprint; private bool _supportsBiometric;
private bool _pin; private bool _pin;
private bool _fingerprint; private bool _biometric;
private string _lastSyncDate; private string _lastSyncDate;
private string _vaultTimeoutDisplayValue; private string _vaultTimeoutDisplayValue;
private string _vaultTimeoutActionDisplayValue; private string _vaultTimeoutActionDisplayValue;
@ -69,7 +69,7 @@ namespace Bit.App.Pages
public async Task InitAsync() public async Task InitAsync()
{ {
_supportsFingerprint = await _platformUtilsService.SupportsBiometricAsync(); _supportsBiometric = await _platformUtilsService.SupportsBiometricAsync();
var lastSync = await _syncService.GetLastSyncAsync(); var lastSync = await _syncService.GetLastSyncAsync();
if (lastSync != null) if (lastSync != null)
{ {
@ -83,7 +83,7 @@ namespace Bit.App.Pages
_vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key; _vaultTimeoutActionDisplayValue = _vaultTimeoutActions.FirstOrDefault(o => o.Value == action).Key;
var pinSet = await _vaultTimeoutService.IsPinLockSetAsync(); var pinSet = await _vaultTimeoutService.IsPinLockSetAsync();
_pin = pinSet.Item1 || pinSet.Item2; _pin = pinSet.Item1 || pinSet.Item2;
_fingerprint = await _vaultTimeoutService.IsFingerprintLockSetAsync(); _biometric = await _vaultTimeoutService.IsBiometricLockSetAsync();
BuildList(); BuildList();
} }
@ -288,31 +288,31 @@ namespace Bit.App.Pages
BuildList(); BuildList();
} }
public async Task UpdateFingerprintAsync() public async Task UpdateBiometricAsync()
{ {
var current = _fingerprint; var current = _biometric;
if (_fingerprint) if (_biometric)
{ {
_fingerprint = false; _biometric = false;
} }
else if (await _platformUtilsService.SupportsBiometricAsync()) else if (await _platformUtilsService.SupportsBiometricAsync())
{ {
_fingerprint = await _platformUtilsService.AuthenticateBiometricAsync(null, _biometric = await _platformUtilsService.AuthenticateBiometricAsync(null,
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null); _deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
} }
if (_fingerprint == current) if (_biometric == current)
{ {
return; return;
} }
if (_fingerprint) if (_biometric)
{ {
await _storageService.SaveAsync(Constants.FingerprintUnlockKey, true); await _storageService.SaveAsync(Constants.BiometricUnlockKey, true);
} }
else else
{ {
await _storageService.RemoveAsync(Constants.FingerprintUnlockKey); await _storageService.RemoveAsync(Constants.BiometricUnlockKey);
} }
_vaultTimeoutService.FingerprintLocked = false; _vaultTimeoutService.BiometricLocked = false;
await _cryptoService.ToggleKeyAsync(); await _cryptoService.ToggleKeyAsync();
BuildList(); BuildList();
} }
@ -371,22 +371,18 @@ namespace Bit.App.Pages
new SettingsPageListItem { Name = AppResources.LockNow }, new SettingsPageListItem { Name = AppResources.LockNow },
new SettingsPageListItem { Name = AppResources.TwoStepLogin } new SettingsPageListItem { Name = AppResources.TwoStepLogin }
}; };
if (_supportsFingerprint || _fingerprint) if (_supportsBiometric || _biometric)
{ {
var fingerprintName = AppResources.Fingerprint; var biometricName = AppResources.Biometrics;
if (Device.RuntimePlatform == Device.iOS) if (Device.RuntimePlatform == Device.iOS)
{ {
fingerprintName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID : biometricName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
AppResources.TouchID; AppResources.TouchID;
} }
else if (Device.RuntimePlatform == Device.Android && _deviceActionService.UseNativeBiometric())
{
fingerprintName = AppResources.Biometrics;
}
var item = new SettingsPageListItem var item = new SettingsPageListItem
{ {
Name = string.Format(AppResources.UnlockWith, fingerprintName), Name = string.Format(AppResources.UnlockWith, biometricName),
SubLabel = _fingerprint ? AppResources.Enabled : AppResources.Disabled SubLabel = _biometric ? AppResources.Enabled : AppResources.Disabled
}; };
securityItems.Insert(2, item); securityItems.Insert(2, item);
} }

View File

@ -9,7 +9,6 @@
namespace Bit.App.Resources { namespace Bit.App.Resources {
using System; using System;
using System.Reflection;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]

View File

@ -1564,7 +1564,7 @@
<value>Your login session has expired.</value> <value>Your login session has expired.</value>
</data> </data>
<data name="BiometricsDirection" xml:space="preserve"> <data name="BiometricsDirection" xml:space="preserve">
<value>Use biometrics to verify.</value> <value>Biometric Verification</value>
</data> </data>
<data name="Biometrics" xml:space="preserve"> <data name="Biometrics" xml:space="preserve">
<value>Biometrics</value> <value>Biometrics</value>

View File

@ -199,36 +199,41 @@ namespace Bit.App.Services
public async Task<bool> SupportsBiometricAsync() public async Task<bool> SupportsBiometricAsync()
{ {
return await _deviceActionService.BiometricAvailableAsync(); try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
} }
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
Action fallback = null) Action fallback = null)
{
if (_deviceActionService.UseNativeBiometric())
{
return await _deviceActionService.AuthenticateBiometricAsync(text);
}
else
{ {
try try
{ {
if (text == null) if (text == null)
{
text = AppResources.BiometricsDirection;
if (Device.RuntimePlatform == Device.iOS)
{ {
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync(); var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection; text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
} }
var fingerprintRequest = new AuthenticationRequestConfiguration(text, text) }
var biometricRequest = new AuthenticationRequestConfiguration(AppResources.Bitwarden, text)
{ {
CancelTitle = AppResources.Cancel, CancelTitle = AppResources.Cancel,
FallbackTitle = fallbackText FallbackTitle = fallbackText
}; };
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest); var result = await CrossFingerprint.Current.AuthenticateAsync(biometricRequest);
if (result.Authenticated) if (result.Authenticated)
{ {
return true; return true;
} }
else if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested) if (result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
{ {
fallback?.Invoke(); fallback?.Invoke();
} }
@ -238,4 +243,3 @@ namespace Bit.App.Services
} }
} }
} }
}

View File

@ -7,13 +7,13 @@ namespace Bit.Core.Abstractions
public interface IVaultTimeoutService public interface IVaultTimeoutService
{ {
CipherString PinProtectedKey { get; set; } CipherString PinProtectedKey { get; set; }
bool FingerprintLocked { get; set; } bool BiometricLocked { get; set; }
Task CheckVaultTimeoutAsync(); Task CheckVaultTimeoutAsync();
Task ClearAsync(); Task ClearAsync();
Task<bool> IsLockedAsync(); Task<bool> IsLockedAsync();
Task<Tuple<bool, bool>> IsPinLockSetAsync(); Task<Tuple<bool, bool>> IsPinLockSetAsync();
Task<bool> IsFingerprintLockSetAsync(); Task<bool> IsBiometricLockSetAsync();
Task LockAsync(bool allowSoftLock = false, bool userInitiated = false); Task LockAsync(bool allowSoftLock = false, bool userInitiated = false);
Task LogOutAsync(); Task LogOutAsync();
Task SetVaultTimeoutOptionsAsync(int? timeout, string action); Task SetVaultTimeoutOptionsAsync(int? timeout, string action);

View File

@ -7,7 +7,7 @@
public static string VaultTimeoutKey = "lockOption"; public static string VaultTimeoutKey = "lockOption";
public static string VaultTimeoutActionKey = "vaultTimeoutAction"; public static string VaultTimeoutActionKey = "vaultTimeoutAction";
public static string LastActiveKey = "lastActive"; public static string LastActiveKey = "lastActive";
public static string FingerprintUnlockKey = "fingerprintUnlock"; public static string BiometricUnlockKey = "fingerprintUnlock";
public static string ProtectedPin = "protectedPin"; public static string ProtectedPin = "protectedPin";
public static string PinProtectedKey = "pinProtectedKey"; public static string PinProtectedKey = "pinProtectedKey";
public static string DefaultUriMatch = "defaultUriMatch"; public static string DefaultUriMatch = "defaultUriMatch";

View File

@ -315,7 +315,7 @@ namespace Bit.Core.Services
await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey); await _cryptoService.SetEncPrivateKeyAsync(tokenResponse.PrivateKey);
} }
_vaultTimeoutService.FingerprintLocked = false; _vaultTimeoutService.BiometricLocked = false;
_messagingService.Send("loggedIn"); _messagingService.Send("loggedIn");
return result; return result;
} }

View File

@ -48,8 +48,8 @@ namespace Bit.Core.Services
{ {
_key = key; _key = key;
var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey); var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey); var biometric = await _storageService.GetAsync<bool?>(Constants.BiometricUnlockKey);
if (option.HasValue && !fingerprint.GetValueOrDefault()) if (option.HasValue && !biometric.GetValueOrDefault())
{ {
// If we have a lock option set, we do not store the key // If we have a lock option set, we do not store the key
return; return;
@ -354,8 +354,8 @@ namespace Bit.Core.Services
{ {
var key = await GetKeyAsync(); var key = await GetKeyAsync();
var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey); var option = await _storageService.GetAsync<int?>(Constants.VaultTimeoutKey);
var fingerprint = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey); var biometric = await _storageService.GetAsync<bool?>(Constants.BiometricUnlockKey);
if (!fingerprint.GetValueOrDefault() && (option != null || option == 0)) if (!biometric.GetValueOrDefault() && (option != null || option == 0))
{ {
await ClearKeyAsync(); await ClearKeyAsync();
_key = key; _key = key;

View File

@ -49,15 +49,15 @@ namespace Bit.Core.Services
} }
public CipherString PinProtectedKey { get; set; } = null; public CipherString PinProtectedKey { get; set; } = null;
public bool FingerprintLocked { get; set; } = true; public bool BiometricLocked { get; set; } = true;
public async Task<bool> IsLockedAsync() public async Task<bool> IsLockedAsync()
{ {
var hasKey = await _cryptoService.HasKeyAsync(); var hasKey = await _cryptoService.HasKeyAsync();
if (hasKey) if (hasKey)
{ {
var fingerprintSet = await IsFingerprintLockSetAsync(); var biometricSet = await IsBiometricLockSetAsync();
if (fingerprintSet && FingerprintLocked) if (biometricSet && BiometricLocked)
{ {
return true; return true;
} }
@ -120,8 +120,8 @@ namespace Bit.Core.Services
} }
if (allowSoftLock) if (allowSoftLock)
{ {
FingerprintLocked = await IsFingerprintLockSetAsync(); BiometricLocked = await IsBiometricLockSetAsync();
if (FingerprintLocked) if (BiometricLocked)
{ {
_messagingService.Send("locked", userInitiated); _messagingService.Send("locked", userInitiated);
_lockedCallback?.Invoke(userInitiated); _lockedCallback?.Invoke(userInitiated);
@ -165,10 +165,10 @@ namespace Bit.Core.Services
return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null); return new Tuple<bool, bool>(protectedPin != null, pinProtectedKey != null);
} }
public async Task<bool> IsFingerprintLockSetAsync() public async Task<bool> IsBiometricLockSetAsync()
{ {
var fingerprintLock = await _storageService.GetAsync<bool?>(Constants.FingerprintUnlockKey); var biometricLock = await _storageService.GetAsync<bool?>(Constants.BiometricUnlockKey);
return fingerprintLock.GetValueOrDefault(); return biometricLock.GetValueOrDefault();
} }
public async Task ClearAsync() public async Task ClearAsync()

View File

@ -52,7 +52,7 @@ namespace Bit.iOS.Core.Controllers
_pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult(); _pinSet = _vaultTimeoutService.IsPinLockSetAsync().GetAwaiter().GetResult();
_pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2; _pinLock = (_pinSet.Item1 && _vaultTimeoutService.PinProtectedKey != null) || _pinSet.Item2;
_fingerprintLock = _vaultTimeoutService.IsFingerprintLockSetAsync().GetAwaiter().GetResult(); _fingerprintLock = _vaultTimeoutService.IsBiometricLockSetAsync().GetAwaiter().GetResult();
BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword; BaseNavItem.Title = _pinLock ? AppResources.VerifyPIN : AppResources.VerifyMasterPassword;
BaseCancelButton.Title = AppResources.Cancel; BaseCancelButton.Title = AppResources.Cancel;
@ -205,7 +205,7 @@ namespace Bit.iOS.Core.Controllers
private void DoContinue() private void DoContinue()
{ {
_vaultTimeoutService.FingerprintLocked = false; _vaultTimeoutService.BiometricLocked = false;
MasterPasswordCell.TextField.ResignFirstResponder(); MasterPasswordCell.TextField.ResignFirstResponder();
Success(); Success();
} }
@ -219,7 +219,7 @@ namespace Bit.iOS.Core.Controllers
var success = await _platformUtilsService.AuthenticateBiometricAsync(null, var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
_pinLock ? AppResources.PIN : AppResources.MasterPassword, _pinLock ? AppResources.PIN : AppResources.MasterPassword,
() => MasterPasswordCell.TextField.BecomeFirstResponder()); () => MasterPasswordCell.TextField.BecomeFirstResponder());
_vaultTimeoutService.FingerprintLocked = !success; _vaultTimeoutService.BiometricLocked = !success;
if (success) if (success)
{ {
DoContinue(); DoContinue();
@ -261,11 +261,11 @@ namespace Bit.iOS.Core.Controllers
{ {
if (indexPath.Row == 0) if (indexPath.Row == 0)
{ {
var fingerprintButtonText = _controller._deviceActionService.SupportsFaceBiometric() ? var biometricButtonText = _controller._deviceActionService.SupportsFaceBiometric() ?
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock; AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
var cell = new ExtendedUITableViewCell(); var cell = new ExtendedUITableViewCell();
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor; cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;
cell.TextLabel.Text = fingerprintButtonText; cell.TextLabel.Text = biometricButtonText;
return cell; return cell;
} }
} }

View File

@ -15,7 +15,6 @@ using Foundation;
using LocalAuthentication; using LocalAuthentication;
using MobileCoreServices; using MobileCoreServices;
using Photos; using Photos;
using Plugin.Fingerprint;
using UIKit; using UIKit;
using Xamarin.Forms; using Xamarin.Forms;
@ -271,28 +270,6 @@ namespace Bit.iOS.Core.Services
return Task.FromResult(SupportsFaceBiometric()); return Task.FromResult(SupportsFaceBiometric());
} }
public async Task<bool> BiometricAvailableAsync()
{
try
{
return await CrossFingerprint.Current.IsAvailableAsync();
}
catch
{
return false;
}
}
public bool UseNativeBiometric()
{
return false;
}
public Task<bool> AuthenticateBiometricAsync(string text = null)
{
throw new NotSupportedException();
}
public bool SupportsNfc() public bool SupportsNfc()
{ {
if(Application.Current is App.App currentApp && !currentApp.Options.IosExtension) if(Application.Current is App.App currentApp && !currentApp.Options.IosExtension)