|  | // Copyright 2017 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  | #import <LocalAuthentication/LocalAuthentication.h> | 
|  |  | 
|  | #import "FLTLocalAuthPlugin.h" | 
|  |  | 
|  | @interface FLTLocalAuthPlugin () | 
|  | @property(copy, nullable) NSDictionary<NSString *, NSNumber *> *lastCallArgs; | 
|  | @property(nullable) FlutterResult lastResult; | 
|  | @end | 
|  |  | 
|  | @implementation FLTLocalAuthPlugin | 
|  |  | 
|  | + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { | 
|  | FlutterMethodChannel *channel = | 
|  | [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/local_auth" | 
|  | binaryMessenger:[registrar messenger]]; | 
|  | FLTLocalAuthPlugin *instance = [[FLTLocalAuthPlugin alloc] init]; | 
|  | [registrar addMethodCallDelegate:instance channel:channel]; | 
|  | [registrar addApplicationDelegate:instance]; | 
|  | } | 
|  |  | 
|  | - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { | 
|  | if ([@"authenticateWithBiometrics" isEqualToString:call.method]) { | 
|  | [self authenticateWithBiometrics:call.arguments withFlutterResult:result]; | 
|  | } else if ([@"getAvailableBiometrics" isEqualToString:call.method]) { | 
|  | [self getAvailableBiometrics:result]; | 
|  | } else { | 
|  | result(FlutterMethodNotImplemented); | 
|  | } | 
|  | } | 
|  |  | 
|  | #pragma mark Private Methods | 
|  |  | 
|  | - (void)alertMessage:(NSString *)message | 
|  | firstButton:(NSString *)firstButton | 
|  | flutterResult:(FlutterResult)result | 
|  | additionalButton:(NSString *)secondButton { | 
|  | UIAlertController *alert = | 
|  | [UIAlertController alertControllerWithTitle:@"" | 
|  | message:message | 
|  | preferredStyle:UIAlertControllerStyleAlert]; | 
|  |  | 
|  | UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:firstButton | 
|  | style:UIAlertActionStyleDefault | 
|  | handler:^(UIAlertAction *action) { | 
|  | result(@NO); | 
|  | }]; | 
|  |  | 
|  | [alert addAction:defaultAction]; | 
|  | if (secondButton != nil) { | 
|  | UIAlertAction *additionalAction = [UIAlertAction | 
|  | actionWithTitle:secondButton | 
|  | style:UIAlertActionStyleDefault | 
|  | handler:^(UIAlertAction *action) { | 
|  | if (UIApplicationOpenSettingsURLString != NULL) { | 
|  | NSURL *url = [NSURL URLWithString:UIApplicationOpenSettingsURLString]; | 
|  | [[UIApplication sharedApplication] openURL:url]; | 
|  | result(@NO); | 
|  | } | 
|  | }]; | 
|  | [alert addAction:additionalAction]; | 
|  | } | 
|  | [[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:alert | 
|  | animated:YES | 
|  | completion:nil]; | 
|  | } | 
|  |  | 
|  | - (void)getAvailableBiometrics:(FlutterResult)result { | 
|  | LAContext *context = [[LAContext alloc] init]; | 
|  | NSError *authError = nil; | 
|  | NSMutableArray<NSString *> *biometrics = [[NSMutableArray<NSString *> alloc] init]; | 
|  | if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics | 
|  | error:&authError]) { | 
|  | if (authError == nil) { | 
|  | if (@available(iOS 11.0.1, *)) { | 
|  | if (context.biometryType == LABiometryTypeFaceID) { | 
|  | [biometrics addObject:@"face"]; | 
|  | } else if (context.biometryType == LABiometryTypeTouchID) { | 
|  | [biometrics addObject:@"fingerprint"]; | 
|  | } | 
|  | } else { | 
|  | [biometrics addObject:@"fingerprint"]; | 
|  | } | 
|  | } | 
|  | } else if (authError.code == LAErrorTouchIDNotEnrolled) { | 
|  | [biometrics addObject:@"undefined"]; | 
|  | } | 
|  | result(biometrics); | 
|  | } | 
|  |  | 
|  | - (void)authenticateWithBiometrics:(NSDictionary *)arguments | 
|  | withFlutterResult:(FlutterResult)result { | 
|  | LAContext *context = [[LAContext alloc] init]; | 
|  | NSError *authError = nil; | 
|  | self.lastCallArgs = nil; | 
|  | self.lastResult = nil; | 
|  | context.localizedFallbackTitle = @""; | 
|  |  | 
|  | if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics | 
|  | error:&authError]) { | 
|  | [context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics | 
|  | localizedReason:arguments[@"localizedReason"] | 
|  | reply:^(BOOL success, NSError *error) { | 
|  | if (success) { | 
|  | result(@YES); | 
|  | } else { | 
|  | switch (error.code) { | 
|  | case LAErrorPasscodeNotSet: | 
|  | case LAErrorTouchIDNotAvailable: | 
|  | case LAErrorTouchIDNotEnrolled: | 
|  | case LAErrorTouchIDLockout: | 
|  | [self handleErrors:error | 
|  | flutterArguments:arguments | 
|  | withFlutterResult:result]; | 
|  | return; | 
|  | case LAErrorSystemCancel: | 
|  | if ([arguments[@"stickyAuth"] boolValue]) { | 
|  | self.lastCallArgs = arguments; | 
|  | self.lastResult = result; | 
|  | return; | 
|  | } | 
|  | } | 
|  | result(@NO); | 
|  | } | 
|  | }]; | 
|  | } else { | 
|  | [self handleErrors:authError flutterArguments:arguments withFlutterResult:result]; | 
|  | } | 
|  | } | 
|  |  | 
|  | - (void)handleErrors:(NSError *)authError | 
|  | flutterArguments:(NSDictionary *)arguments | 
|  | withFlutterResult:(FlutterResult)result { | 
|  | NSString *errorCode = @"NotAvailable"; | 
|  | switch (authError.code) { | 
|  | case LAErrorPasscodeNotSet: | 
|  | case LAErrorTouchIDNotEnrolled: | 
|  | if ([arguments[@"useErrorDialogs"] boolValue]) { | 
|  | [self alertMessage:arguments[@"goToSettingDescriptionIOS"] | 
|  | firstButton:arguments[@"okButton"] | 
|  | flutterResult:result | 
|  | additionalButton:arguments[@"goToSetting"]]; | 
|  | return; | 
|  | } | 
|  | errorCode = authError.code == LAErrorPasscodeNotSet ? @"PasscodeNotSet" : @"NotEnrolled"; | 
|  | break; | 
|  | case LAErrorTouchIDLockout: | 
|  | [self alertMessage:arguments[@"lockOut"] | 
|  | firstButton:arguments[@"okButton"] | 
|  | flutterResult:result | 
|  | additionalButton:nil]; | 
|  | return; | 
|  | } | 
|  | result([FlutterError errorWithCode:errorCode | 
|  | message:authError.localizedDescription | 
|  | details:authError.domain]); | 
|  | } | 
|  |  | 
|  | #pragma mark - AppDelegate | 
|  |  | 
|  | - (void)applicationDidBecomeActive:(UIApplication *)application { | 
|  | if (self.lastCallArgs != nil && self.lastResult != nil) { | 
|  | [self authenticateWithBiometrics:_lastCallArgs withFlutterResult:self.lastResult]; | 
|  | } | 
|  | } | 
|  |  | 
|  | @end |