diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 88a47fb3a..f41232d1b 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -719,6 +719,7 @@ EC1CA87224C823E700DE9EBF /* KeyValueStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87124C823E700DE9EBF /* KeyValueStore.swift */; }; EC1CA87524C8259700DE9EBF /* KeychainStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87424C8259700DE9EBF /* KeychainStore.swift */; }; EC1CA87724C82D0E00DE9EBF /* MemoryStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87624C82D0E00DE9EBF /* MemoryStore.swift */; }; + EC1CA87A24C8841C00DE9EBF /* LocalAuthenticationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1CA87924C8841C00DE9EBF /* LocalAuthenticationService.swift */; }; EC2B4EF124A1EEBD005EB739 /* DataProtectionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */; }; EC2B4EF224A1EF34005EB739 /* DataProtectionHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */; }; EC3B066924AC6ADE000DF9BF /* CrossSigningService.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3B066424AC6ADD000DF9BF /* CrossSigningService.swift */; }; @@ -1817,6 +1818,7 @@ EC1CA87124C823E700DE9EBF /* KeyValueStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyValueStore.swift; sourceTree = ""; }; EC1CA87424C8259700DE9EBF /* KeychainStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainStore.swift; sourceTree = ""; }; EC1CA87624C82D0E00DE9EBF /* MemoryStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryStore.swift; sourceTree = ""; }; + EC1CA87924C8841C00DE9EBF /* LocalAuthenticationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationService.swift; sourceTree = ""; }; EC2B4EF024A1EEBD005EB739 /* DataProtectionHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataProtectionHelper.swift; sourceTree = ""; }; EC3B066424AC6ADD000DF9BF /* CrossSigningService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CrossSigningService.swift; sourceTree = ""; }; EC3B066624AC6ADD000DF9BF /* CrossSigningSetupBannerCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CrossSigningSetupBannerCell.xib; sourceTree = ""; }; @@ -4143,6 +4145,7 @@ B1B5597C20EFC3DF00210D55 /* Managers */ = { isa = PBXGroup; children = ( + EC1CA87824C8840B00DE9EBF /* LocalAuthentication */, EC1CA87324C8257F00DE9EBF /* KeyValueStorage */, B185145324B7CF9500EE19EA /* AppVersion */, EC85D73B2477DDD0002C44C9 /* PushNotification */, @@ -4501,6 +4504,14 @@ path = KeyValueStorage; sourceTree = ""; }; + EC1CA87824C8840B00DE9EBF /* LocalAuthentication */ = { + isa = PBXGroup; + children = ( + EC1CA87924C8841C00DE9EBF /* LocalAuthenticationService.swift */, + ); + path = LocalAuthentication; + sourceTree = ""; + }; EC3B066324AC6ADD000DF9BF /* CrossSigning */ = { isa = PBXGroup; children = ( @@ -6071,6 +6082,7 @@ B1963B3822933BC800CBA17F /* AutosizedCollectionView.swift in Sources */, EC711B7A24A63B37008F830C /* SecretsSetupRecoveryKeyViewAction.swift in Sources */, B12D79FB23E2462200FACEDC /* UserVerificationStartCoordinator.swift in Sources */, + EC1CA87A24C8841C00DE9EBF /* LocalAuthenticationService.swift in Sources */, EC3B066B24AC6ADE000DF9BF /* CrossSigningBannerPreferences.swift in Sources */, ECB101322477CFDB00CF8C11 /* UIDevice.swift in Sources */, B169330320F3C98900746532 /* RoomBubbleCellData.m in Sources */, diff --git a/Riot/AppDelegate.m b/Riot/AppDelegate.m index 16bdfde54..aa663978b 100644 --- a/Riot/AppDelegate.m +++ b/Riot/AppDelegate.m @@ -210,8 +210,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni */ UIView *launchAnimationContainerView; NSDate *launchAnimationStart; - - NSDate *appLastActiveDate; } @property (strong, nonatomic) UIAlertController *mxInAppNotification; @@ -239,6 +237,7 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni Related push notification service instance. Will be created when launch finished. */ @property (nonatomic, strong) PushNotificationService *pushNotificationService; +@property (nonatomic, strong) LocalAuthenticationService *localAuthenticationService; @property (nonatomic, strong) MajorUpdateManager *majorUpdateManager; @@ -520,6 +519,8 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni self.pushNotificationService = [PushNotificationService new]; self.pushNotificationService.delegate = self; + + self.localAuthenticationService = [[LocalAuthenticationService alloc] initWithPinCodePreferences:[PinCodePreferences shared]]; // Add matrix observers, and initialize matrix sessions if the app is not launched in background. [self initMatrixSessions]; @@ -583,9 +584,6 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni [wrongBackupVersionAlert dismissViewControllerAnimated:NO completion:nil]; wrongBackupVersionAlert = nil; } - - // store last active date - appLastActiveDate = [NSDate new]; } - (void)applicationDidEnterBackground:(UIApplication *)application @@ -657,20 +655,16 @@ NSString *const AppDelegateUniversalLinkDidChangeNotification = @"AppDelegateUni { NSLog(@"[AppDelegate] applicationDidBecomeActive"); - if ([PinCodePreferences shared].isPinSet && [MXKAccountManager sharedManager].activeAccounts.count > 0) + if ([self.localAuthenticationService shouldShowPinCode]) { - // check should show the pinpad - if (appLastActiveDate == nil || [[NSDate new] timeIntervalSinceDate:appLastActiveDate] > [PinCodePreferences shared].graceTimeInSeconds) + if (self.setPinCoordinatorBridgePresenter) { - if (self.setPinCoordinatorBridgePresenter) - { - // it's already on screen - return; - } - self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:mxSessionArray.firstObject viewMode:SetPinCoordinatorViewModeUnlockByPin]; - self.setPinCoordinatorBridgePresenter.delegate = self; - [self.setPinCoordinatorBridgePresenter presentIn:self.window]; + // it's already on screen + return; } + self.setPinCoordinatorBridgePresenter = [[SetPinCoordinatorBridgePresenter alloc] initWithSession:mxSessionArray.firstObject viewMode:SetPinCoordinatorViewModeUnlockByPin]; + self.setPinCoordinatorBridgePresenter.delegate = self; + [self.setPinCoordinatorBridgePresenter presentIn:self.window]; } } diff --git a/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift new file mode 100644 index 000000000..b7f6dd6d6 --- /dev/null +++ b/Riot/Managers/LocalAuthentication/LocalAuthenticationService.swift @@ -0,0 +1,63 @@ +// +// Copyright 2020 Vector Creations Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + +@objcMembers +class LocalAuthenticationService: NSObject { + + private var pinCodePreferences: PinCodePreferences + + init(pinCodePreferences: PinCodePreferences) { + self.pinCodePreferences = pinCodePreferences + super.init() + + setup() + } + + private var appLastActiveTime: TimeInterval? + + private var systemUptime: TimeInterval { + var uptime = timespec() + if 0 != clock_gettime(CLOCK_MONOTONIC_RAW, &uptime) { + fatalError("Could not execute clock_gettime, errno: \(errno)") + } + + return TimeInterval(uptime.tv_sec) + } + + private func setup() { + NotificationCenter.default.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) + } + + var shouldShowPinCode: Bool { + if !pinCodePreferences.isPinSet { + return false + } + if MXKAccountManager.shared()?.activeAccounts.count == 0 { + return false + } + guard let appLastActiveTime = appLastActiveTime else { + return true + } + return (systemUptime - appLastActiveTime) > pinCodePreferences.graceTimeInSeconds + } + + func applicationWillResignActive() { + appLastActiveTime = systemUptime + } + +} diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinator.swift b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinator.swift index 5a64606c4..c14bd2674 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinator.swift +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeCoordinator.swift @@ -41,7 +41,7 @@ final class EnterPinCodeCoordinator: EnterPinCodeCoordinatorType { init(session: MXSession?, viewMode: SetPinCoordinatorViewMode) { self.session = session - let enterPinCodeViewModel = EnterPinCodeViewModel(session: self.session, viewMode: viewMode) + let enterPinCodeViewModel = EnterPinCodeViewModel(session: self.session, viewMode: viewMode, pinCodePreferences: .shared) let enterPinCodeViewController = EnterPinCodeViewController.instantiate(with: enterPinCodeViewModel) self.enterPinCodeViewModel = enterPinCodeViewModel self.enterPinCodeViewController = enterPinCodeViewController diff --git a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift index 371b082ec..07c4ec9e5 100644 --- a/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift +++ b/Riot/Modules/SetPinCode/EnterPinCode/EnterPinCodeViewModel.swift @@ -39,12 +39,14 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType { weak var viewDelegate: EnterPinCodeViewModelViewDelegate? weak var coordinatorDelegate: EnterPinCodeViewModelCoordinatorDelegate? + var pinCodePreferences: PinCodePreferences // MARK: - Setup - init(session: MXSession?, viewMode: SetPinCoordinatorViewMode) { + init(session: MXSession?, viewMode: SetPinCoordinatorViewMode, pinCodePreferences: PinCodePreferences) { self.session = session self.viewMode = viewMode + self.pinCodePreferences = pinCodePreferences } // MARK: - Public @@ -84,7 +86,7 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType { // a digit tapped currentPin += String(tag) - if currentPin.count == PinCodePreferences.shared.numberOfDigits { + if currentPin.count == pinCodePreferences.numberOfDigits { switch viewMode { case .setPin: // choosing pin @@ -106,10 +108,10 @@ final class EnterPinCodeViewModel: EnterPinCodeViewModelType { } case .unlockByPin, .confirmPinToDeactivate: // unlocking - if currentPin != PinCodePreferences.shared.pin { + if currentPin != pinCodePreferences.pin { // no match numberOfFailuresDuringEnterPIN += 1 - if numberOfFailuresDuringEnterPIN < PinCodePreferences.shared.allowedNumberOfTrialsBeforeAlert { + if numberOfFailuresDuringEnterPIN < pinCodePreferences.allowedNumberOfTrialsBeforeAlert { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.viewDelegate?.enterPinCodeViewModel(self, didUpdateViewState: .wrongPin) self.currentPin.removeAll() diff --git a/Riot/Modules/SetPinCode/PinCodePreferences.swift b/Riot/Modules/SetPinCode/PinCodePreferences.swift index a656cf7f9..dbf961918 100644 --- a/Riot/Modules/SetPinCode/PinCodePreferences.swift +++ b/Riot/Modules/SetPinCode/PinCodePreferences.swift @@ -39,6 +39,7 @@ final class PinCodePreferences: NSObject { override init() { store = KeychainStore(withKeychain: Keychain(service: PinConstants.pinCodeKeychainService, accessGroup: Constants.keychainAccessGroup)) + super.init() } // MARK: - Public