-
Notifications
You must be signed in to change notification settings - Fork 9.8k
[local_auth] Fix failed biometric authentication not throwing error #6821
Conversation
Removing myself until this out of draft and ready to review 🙂 |
Change this to "fixes: flutter/flutter#103380" to link the issue for auto-close |
@@ -41,6 +41,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result | |||
[self deviceSupportsBiometrics:result]; | |||
} else if ([@"isDeviceSupported" isEqualToString:call.method]) { | |||
result(@YES); | |||
} else if ([@"handleAuthReplyWithSuccess" isEqualToString:call.method]) { | |||
bool success = [call.arguments[@"success"] boolValue]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BOOL
@@ -41,6 +41,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result | |||
[self deviceSupportsBiometrics:result]; | |||
} else if ([@"isDeviceSupported" isEqualToString:call.method]) { | |||
result(@YES); | |||
} else if ([@"handleAuthReplyWithSuccess" isEqualToString:call.method]) { | |||
bool success = [call.arguments[@"success"] boolValue]; | |||
NSError* error = call.arguments[@"error"]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is call.arguments[@"error"]
indeed NSError
type?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should be!
error:(NSError *)error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you double check by setting a break point here and inspecting the type of error
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#pragma clang diagnostic push | ||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | ||
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in these constants when | ||
// iOS 10 support is dropped. The values are the same, only the names have changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is ios 10 dropped?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes (?), I was under the impression minimum ios version is 11.0 now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm, i thought it was ios 9 but i could remember it wrongly. Can you double check other plugins?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the flutter docs it says minimum version is 11 https://docs.flutter.dev/deployment/ios#:~:text=The%20minimum%20iOS%20version%20that,to%20the%20highest%20required%20version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that's the current flutter version (people could be using older flutter)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
quick search and found this - we are still at ios 9 here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huan is right, this version of the plugin should continue to work on iOS 9 until the minimum version is bumped to 11. The tests are passing because PRs only run on Flutter master, which is auto-migrating the example app to iOS 11 so there's no issue. I think that would even happen in stable too, which only gets run on post-submit.
Trying to think how to force the app to run against the iOS 9 SDK in CI. I guess we just haven't run into issues related to this yet... cc @stuartmorgan
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to think how to force the app to run against the iOS 9 SDK in CI.
Yeah, that's a problem given the auto-migrate logic. I would suggest someone just do a mass-update of all the iOS implementation packages to Flutter 3.3 and iOS 11 now (or maybe 3.3 first, and then iOS 11 as a second pass if that's non-trivial due to dead code removal).
That's the approach I've adopted for our old-Flutter-version support: when we update the N-2 stable version in CI, I mass-update all plugins to at least that minimum version, because we're no longer getting any automated testing that we aren't accidentally breaking people using older versions. (That kind of update doesn't cause any churn for users, because people still using old Flutter versions will just not be given the updates they can't use due to the resolver constraint on the Flutter version.)
case LAErrorSystemCancel: | ||
if ([arguments[@"stickyAuth"] boolValue]) { | ||
self->_lastCallArgs = arguments; | ||
self->_lastResult = result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need result(@YES)
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think result(@yES) should only get returned if it's successful, so if the system cancels it should return no error and exit silently
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you explain what is stickyAuth
is?
I believe the result
must be called with a value or with an error, so that the dart side does not keep waiting (tho this is probably not related to your PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from what I can tell, stickyauth makes it so that when you leave then return to the app, the authentication screen persists. I didn't add it btw, I just moved the cases around
As for the result, that makes sense - should it return result(@no) instead? Since otherwise it would imply the authentication was successful
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked into the doc. It's intended behavior that we don't call the result
callback here. So we can just leave it as it is.
case LAErrorUserFallback: | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when will this default be called?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I added the default so it would catch any unknown error codes and throw an error - the original issue was happening because an error code was thrown that wasnt listed and it was failing silently. Should I change my approach?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the original issue was happening because an error code was thrown that wasnt listed and it was failing silently.
can you inspect the error code and explicitly list here? The default
case shadows all potential future cases and may cause hard-to-find bugs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the error code was
case LAErrorAuthenticationFailed: |
So I already added it to the list
Should I remove the default case then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, we should handle all enums and remove the default
to detect (via compilation warning) when this problem happens again the next time we update the SDK.
[plugin handleMethodCall:call | ||
result:^(id _Nullable result) { | ||
XCTAssertTrue([NSThread isMainThread]); | ||
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]); | |
XCTAssertTrue([result isKindOfClass:[FlutterError class]]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump this.
@@ -41,6 +41,10 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result | |||
[self deviceSupportsBiometrics:result]; | |||
} else if ([@"isDeviceSupported" isEqualToString:call.method]) { | |||
result(@YES); | |||
} else if ([@"handleAuthReplyWithSuccess" isEqualToString:call.method]) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, why was this added? These should correspond to the messages coming from the dart API, like:
plugins/packages/local_auth/local_auth_platform_interface/lib/default_method_channel_platform.dart
Lines 78 to 79 in 2eb6165
Future<bool> isDeviceSupported() async => | |
(await _channel.invokeMethod<bool>('isDeviceSupported')) ?? false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I added it because thats how the other tests were testing methods - Ill test it another way then, good to know its just for dart API messages
case LAErrorUserFallback: | ||
default: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, we should handle all enums and remove the default
to detect (via compilation warning) when this problem happens again the next time we update the SDK.
#pragma clang diagnostic push | ||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | ||
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in these constants when | ||
// iOS 10 support is dropped. The values are the same, only the names have changed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huan is right, this version of the plugin should continue to work on iOS 9 until the minimum version is bumped to 11. The tests are passing because PRs only run on Flutter master, which is auto-migrating the example app to iOS 11 so there's no issue. I think that would even happen in stable too, which only gets run on post-submit.
Trying to think how to force the app to run against the iOS 9 SDK in CI. I guess we just haven't run into issues related to this yet... cc @stuartmorgan
Address the format issues: |
@@ -3,7 +3,7 @@ description: Flutter plugin for Android and iOS devices to allow local | |||
authentication via fingerprint, touch ID, face ID, passcode, pin, or pattern. | |||
repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth | |||
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22 | |||
version: 2.1.2 | |||
version: 2.1.3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No updates to local_auth
are needed in this case, just the implementation package.
@@ -27,451 +27,543 @@ @interface FLTLocalAuthPluginTests : XCTestCase | |||
@implementation FLTLocalAuthPluginTests | |||
|
|||
- (void)setUp { | |||
self.continueAfterFailure = NO; | |||
self.continueAfterFailure = NO; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll probably want to set your Xcode settings (or whatever editor you are using) to 2-space indent for ObjC :)
FYI if you install clang-format (go/clang-format-mac has pointers internally) you can just run the format step locally to get the format that CI is expecting. |
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | ||
// TODO(stuartmorgan): Remove the pragma and s/TouchID/Biometry/ in this constant when | ||
// iOS 10 support is dropped. The values are the same, only the names have changed. | ||
case LAErrorTouchIDNotEnrolled: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be put back too then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, missed that thanks!
[plugin handleMethodCall:call | ||
result:^(id _Nullable result) { | ||
XCTAssertTrue([NSThread isMainThread]); | ||
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bump this.
@@ -216,26 +216,28 @@ - (void)handleAuthReplyWithSuccess:(BOOL)success | |||
result(@YES); | |||
} else { | |||
switch (error.code) { | |||
case LAErrorPasscodeNotSet: | |||
case LAErrorSystemCancel: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You could put this back to where it was at the bottom to avoid dirtying the git history and just add the result(@NO)
part.
XCTAssertTrue([NSThread isMainThread]); | ||
XCTAssertTrue([result isKindOfClass:[NSNumber class]]); | ||
XCTAssertFalse([result boolValue]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you mean to change these result blocks?
XCTAssertTrue([NSThread isMainThread]); | ||
XCTAssertTrue([result isKindOfClass:[NSNumber class]]); | ||
OCMVerify([mockAuthContext setLocalizedFallbackTitle:localizedFallbackTitle]); | ||
XCTAssertFalse([result boolValue]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.
XCTAssertTrue([NSThread isMainThread]); | ||
XCTAssertTrue([result isKindOfClass:[NSNumber class]]); | ||
OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]); | ||
XCTAssertFalse([result boolValue]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I changed it because in these tests, they were originally using a random errorCode of 99 which is not an enum value, so it was falling through the switch case and returning result(No) instead of throwing an error. I changed them to use appropriate error codes instead cuz I thought it would be more accurate to throw an error, but what do you think? Should I just leave it as it was before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the second and third copy of this, the fact that it's simulating an error and asserting things about the error is a copypasta issue in these tests that I missed in review; there's no reason for a test about localized fallback title handling to be using the error path, or asserting anything other than the title part and the main thread. They were just created by copying and pasting from the closest test in the file it looks like. We should just update the dummy reply to a success path, and remove the irrelevant assertions.
For the first, if we intentionally have a codepath that returns NO instead of throwing an error, we should ideally test both that and the error path. The random error code test simulates what would happen if Apple added a new error that we didn't handle yet, for instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The random error code test simulates what would happen if Apple added a new error that we didn't handle yet, for instance.
+1, there should be a test for a new enum that we haven't handled yet.
[self waitForExpectationsWithTimeout:kTimeout handler:nil]; | ||
} | ||
|
||
- (void)testFailedWithKnownErrorCode { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think testFailedAuthWithBiometrics
already handles a "known error code", this is more testing LAErrorTouchIDNotEnrolled
explicitly, right?
- (void)testFailedWithKnownErrorCode { | |
- (void)testFailedTouchIDNotEnrolled { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Youre right, it's kind of a dupe - Ill remove the test
I saw multiple email updates. Let me know when it's ready for a final review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
- You should update the description with
Fixes https://github.com/flutter/flutter/issues/103380
so GitHub will auto-link the issue to the PR and close the issue for you. I manually linked them this time. - When you're ready to merge, put the
autosubmit
label on the issue and the bot will do the rest, and the plugin will be published.
* 5f62d21eb [local_auth] Fix failed biometric authentication not throwing error (flutter/plugins#6821) * ca974ab0c [webview_flutter_web] Copies web implementation of webview_flutter from v4_webview (flutter/plugins#6854) * 4d11be416 [image_picker] Don't store null paths in lost cache (flutter/plugins#6678) * fd2841fd0 [webview_flutter_android] Fix timeouts in the integration tests (flutter/plugins#6857) * abc9f9a9b [flutter_plugin_tools] If `clang-format` does not run, fall back to other executables in PATH (flutter/plugins#6853) * 7efb5e89d [video_player] Add compatibility with the current platform interface (flutter/plugins#6855) * 32dcbf3e3 [image_picker] Improve image_picker for iOS to handle more image types (flutter/plugins#6812) * 840a04954 [webview_flutter] Copies app-facing implementation of webview_flutter from v4_webview (flutter/plugins#6856)
…#117314) * 5f62d21eb [local_auth] Fix failed biometric authentication not throwing error (flutter/plugins#6821) * ca974ab0c [webview_flutter_web] Copies web implementation of webview_flutter from v4_webview (flutter/plugins#6854) * 4d11be416 [image_picker] Don't store null paths in lost cache (flutter/plugins#6678) * fd2841fd0 [webview_flutter_android] Fix timeouts in the integration tests (flutter/plugins#6857) * abc9f9a9b [flutter_plugin_tools] If `clang-format` does not run, fall back to other executables in PATH (flutter/plugins#6853) * 7efb5e89d [video_player] Add compatibility with the current platform interface (flutter/plugins#6855) * 32dcbf3e3 [image_picker] Improve image_picker for iOS to handle more image types (flutter/plugins#6812) * 840a04954 [webview_flutter] Copies app-facing implementation of webview_flutter from v4_webview (flutter/plugins#6856)
…lutter#6821) * fix failed biometric authentication not throwing error + tests * fix test names * Revert "fix test names" This reverts commit 89ba69c. * Revert "fix failed biometric authentication not throwing error + tests" This reverts commit 684790a. * fix authentication not throwing error + tests * fix test name * auto format * cr fixes * addressed pr comments * formatting * formatting * formatting * format attempt * format attempt * formatting fixes * change incorrect versionning * fix test * add back macro * fixed up tests, removed unnecessary assertions, replaced isAMemberOf * add back error for unknown error codes * tests * changed enum to something thats not deprecated * remove redundant test
On iPhones which support TouchID, when failing biometric authentication multiple times, no error will be thrown. This is because the error code that is being thrown is not being handled - this PR fixes that issues, and also ensures that all error codes regardless if known will be handled and thus throw an appropriate error. This also touches old tests, which were using dummy errors codes - they have now been replaced with appropriate errors for the test.
List which issues are fixed by this PR. You must list at least one issue.
Fixes flutter/flutter#103380
If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].
Pre-launch Checklist
dart format
.)[shared_preferences]
pubspec.yaml
with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].CHANGELOG.md
to add a description of the change, [following repository CHANGELOG style].///
).