mirror of
https://github.com/bitwarden/android.git
synced 2024-12-24 18:08:26 +03:00
use native biomatrics on Android
This commit is contained in:
parent
aed3ec5474
commit
4b989b01e9
11 changed files with 199 additions and 52 deletions
|
@ -8,9 +8,11 @@ using Android.App;
|
|||
using Android.App.Assist;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Hardware.Biometrics;
|
||||
using Android.Nfc;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Runtime;
|
||||
using Android.Support.V4.App;
|
||||
using Android.Support.V4.Content;
|
||||
using Android.Text;
|
||||
|
@ -28,6 +30,7 @@ using Bit.Core.Models.View;
|
|||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Autofill;
|
||||
using Plugin.CurrentActivity;
|
||||
using Plugin.Fingerprint;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
|
@ -335,11 +338,72 @@ namespace Bit.Droid.Services
|
|||
Application.Context.PackageName, 0).VersionCode.ToString();
|
||||
}
|
||||
|
||||
public bool SupportsFaceId()
|
||||
public bool SupportsFaceBiometric()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<bool> SupportsFaceBiometricAsync()
|
||||
{
|
||||
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 >= 28;
|
||||
}
|
||||
|
||||
public Task<bool> AuthenticateBiometricAsync(string text = null)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
text = AppResources.BiometricDirection;
|
||||
}
|
||||
|
||||
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),
|
||||
Failed = () => result.TrySetResult(false),
|
||||
Help = (helpCode, helpString) => { }
|
||||
});
|
||||
return result.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsNfc()
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
|
@ -715,5 +779,41 @@ namespace Bit.Droid.Services
|
|||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
||||
clipboardManager.Text = text;
|
||||
}
|
||||
|
||||
private class BiometricAuthenticationCallback : BiometricPrompt.AuthenticationCallback
|
||||
{
|
||||
public Action<BiometricPrompt.AuthenticationResult> Success { 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 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,11 @@ namespace Bit.App.Abstractions
|
|||
string okButtonText = null, string cancelButtonText = null, bool numericKeyboard = false,
|
||||
bool autofocus = true);
|
||||
void RateApp();
|
||||
bool SupportsFaceId();
|
||||
bool SupportsFaceBiometric();
|
||||
Task<bool> SupportsFaceBiometricAsync();
|
||||
Task<bool> BiometricAvailableAsync();
|
||||
bool UseNativeBiometric();
|
||||
Task<bool> AuthenticateBiometricAsync(string text = null);
|
||||
bool SupportsNfc();
|
||||
bool SupportsCamera();
|
||||
bool SupportsAutofillService();
|
||||
|
|
|
@ -126,7 +126,8 @@ namespace Bit.App.Pages
|
|||
|
||||
if(FingerprintLock)
|
||||
{
|
||||
FingerprintButtonText = _deviceActionService.SupportsFaceId() ? AppResources.UseFaceIDToUnlock :
|
||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||
FingerprintButtonText = supportsFace ? AppResources.UseFaceIDToUnlock :
|
||||
AppResources.UseFingerprintToUnlock;
|
||||
if(autoPromptFingerprint)
|
||||
{
|
||||
|
@ -266,7 +267,7 @@ namespace Bit.App.Pages
|
|||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
PinLock ? AppResources.PIN : AppResources.MasterPassword, () =>
|
||||
{
|
||||
var page = Page as LockPage;
|
||||
|
|
|
@ -142,8 +142,8 @@ namespace Bit.App.Pages
|
|||
var fingerprintName = AppResources.Fingerprint;
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
fingerprintName = _deviceActionService.SupportsFaceId() ?
|
||||
AppResources.FaceID : AppResources.TouchID;
|
||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||
fingerprintName = supportsFace ? AppResources.FaceID : AppResources.TouchID;
|
||||
}
|
||||
if(item.Name == string.Format(AppResources.UnlockWith, fingerprintName))
|
||||
{
|
||||
|
|
|
@ -62,7 +62,7 @@ namespace Bit.App.Pages
|
|||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_supportsFingerprint = await _platformUtilsService.SupportsFingerprintAsync();
|
||||
_supportsFingerprint = await _platformUtilsService.SupportsBiometricAsync();
|
||||
var lastSync = await _syncService.GetLastSyncAsync();
|
||||
if(lastSync != null)
|
||||
{
|
||||
|
@ -255,9 +255,9 @@ namespace Bit.App.Pages
|
|||
{
|
||||
_fingerprint = false;
|
||||
}
|
||||
else if(await _platformUtilsService.SupportsFingerprintAsync())
|
||||
else if(await _platformUtilsService.SupportsBiometricAsync())
|
||||
{
|
||||
_fingerprint = await _platformUtilsService.AuthenticateFingerprintAsync(null,
|
||||
_fingerprint = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_deviceActionService.DeviceType == Core.Enums.DeviceType.Android ? "." : null);
|
||||
}
|
||||
if(_fingerprint == current)
|
||||
|
@ -328,8 +328,8 @@ namespace Bit.App.Pages
|
|||
var fingerprintName = AppResources.Fingerprint;
|
||||
if(Device.RuntimePlatform == Device.iOS)
|
||||
{
|
||||
fingerprintName = _deviceActionService.SupportsFaceId() ?
|
||||
AppResources.FaceID : AppResources.TouchID;
|
||||
fingerprintName = _deviceActionService.SupportsFaceBiometric() ? AppResources.FaceID :
|
||||
AppResources.TouchID;
|
||||
}
|
||||
var item = new SettingsPageListItem
|
||||
{
|
||||
|
|
9
src/App/Resources/AppResources.Designer.cs
generated
9
src/App/Resources/AppResources.Designer.cs
generated
|
@ -537,6 +537,15 @@ namespace Bit.App.Resources {
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Use biometrics to verify..
|
||||
/// </summary>
|
||||
public static string BiometricDirection {
|
||||
get {
|
||||
return ResourceManager.GetString("BiometricDirection", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Bitwarden.
|
||||
/// </summary>
|
||||
|
|
|
@ -1578,4 +1578,7 @@
|
|||
<data name="LoginExpired" xml:space="preserve">
|
||||
<value>Your login session has expired.</value>
|
||||
</data>
|
||||
<data name="BiometricDirection" xml:space="preserve">
|
||||
<value>Use biometrics to verify.</value>
|
||||
</data>
|
||||
</root>
|
|
@ -197,45 +197,45 @@ namespace Bit.App.Services
|
|||
return await Clipboard.GetTextAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> SupportsFingerprintAsync()
|
||||
public async Task<bool> SupportsBiometricAsync()
|
||||
{
|
||||
try
|
||||
return await _deviceActionService.BiometricAvailableAsync();
|
||||
}
|
||||
|
||||
public async Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null,
|
||||
Action fallback = null)
|
||||
{
|
||||
if(_deviceActionService.UseNativeBiometric())
|
||||
{
|
||||
return await CrossFingerprint.Current.IsAvailableAsync();
|
||||
return await _deviceActionService.AuthenticateBiometricAsync(text);
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if(text == null)
|
||||
{
|
||||
var supportsFace = await _deviceActionService.SupportsFaceBiometricAsync();
|
||||
text = supportsFace ? AppResources.FaceIDDirection : AppResources.FingerprintDirection;
|
||||
}
|
||||
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
|
||||
{
|
||||
CancelTitle = AppResources.Cancel,
|
||||
FallbackTitle = fallbackText
|
||||
};
|
||||
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
|
||||
if(result.Authenticated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
||||
{
|
||||
fallback?.Invoke();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null,
|
||||
Action fallback = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(text == null)
|
||||
{
|
||||
text = _deviceActionService.SupportsFaceId() ? AppResources.FaceIDDirection :
|
||||
AppResources.FingerprintDirection;
|
||||
}
|
||||
var fingerprintRequest = new AuthenticationRequestConfiguration(text)
|
||||
{
|
||||
CancelTitle = AppResources.Cancel,
|
||||
FallbackTitle = fallbackText
|
||||
};
|
||||
var result = await CrossFingerprint.Current.AuthenticateAsync(fingerprintRequest);
|
||||
if(result.Authenticated)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if(result.Status == FingerprintAuthenticationResultStatus.FallbackRequested)
|
||||
{
|
||||
fallback?.Invoke();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace Bit.Core.Abstractions
|
|||
void ShowToast(string type, string title, string[] text, Dictionary<string, object> options = null);
|
||||
bool SupportsU2f();
|
||||
bool SupportsDuo();
|
||||
Task<bool> SupportsFingerprintAsync();
|
||||
Task<bool> AuthenticateFingerprintAsync(string text = null, string fallbackText = null, Action fallback = null);
|
||||
Task<bool> SupportsBiometricAsync();
|
||||
Task<bool> AuthenticateBiometricAsync(string text = null, string fallbackText = null, Action fallback = null);
|
||||
}
|
||||
}
|
|
@ -216,7 +216,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
{
|
||||
return;
|
||||
}
|
||||
var success = await _platformUtilsService.AuthenticateFingerprintAsync(null,
|
||||
var success = await _platformUtilsService.AuthenticateBiometricAsync(null,
|
||||
_pinLock ? AppResources.PIN : AppResources.MasterPassword,
|
||||
() => MasterPasswordCell.TextField.BecomeFirstResponder());
|
||||
_lockService.FingerprintLocked = !success;
|
||||
|
@ -261,7 +261,7 @@ namespace Bit.iOS.Core.Controllers
|
|||
{
|
||||
if(indexPath.Row == 0)
|
||||
{
|
||||
var fingerprintButtonText = _controller._deviceActionService.SupportsFaceId() ?
|
||||
var fingerprintButtonText = _controller._deviceActionService.SupportsFaceBiometric() ?
|
||||
AppResources.UseFaceIDToUnlock : AppResources.UseFingerprintToUnlock;
|
||||
var cell = new ExtendedUITableViewCell();
|
||||
cell.TextLabel.TextColor = ThemeHelpers.PrimaryColor;
|
||||
|
|
|
@ -15,6 +15,7 @@ using Foundation;
|
|||
using LocalAuthentication;
|
||||
using MobileCoreServices;
|
||||
using Photos;
|
||||
using Plugin.Fingerprint;
|
||||
using UIKit;
|
||||
using Xamarin.Forms;
|
||||
|
||||
|
@ -243,18 +244,47 @@ namespace Bit.iOS.Core.Services
|
|||
Device.OpenUri(new Uri(uri));
|
||||
}
|
||||
|
||||
public bool SupportsFaceId()
|
||||
public bool SupportsFaceBiometric()
|
||||
{
|
||||
if(SystemMajorVersion() < 11)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var context = new LAContext();
|
||||
if(!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out NSError e))
|
||||
using(var context = new LAContext())
|
||||
{
|
||||
if(!context.CanEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, out var e))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return context.BiometryType == LABiometryType.FaceId;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> SupportsFaceBiometricAsync()
|
||||
{
|
||||
return Task.FromResult(SupportsFaceBiometric());
|
||||
}
|
||||
|
||||
public async Task<bool> BiometricAvailableAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await CrossFingerprint.Current.IsAvailableAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return context.BiometryType == LABiometryType.FaceId;
|
||||
}
|
||||
|
||||
public bool UseNativeBiometric()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public Task<bool> AuthenticateBiometricAsync(string text = null)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public bool SupportsNfc()
|
||||
|
|
Loading…
Reference in a new issue