VLCKeychainCoordinator.m 6.18 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*****************************************************************************
 * VLCKeychainCoordinator.m
 * VLC for iOS
 *****************************************************************************
 * Copyright (c) 2015 VideoLAN. All rights reserved.
 * $Id$
 *
 * Authors: Felix Paul Kühne <fkuehne # videolan.org>
 *
 * Refer to the COPYING file of the official project for license.
 *****************************************************************************/

#import "VLCKeychainCoordinator.h"
#import "PAPasscodeViewController.h"
15
#import <XKKeychain/XKKeychainGenericPasswordItem.h>
16
#import <LocalAuthentication/LocalAuthentication.h>
17

18 19
NSString *const VLCPasscode = @"org.videolan.vlc-ios.passcode";

20 21 22
@interface VLCKeychainCoordinator () <PAPasscodeViewControllerDelegate>
{
    PAPasscodeViewController *_passcodeLockController;
23
    void (^_completion)(void);
24
    BOOL _avoidPromptingTouchOrFaceID;
25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
}

@end

@implementation VLCKeychainCoordinator

+ (instancetype)defaultCoordinator
{
    static VLCKeychainCoordinator *sharedInstance = nil;
    static dispatch_once_t pred;

    dispatch_once(&pred, ^{
        sharedInstance = [VLCKeychainCoordinator new];
    });

    return sharedInstance;
}

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
- (instancetype)init
{
    self = [super init];
    if (!self)
        return nil;

    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appInForeground:)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];
    return self;
}

- (void)appInForeground:(NSNotification *)notification
{
    /* our touch ID session is killed by the OS if the app moves to background, so re-init */
59 60 61 62
    UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
    if ([rootViewController.presentedViewController isKindOfClass:[UINavigationController class]]){
        UINavigationController *navCon = (UINavigationController *)rootViewController.presentedViewController;
        if ([navCon.topViewController isKindOfClass:[PAPasscodeViewController class]] && [self touchIDEnabled]){
63
            [self _touchOrFaceIDQuery];
64
        }
65 66 67
    }
}

68
- (NSString *)passcodeFromKeychain
69
{
70 71 72
    XKKeychainGenericPasswordItem *item = [XKKeychainGenericPasswordItem itemForService:VLCPasscode account:VLCPasscode error:nil];
    NSString *passcode = item.secret.stringValue;
    return passcode;
73 74
}

75
- (BOOL)touchIDEnabled
76
{
77
    return [[NSUserDefaults standardUserDefaults] boolForKey:kVLCSettingPasscodeAllowTouchID];
78 79
}

80 81 82 83 84
- (BOOL)faceIDEnabled
{
    return [[NSUserDefaults standardUserDefaults] boolForKey:kVLCSettingPasscodeAllowFaceID];
}

85
- (BOOL)passcodeLockEnabled
86
{
87 88
    return [[NSUserDefaults standardUserDefaults] boolForKey:kVLCSettingPasscodeOnKey];
}
89

90 91
- (void)validatePasscodeWithCompletion:(void(^)(void))completion
{
92 93
    _passcodeLockController = [[PAPasscodeViewController alloc] initForAction:PasscodeActionEnter];
    _passcodeLockController.delegate = self;
94
    _passcodeLockController.passcode = [self passcodeFromKeychain];
95
    _completion = completion;
96

97
    UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
98

99 100
    if (rootViewController.presentedViewController)
        [rootViewController dismissViewControllerAnimated:NO completion:nil];
101 102 103

    UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:_passcodeLockController];
    navCon.modalPresentationStyle = UIModalPresentationFullScreen;
104
    [rootViewController presentViewController:navCon animated:YES completion:^{
105 106
        if ([self touchIDEnabled] || [self faceIDEnabled]) {
            [self _touchOrFaceIDQuery];
107
        }
108
    }];
109 110
}

111
- (void)_touchOrFaceIDQuery
112
{
113
    //if we just entered background don't show TouchID
114
    if (_avoidPromptingTouchOrFaceID || [UIApplication sharedApplication].applicationState != UIApplicationStateActive)
115
        return;
116

117 118 119 120 121 122
    LAContext *laContext = [[LAContext alloc] init];
    if ([laContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil]) {
        _avoidPromptingTouchOrFaceID = YES;

        [laContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
                  localizedReason:NSLocalizedString(@"BIOMETRIC_UNLOCK", nil)
123
                            reply:^(BOOL success, NSError *error) {
124 125 126 127 128
                                //if we cancel we don't want to show TouchID again
                                dispatch_async(dispatch_get_main_queue(), ^{
                                    if (success) {
                                        [[UIApplication sharedApplication].delegate.window.rootViewController dismissViewControllerAnimated:YES completion:^{
                                            _completion();
129
                                            _completion = nil;
130
                                            _avoidPromptingTouchOrFaceID = NO;
131
                                        }];
132 133
                                    } else {
                                        //user hit cancel and wants to enter the passcode
134
                                        _avoidPromptingTouchOrFaceID = YES;
135 136
                                    }
                                });
137 138 139
                            }];
    }
}
140 141 142

- (void)PAPasscodeViewControllerDidEnterPasscode:(PAPasscodeViewController *)controller
{
143
    _avoidPromptingTouchOrFaceID = NO;
144 145
    [[UIApplication sharedApplication].delegate.window.rootViewController dismissViewControllerAnimated:YES completion:^{
        _completion();
146
        _completion = nil;
147
    }];
148 149 150 151 152 153 154 155 156
}

- (void)PAPasscodeViewController:(PAPasscodeViewController *)controller didFailToEnterPasscode:(NSInteger)attempts
{
    // FIXME: handle countless failed passcode attempts
}

- (void)setPasscode:(NSString *)passcode
{
157
    if (!passcode) {
158
        [XKKeychainGenericPasswordItem removeItemsForService:VLCPasscode error:nil];
159 160
        return;
    }
161

162 163 164 165 166
    XKKeychainGenericPasswordItem *keychainItem = [[XKKeychainGenericPasswordItem alloc] init];
    keychainItem.service = VLCPasscode;
    keychainItem.account = VLCPasscode;
    keychainItem.secret.stringValue = passcode;
    [keychainItem saveWithError:nil];
167 168 169
}

@end