Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[local_auth] Avoid user confirmation on face unlock #2047

Merged
merged 3 commits into from
Sep 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/local_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/local_auth/android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general we've been trying to guard dependency upgrades behind breaking version change bumps, out of an abundance of caution for when there's breaking changes in the dependency itself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both androidx.biometrics and this plugin is not 1.0 yet. So breaking changes are expected. Having said that, I am happy to bump version to 0.6. Would that be sufficient?

Copy link
Contributor

@mklim mklim Sep 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0.6 SGTM, thanks. The pub convention for semantic versioning plugins under 1.0.0 is 0.major.minor+patch instead of major.minor.patch, so 0.6.0 would be the breaking change update here.

https://dart.dev/tools/pub/versioning#semantic-versions

api "androidx.fragment:fragment:1.1.0-alpha06"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand All @@ -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")) {
Expand Down
22 changes: 18 additions & 4 deletions packages/local_auth/lib/local_auth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -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<String, Object> args = <String, Object>{
'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<bool>(
'authenticateWithBiometrics', args);
Expand Down
7 changes: 6 additions & 1 deletion packages/local_auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS device authentication sensors
such as Fingerprint Reader and Touch ID.
author: Flutter Team <[email protected]>
homepage: https://github.com/flutter/plugins/tree/master/packages/local_auth
version: 0.5.3
version: 0.6.0

flutter:
plugin:
Expand All @@ -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"
Expand Down
90 changes: 90 additions & 0 deletions packages/local_auth/test/local_auth_test.dart
Original file line number Diff line number Diff line change
@@ -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<MethodCall> log = <MethodCall>[];
LocalAuthentication localAuthentication;

setUp(() {
channel.setMockMethodCallHandler((MethodCall methodCall) {
log.add(methodCall);
return Future<dynamic>.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,
<Matcher>[
isMethodCall('authenticateWithBiometrics',
arguments: <String, dynamic>{
'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,
<Matcher>[
isMethodCall('authenticateWithBiometrics',
arguments: <String, dynamic>{
'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,
<Matcher>[
isMethodCall('authenticateWithBiometrics',
arguments: <String, dynamic>{
'localizedReason': 'Insecure',
'useErrorDialogs': false,
'stickyAuth': false,
'sensitiveTransaction': false,
}..addAll(const AndroidAuthMessages().args)),
],
);
});
});
}