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

Commit

Permalink
[local_auth] Convert Windows to Pigeon (#7012)
Browse files Browse the repository at this point in the history
Updates `local_auth_windows` to use Pigeon, and moves to a more platform-tailored and Dart-centric implementation (rather than keeping the previous cross-platform method channel interface that the current implementation was duplicated from):
- Eliminates `deviceSupportsBiometrics` from the platform interface, since it's always the same as `isDeviceSupported`, in favor of doing that mapping in Dart.
- Eliminates `getEnrolledBiometrics` from the platform interface, since it was the same implementation as `isDeviceSupported` just with a different return value, in favor of doing that logic in Dart.
- Moves throwing for the `biometricOnly` option to the Dart side, simplifying the native logic.

Related changes:
- Adds a significant amount of previously-missing Dart unit test coverage.
- Removes the `biometricOnly` UI from the example app, since it will always fail.

Part of flutter/flutter#117912
  • Loading branch information
stuartmorgan authored Jan 24, 2023
1 parent 729c14a commit d649e18
Show file tree
Hide file tree
Showing 15 changed files with 512 additions and 403 deletions.
4 changes: 4 additions & 0 deletions packages/local_auth/local_auth_windows/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.5

* Switches internal implementation to Pigeon.

## 1.0.4

* Updates imports for `prefer_relative_imports`.
Expand Down
50 changes: 0 additions & 50 deletions packages/local_auth/local_auth_windows/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,44 +108,6 @@ class _MyAppState extends State<MyApp> {
() => _authorized = authenticated ? 'Authorized' : 'Not Authorized');
}

Future<void> _authenticateWithBiometrics() async {
bool authenticated = false;
try {
setState(() {
_isAuthenticating = true;
_authorized = 'Authenticating';
});
authenticated = await LocalAuthPlatform.instance.authenticate(
localizedReason:
'Scan your fingerprint (or face or whatever) to authenticate',
authMessages: <AuthMessages>[const WindowsAuthMessages()],
options: const AuthenticationOptions(
stickyAuth: true,
biometricOnly: true,
),
);
setState(() {
_isAuthenticating = false;
_authorized = 'Authenticating';
});
} on PlatformException catch (e) {
print(e);
setState(() {
_isAuthenticating = false;
_authorized = 'Error - ${e.message}';
});
return;
}
if (!mounted) {
return;
}

final String message = authenticated ? 'Authorized' : 'Not Authorized';
setState(() {
_authorized = message;
});
}

Future<void> _cancelAuthentication() async {
await LocalAuthPlatform.instance.stopAuthentication();
setState(() => _isAuthenticating = false);
Expand Down Expand Up @@ -209,18 +171,6 @@ class _MyAppState extends State<MyApp> {
],
),
),
ElevatedButton(
onPressed: _authenticateWithBiometrics,
child: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(_isAuthenticating
? 'Cancel'
: 'Authenticate: biometrics only'),
const Icon(Icons.fingerprint),
],
),
),
],
),
],
Expand Down
66 changes: 26 additions & 40 deletions packages/local_auth/local_auth_windows/lib/local_auth_windows.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'package:local_auth_platform_interface/local_auth_platform_interface.dart';
import 'types/auth_messages_windows.dart';

import 'src/messages.g.dart';

export 'package:local_auth_platform_interface/types/auth_messages.dart';
export 'package:local_auth_platform_interface/types/auth_options.dart';
export 'package:local_auth_platform_interface/types/biometric_type.dart';
export 'package:local_auth_windows/types/auth_messages_windows.dart';

const MethodChannel _channel =
MethodChannel('plugins.flutter.io/local_auth_windows');

/// The implementation of [LocalAuthPlatform] for Windows.
class LocalAuthWindows extends LocalAuthPlatform {
/// Creates a new plugin implementation instance.
LocalAuthWindows({
@visibleForTesting LocalAuthApi? api,
}) : _api = api ?? LocalAuthApi();

final LocalAuthApi _api;

/// Registers this class as the default instance of [LocalAuthPlatform].
static void registerWith() {
LocalAuthPlatform.instance = LocalAuthWindows();
Expand All @@ -28,55 +33,36 @@ class LocalAuthWindows extends LocalAuthPlatform {
AuthenticationOptions options = const AuthenticationOptions(),
}) async {
assert(localizedReason.isNotEmpty);
final Map<String, Object> args = <String, Object>{
'localizedReason': localizedReason,
'useErrorDialogs': options.useErrorDialogs,
'stickyAuth': options.stickyAuth,
'sensitiveTransaction': options.sensitiveTransaction,
'biometricOnly': options.biometricOnly,
};
args.addAll(const WindowsAuthMessages().args);
for (final AuthMessages messages in authMessages) {
if (messages is WindowsAuthMessages) {
args.addAll(messages.args);
}

if (options.biometricOnly) {
throw UnsupportedError(
"Windows doesn't support the biometricOnly parameter.");
}
return (await _channel.invokeMethod<bool>('authenticate', args)) ?? false;

return _api.authenticate(localizedReason);
}

@override
Future<bool> deviceSupportsBiometrics() async {
return (await _channel.invokeMethod<bool>('deviceSupportsBiometrics')) ??
false;
// Biometrics are supported on any supported device.
return isDeviceSupported();
}

@override
Future<List<BiometricType>> getEnrolledBiometrics() async {
final List<String> result = (await _channel.invokeListMethod<String>(
'getEnrolledBiometrics',
)) ??
<String>[];
final List<BiometricType> biometrics = <BiometricType>[];
for (final String value in result) {
switch (value) {
case 'weak':
biometrics.add(BiometricType.weak);
break;
case 'strong':
biometrics.add(BiometricType.strong);
break;
}
// Windows doesn't support querying specific biometric types. Since the
// OS considers this a strong authentication API, return weak+strong on
// any supported device.
if (await isDeviceSupported()) {
return <BiometricType>[BiometricType.weak, BiometricType.strong];
}
return biometrics;
return <BiometricType>[];
}

@override
Future<bool> isDeviceSupported() async =>
(await _channel.invokeMethod<bool>('isDeviceSupported')) ?? false;
Future<bool> isDeviceSupported() async => _api.isDeviceSupported();

/// Always returns false as this method is not supported on Windows.
@override
Future<bool> stopAuthentication() async {
return false;
}
Future<bool> stopAuthentication() async => false;
}
81 changes: 81 additions & 0 deletions packages/local_auth/local_auth_windows/lib/src/messages.g.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2013 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.
// Autogenerated from Pigeon (v5.0.1), do not edit directly.
// See also: https://pub.dev/packages/pigeon
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import
import 'dart:async';
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;

import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
import 'package:flutter/services.dart';

class LocalAuthApi {
/// Constructor for [LocalAuthApi]. The [binaryMessenger] named argument is
/// available for dependency injection. If it is left null, the default
/// BinaryMessenger will be used which routes to the host platform.
LocalAuthApi({BinaryMessenger? binaryMessenger})
: _binaryMessenger = binaryMessenger;
final BinaryMessenger? _binaryMessenger;

static const MessageCodec<Object?> codec = StandardMessageCodec();

/// Returns true if this device supports authentication.
Future<bool> isDeviceSupported() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.LocalAuthApi.isDeviceSupported', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList = await channel.send(null) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}

/// Attempts to authenticate the user with the provided [localizedReason] as
/// the user-facing explanation for the authorization request.
///
/// Returns true if authorization succeeds, false if it is attempted but is
/// not successful, and an error if authorization could not be attempted.
Future<bool> authenticate(String arg_localizedReason) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.LocalAuthApi.authenticate', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_localizedReason]) as List<Object?>?;
if (replyList == null) {
throw PlatformException(
code: 'channel-error',
message: 'Unable to establish connection on channel.',
);
} else if (replyList.length > 1) {
throw PlatformException(
code: replyList[0]! as String,
message: replyList[1] as String?,
details: replyList[2],
);
} else if (replyList[0] == null) {
throw PlatformException(
code: 'null-error',
message: 'Host platform returned null value for non-null return value.',
);
} else {
return (replyList[0] as bool?)!;
}
}
}
3 changes: 3 additions & 0 deletions packages/local_auth/local_auth_windows/pigeons/copyright.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright 2013 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.
27 changes: 27 additions & 0 deletions packages/local_auth/local_auth_windows/pigeons/messages.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2013 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 'package:pigeon/pigeon.dart';

@ConfigurePigeon(PigeonOptions(
dartOut: 'lib/src/messages.g.dart',
cppOptions: CppOptions(namespace: 'local_auth_windows'),
cppHeaderOut: 'windows/messages.g.h',
cppSourceOut: 'windows/messages.g.cpp',
copyrightHeader: 'pigeons/copyright.txt',
))
@HostApi()
abstract class LocalAuthApi {
/// Returns true if this device supports authentication.
@async
bool isDeviceSupported();

/// Attempts to authenticate the user with the provided [localizedReason] as
/// the user-facing explanation for the authorization request.
///
/// Returns true if authorization succeeds, false if it is attempted but is
/// not successful, and an error if authorization could not be attempted.
@async
bool authenticate(String localizedReason);
}
3 changes: 2 additions & 1 deletion packages/local_auth/local_auth_windows/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: local_auth_windows
description: Windows implementation of the local_auth plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_windows
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
version: 1.0.4
version: 1.0.5

environment:
sdk: ">=2.14.0 <3.0.0"
Expand All @@ -24,3 +24,4 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
pigeon: ^5.0.1
Loading

0 comments on commit d649e18

Please sign in to comment.