Skip to content

Commit

Permalink
Add stickyAuth option which is off by default (flutter#216)
Browse files Browse the repository at this point in the history
* Add stickyAuth option which is off by default

* Review comments
  • Loading branch information
mehmetf authored Sep 19, 2017
1 parent 8e831cc commit 1986b4b
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 17 deletions.
4 changes: 4 additions & 0 deletions packages/local_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [0.0.2]

* Support stickyAuth mode.

## [0.0.1] - 6/21/2017

* Initial release of local authentication plugin.
9 changes: 9 additions & 0 deletions packages/local_auth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,15 @@ Update your project's `AndroidManifest.xml` file to include the
<manifest>
```

## Sticky Auth

You can set the `stickyAuth` option on the plugin to true so that plugin does not
return failure if the app is put to background by the system. This might happen
if the user receives a phone call before they get a chance to authenticate. With
`stickyAuth` set to false, this would result in plugin returning failure result
to the Dart app. If set to true, the plugin will retry authenticating when the
app resumes.

## Getting Started

For help getting started with Flutter, view our online
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ interface AuthCompletionHandler {
private final AuthCompletionHandler completionHandler;
private final KeyguardManager keyguardManager;
private final FingerprintManagerCompat fingerprintManager;
private final CancellationSignal cancellationSignal;
private final MethodCall call;

/**
Expand All @@ -82,12 +81,13 @@ interface AuthCompletionHandler {
*/
private AlertDialog fingerprintDialog;

private CancellationSignal cancellationSignal;

AuthenticationHelper(
Activity activity, MethodCall call, AuthCompletionHandler completionHandler) {
this.activity = activity;
this.completionHandler = completionHandler;
this.call = call;
this.cancellationSignal = new CancellationSignal();
this.keyguardManager = (KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
this.fingerprintManager = FingerprintManagerCompat.from(activity);
}
Expand All @@ -112,23 +112,31 @@ void authenticate() {
}
}

/** Starts the fingerprint listener and shows the fingerprint dialog. */
private void start() {
activity.getApplication().registerActivityLifecycleCallbacks(this);
resume();
}

private void resume() {
cancellationSignal = new CancellationSignal();
showFingerprintDialog();
fingerprintManager.authenticate(null, 0, cancellationSignal, this, null);
}

private void pause() {
cancellationSignal.cancel();
if (fingerprintDialog != null && fingerprintDialog.isShowing()) {
fingerprintDialog.dismiss();
}
}

/**
* Stops the fingerprint listener and dismisses the fingerprint dialog.
*
* @param success If the authentication was successful.
*/
private void stop(boolean success) {
cancellationSignal.cancel();
if (fingerprintDialog != null && fingerprintDialog.isShowing()) {
fingerprintDialog.dismiss();
}
pause();
activity.getApplication().unregisterActivityLifecycleCallbacks(this);
if (success) {
completionHandler.onSuccess();
Expand All @@ -143,7 +151,18 @@ private void stop(boolean success) {
*/
@Override
public void onActivityPaused(Activity activity) {
stop(false);
if (call.argument("stickyAuth")) {
pause();
} else {
stop(false);
}
}

@Override
public void onActivityResumed(Activity activity) {
if (call.argument("stickyAuth")) {
resume();
}
}

@Override
Expand Down Expand Up @@ -261,9 +280,6 @@ public void onActivityCreated(Activity activity, Bundle bundle) {}
@Override
public void onActivityStarted(Activity activity) {}

@Override
public void onActivityResumed(Activity activity) {}

@Override
public void onActivityStopped(Activity activity) {}

Expand Down
3 changes: 2 additions & 1 deletion packages/local_auth/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ class _MyAppState extends State<MyApp> {
try {
authenticated = await auth.authenticateWithBiometrics(
localizedReason: 'Scan your fingerprint to authenticate',
useErrorDialogs: true);
useErrorDialogs: true,
stickyAuth: false);
} on PlatformException catch (e) {
print(e);
}
Expand Down
26 changes: 23 additions & 3 deletions packages/local_auth/ios/Classes/LocalAuthPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@

#import "LocalAuthPlugin.h"

@implementation LocalAuthPlugin
@implementation LocalAuthPlugin {
NSDictionary *lastCallArgs;
FlutterResult lastResult;
}
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
FlutterMethodChannel *channel =
[FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/local_auth"
binaryMessenger:[registrar messenger]];
LocalAuthPlugin *instance = [[LocalAuthPlugin alloc] init];
[registrar addMethodCallDelegate:instance channel:channel];
[registrar addApplicationDelegate:instance];
}

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
Expand Down Expand Up @@ -45,7 +49,7 @@ - (void)alertMessage:(NSString *)message
actionWithTitle:secondButton
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
if (&UIApplicationOpenSettingsURLString != NULL) {
if (UIApplicationOpenSettingsURLString != NULL) {
NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:url];
result(@NO);
Expand All @@ -62,6 +66,8 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments
withFlutterResult:(FlutterResult)result {
LAContext *context = [[LAContext alloc] init];
NSError *authError = nil;
lastCallArgs = nil;
lastResult = nil;
context.localizedFallbackTitle = @"";

if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
Expand All @@ -81,6 +87,12 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments
flutterArguments:arguments
withFlutterResult:result];
return;
case LAErrorSystemCancel:
if ([arguments[@"stickyAuth"] boolValue]) {
lastCallArgs = arguments;
lastResult = result;
return;
}
}
result(@NO);
}
Expand All @@ -97,7 +109,7 @@ - (void)handleErrors:(NSError *)authError
switch (authError.code) {
case LAErrorPasscodeNotSet:
case LAErrorTouchIDNotEnrolled:
if (arguments[@"useErrorDialogs"]) {
if ([arguments[@"useErrorDialogs"] boolValue]) {
[self alertMessage:arguments[@"goToSettingDescriptionIOS"]
firstButton:arguments[@"okButton"]
flutterResult:result
Expand All @@ -118,4 +130,12 @@ - (void)handleErrors:(NSError *)authError
details:authError.domain]);
}

#pragma mark - AppDelegate

- (void)applicationDidBecomeActive:(UIApplication *)application {
if (lastCallArgs != nil && lastResult != nil) {
[self authenticateWithBiometrics:lastCallArgs withFlutterResult:lastResult];
}
}

@end
12 changes: 11 additions & 1 deletion packages/local_auth/lib/local_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,21 @@ class LocalAuthentication {
/// for authentication. This is typically along the lines of: 'Please scan
/// your finger to access MyApp.'
///
/// useErrorDialogs = true means the system will attempt to handle user
/// [useErrorDialogs] = true means the system will attempt to handle user
/// fixable issues encountered while authenticating. For instance, if
/// fingerprint reader exists on the phone but there's no fingerprint
/// registered, the plugin will attempt to take the user to settings to add
/// one. Anything that is not user fixable, such as no biometric sensor on
/// device, will be returned as a [PlatformException].
///
/// [stickyAuth] is used when the application goes into background for any
/// reason while the authentication is in progress. Due to security reasons,
/// the authentication has to be stopped at that time. If stickyAuth is set
/// to true, authentication resumes when the app is resumed. If it is set to
/// false (default), then as soon as app is paused a failure message is sent
/// back to Dart and it is up to the client app to restart authentication or
/// do something else.
///
/// Construct [AndroidAuthStrings] and [IOSAuthStrings] if you want to
/// customize messages in the dialogs.
///
Expand All @@ -42,13 +50,15 @@ class LocalAuthentication {
Future<bool> authenticateWithBiometrics({
@required String localizedReason,
bool useErrorDialogs: true,
bool stickyAuth: false,
AndroidAuthMessages androidAuthStrings: const AndroidAuthMessages(),
IOSAuthMessages iOSAuthStrings: const IOSAuthMessages(),
}) {
assert(localizedReason != null);
final Map<String, Object> args = <String, Object>{
'localizedReason': localizedReason,
'useErrorDialogs': useErrorDialogs,
'stickyAuth': stickyAuth,
};
if (Platform.isIOS) {
args.addAll(iOSAuthStrings.args);
Expand Down
2 changes: 1 addition & 1 deletion packages/local_auth/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: local_auth
description: A plugin that uses local sensors to authenticate users (such as Fingerprint Reader/Touch ID).
version: 0.0.1
version: 0.0.2
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth

Expand Down

0 comments on commit 1986b4b

Please sign in to comment.