diff --git a/packages/local_auth/CHANGELOG.md b/packages/local_auth/CHANGELOG.md index 1988028a1f9d..832efc795a95 100644 --- a/packages/local_auth/CHANGELOG.md +++ b/packages/local_auth/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.6.0 + +* Define a new parameter for signaling that the transaction is sensitive. +* Up the biometric version to beta01. +* Handle no device credential error. + ## 0.5.3 * Add face id detection as well by not relying on FingerprintCompat. diff --git a/packages/local_auth/android/build.gradle b/packages/local_auth/android/build.gradle index 142b606405c4..74fe6c550517 100644 --- a/packages/local_auth/android/build.gradle +++ b/packages/local_auth/android/build.gradle @@ -48,6 +48,6 @@ android { dependencies { api "androidx.core:core:1.1.0-beta01" - api "androidx.biometric:biometric:1.0.0-alpha04" + api "androidx.biometric:biometric:1.0.0-beta01" api "androidx.fragment:fragment:1.1.0-alpha06" } diff --git a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java index 46d7bf3dec9a..a4ef01bfa504 100644 --- a/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java +++ b/packages/local_auth/android/src/main/java/io/flutter/plugins/localauth/AuthenticationHelper.java @@ -77,6 +77,7 @@ public AuthenticationHelper( .setTitle((String) call.argument("signInTitle")) .setSubtitle((String) call.argument("fingerprintHint")) .setNegativeButtonText((String) call.argument("cancelButton")) + .setConfirmationRequired((Boolean) call.argument("sensitiveTransaction")) .build(); } @@ -95,13 +96,11 @@ private void stop() { @Override public void onAuthenticationError(int errorCode, CharSequence errString) { switch (errorCode) { - // TODO(mehmetf): Re-enable when biometric alpha05 is released. - // https://developer.android.com/jetpack/androidx/releases/biometric - // case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: - // completionHandler.onError( - // "PasscodeNotSet", - // "Phone not secured by PIN, pattern or password, or SIM is currently locked."); - // break; + case BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL: + completionHandler.onError( + "PasscodeNotSet", + "Phone not secured by PIN, pattern or password, or SIM is currently locked."); + break; case BiometricPrompt.ERROR_NO_SPACE: case BiometricPrompt.ERROR_NO_BIOMETRICS: if (call.argument("useErrorDialogs")) { diff --git a/packages/local_auth/lib/local_auth.dart b/packages/local_auth/lib/local_auth.dart index d5f2ac7c4bae..df69a120aa39 100644 --- a/packages/local_auth/lib/local_auth.dart +++ b/packages/local_auth/lib/local_auth.dart @@ -3,10 +3,10 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:io'; import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; +import 'package:platform/platform.dart'; import 'auth_strings.dart'; import 'error_codes.dart'; @@ -15,6 +15,13 @@ enum BiometricType { face, fingerprint, iris } const MethodChannel _channel = MethodChannel('plugins.flutter.io/local_auth'); +Platform _platform = const LocalPlatform(); + +@visibleForTesting +void setMockPathProviderPlatform(Platform platform) { + _platform = platform; +} + /// A Flutter plugin for authenticating the user identity locally. class LocalAuthentication { /// Authenticates the user with biometrics available on the device. @@ -44,6 +51,11 @@ class LocalAuthentication { /// Construct [AndroidAuthStrings] and [IOSAuthStrings] if you want to /// customize messages in the dialogs. /// + /// Setting [sensitiveTransaction] to true enables platform specific + /// precautions. For instance, on face unlock, Android opens a confirmation + /// dialog after the face is recognized to make sure the user meant to unlock + /// their phone. + /// /// Throws an [PlatformException] if there were technical problems with local /// authentication (e.g. lack of relevant hardware). This might throw /// [PlatformException] with error code [otherOperatingSystem] on the iOS @@ -54,23 +66,25 @@ class LocalAuthentication { bool stickyAuth = false, AndroidAuthMessages androidAuthStrings = const AndroidAuthMessages(), IOSAuthMessages iOSAuthStrings = const IOSAuthMessages(), + bool sensitiveTransaction = true, }) async { assert(localizedReason != null); final Map args = { 'localizedReason': localizedReason, 'useErrorDialogs': useErrorDialogs, 'stickyAuth': stickyAuth, + 'sensitiveTransaction': sensitiveTransaction, }; - if (Platform.isIOS) { + if (_platform.isIOS) { args.addAll(iOSAuthStrings.args); - } else if (Platform.isAndroid) { + } else if (_platform.isAndroid) { args.addAll(androidAuthStrings.args); } else { throw PlatformException( code: otherOperatingSystem, message: 'Local authentication does not support non-Android/iOS ' 'operating systems.', - details: 'Your operating system is ${Platform.operatingSystem}'); + details: 'Your operating system is ${_platform.operatingSystem}'); } return await _channel.invokeMethod( 'authenticateWithBiometrics', args); diff --git a/packages/local_auth/pubspec.yaml b/packages/local_auth/pubspec.yaml index 286d7aa73871..a78f6287128e 100644 --- a/packages/local_auth/pubspec.yaml +++ b/packages/local_auth/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS device authentication sensors such as Fingerprint Reader and Touch ID. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth -version: 0.5.3 +version: 0.6.0 flutter: plugin: @@ -16,6 +16,11 @@ dependencies: sdk: flutter meta: ^1.0.5 intl: ^0.15.1 + platform: ^2.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" diff --git a/packages/local_auth/test/local_auth_test.dart b/packages/local_auth/test/local_auth_test.dart new file mode 100644 index 000000000000..205c5f785708 --- /dev/null +++ b/packages/local_auth/test/local_auth_test.dart @@ -0,0 +1,90 @@ +// Copyright 2019 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:local_auth/auth_strings.dart'; +import 'package:local_auth/local_auth.dart'; +import 'package:platform/platform.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('LocalAuth', () { + const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/local_auth', + ); + + final List log = []; + LocalAuthentication localAuthentication; + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) { + log.add(methodCall); + return Future.value(true); + }); + localAuthentication = LocalAuthentication(); + log.clear(); + }); + + test('authenticate with no args on Android.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + + test('authenticate with no args on iOS.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios')); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Needs secure'); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Needs secure', + 'useErrorDialogs': true, + 'stickyAuth': false, + 'sensitiveTransaction': true, + }..addAll(const IOSAuthMessages().args)), + ], + ); + }); + + test('authenticate with no sensitive transaction.', () async { + setMockPathProviderPlatform(FakePlatform(operatingSystem: 'android')); + await localAuthentication.authenticateWithBiometrics( + localizedReason: 'Insecure', + sensitiveTransaction: false, + useErrorDialogs: false, + ); + expect( + log, + [ + isMethodCall('authenticateWithBiometrics', + arguments: { + 'localizedReason': 'Insecure', + 'useErrorDialogs': false, + 'stickyAuth': false, + 'sensitiveTransaction': false, + }..addAll(const AndroidAuthMessages().args)), + ], + ); + }); + }); +}