Skip to content

Commit

Permalink
[local_auth] Fix failed biometric authentication not throwing error (f…
Browse files Browse the repository at this point in the history
…lutter#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
  • Loading branch information
LouiseHsu authored Dec 16, 2022
1 parent cbcf507 commit 5f62d21
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 18 deletions.
4 changes: 4 additions & 0 deletions packages/local_auth/local_auth_ios/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.0.11

* Fixes issue where failed authentication was failing silently

## 1.0.10

* Updates imports for `prefer_relative_imports`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ - (void)testFailedAuthWithBiometrics {
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]])
Expand All @@ -140,14 +140,13 @@ - (void)testFailedAuthWithBiometrics {
[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];
}

- (void)testFailedAuthWithoutBiometrics {
- (void)testFailedWithUnknownErrorCode {
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
id mockAuthContext = OCMClassMock([LAContext class]);
plugin.authContextOverrides = @[ mockAuthContext ];
Expand Down Expand Up @@ -175,6 +174,45 @@ - (void)testFailedAuthWithoutBiometrics {
@"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) {
Expand All @@ -186,6 +224,44 @@ - (void)testFailedAuthWithoutBiometrics {
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}

- (void)testFailedAuthWithoutBiometrics {
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:LAErrorAuthenticationFailed 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)testLocalizedFallbackTitle {
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
id mockAuthContext = OCMClassMock([LAContext class]);
Expand All @@ -203,7 +279,7 @@ - (void)testLocalizedFallbackTitle {
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]])
Expand All @@ -220,10 +296,7 @@ - (void)testLocalizedFallbackTitle {
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];
Expand All @@ -245,7 +318,7 @@ - (void)testSkippedLocalizedFallbackTitle {
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]])
Expand All @@ -260,10 +333,7 @@ - (void)testSkippedLocalizedFallbackTitle {
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];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,26 +216,29 @@ - (void)handleAuthReplyWithSuccess:(BOOL)success
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];
}
}

Expand Down
2 changes: 1 addition & 1 deletion packages/local_auth/local_auth_ios/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: local_auth_ios
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"
Expand Down

0 comments on commit 5f62d21

Please sign in to comment.