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

[local_auth] support localizedFallbackTitle in IOSAuthMessages #3806

Merged
merged 27 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
71f3a02
Added: hardcoded localizedFallbackTitle
furaiev Apr 12, 2021
d0f59cb
sync commit
furaiev Sep 18, 2021
56323ba
Merge branch 'flutter-master'
furaiev Sep 18, 2021
ad80f96
localizedFallbackTitle as non nullable param
furaiev Sep 18, 2021
2e52b89
added: localizedFallbackTitle test
furaiev Sep 19, 2021
1b6c9bb
test formatting fix
furaiev Sep 20, 2021
399f787
test formatting fix
furaiev Sep 20, 2021
a011421
fixed: localizedFallbackTitle will not sent if it is null
furaiev Oct 12, 2021
dcc6e91
fixed: localizedFallbackTitle [NSNull null] check, refactored IOSAuth…
furaiev Oct 15, 2021
31b6a3e
Merge branch 'flutter:master' into master
furaiev Oct 15, 2021
a55fa1d
reverted LocalAuthentication authenticate arguments to non-nullable
furaiev Oct 15, 2021
dc4bee3
fixed: localizedFallbackTitle key, added: dart tests
furaiev Oct 25, 2021
0fb27d6
Merge branch 'flutter:master' into master
furaiev Oct 25, 2021
50310fd
formatting
furaiev Oct 25, 2021
9b4b001
Merge branch 'flutter:master' into master
furaiev Oct 30, 2021
fd4356d
updated: local_auth_test.dart
furaiev Oct 30, 2021
ff6b467
sync commit
furaiev Jan 16, 2022
3d265b7
Merge branch 'flutter-main'
furaiev Jan 16, 2022
63fd16b
fixed: tests
furaiev Jan 16, 2022
0b408fd
fixed: tests (lintering)
furaiev Jan 16, 2022
0cf0a8c
fixed: added setLocalizedFallbackTitle test
furaiev Jan 16, 2022
efec6b5
fixed: formatting
furaiev Jan 16, 2022
71375d2
Merge branch 'main' into master
furaiev Jan 17, 2022
cd7e066
fixed: changelog comment style
furaiev Jan 18, 2022
e4ac67f
Merge remote-tracking branch 'origin/master'
furaiev Jan 18, 2022
0474601
Merge branch 'master' of https://github.com/flutter/plugins
furaiev Feb 15, 2022
2ca4cba
ObjC formatted
furaiev Feb 15, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/local_auth/local_auth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.1.11

* Adds support `localizedFallbackTitle` in authenticateWithBiometrics on iOS.

## 1.1.10

* Removes dependency on `meta`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,87 @@ - (void)testFailedAuthWithoutBiometrics {
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}

- (void)testLocalizedFallbackTitle {
FLTLocalAuthPlugin *plugin = [[FLTLocalAuthPlugin alloc] init];
id mockAuthContext = OCMClassMock([LAContext class]);
plugin.authContextOverrides = @[ mockAuthContext ];

const LAPolicy policy = LAPolicyDeviceOwnerAuthentication;
NSString *reason = @"a reason";
NSString *localizedFallbackTitle = @"a title";
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,
@"localizedFallbackTitle" : localizedFallbackTitle,
}];

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];
}

- (void)testSkippedLocalizedFallbackTitle {
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:[NSNumber class]]);
OCMVerify([mockAuthContext setLocalizedFallbackTitle:nil]);
XCTAssertFalse([result boolValue]);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:kTimeout handler:nil];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ - (void)authenticateWithBiometrics:(NSDictionary *)arguments
NSError *authError = nil;
self.lastCallArgs = nil;
self.lastResult = nil;
context.localizedFallbackTitle = @"";
context.localizedFallbackTitle = arguments[@"localizedFallbackTitle"] == [NSNull null]
? nil
: arguments[@"localizedFallbackTitle"];

if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&authError]) {
Expand All @@ -146,7 +148,9 @@ - (void)authenticate:(NSDictionary *)arguments withFlutterResult:(FlutterResult)
NSError *authError = nil;
_lastCallArgs = nil;
_lastResult = nil;
context.localizedFallbackTitle = @"";
context.localizedFallbackTitle = arguments[@"localizedFallbackTitle"] == [NSNull null]
? nil
: arguments[@"localizedFallbackTitle"];

if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) {
[context evaluatePolicy:kLAPolicyDeviceOwnerAuthentication
Expand Down Expand Up @@ -176,6 +180,7 @@ - (void)handleAuthReplyWithSuccess:(BOOL)success
case LAErrorPasscodeNotSet:
case LAErrorTouchIDNotAvailable:
case LAErrorTouchIDNotEnrolled:
case LAErrorUserFallback:
case LAErrorTouchIDLockout:
[self handleErrors:error flutterArguments:arguments withFlutterResult:result];
return;
Expand Down
4 changes: 4 additions & 0 deletions packages/local_auth/local_auth/lib/auth_strings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,14 @@ class IOSAuthMessages {
this.goToSettingsButton,
this.goToSettingsDescription,
this.cancelButton,
this.localizedFallbackTitle,
});

final String? lockOut;
final String? goToSettingsButton;
final String? goToSettingsDescription;
final String? cancelButton;
final String? localizedFallbackTitle;

Map<String, String> get args {
return <String, String>{
Expand All @@ -82,6 +84,8 @@ class IOSAuthMessages {
'goToSettingDescriptionIOS':
goToSettingsDescription ?? iOSGoToSettingsDescription,
'okButton': cancelButton ?? iOSOkButton,
if (localizedFallbackTitle != null)
'localizedFallbackTitle': localizedFallbackTitle!,
};
}
}
Expand Down
2 changes: 1 addition & 1 deletion packages/local_auth/local_auth/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: 1.1.10
version: 1.1.11

environment:
sdk: ">=2.14.0 <3.0.0"
Expand Down
177 changes: 129 additions & 48 deletions packages/local_auth/local_auth/test/local_auth_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,28 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': true,
}..addAll(const AndroidAuthMessages().args)),
isMethodCall(
'authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': true,
'biometricHint': androidBiometricHint,
'biometricNotRecognized': androidBiometricNotRecognized,
'biometricSuccess': androidBiometricSuccess,
'biometricRequired': androidBiometricRequiredTitle,
'cancelButton': androidCancelButton,
'deviceCredentialsRequired':
androidDeviceCredentialsRequiredTitle,
'deviceCredentialsSetupDescription':
androidDeviceCredentialsSetupDescription,
'goToSetting': goToSettings,
'goToSettingDescription': androidGoToSettingsDescription,
'signInTitle': androidSignInTitle,
},
),
],
);
});
Expand All @@ -61,14 +75,45 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': true,
}..addAll(const IOSAuthMessages().args)),
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': true,
'lockOut': iOSLockOut,
'goToSetting': goToSettings,
'goToSettingDescriptionIOS': iOSGoToSettingsDescription,
'okButton': iOSOkButton,
}),
],
);
});

test('authenticate with `localizedFallbackTitle` on iOS.', () async {
const IOSAuthMessages iosAuthMessages =
IOSAuthMessages(localizedFallbackTitle: 'Enter PIN');
setMockPathProviderPlatform(FakePlatform(operatingSystem: 'ios'));
await localAuthentication.authenticate(
localizedReason: 'Needs secure',
biometricOnly: true,
iOSAuthStrings: iosAuthMessages,
);
expect(
log,
<Matcher>[
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': true,
'lockOut': iOSLockOut,
'goToSetting': goToSettings,
'goToSettingDescriptionIOS': iOSGoToSettingsDescription,
'okButton': iOSOkButton,
'localizedFallbackTitle': 'Enter PIN',
}),
],
);
});
Expand All @@ -95,14 +140,25 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Insecure',
'useErrorDialogs': false,
'stickyAuth': false,
'sensitiveTransaction': false,
'biometricOnly': true,
}..addAll(const AndroidAuthMessages().args)),
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Insecure',
'useErrorDialogs': false,
'stickyAuth': false,
'sensitiveTransaction': false,
'biometricOnly': true,
'biometricHint': androidBiometricHint,
'biometricNotRecognized': androidBiometricNotRecognized,
'biometricSuccess': androidBiometricSuccess,
'biometricRequired': androidBiometricRequiredTitle,
'cancelButton': androidCancelButton,
'deviceCredentialsRequired':
androidDeviceCredentialsRequiredTitle,
'deviceCredentialsSetupDescription':
androidDeviceCredentialsSetupDescription,
'goToSetting': goToSettings,
'goToSettingDescription': androidGoToSettingsDescription,
'signInTitle': androidSignInTitle,
}),
],
);
});
Expand All @@ -117,14 +173,25 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': false,
}..addAll(const AndroidAuthMessages().args)),
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': false,
'biometricHint': androidBiometricHint,
'biometricNotRecognized': androidBiometricNotRecognized,
'biometricSuccess': androidBiometricSuccess,
'biometricRequired': androidBiometricRequiredTitle,
'cancelButton': androidCancelButton,
'deviceCredentialsRequired':
androidDeviceCredentialsRequiredTitle,
'deviceCredentialsSetupDescription':
androidDeviceCredentialsSetupDescription,
'goToSetting': goToSettings,
'goToSettingDescription': androidGoToSettingsDescription,
'signInTitle': androidSignInTitle,
}),
],
);
});
Expand All @@ -137,14 +204,17 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': false,
}..addAll(const IOSAuthMessages().args)),
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Needs secure',
'useErrorDialogs': true,
'stickyAuth': false,
'sensitiveTransaction': true,
'biometricOnly': false,
'lockOut': iOSLockOut,
'goToSetting': goToSettings,
'goToSettingDescriptionIOS': iOSGoToSettingsDescription,
'okButton': iOSOkButton,
}),
],
);
});
Expand All @@ -159,14 +229,25 @@ void main() {
expect(
log,
<Matcher>[
isMethodCall('authenticate',
arguments: <String, dynamic>{
'localizedReason': 'Insecure',
'useErrorDialogs': false,
'stickyAuth': false,
'sensitiveTransaction': false,
'biometricOnly': false,
}..addAll(const AndroidAuthMessages().args)),
isMethodCall('authenticate', arguments: <String, dynamic>{
'localizedReason': 'Insecure',
'useErrorDialogs': false,
'stickyAuth': false,
'sensitiveTransaction': false,
'biometricOnly': false,
'biometricHint': androidBiometricHint,
'biometricNotRecognized': androidBiometricNotRecognized,
'biometricSuccess': androidBiometricSuccess,
'biometricRequired': androidBiometricRequiredTitle,
'cancelButton': androidCancelButton,
'deviceCredentialsRequired':
androidDeviceCredentialsRequiredTitle,
'deviceCredentialsSetupDescription':
androidDeviceCredentialsSetupDescription,
'goToSetting': goToSettings,
'goToSettingDescription': androidGoToSettingsDescription,
'signInTitle': androidSignInTitle,
}),
],
);
});
Expand Down