KeychainCoordinator.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. /*****************************************************************************
  2. * KeychainCoordinator.m
  3. * VLC for iOS
  4. *****************************************************************************
  5. * Copyright (c) 2017 VideoLAN. All rights reserved.
  6. * $Id$
  7. *
  8. * Authors:Carola Nitz <caro # videolan.org>
  9. *
  10. * Refer to the COPYING file of the official project for license.
  11. *****************************************************************************/
  12. import Foundation
  13. import LocalAuthentication
  14. @objc(VLCKeychainCoordinator)
  15. class KeychainCoordinator:NSObject, PAPasscodeViewControllerDelegate {
  16. @objc class var passcodeLockEnabled:Bool {
  17. return UserDefaults.standard.bool(forKey:kVLCSettingPasscodeOnKey)
  18. }
  19. private var touchIDEnabled:Bool {
  20. return UserDefaults.standard.bool(forKey:kVLCSettingPasscodeAllowTouchID)
  21. }
  22. private var faceIDEnabled:Bool {
  23. return UserDefaults.standard.bool(forKey:kVLCSettingPasscodeAllowFaceID)
  24. }
  25. static let passcodeService = "org.videolan.vlc-ios.passcode"
  26. var completion: (() -> ())? = nil
  27. private var avoidPromptingTouchOrFaceID = false
  28. private lazy var passcodeLockController:PAPasscodeViewController = {
  29. let passcodeController = PAPasscodeViewController(for: PasscodeActionEnter)
  30. passcodeController!.delegate = self
  31. return passcodeController!
  32. }()
  33. override init() {
  34. super.init()
  35. NotificationCenter.default.addObserver(self, selector: #selector(appInForeground), name: .UIApplicationDidBecomeActive, object: nil)
  36. }
  37. @objc class func setPasscode(passcode:String?) throws {
  38. guard let passcode = passcode else {
  39. do {
  40. try XKKeychainGenericPasswordItem.removeItems(forService: passcodeService)
  41. } catch let error {
  42. throw error
  43. }
  44. return
  45. }
  46. let keychainItem = XKKeychainGenericPasswordItem()
  47. keychainItem.service = passcodeService
  48. keychainItem.account = passcodeService
  49. keychainItem.secret.stringValue = passcode
  50. do {
  51. try keychainItem.save()
  52. } catch let error {
  53. throw error
  54. }
  55. }
  56. @objc func validatePasscode(completion:@escaping ()->()) {
  57. passcodeLockController.passcode = passcodeFromKeychain()
  58. self.completion = completion
  59. guard let rootViewController = UIApplication.shared.delegate?.window??.rootViewController else {
  60. return
  61. }
  62. if rootViewController.presentedViewController != nil {
  63. rootViewController.dismiss(animated: false, completion: nil)
  64. }
  65. let navigationController = UINavigationController(rootViewController: passcodeLockController)
  66. navigationController.modalPresentationStyle = .fullScreen
  67. navigationController.modalTransitionStyle = .crossDissolve
  68. rootViewController.present(navigationController, animated: true) {
  69. [weak self] in
  70. if (self?.touchIDEnabled == true || self?.faceIDEnabled == true) {
  71. self?.touchOrFaceIDQuery()
  72. }
  73. }
  74. }
  75. @objc private func appInForeground(notification:Notification) {
  76. if let navigationController = UIApplication.shared.delegate?.window??.rootViewController?.presentedViewController as? UINavigationController, navigationController.topViewController is PAPasscodeViewController, touchIDEnabled {
  77. touchOrFaceIDQuery()
  78. }
  79. }
  80. private func touchOrFaceIDQuery() {
  81. if (avoidPromptingTouchOrFaceID || UIApplication.shared.applicationState != .active) {
  82. return
  83. }
  84. let laContext = LAContext()
  85. if laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil){
  86. avoidPromptingTouchOrFaceID = true
  87. laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics,
  88. localizedReason: NSLocalizedString("BIOMETRIC_UNLOCK", comment: ""),
  89. reply: { [weak self ] success, _ in
  90. DispatchQueue.main.async {
  91. if success {
  92. UIApplication.shared.delegate?.window??.rootViewController?.dismiss(animated: true, completion: {
  93. self?.completion?()
  94. self?.completion = nil
  95. self?.avoidPromptingTouchOrFaceID = false
  96. })
  97. } else {
  98. //user hit cancel and wants to enter the passcode
  99. self?.avoidPromptingTouchOrFaceID = true
  100. }
  101. }
  102. })
  103. }
  104. }
  105. private func passcodeFromKeychain() -> String {
  106. if let item = try? XKKeychainGenericPasswordItem(forService: KeychainCoordinator.passcodeService, account: KeychainCoordinator.passcodeService) {
  107. return item.secret.stringValue
  108. }
  109. assert(false, "Couldn't retrieve item from Keychain! If passcodeLockEnabled we should have an item and secret")
  110. return ""
  111. }
  112. //MARK: PAPassCodeDelegate
  113. func paPasscodeViewControllerDidEnterPasscode(_ controller: PAPasscodeViewController!) {
  114. avoidPromptingTouchOrFaceID = false
  115. UIApplication.shared.delegate?.window??.rootViewController?.dismiss(animated: true, completion: {
  116. self.completion?()
  117. self.completion = nil
  118. })
  119. }
  120. }