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

[local_auth] Fix failed biometric authentication not throwing error #6821

Merged
merged 23 commits into from
Dec 16, 2022

Conversation

LouiseHsu
Copy link
Contributor

@LouiseHsu LouiseHsu commented Dec 10, 2022

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

  • I read the [Contributor Guide] and followed the process outlined there for submitting PRs.
  • I read the [Tree Hygiene] wiki page, which explains my responsibilities.
  • I read and followed the [relevant style guides] and ran [the auto-formatter]. (Unlike the flutter/flutter repo, the flutter/plugins repo does use dart format.)
  • I signed the [CLA].
  • The title of the PR starts with the name of the plugin surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the [pub versioning philosophy], or this PR is [exempt from version changes].
  • I updated CHANGELOG.md to add a description of the change, [following repository CHANGELOG style].
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is [test-exempt].
  • All existing and new tests are passing.

@jmagman
Copy link
Member

jmagman commented Dec 12, 2022

Removing myself until this out of draft and ready to review 🙂

@jmagman jmagman removed their request for review December 12, 2022 20:15
@jmagman
Copy link
Member

jmagman commented Dec 12, 2022

List which issues are fixed by this PR. You must list at least one issue.

flutter/flutter#103380

Change this to "fixes: flutter/flutter#103380" to link the issue for auto-close
https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword

@@ -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];
Copy link
Contributor

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"];
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Screen Shot 2022-12-12 at 1 23 04 PM

#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.
Copy link
Contributor

Choose a reason for hiding this comment

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

is ios 10 dropped?

Copy link
Contributor Author

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

Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Contributor

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)

Copy link
Contributor

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.

Copy link
Member

@jmagman jmagman Dec 12, 2022

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

Copy link
Contributor

@stuartmorgan stuartmorgan Dec 13, 2022

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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).

Copy link
Contributor Author

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

Copy link
Contributor

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:
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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.

Copy link
Contributor Author

@LouiseHsu LouiseHsu Dec 12, 2022

Choose a reason for hiding this comment

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

the error code was

So I already added it to the list
Should I remove the default case then?

Copy link
Member

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]]);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
XCTAssertTrue([result isMemberOfClass:[FlutterError class]]);
XCTAssertTrue([result isKindOfClass:[FlutterError class]]);

Copy link
Member

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]) {
Copy link
Member

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:

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

Copy link
Contributor Author

@LouiseHsu LouiseHsu Dec 12, 2022

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:
Copy link
Member

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.
Copy link
Member

@jmagman jmagman Dec 12, 2022

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

@jmagman
Copy link
Member

jmagman commented Dec 12, 2022

Address the format issues:
https://cirrus-ci.com/task/6643342537129984?logs=format#L19

@LouiseHsu LouiseHsu marked this pull request as ready for review December 12, 2022 22:07
@@ -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
Copy link
Contributor

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;
Copy link
Contributor

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 :)

@stuartmorgan
Copy link
Contributor

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:
Copy link
Member

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?

Copy link
Contributor Author

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]]);
Copy link
Member

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:
Copy link
Member

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.

Comment on lines 258 to 183
XCTAssertTrue([NSThread isMainThread]);
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
XCTAssertFalse([result boolValue]);
Copy link
Member

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?

Comment on lines -223 to -226
XCTAssertTrue([NSThread isMainThread]);
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
OCMVerify([mockAuthContext setLocalizedFallbackTitle:localizedFallbackTitle]);
XCTAssertFalse([result boolValue]);
Copy link
Member

Choose a reason for hiding this comment

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

Same.

Comment on lines -263 to -266
XCTAssertTrue([NSThread isMainThread]);
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]);
XCTAssertFalse([result boolValue]);
Copy link
Member

Choose a reason for hiding this comment

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

Same

Copy link
Contributor Author

@LouiseHsu LouiseHsu Dec 14, 2022

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?

Copy link
Contributor

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.

Copy link
Member

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 {
Copy link
Member

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?

Suggested change
- (void)testFailedWithKnownErrorCode {
- (void)testFailedTouchIDNotEnrolled {

Copy link
Contributor Author

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

@jmagman jmagman changed the title [local-auth] Fix failed biometric authentication not throwing error [local_auth] Fix failed biometric authentication not throwing error Dec 15, 2022
@hellohuanlin
Copy link
Contributor

I saw multiple email updates. Let me know when it's ready for a final review.

Copy link
Member

@jmagman jmagman left a comment

Choose a reason for hiding this comment

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

LGTM!

  1. 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.
  2. 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.

@LouiseHsu LouiseHsu added the autosubmit Merge PR when tree becomes green via auto submit App label Dec 16, 2022
@auto-submit auto-submit bot merged commit 5f62d21 into flutter:main Dec 16, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Dec 19, 2022
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Dec 19, 2022
* 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)
gspencergoog pushed a commit to gspencergoog/flutter that referenced this pull request Jan 19, 2023
…#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)
mauricioluz pushed a commit to mauricioluz/plugins that referenced this pull request Jan 26, 2023
…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
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: local_auth platform-ios
Projects
None yet
4 participants