[local_auth] Convert Windows to Pigeon (#7012)
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 https://github.com/flutter/flutter/issues/117912
diff --git a/packages/local_auth/local_auth_windows/CHANGELOG.md b/packages/local_auth/local_auth_windows/CHANGELOG.md
index b4f2061..d7bc77d 100644
--- a/packages/local_auth/local_auth_windows/CHANGELOG.md
+++ b/packages/local_auth/local_auth_windows/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.5
+
+* Switches internal implementation to Pigeon.
+
## 1.0.4
* Updates imports for `prefer_relative_imports`.
diff --git a/packages/local_auth/local_auth_windows/example/lib/main.dart b/packages/local_auth/local_auth_windows/example/lib/main.dart
index 546b635..b173e54 100644
--- a/packages/local_auth/local_auth_windows/example/lib/main.dart
+++ b/packages/local_auth/local_auth_windows/example/lib/main.dart
@@ -108,44 +108,6 @@
() => _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);
@@ -209,18 +171,6 @@
],
),
),
- ElevatedButton(
- onPressed: _authenticateWithBiometrics,
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: <Widget>[
- Text(_isAuthenticating
- ? 'Cancel'
- : 'Authenticate: biometrics only'),
- const Icon(Icons.fingerprint),
- ],
- ),
- ),
],
),
],
diff --git a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart
index b373782..9f918aa 100644
--- a/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart
+++ b/packages/local_auth/local_auth_windows/lib/local_auth_windows.dart
@@ -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();
@@ -28,55 +33,36 @@
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;
}
diff --git a/packages/local_auth/local_auth_windows/lib/src/messages.g.dart b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart
new file mode 100644
index 0000000..312d1c0
--- /dev/null
+++ b/packages/local_auth/local_auth_windows/lib/src/messages.g.dart
@@ -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?)!;
+ }
+ }
+}
diff --git a/packages/local_auth/local_auth_windows/pigeons/copyright.txt b/packages/local_auth/local_auth_windows/pigeons/copyright.txt
new file mode 100644
index 0000000..1236b63
--- /dev/null
+++ b/packages/local_auth/local_auth_windows/pigeons/copyright.txt
@@ -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.
diff --git a/packages/local_auth/local_auth_windows/pigeons/messages.dart b/packages/local_auth/local_auth_windows/pigeons/messages.dart
new file mode 100644
index 0000000..683becd
--- /dev/null
+++ b/packages/local_auth/local_auth_windows/pigeons/messages.dart
@@ -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);
+}
diff --git a/packages/local_auth/local_auth_windows/pubspec.yaml b/packages/local_auth/local_auth_windows/pubspec.yaml
index 9a2effe..45671e1 100644
--- a/packages/local_auth/local_auth_windows/pubspec.yaml
+++ b/packages/local_auth/local_auth_windows/pubspec.yaml
@@ -2,7 +2,7 @@
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"
@@ -24,3 +24,4 @@
dev_dependencies:
flutter_test:
sdk: flutter
+ pigeon: ^5.0.1
diff --git a/packages/local_auth/local_auth_windows/test/local_auth_test.dart b/packages/local_auth/local_auth_windows/test/local_auth_test.dart
index b11c19e..917e7b1 100644
--- a/packages/local_auth/local_auth_windows/test/local_auth_test.dart
+++ b/packages/local_auth/local_auth_windows/test/local_auth_test.dart
@@ -2,78 +2,123 @@
// 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_test/flutter_test.dart';
import 'package:local_auth_windows/local_auth_windows.dart';
+import 'package:local_auth_windows/src/messages.g.dart';
void main() {
- TestWidgetsFlutterBinding.ensureInitialized();
-
group('authenticate', () {
- const MethodChannel channel = MethodChannel(
- 'plugins.flutter.io/local_auth_windows',
- );
-
- final List<MethodCall> log = <MethodCall>[];
- late LocalAuthWindows localAuthentication;
+ late _FakeLocalAuthApi api;
+ late LocalAuthWindows plugin;
setUp(() {
- channel.setMockMethodCallHandler((MethodCall methodCall) {
- log.add(methodCall);
- switch (methodCall.method) {
- case 'getEnrolledBiometrics':
- return Future<List<String>>.value(<String>['weak', 'strong']);
- default:
- return Future<dynamic>.value(true);
- }
- });
- localAuthentication = LocalAuthWindows();
- log.clear();
+ api = _FakeLocalAuthApi();
+ plugin = LocalAuthWindows(api: api);
});
- test('authenticate with no arguments passes expected defaults', () async {
- await localAuthentication.authenticate(
+ test('authenticate handles success', () async {
+ api.returnValue = true;
+
+ final bool result = await plugin.authenticate(
authMessages: <AuthMessages>[const WindowsAuthMessages()],
localizedReason: 'My localized reason');
- expect(
- log,
- <Matcher>[
- isMethodCall('authenticate',
- arguments: <String, dynamic>{
- 'localizedReason': 'My localized reason',
- 'useErrorDialogs': true,
- 'stickyAuth': false,
- 'sensitiveTransaction': true,
- 'biometricOnly': false,
- }..addAll(const WindowsAuthMessages().args)),
- ],
- );
+
+ expect(result, true);
+ expect(api.passedReason, 'My localized reason');
});
- test('authenticate passes all options.', () async {
- await localAuthentication.authenticate(
- authMessages: <AuthMessages>[const WindowsAuthMessages()],
- localizedReason: 'My localized reason',
- options: const AuthenticationOptions(
- useErrorDialogs: false,
- stickyAuth: true,
- sensitiveTransaction: false,
- biometricOnly: true,
- ),
- );
+ test('authenticate handles failure', () async {
+ api.returnValue = false;
+
+ final bool result = await plugin.authenticate(
+ authMessages: <AuthMessages>[const WindowsAuthMessages()],
+ localizedReason: 'My localized reason');
+
+ expect(result, false);
+ expect(api.passedReason, 'My localized reason');
+ });
+
+ test('authenticate throws for biometricOnly', () async {
expect(
- log,
- <Matcher>[
- isMethodCall('authenticate',
- arguments: <String, dynamic>{
- 'localizedReason': 'My localized reason',
- 'useErrorDialogs': false,
- 'stickyAuth': true,
- 'sensitiveTransaction': false,
- 'biometricOnly': true,
- }..addAll(const WindowsAuthMessages().args)),
- ],
- );
+ plugin.authenticate(
+ authMessages: <AuthMessages>[const WindowsAuthMessages()],
+ localizedReason: 'My localized reason',
+ options: const AuthenticationOptions(biometricOnly: true)),
+ throwsA(isUnsupportedError));
+ });
+
+ test('isDeviceSupported handles supported', () async {
+ api.returnValue = true;
+
+ final bool result = await plugin.isDeviceSupported();
+
+ expect(result, true);
+ });
+
+ test('isDeviceSupported handles unsupported', () async {
+ api.returnValue = false;
+
+ final bool result = await plugin.isDeviceSupported();
+
+ expect(result, false);
+ });
+
+ test('deviceSupportsBiometrics handles supported', () async {
+ api.returnValue = true;
+
+ final bool result = await plugin.deviceSupportsBiometrics();
+
+ expect(result, true);
+ });
+
+ test('deviceSupportsBiometrics handles unsupported', () async {
+ api.returnValue = false;
+
+ final bool result = await plugin.deviceSupportsBiometrics();
+
+ expect(result, false);
+ });
+
+ test('getEnrolledBiometrics returns expected values when supported',
+ () async {
+ api.returnValue = true;
+
+ final List<BiometricType> result = await plugin.getEnrolledBiometrics();
+
+ expect(result, <BiometricType>[BiometricType.weak, BiometricType.strong]);
+ });
+
+ test('getEnrolledBiometrics returns nothing when unsupported', () async {
+ api.returnValue = false;
+
+ final List<BiometricType> result = await plugin.getEnrolledBiometrics();
+
+ expect(result, isEmpty);
+ });
+
+ test('stopAuthentication returns false', () async {
+ final bool result = await plugin.stopAuthentication();
+
+ expect(result, false);
});
});
}
+
+class _FakeLocalAuthApi implements LocalAuthApi {
+ /// The return value for [isDeviceSupported] and [authenticate].
+ bool returnValue = false;
+
+ /// The argument that was passed to [authenticate].
+ String? passedReason;
+
+ @override
+ Future<bool> authenticate(String localizedReason) async {
+ passedReason = localizedReason;
+ return returnValue;
+ }
+
+ @override
+ Future<bool> isDeviceSupported() async {
+ return returnValue;
+ }
+}
diff --git a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt
index bcf59bb..9784aa5 100644
--- a/packages/local_auth/local_auth_windows/windows/CMakeLists.txt
+++ b/packages/local_auth/local_auth_windows/windows/CMakeLists.txt
@@ -49,12 +49,14 @@
list(APPEND PLUGIN_SOURCES
"local_auth_plugin.cpp"
+ "local_auth.h"
+ "messages.g.cpp"
+ "messages.g.h"
)
add_library(${PLUGIN_NAME} SHARED
"include/local_auth_windows/local_auth_plugin.h"
"local_auth_windows.cpp"
- "local_auth.h"
${PLUGIN_SOURCES}
)
apply_standard_settings(${PLUGIN_NAME})
diff --git a/packages/local_auth/local_auth_windows/windows/local_auth.h b/packages/local_auth/local_auth_windows/windows/local_auth.h
index 94b91f8..9cdc6ef 100644
--- a/packages/local_auth/local_auth_windows/windows/local_auth.h
+++ b/packages/local_auth/local_auth_windows/windows/local_auth.h
@@ -10,8 +10,6 @@
#include <pplawait.h>
#include <ppltasks.h>
-#include "include/local_auth_windows/local_auth_plugin.h"
-
// Include prior to C++/WinRT Headers
#include <wil/cppwinrt.h>
#include <wil/win32_helpers.h>
@@ -23,6 +21,8 @@
#include <memory>
#include <sstream>
+#include "messages.g.h"
+
namespace local_auth_windows {
// Abstract class that is used to determine whether a user
@@ -50,7 +50,7 @@
UserConsentVerifier& operator=(const UserConsentVerifier&) = delete;
};
-class LocalAuthPlugin : public flutter::Plugin {
+class LocalAuthPlugin : public flutter::Plugin, public LocalAuthApi {
public:
static void RegisterWithRegistrar(flutter::PluginRegistrarWindows* registrar);
@@ -62,28 +62,25 @@
// Exists for unit testing with mock implementations.
LocalAuthPlugin(std::unique_ptr<UserConsentVerifier> user_consent_verifier);
- // Handles method calls from Dart on this plugin's channel.
- void HandleMethodCall(
- const flutter::MethodCall<flutter::EncodableValue>& method_call,
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
-
virtual ~LocalAuthPlugin();
+ // LocalAuthApi:
+ void IsDeviceSupported(
+ std::function<void(ErrorOr<bool> reply)> result) override;
+ void Authenticate(const std::string& localized_reason,
+ std::function<void(ErrorOr<bool> reply)> result) override;
+
private:
std::unique_ptr<UserConsentVerifier> user_consent_verifier_;
// Starts authentication process.
- winrt::fire_and_forget Authenticate(
- const flutter::MethodCall<flutter::EncodableValue>& method_call,
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
-
- // Returns enrolled biometric types available on device.
- winrt::fire_and_forget GetEnrolledBiometrics(
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
+ winrt::fire_and_forget AuthenticateCoroutine(
+ const std::string& localized_reason,
+ std::function<void(ErrorOr<bool> reply)> result);
// Returns whether the system supports Windows Hello.
- winrt::fire_and_forget IsDeviceSupported(
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result);
+ winrt::fire_and_forget IsDeviceSupportedCoroutine(
+ std::function<void(ErrorOr<bool> reply)> result);
};
-} // namespace local_auth_windows
\ No newline at end of file
+} // namespace local_auth_windows
diff --git a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp
index 7a25abb..80fab37 100644
--- a/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp
+++ b/packages/local_auth/local_auth_windows/windows/local_auth_plugin.cpp
@@ -4,24 +4,10 @@
#include <winstring.h>
#include "local_auth.h"
+#include "messages.g.h"
namespace {
-template <typename T>
-// Helper method for getting an argument from an EncodableValue.
-T GetArgument(const std::string arg, const flutter::EncodableValue* args,
- T fallback) {
- T result{fallback};
- const auto* arguments = std::get_if<flutter::EncodableMap>(args);
- if (arguments) {
- auto result_it = arguments->find(flutter::EncodableValue(arg));
- if (result_it != arguments->end()) {
- result = std::get<T>(result_it->second);
- }
- }
- return result;
-}
-
// Returns the window's HWND for a given FlutterView.
HWND GetRootWindow(flutter::FlutterView* view) {
return ::GetAncestor(view->GetNativeWindow(), GA_ROOT);
@@ -110,19 +96,9 @@
// static
void LocalAuthPlugin::RegisterWithRegistrar(
flutter::PluginRegistrarWindows* registrar) {
- auto channel =
- std::make_unique<flutter::MethodChannel<flutter::EncodableValue>>(
- registrar->messenger(), "plugins.flutter.io/local_auth_windows",
- &flutter::StandardMethodCodec::GetInstance());
-
auto plugin = std::make_unique<LocalAuthPlugin>(
[registrar]() { return GetRootWindow(registrar->GetView()); });
-
- channel->SetMethodCallHandler(
- [plugin_pointer = plugin.get()](const auto& call, auto result) {
- plugin_pointer->HandleMethodCall(call, std::move(result));
- });
-
+ LocalAuthApi::SetUp(registrar->messenger(), plugin.get());
registrar->AddPlugin(std::move(plugin));
}
@@ -137,36 +113,22 @@
LocalAuthPlugin::~LocalAuthPlugin() {}
-void LocalAuthPlugin::HandleMethodCall(
- const flutter::MethodCall<flutter::EncodableValue>& method_call,
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
- if (method_call.method_name().compare("authenticate") == 0) {
- Authenticate(method_call, std::move(result));
- } else if (method_call.method_name().compare("getEnrolledBiometrics") == 0) {
- GetEnrolledBiometrics(std::move(result));
- } else if (method_call.method_name().compare("isDeviceSupported") == 0 ||
- method_call.method_name().compare("deviceSupportsBiometrics") ==
- 0) {
- IsDeviceSupported(std::move(result));
- } else {
- result->NotImplemented();
- }
+void LocalAuthPlugin::IsDeviceSupported(
+ std::function<void(ErrorOr<bool> reply)> result) {
+ IsDeviceSupportedCoroutine(std::move(result));
+}
+
+void LocalAuthPlugin::Authenticate(
+ const std::string& localized_reason,
+ std::function<void(ErrorOr<bool> reply)> result) {
+ AuthenticateCoroutine(localized_reason, std::move(result));
}
// Starts authentication process.
-winrt::fire_and_forget LocalAuthPlugin::Authenticate(
- const flutter::MethodCall<flutter::EncodableValue>& method_call,
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
- std::wstring reason = Utf16FromUtf8(GetArgument<std::string>(
- "localizedReason", method_call.arguments(), std::string()));
-
- bool biometric_only =
- GetArgument<bool>("biometricOnly", method_call.arguments(), false);
- if (biometric_only) {
- result->Error("biometricOnlyNotSupported",
- "Windows doesn't support the biometricOnly parameter.");
- co_return;
- }
+winrt::fire_and_forget LocalAuthPlugin::AuthenticateCoroutine(
+ const std::string& localized_reason,
+ std::function<void(ErrorOr<bool> reply)> result) {
+ std::wstring reason = Utf16FromUtf8(localized_reason);
winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability
ucv_availability =
@@ -175,17 +137,19 @@
if (ucv_availability ==
winrt::Windows::Security::Credentials::UI::
UserConsentVerifierAvailability::DeviceNotPresent) {
- result->Error("NoHardware", "No biometric hardware found");
+ result(FlutterError("NoHardware", "No biometric hardware found"));
co_return;
} else if (ucv_availability ==
winrt::Windows::Security::Credentials::UI::
UserConsentVerifierAvailability::NotConfiguredForUser) {
- result->Error("NotEnrolled", "No biometrics enrolled on this device.");
+ result(
+ FlutterError("NotEnrolled", "No biometrics enrolled on this device."));
co_return;
} else if (ucv_availability !=
winrt::Windows::Security::Credentials::UI::
UserConsentVerifierAvailability::Available) {
- result->Error("NotAvailable", "Required security features not enabled");
+ result(
+ FlutterError("NotAvailable", "Required security features not enabled"));
co_return;
}
@@ -195,42 +159,21 @@
co_await user_consent_verifier_->RequestVerificationForWindowAsync(
reason);
- result->Success(flutter::EncodableValue(
- consent_result == winrt::Windows::Security::Credentials::UI::
- UserConsentVerificationResult::Verified));
+ result(consent_result == winrt::Windows::Security::Credentials::UI::
+ UserConsentVerificationResult::Verified);
} catch (...) {
- result->Success(flutter::EncodableValue(false));
- }
-}
-
-// Returns biometric types available on device.
-winrt::fire_and_forget LocalAuthPlugin::GetEnrolledBiometrics(
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
- try {
- flutter::EncodableList biometrics;
- winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability
- ucv_availability =
- co_await user_consent_verifier_->CheckAvailabilityAsync();
- if (ucv_availability == winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability::Available) {
- biometrics.push_back(flutter::EncodableValue("weak"));
- biometrics.push_back(flutter::EncodableValue("strong"));
- }
- result->Success(biometrics);
- } catch (const std::exception& e) {
- result->Error("no_biometrics_available", e.what());
+ result(false);
}
}
// Returns whether the device supports Windows Hello or not.
-winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupported(
- std::unique_ptr<flutter::MethodResult<flutter::EncodableValue>> result) {
+winrt::fire_and_forget LocalAuthPlugin::IsDeviceSupportedCoroutine(
+ std::function<void(ErrorOr<bool> reply)> result) {
winrt::Windows::Security::Credentials::UI::UserConsentVerifierAvailability
ucv_availability =
co_await user_consent_verifier_->CheckAvailabilityAsync();
- result->Success(flutter::EncodableValue(
- ucv_availability == winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability::Available));
+ result(ucv_availability == winrt::Windows::Security::Credentials::UI::
+ UserConsentVerifierAvailability::Available);
}
} // namespace local_auth_windows
diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.cpp b/packages/local_auth/local_auth_windows/windows/messages.g.cpp
new file mode 100644
index 0000000..e44b17c
--- /dev/null
+++ b/packages/local_auth/local_auth_windows/windows/messages.g.cpp
@@ -0,0 +1,110 @@
+// 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
+
+#undef _HAS_EXCEPTIONS
+
+#include "messages.g.h"
+
+#include <flutter/basic_message_channel.h>
+#include <flutter/binary_messenger.h>
+#include <flutter/encodable_value.h>
+#include <flutter/standard_message_codec.h>
+
+#include <map>
+#include <optional>
+#include <string>
+
+namespace local_auth_windows {
+/// The codec used by LocalAuthApi.
+const flutter::StandardMessageCodec& LocalAuthApi::GetCodec() {
+ return flutter::StandardMessageCodec::GetInstance(
+ &flutter::StandardCodecSerializer::GetInstance());
+}
+
+// Sets up an instance of `LocalAuthApi` to handle messages through the
+// `binary_messenger`.
+void LocalAuthApi::SetUp(flutter::BinaryMessenger* binary_messenger,
+ LocalAuthApi* api) {
+ {
+ auto channel =
+ std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(
+ binary_messenger,
+ "dev.flutter.pigeon.LocalAuthApi.isDeviceSupported", &GetCodec());
+ if (api != nullptr) {
+ channel->SetMessageHandler(
+ [api](const flutter::EncodableValue& message,
+ const flutter::MessageReply<flutter::EncodableValue>& reply) {
+ try {
+ api->IsDeviceSupported([reply](ErrorOr<bool>&& output) {
+ if (output.has_error()) {
+ reply(WrapError(output.error()));
+ return;
+ }
+ flutter::EncodableList wrapped;
+ wrapped.push_back(
+ flutter::EncodableValue(std::move(output).TakeValue()));
+ reply(flutter::EncodableValue(std::move(wrapped)));
+ });
+ } catch (const std::exception& exception) {
+ reply(WrapError(exception.what()));
+ }
+ });
+ } else {
+ channel->SetMessageHandler(nullptr);
+ }
+ }
+ {
+ auto channel =
+ std::make_unique<flutter::BasicMessageChannel<flutter::EncodableValue>>(
+ binary_messenger, "dev.flutter.pigeon.LocalAuthApi.authenticate",
+ &GetCodec());
+ if (api != nullptr) {
+ channel->SetMessageHandler(
+ [api](const flutter::EncodableValue& message,
+ const flutter::MessageReply<flutter::EncodableValue>& reply) {
+ try {
+ const auto& args = std::get<flutter::EncodableList>(message);
+ const auto& encodable_localized_reason_arg = args.at(0);
+ if (encodable_localized_reason_arg.IsNull()) {
+ reply(WrapError("localized_reason_arg unexpectedly null."));
+ return;
+ }
+ const auto& localized_reason_arg =
+ std::get<std::string>(encodable_localized_reason_arg);
+ api->Authenticate(
+ localized_reason_arg, [reply](ErrorOr<bool>&& output) {
+ if (output.has_error()) {
+ reply(WrapError(output.error()));
+ return;
+ }
+ flutter::EncodableList wrapped;
+ wrapped.push_back(
+ flutter::EncodableValue(std::move(output).TakeValue()));
+ reply(flutter::EncodableValue(std::move(wrapped)));
+ });
+ } catch (const std::exception& exception) {
+ reply(WrapError(exception.what()));
+ }
+ });
+ } else {
+ channel->SetMessageHandler(nullptr);
+ }
+ }
+}
+
+flutter::EncodableValue LocalAuthApi::WrapError(
+ std::string_view error_message) {
+ return flutter::EncodableValue(flutter::EncodableList{
+ flutter::EncodableValue(std::string(error_message)),
+ flutter::EncodableValue("Error"), flutter::EncodableValue()});
+}
+flutter::EncodableValue LocalAuthApi::WrapError(const FlutterError& error) {
+ return flutter::EncodableValue(flutter::EncodableList{
+ flutter::EncodableValue(error.message()),
+ flutter::EncodableValue(error.code()), error.details()});
+}
+
+} // namespace local_auth_windows
diff --git a/packages/local_auth/local_auth_windows/windows/messages.g.h b/packages/local_auth/local_auth_windows/windows/messages.g.h
new file mode 100644
index 0000000..2ceff77
--- /dev/null
+++ b/packages/local_auth/local_auth_windows/windows/messages.g.h
@@ -0,0 +1,93 @@
+// 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
+
+#ifndef PIGEON_LOCAL_AUTH_WINDOWS_H_
+#define PIGEON_LOCAL_AUTH_WINDOWS_H_
+#include <flutter/basic_message_channel.h>
+#include <flutter/binary_messenger.h>
+#include <flutter/encodable_value.h>
+#include <flutter/standard_message_codec.h>
+
+#include <map>
+#include <optional>
+#include <string>
+
+namespace local_auth_windows {
+
+// Generated class from Pigeon.
+
+class FlutterError {
+ public:
+ explicit FlutterError(const std::string& code) : code_(code) {}
+ explicit FlutterError(const std::string& code, const std::string& message)
+ : code_(code), message_(message) {}
+ explicit FlutterError(const std::string& code, const std::string& message,
+ const flutter::EncodableValue& details)
+ : code_(code), message_(message), details_(details) {}
+
+ const std::string& code() const { return code_; }
+ const std::string& message() const { return message_; }
+ const flutter::EncodableValue& details() const { return details_; }
+
+ private:
+ std::string code_;
+ std::string message_;
+ flutter::EncodableValue details_;
+};
+
+template <class T>
+class ErrorOr {
+ public:
+ ErrorOr(const T& rhs) { new (&v_) T(rhs); }
+ ErrorOr(const T&& rhs) { v_ = std::move(rhs); }
+ ErrorOr(const FlutterError& rhs) { new (&v_) FlutterError(rhs); }
+ ErrorOr(const FlutterError&& rhs) { v_ = std::move(rhs); }
+
+ bool has_error() const { return std::holds_alternative<FlutterError>(v_); }
+ const T& value() const { return std::get<T>(v_); };
+ const FlutterError& error() const { return std::get<FlutterError>(v_); };
+
+ private:
+ friend class LocalAuthApi;
+ ErrorOr() = default;
+ T TakeValue() && { return std::get<T>(std::move(v_)); }
+
+ std::variant<T, FlutterError> v_;
+};
+
+// Generated interface from Pigeon that represents a handler of messages from
+// Flutter.
+class LocalAuthApi {
+ public:
+ LocalAuthApi(const LocalAuthApi&) = delete;
+ LocalAuthApi& operator=(const LocalAuthApi&) = delete;
+ virtual ~LocalAuthApi(){};
+ // Returns true if this device supports authentication.
+ virtual void IsDeviceSupported(
+ std::function<void(ErrorOr<bool> reply)> result) = 0;
+ // 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.
+ virtual void Authenticate(
+ const std::string& localized_reason,
+ std::function<void(ErrorOr<bool> reply)> result) = 0;
+
+ // The codec used by LocalAuthApi.
+ static const flutter::StandardMessageCodec& GetCodec();
+ // Sets up an instance of `LocalAuthApi` to handle messages through the
+ // `binary_messenger`.
+ static void SetUp(flutter::BinaryMessenger* binary_messenger,
+ LocalAuthApi* api);
+ static flutter::EncodableValue WrapError(std::string_view error_message);
+ static flutter::EncodableValue WrapError(const FlutterError& error);
+
+ protected:
+ LocalAuthApi() = default;
+};
+} // namespace local_auth_windows
+#endif // PIGEON_LOCAL_AUTH_WINDOWS_H_
diff --git a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp
index 3828b05..6b1b0ed 100644
--- a/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp
+++ b/packages/local_auth/local_auth_windows/windows/test/local_auth_plugin_test.cpp
@@ -4,10 +4,6 @@
#include "include/local_auth_windows/local_auth_plugin.h"
-#include <flutter/method_call.h>
-#include <flutter/method_result_functions.h>
-#include <flutter/standard_method_codec.h>
-#include <flutter/texture_registrar.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <windows.h>
@@ -32,9 +28,6 @@
using ::testing::Return;
TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierAvailable) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
std::make_unique<MockUserConsentVerifier>();
@@ -48,20 +41,14 @@
});
LocalAuthPlugin plugin(std::move(mockConsentVerifier));
+ ErrorOr<bool> result(false);
+ plugin.IsDeviceSupported([&result](ErrorOr<bool> reply) { result = reply; });
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true))));
-
- plugin.HandleMethodCall(
- flutter::MethodCall("isDeviceSupported",
- std::make_unique<EncodableValue>()),
- std::move(result));
+ EXPECT_FALSE(result.has_error());
+ EXPECT_TRUE(result.value());
}
TEST(LocalAuthPlugin, IsDeviceSupportedHandlerSuccessIfVerifierNotAvailable) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
std::make_unique<MockUserConsentVerifier>();
@@ -75,100 +62,14 @@
});
LocalAuthPlugin plugin(std::move(mockConsentVerifier));
+ ErrorOr<bool> result(true);
+ plugin.IsDeviceSupported([&result](ErrorOr<bool> reply) { result = reply; });
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false))));
-
- plugin.HandleMethodCall(
- flutter::MethodCall("isDeviceSupported",
- std::make_unique<EncodableValue>()),
- std::move(result));
-}
-
-TEST(LocalAuthPlugin,
- GetEnrolledBiometricsHandlerReturnEmptyListIfVerifierNotAvailable) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
- std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
- std::make_unique<MockUserConsentVerifier>();
-
- EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync)
- .Times(1)
- .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation<
- winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability> {
- co_return winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability::DeviceNotPresent;
- });
-
- LocalAuthPlugin plugin(std::move(mockConsentVerifier));
-
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableList())));
-
- plugin.HandleMethodCall(
- flutter::MethodCall("getEnrolledBiometrics",
- std::make_unique<EncodableValue>()),
- std::move(result));
-}
-
-TEST(LocalAuthPlugin,
- GetEnrolledBiometricsHandlerReturnNonEmptyListIfVerifierAvailable) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
- std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
- std::make_unique<MockUserConsentVerifier>();
-
- EXPECT_CALL(*mockConsentVerifier, CheckAvailabilityAsync)
- .Times(1)
- .WillOnce([]() -> winrt::Windows::Foundation::IAsyncOperation<
- winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability> {
- co_return winrt::Windows::Security::Credentials::UI::
- UserConsentVerifierAvailability::Available;
- });
-
- LocalAuthPlugin plugin(std::move(mockConsentVerifier));
-
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result,
- SuccessInternal(Pointee(EncodableList(
- {EncodableValue("weak"), EncodableValue("strong")}))));
-
- plugin.HandleMethodCall(
- flutter::MethodCall("getEnrolledBiometrics",
- std::make_unique<EncodableValue>()),
- std::move(result));
-}
-
-TEST(LocalAuthPlugin, AuthenticateHandlerDoesNotSupportBiometricOnly) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
- std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
- std::make_unique<MockUserConsentVerifier>();
-
- LocalAuthPlugin plugin(std::move(mockConsentVerifier));
-
- EXPECT_CALL(*result, ErrorInternal).Times(1);
- EXPECT_CALL(*result, SuccessInternal).Times(0);
-
- std::unique_ptr<EncodableValue> args =
- std::make_unique<EncodableValue>(EncodableMap({
- {"localizedReason", EncodableValue("My Reason")},
- {"biometricOnly", EncodableValue(true)},
- }));
-
- plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)),
- std::move(result));
+ EXPECT_FALSE(result.has_error());
+ EXPECT_FALSE(result.value());
}
TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenAuthorized) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
std::make_unique<MockUserConsentVerifier>();
@@ -193,24 +94,15 @@
});
LocalAuthPlugin plugin(std::move(mockConsentVerifier));
+ ErrorOr<bool> result(false);
+ plugin.Authenticate("My Reason",
+ [&result](ErrorOr<bool> reply) { result = reply; });
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(true))));
-
- std::unique_ptr<EncodableValue> args =
- std::make_unique<EncodableValue>(EncodableMap({
- {"localizedReason", EncodableValue("My Reason")},
- {"biometricOnly", EncodableValue(false)},
- }));
-
- plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)),
- std::move(result));
+ EXPECT_FALSE(result.has_error());
+ EXPECT_TRUE(result.value());
}
TEST(LocalAuthPlugin, AuthenticateHandlerWorksWhenNotAuthorized) {
- std::unique_ptr<MockMethodResult> result =
- std::make_unique<MockMethodResult>();
-
std::unique_ptr<MockUserConsentVerifier> mockConsentVerifier =
std::make_unique<MockUserConsentVerifier>();
@@ -235,18 +127,12 @@
});
LocalAuthPlugin plugin(std::move(mockConsentVerifier));
+ ErrorOr<bool> result(true);
+ plugin.Authenticate("My Reason",
+ [&result](ErrorOr<bool> reply) { result = reply; });
- EXPECT_CALL(*result, ErrorInternal).Times(0);
- EXPECT_CALL(*result, SuccessInternal(Pointee(EncodableValue(false))));
-
- std::unique_ptr<EncodableValue> args =
- std::make_unique<EncodableValue>(EncodableMap({
- {"localizedReason", EncodableValue("My Reason")},
- {"biometricOnly", EncodableValue(false)},
- }));
-
- plugin.HandleMethodCall(flutter::MethodCall("authenticate", std::move(args)),
- std::move(result));
+ EXPECT_FALSE(result.has_error());
+ EXPECT_FALSE(result.value());
}
} // namespace test
diff --git a/packages/local_auth/local_auth_windows/windows/test/mocks.h b/packages/local_auth/local_auth_windows/windows/test/mocks.h
index d82ae80..a31eb98 100644
--- a/packages/local_auth/local_auth_windows/windows/test/mocks.h
+++ b/packages/local_auth/local_auth_windows/windows/test/mocks.h
@@ -5,10 +5,6 @@
#ifndef PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_
#define PACKAGES_LOCAL_AUTH_LOCAL_AUTH_WINDOWS_WINDOWS_TEST_MOCKS_H_
-#include <flutter/method_call.h>
-#include <flutter/method_result_functions.h>
-#include <flutter/standard_method_codec.h>
-#include <flutter/texture_registrar.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -19,23 +15,8 @@
namespace {
-using flutter::EncodableMap;
-using flutter::EncodableValue;
using ::testing::_;
-class MockMethodResult : public flutter::MethodResult<> {
- public:
- ~MockMethodResult() = default;
-
- MOCK_METHOD(void, SuccessInternal, (const EncodableValue* result),
- (override));
- MOCK_METHOD(void, ErrorInternal,
- (const std::string& error_code, const std::string& error_message,
- const EncodableValue* details),
- (override));
- MOCK_METHOD(void, NotImplementedInternal, (), (override));
-};
-
class MockUserConsentVerifier : public UserConsentVerifier {
public:
explicit MockUserConsentVerifier(){};