[local_auth] Fix failed biometric authentication not throwing error (#6821)
* fix failed biometric authentication not throwing error + tests
* fix test names
* Revert "fix test names"
This reverts commit 89ba69ccc33c37b98092a5fbfa5c71ebc23cd468.
* Revert "fix failed biometric authentication not throwing error + tests"
This reverts commit 684790a7c7756c963ac3aa3b6e4de48cc9b33df5.
* 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
diff --git a/packages/local_auth/local_auth_ios/CHANGELOG.md b/packages/local_auth/local_auth_ios/CHANGELOG.md
index e67f2a4..eb95e2f 100644
--- a/packages/local_auth/local_auth_ios/CHANGELOG.md
+++ b/packages/local_auth/local_auth_ios/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.0.11
+
+* Fixes issue where failed authentication was failing silently
+
## 1.0.10
* Updates imports for `prefer_relative_imports`.
diff --git a/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m b/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m
index 50dbb1a..51c94cc 100644
--- a/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m
+++ b/packages/local_auth/local_auth_ios/example/ios/RunnerTests/FLTLocalAuthPluginTests.m
@@ -124,7 +124,7 @@
void (^reply)(BOOL, NSError *);
[invocation getArgument:&reply atIndex:4];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
- reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
+ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]);
});
};
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -140,6 +140,83 @@
[plugin handleMethodCall:call
result:^(id _Nullable result) {
XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertTrue([result isKindOfClass:[FlutterError class]]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+}
+
+- (void)testFailedWithUnknownErrorCode {
+ FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
+ id mockAuthContext = OCMClassMock([LAContext class]);
+ plugin.authContextOverrides = @[ mockAuthContext ];
+
+ const LAPolicy policy = LAPolicyDeviceOwnerAuthentication;
+ NSString *reason = @"a reason";
+ OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES);
+
+ // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not
+ // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on
+ // a background thread.
+ void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) {
+ void (^reply)(BOOL, NSError *);
+ [invocation getArgument:&reply atIndex:4];
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
+ reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
+ });
+ };
+ OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
+ .andDo(backgroundThreadReplyCaller);
+
+ FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate"
+ arguments:@{
+ @"biometricOnly" : @(NO),
+ @"localizedReason" : reason,
+ }];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
+ [plugin handleMethodCall:call
+ result:^(id _Nullable result) {
+ XCTAssertTrue([NSThread isMainThread]);
+ XCTAssertTrue([result isKindOfClass:[FlutterError class]]);
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+}
+
+- (void)testSystemCancelledWithoutStickyAuth {
+ FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
+ id mockAuthContext = OCMClassMock([LAContext class]);
+ plugin.authContextOverrides = @[ mockAuthContext ];
+
+ const LAPolicy policy = LAPolicyDeviceOwnerAuthentication;
+ NSString *reason = @"a reason";
+ OCMStub([mockAuthContext canEvaluatePolicy:policy error:[OCMArg setTo:nil]]).andReturn(YES);
+
+ // evaluatePolicy:localizedReason:reply: calls back on an internal queue, which is not
+ // guaranteed to be on the main thread. Ensure that's handled correctly by calling back on
+ // a background thread.
+ void (^backgroundThreadReplyCaller)(NSInvocation *) = ^(NSInvocation *invocation) {
+ void (^reply)(BOOL, NSError *);
+ [invocation getArgument:&reply atIndex:4];
+ dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
+ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorSystemCancel userInfo:nil]);
+ });
+ };
+ OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
+ .andDo(backgroundThreadReplyCaller);
+
+ FlutterMethodCall *call = [FlutterMethodCall methodCallWithMethodName:@"authenticate"
+ arguments:@{
+ @"biometricOnly" : @(NO),
+ @"localizedReason" : reason,
+ @"stickyAuth" : @(NO)
+ }];
+
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
+ [plugin handleMethodCall:call
+ result:^(id _Nullable result) {
+ XCTAssertTrue([NSThread isMainThread]);
XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
XCTAssertFalse([result boolValue]);
[expectation fulfill];
@@ -163,7 +240,7 @@
void (^reply)(BOOL, NSError *);
[invocation getArgument:&reply atIndex:4];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
- reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
+ reply(NO, [NSError errorWithDomain:@"error" code:LAErrorAuthenticationFailed userInfo:nil]);
});
};
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -179,8 +256,7 @@
[plugin handleMethodCall:call
result:^(id _Nullable result) {
XCTAssertTrue([NSThread isMainThread]);
- XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
- XCTAssertFalse([result boolValue]);
+ XCTAssertTrue([result isKindOfClass:[FlutterError class]]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
@@ -203,7 +279,7 @@
void (^reply)(BOOL, NSError *);
[invocation getArgument:&reply atIndex:4];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
- reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
+ reply(YES, nil);
});
};
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -220,10 +296,7 @@
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
[plugin handleMethodCall:call
result:^(id _Nullable result) {
- XCTAssertTrue([NSThread isMainThread]);
- XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
OCMVerify([mockAuthContext setLocalizedFallbackTitle:localizedFallbackTitle]);
- XCTAssertFalse([result boolValue]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
@@ -245,7 +318,7 @@
void (^reply)(BOOL, NSError *);
[invocation getArgument:&reply atIndex:4];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
- reply(NO, [NSError errorWithDomain:@"error" code:99 userInfo:nil]);
+ reply(YES, nil);
});
};
OCMStub([mockAuthContext evaluatePolicy:policy localizedReason:reason reply:[OCMArg any]])
@@ -260,10 +333,7 @@
XCTestExpectation *expectation = [self expectationWithDescription:@"Result is called"];
[plugin handleMethodCall:call
result:^(id _Nullable result) {
- XCTAssertTrue([NSThread isMainThread]);
- XCTAssertTrue([result isKindOfClass:[NSNumber class]]);
OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]);
- XCTAssertFalse([result boolValue]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
diff --git a/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m b/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m
index 8f61fec..4d98254 100644
--- a/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m
+++ b/packages/local_auth/local_auth_ios/ios/Classes/FLTLocalAuthPlugin.m
@@ -216,26 +216,29 @@
result(@YES);
} else {
switch (error.code) {
- case LAErrorPasscodeNotSet:
#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.
+ // 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.
case LAErrorTouchIDNotAvailable:
case LAErrorTouchIDNotEnrolled:
case LAErrorTouchIDLockout:
#pragma clang diagnostic pop
case LAErrorUserFallback:
+ case LAErrorPasscodeNotSet:
+ case LAErrorAuthenticationFailed:
[self handleErrors:error flutterArguments:arguments withFlutterResult:result];
return;
case LAErrorSystemCancel:
if ([arguments[@"stickyAuth"] boolValue]) {
self->_lastCallArgs = arguments;
self->_lastResult = result;
- return;
+ } else {
+ result(@NO);
}
+ return;
}
- result(@NO);
+ [self handleErrors:error flutterArguments:arguments withFlutterResult:result];
}
}
diff --git a/packages/local_auth/local_auth_ios/pubspec.yaml b/packages/local_auth/local_auth_ios/pubspec.yaml
index 9cdeef9..d6cab0f 100644
--- a/packages/local_auth/local_auth_ios/pubspec.yaml
+++ b/packages/local_auth/local_auth_ios/pubspec.yaml
@@ -2,7 +2,7 @@
description: iOS implementation of the local_auth plugin.
repository: https://github.com/flutter/plugins/tree/main/packages/local_auth/local_auth_ios
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+local_auth%22
-version: 1.0.10
+version: 1.0.11
environment:
sdk: ">=2.14.0 <3.0.0"