From a3f1c1627e1597d3f9734d2a8920e04a4337e90a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Mon, 19 Feb 2024 21:26:58 +0100 Subject: [PATCH 1/6] Prevent the user from changing the device time to bypass the lock prevention of the app. --- ownCloud.xcodeproj/project.pbxproj | 4 +++ ownCloud/PrivacyInfo.xcprivacy | 36 +++++++++++++++++++ .../AppLock/AppLockManager.swift | 26 +++++++++++++- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 ownCloud/PrivacyInfo.xcprivacy diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 8bacbe3de..5de5df7b8 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -67,6 +67,7 @@ 3968C883239C54AD00AC28AC /* ReleaseNotes.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3968C87B239C54AC00AC28AC /* ReleaseNotes.plist */; }; 396C82FB2319AFDD00938262 /* CollaborateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396C82FA2319AFDD00938262 /* CollaborateAction.swift */; }; 396D7C6523224A53002380C1 /* DiscardSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7C5F23224A53002380C1 /* DiscardSceneAction.swift */; }; + 396DAD882B83F00900957C58 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */; }; 397754F82327A33500119FCB /* OpenSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397754F22327A33500119FCB /* OpenSceneAction.swift */; }; 39878B7421FB1DE800DBF693 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */; }; 399697F5260255B100E5AEBA /* PDFGotoPageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399697F1260255B100E5AEBA /* PDFGotoPageAction.swift */; }; @@ -1069,6 +1070,7 @@ 396BE4C92289500E00B254A9 /* RoundedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabel.swift; sourceTree = ""; }; 396C82FA2319AFDD00938262 /* CollaborateAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollaborateAction.swift; sourceTree = ""; }; 396D7C5F23224A53002380C1 /* DiscardSceneAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardSceneAction.swift; sourceTree = ""; }; + 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 397754E123279EED00119FCB /* OCItem+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OCItem+Extension.swift"; sourceTree = ""; }; 397754F22327A33500119FCB /* OpenSceneAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSceneAction.swift; sourceTree = ""; }; 397E276B23D05A5400117B07 /* ServerListToolCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerListToolCell.swift; sourceTree = ""; }; @@ -1893,6 +1895,7 @@ DC27A19B20CAB5D7008ACB6C /* FileProvider Integration */, DC85573120513C7500189B9A /* Resources */, DC0B37952051541C00189B9A /* ownCloud.entitlements */, + 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */, ); path = ownCloud; sourceTree = ""; @@ -4249,6 +4252,7 @@ 39DF77D524EA854C0066E8F0 /* LaunchScreen.storyboard in Resources */, 3968C883239C54AD00AC28AC /* ReleaseNotes.plist in Resources */, 59D4895220C83F2E00369C2E /* InfoPlist.strings in Resources */, + 396DAD882B83F00900957C58 /* PrivacyInfo.xcprivacy in Resources */, 593A821120C7D4C5000E2A90 /* Localizable.strings in Resources */, DCE684F6241BD4E800799C30 /* Branding.plist in Resources */, DC9BFBB320A19AF4007064B5 /* doc in Resources */, diff --git a/ownCloud/PrivacyInfo.xcprivacy b/ownCloud/PrivacyInfo.xcprivacy new file mode 100644 index 000000000..5735273a2 --- /dev/null +++ b/ownCloud/PrivacyInfo.xcprivacy @@ -0,0 +1,36 @@ + + + + + NSPrivacyCollectedDataTypes + + + NSPrivacyCollectedDataType + + NSPrivacyCollectedDataTypeLinked + + NSPrivacyCollectedDataTypeTracking + + NSPrivacyCollectedDataTypePurposes + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPITypeReasons + + To prevent the user from changing the device time to bypass the lock prevention of the app. + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + + + NSPrivacyTrackingDomains + + NSPrivacyTracking + + + diff --git a/ownCloudAppShared/AppLock/AppLockManager.swift b/ownCloudAppShared/AppLock/AppLockManager.swift index 3031e6d28..7e10dfec9 100644 --- a/ownCloudAppShared/AppLock/AppLockManager.swift +++ b/ownCloudAppShared/AppLock/AppLockManager.swift @@ -96,6 +96,14 @@ public class AppLockManager: NSObject { self.userDefaults.set(newValue, forKey: "applock-locked-until-date") } } + private var lockSetTime: TimeInterval? { + get { + return userDefaults.object(forKey: "applock-lock-set-time") as? TimeInterval + } + set(newValue) { + self.userDefaults.set(newValue, forKey: "applock-lock-set-time") + } + } private var biometricalAuthenticationSucceeded: Bool { get { return userDefaults.bool(forKey: "applock-biometrical-authentication-succeeded") @@ -373,6 +381,7 @@ public class AppLockManager: NSObject { let delayUntilNextAttempt = pow(powBaseDelay, Double(failedPasscodeAttempts)) lockedUntilDate = Date().addingTimeInterval(delayUntilNextAttempt) + lockSetTime = ProcessInfo.processInfo.systemUptime startLockCountdown() } @@ -381,6 +390,21 @@ public class AppLockManager: NSObject { } // MARK: - Status + + private var isLockBypassed: Bool { + if let lockedUntilDate = lockedUntilDate, let lockSetTime = lockSetTime { + let currentTime = ProcessInfo.processInfo.systemUptime + let lockDuration = currentTime - lockSetTime + let timeRemaining = lockedUntilDate.timeIntervalSinceNow + + if lockDuration < timeRemaining { + // User has attempted to bypass the lock + return true + } + } + return false + } + private var shouldDisplayLockscreen: Bool { if !AppLockSettings.shared.lockEnabled { return false @@ -417,7 +441,7 @@ public class AppLockManager: NSObject { } private var shouldDisplayCountdown : Bool { - if let startLockBeforeDate = self.lockedUntilDate { + if let startLockBeforeDate = self.lockedUntilDate, isLockBypassed { return startLockBeforeDate > Date() } From 5bf4d2abee2edf67f8307722ebfc70d8fd8a9c2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 20 Feb 2024 08:14:52 +0100 Subject: [PATCH 2/6] changed privacy reason and removed unneeded key/value pairs --- ownCloud/PrivacyInfo.xcprivacy | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/ownCloud/PrivacyInfo.xcprivacy b/ownCloud/PrivacyInfo.xcprivacy index 5735273a2..c43da1fbc 100644 --- a/ownCloud/PrivacyInfo.xcprivacy +++ b/ownCloud/PrivacyInfo.xcprivacy @@ -2,35 +2,16 @@ - NSPrivacyCollectedDataTypes - - - NSPrivacyCollectedDataType - - NSPrivacyCollectedDataTypeLinked - - NSPrivacyCollectedDataTypeTracking - - NSPrivacyCollectedDataTypePurposes - - - - - NSPrivacyAccessedAPITypes NSPrivacyAccessedAPITypeReasons - To prevent the user from changing the device time to bypass the lock prevention of the app. + 35F9.1 NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategorySystemBootTime - NSPrivacyTrackingDomains - - NSPrivacyTracking - From 4e37782e6afc88fbed31bff1d63fa3bf53af73c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 20 Feb 2024 08:16:31 +0100 Subject: [PATCH 3/6] maybe keys are needed --- ownCloud/PrivacyInfo.xcprivacy | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ownCloud/PrivacyInfo.xcprivacy b/ownCloud/PrivacyInfo.xcprivacy index c43da1fbc..78df728b0 100644 --- a/ownCloud/PrivacyInfo.xcprivacy +++ b/ownCloud/PrivacyInfo.xcprivacy @@ -2,6 +2,12 @@ + NSPrivacyTracking + + NSPrivacyCollectedDataTypes + + NSPrivacyTrackingDomains + NSPrivacyAccessedAPITypes From 853e803065941a18a66f9ed46a2304024459be8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 5 Mar 2024 11:13:02 +0100 Subject: [PATCH 4/6] fixed code review findings --- .../AppLock/AppLockManager.swift | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/ownCloudAppShared/AppLock/AppLockManager.swift b/ownCloudAppShared/AppLock/AppLockManager.swift index 7e10dfec9..d9e77a991 100644 --- a/ownCloudAppShared/AppLock/AppLockManager.swift +++ b/ownCloudAppShared/AppLock/AppLockManager.swift @@ -369,6 +369,7 @@ public class AppLockManager: NSObject { if testPasscode == self.passcode { unlocked = true failedPasscodeAttempts = 0 + lockSetTime = nil dismissLockscreen(animated: true) } else { unlocked = false @@ -392,18 +393,18 @@ public class AppLockManager: NSObject { // MARK: - Status private var isLockBypassed: Bool { - if let lockedUntilDate = lockedUntilDate, let lockSetTime = lockSetTime { - let currentTime = ProcessInfo.processInfo.systemUptime - let lockDuration = currentTime - lockSetTime - let timeRemaining = lockedUntilDate.timeIntervalSinceNow - - if lockDuration < timeRemaining { - // User has attempted to bypass the lock - return true - } - } - return false - } + if let lockedUntilDate = lockedUntilDate, let lockSetTime = lockSetTime { + let currentTime = ProcessInfo.processInfo.systemUptime + let lockDuration = currentTime - lockSetTime + + let wiggleRoom: Double = 0.05 + + if abs(lockDuration - lockSetTime) > wiggleRoom { + return true + } + } + return false + } private var shouldDisplayLockscreen: Bool { if !AppLockSettings.shared.lockEnabled { @@ -441,8 +442,10 @@ public class AppLockManager: NSObject { } private var shouldDisplayCountdown : Bool { - if let startLockBeforeDate = self.lockedUntilDate, isLockBypassed { + if let startLockBeforeDate = self.lockedUntilDate { return startLockBeforeDate > Date() + } else if isLockBypassed { + return true } return false From b21c0b68d68ef8dd0b0fa9430ca168dfcef5326d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Fri, 15 Mar 2024 15:20:52 +0100 Subject: [PATCH 5/6] Converted lockUntilDate to lockDelay. This is a better prevention if user changes the system time to bypass the passcode lock. ProcessInfo.processInfo.systemUptime does not work, because system does not restart after setting the system date/time. lockUntilDate is also stored in user defaults than lockedUntilDate before. --- ownCloud.xcodeproj/project.pbxproj | 4 -- ownCloud/PrivacyInfo.xcprivacy | 23 ------ .../AppLock/AppLockManager.swift | 70 +++++++------------ 3 files changed, 26 insertions(+), 71 deletions(-) delete mode 100644 ownCloud/PrivacyInfo.xcprivacy diff --git a/ownCloud.xcodeproj/project.pbxproj b/ownCloud.xcodeproj/project.pbxproj index 5de5df7b8..8bacbe3de 100644 --- a/ownCloud.xcodeproj/project.pbxproj +++ b/ownCloud.xcodeproj/project.pbxproj @@ -67,7 +67,6 @@ 3968C883239C54AD00AC28AC /* ReleaseNotes.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3968C87B239C54AC00AC28AC /* ReleaseNotes.plist */; }; 396C82FB2319AFDD00938262 /* CollaborateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396C82FA2319AFDD00938262 /* CollaborateAction.swift */; }; 396D7C6523224A53002380C1 /* DiscardSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 396D7C5F23224A53002380C1 /* DiscardSceneAction.swift */; }; - 396DAD882B83F00900957C58 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */; }; 397754F82327A33500119FCB /* OpenSceneAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 397754F22327A33500119FCB /* OpenSceneAction.swift */; }; 39878B7421FB1DE800DBF693 /* UINavigationController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39878B7321FB1DE800DBF693 /* UINavigationController+Extension.swift */; }; 399697F5260255B100E5AEBA /* PDFGotoPageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399697F1260255B100E5AEBA /* PDFGotoPageAction.swift */; }; @@ -1070,7 +1069,6 @@ 396BE4C92289500E00B254A9 /* RoundedLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoundedLabel.swift; sourceTree = ""; }; 396C82FA2319AFDD00938262 /* CollaborateAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollaborateAction.swift; sourceTree = ""; }; 396D7C5F23224A53002380C1 /* DiscardSceneAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DiscardSceneAction.swift; sourceTree = ""; }; - 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 397754E123279EED00119FCB /* OCItem+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OCItem+Extension.swift"; sourceTree = ""; }; 397754F22327A33500119FCB /* OpenSceneAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenSceneAction.swift; sourceTree = ""; }; 397E276B23D05A5400117B07 /* ServerListToolCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServerListToolCell.swift; sourceTree = ""; }; @@ -1895,7 +1893,6 @@ DC27A19B20CAB5D7008ACB6C /* FileProvider Integration */, DC85573120513C7500189B9A /* Resources */, DC0B37952051541C00189B9A /* ownCloud.entitlements */, - 396DAD872B83F00900957C58 /* PrivacyInfo.xcprivacy */, ); path = ownCloud; sourceTree = ""; @@ -4252,7 +4249,6 @@ 39DF77D524EA854C0066E8F0 /* LaunchScreen.storyboard in Resources */, 3968C883239C54AD00AC28AC /* ReleaseNotes.plist in Resources */, 59D4895220C83F2E00369C2E /* InfoPlist.strings in Resources */, - 396DAD882B83F00900957C58 /* PrivacyInfo.xcprivacy in Resources */, 593A821120C7D4C5000E2A90 /* Localizable.strings in Resources */, DCE684F6241BD4E800799C30 /* Branding.plist in Resources */, DC9BFBB320A19AF4007064B5 /* doc in Resources */, diff --git a/ownCloud/PrivacyInfo.xcprivacy b/ownCloud/PrivacyInfo.xcprivacy deleted file mode 100644 index 78df728b0..000000000 --- a/ownCloud/PrivacyInfo.xcprivacy +++ /dev/null @@ -1,23 +0,0 @@ - - - - - NSPrivacyTracking - - NSPrivacyCollectedDataTypes - - NSPrivacyTrackingDomains - - NSPrivacyAccessedAPITypes - - - NSPrivacyAccessedAPITypeReasons - - 35F9.1 - - NSPrivacyAccessedAPIType - NSPrivacyAccessedAPICategorySystemBootTime - - - - diff --git a/ownCloudAppShared/AppLock/AppLockManager.swift b/ownCloudAppShared/AppLock/AppLockManager.swift index d9e77a991..aed3cfd88 100644 --- a/ownCloudAppShared/AppLock/AppLockManager.swift +++ b/ownCloudAppShared/AppLock/AppLockManager.swift @@ -88,22 +88,16 @@ public class AppLockManager: NSObject { self.userDefaults.set(newValue, forKey: "applock-failed-passcode-attempts") } } - private var lockedUntilDate: Date? { - get { - return userDefaults.object(forKey: "applock-locked-until-date") as? Date - } - set(newValue) { - self.userDefaults.set(newValue, forKey: "applock-locked-until-date") - } - } - private var lockSetTime: TimeInterval? { + + private var lockDelay: Double? { get { - return userDefaults.object(forKey: "applock-lock-set-time") as? TimeInterval + return userDefaults.object(forKey: "applock-delay") as? Double } set(newValue) { - self.userDefaults.set(newValue, forKey: "applock-lock-set-time") + self.userDefaults.set(newValue, forKey: "applock-delay") } } + private var biometricalAuthenticationSucceeded: Bool { get { return userDefaults.bool(forKey: "applock-biometrical-authentication-succeeded") @@ -369,7 +363,8 @@ public class AppLockManager: NSObject { if testPasscode == self.passcode { unlocked = true failedPasscodeAttempts = 0 - lockSetTime = nil + lockDelay = nil + dismissLockscreen(animated: true) } else { unlocked = false @@ -380,9 +375,7 @@ public class AppLockManager: NSObject { if self.failedPasscodeAttempts >= self.maximumPasscodeAttempts { let delayUntilNextAttempt = pow(powBaseDelay, Double(failedPasscodeAttempts)) - - lockedUntilDate = Date().addingTimeInterval(delayUntilNextAttempt) - lockSetTime = ProcessInfo.processInfo.systemUptime + lockDelay = delayUntilNextAttempt startLockCountdown() } @@ -392,20 +385,6 @@ public class AppLockManager: NSObject { // MARK: - Status - private var isLockBypassed: Bool { - if let lockedUntilDate = lockedUntilDate, let lockSetTime = lockSetTime { - let currentTime = ProcessInfo.processInfo.systemUptime - let lockDuration = currentTime - lockSetTime - - let wiggleRoom: Double = 0.05 - - if abs(lockDuration - lockSetTime) > wiggleRoom { - return true - } - } - return false - } - private var shouldDisplayLockscreen: Bool { if !AppLockSettings.shared.lockEnabled { return false @@ -442,9 +421,7 @@ public class AppLockManager: NSObject { } private var shouldDisplayCountdown : Bool { - if let startLockBeforeDate = self.lockedUntilDate { - return startLockBeforeDate > Date() - } else if isLockBypassed { + if let lockDelay = self.lockDelay, lockDelay > 0 { return true } @@ -459,32 +436,37 @@ public class AppLockManager: NSObject { passcodeViewController.view.setNeedsLayout() } updateLockCountdown() - - lockTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateLockCountdown), userInfo: nil, repeats: true) + + if lockTimer == nil { + lockTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.updateLockCountdown), userInfo: nil, repeats: true) + } } } @objc private func updateLockCountdown() { - if let date = self.lockedUntilDate { - let interval = Int(date.timeIntervalSinceNow) - let seconds = interval % 60 - let minutes = (interval / 60) % 60 - let hours = (interval / 3600) - + if let lockDelay = self.lockDelay { + let hours = Int(lockDelay) / 3600 + let minutes = Int(lockDelay) / 60 % 60 + let seconds = Int(lockDelay) % 60 + + if lockDelay > 0 { + self.lockDelay = (lockDelay - 1) + } + let dateFormatted:String? if hours > 0 { dateFormatted = String(format: "%02d:%02d:%02d", hours, minutes, seconds) } else { dateFormatted = String(format: "%02d:%02d", minutes, seconds) } - + let timeoutMessage:String = NSString(format: "Please try again in %@".localized as NSString, dateFormatted!) as String - + performPasscodeViewControllerUpdates { (passcodeViewController) in passcodeViewController.timeoutMessage = timeoutMessage } - - if date <= Date() { + + if lockDelay <= 0 { // Time elapsed, allow entering passcode again self.lockTimer?.invalidate() performPasscodeViewControllerUpdates { (passcodeViewController) in From 5840f4df161838bc4169ce50bf6ed79bd40f2589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Hu=CC=88hne?= Date: Tue, 16 Apr 2024 10:43:11 +0200 Subject: [PATCH 6/6] converted code to significant time change notification --- ownCloud/AppDelegate.swift | 4 + .../AppLock/AppLockManager.swift | 80 +++++++++++-------- 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/ownCloud/AppDelegate.swift b/ownCloud/AppDelegate.swift index 477b64a1b..77d76e13e 100644 --- a/ownCloud/AppDelegate.swift +++ b/ownCloud/AppDelegate.swift @@ -135,6 +135,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + + func applicationSignificantTimeChange(_ application: UIApplication) { + AppLockManager.shared.significantTimeChangeOccurred() + } func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { if !OCAuthenticationBrowserSessionCustomScheme.handleOpen(url), // No custom scheme URL handling for this URL diff --git a/ownCloudAppShared/AppLock/AppLockManager.swift b/ownCloudAppShared/AppLock/AppLockManager.swift index aed3cfd88..912b9f3e8 100644 --- a/ownCloudAppShared/AppLock/AppLockManager.swift +++ b/ownCloudAppShared/AppLock/AppLockManager.swift @@ -88,16 +88,22 @@ public class AppLockManager: NSObject { self.userDefaults.set(newValue, forKey: "applock-failed-passcode-attempts") } } - + private var lockedUntilDate: Date? { + get { + return userDefaults.object(forKey: "applock-locked-until-date") as? Date + } + set(newValue) { + self.userDefaults.set(newValue, forKey: "applock-locked-until-date") + } + } private var lockDelay: Double? { get { - return userDefaults.object(forKey: "applock-delay") as? Double + return userDefaults.object(forKey: "applock-lock-delay") as? Double } set(newValue) { - self.userDefaults.set(newValue, forKey: "applock-delay") + self.userDefaults.set(newValue, forKey: "applock-lock-delay") } } - private var biometricalAuthenticationSucceeded: Bool { get { return userDefaults.bool(forKey: "applock-biometrical-authentication-succeeded") @@ -153,11 +159,13 @@ public class AppLockManager: NSObject { userDefaults = OCAppIdentity.shared.userDefaults! super.init() + significantTimeChangeOccurred() if AppLockManager.supportedOnDevice { NotificationCenter.default.addObserver(self, selector: #selector(self.appDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.appWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(self.updateLockscreens), name: ThemeWindow.themeWindowListChangedNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(significantTimeChangeOccurred), name: UIApplication.significantTimeChangeNotification, object: nil) } } @@ -166,6 +174,7 @@ public class AppLockManager: NSObject { NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) NotificationCenter.default.removeObserver(self, name: ThemeWindow.themeWindowListChangedNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIApplication.significantTimeChangeNotification, object: nil) } } @@ -175,19 +184,19 @@ public class AppLockManager: NSObject { lockscreenOpenForced = forceShow lockscreenOpen = true - // The following code needs to be executed after a short delay, because in the share sheet the biometrical unlock UI can block adding the PasscodeViewController UI - var delay = 0.0 - if self.passwordViewHostViewController != nil { - delay = 0.5 - } - OnMainThread(after: delay) { - // Show biometrical - if !forceShow, !self.shouldDisplayCountdown, self.biometricalAuthenticationSucceeded { - self.showBiometricalAuthenticationInterface(context: context) - } else if setupMode { - self.showBiometricalAuthenticationInterface(context: context) - } - } + // The following code needs to be executed after a short delay, because in the share sheet the biometrical unlock UI can block adding the PasscodeViewController UI + var delay = 0.0 + if self.passwordViewHostViewController != nil { + delay = 0.5 + } + OnMainThread(after: delay) { + // Show biometrical + if !forceShow, !self.shouldDisplayCountdown, self.biometricalAuthenticationSucceeded { + self.showBiometricalAuthenticationInterface(context: context) + } else if setupMode { + self.showBiometricalAuthenticationInterface(context: context) + } + } } else { dismissLockscreen(animated: true) } @@ -357,14 +366,20 @@ public class AppLockManager: NSObject { dismissLockscreen(animated: false) } } + + @objc public func significantTimeChangeOccurred() { + if let lockDelay = lockDelay { + lockedUntilDate = Date().addingTimeInterval(lockDelay) + } + } // MARK: - Unlock func attemptUnlock(with testPasscode: String?, customErrorMessage: String? = nil, passcodeViewController: PasscodeViewController? = nil) { if testPasscode == self.passcode { unlocked = true failedPasscodeAttempts = 0 + lockedUntilDate = nil lockDelay = nil - dismissLockscreen(animated: true) } else { unlocked = false @@ -375,6 +390,8 @@ public class AppLockManager: NSObject { if self.failedPasscodeAttempts >= self.maximumPasscodeAttempts { let delayUntilNextAttempt = pow(powBaseDelay, Double(failedPasscodeAttempts)) + + lockedUntilDate = Date().addingTimeInterval(delayUntilNextAttempt) lockDelay = delayUntilNextAttempt startLockCountdown() } @@ -421,8 +438,8 @@ public class AppLockManager: NSObject { } private var shouldDisplayCountdown : Bool { - if let lockDelay = self.lockDelay, lockDelay > 0 { - return true + if let startLockBeforeDate = self.lockedUntilDate { + return startLockBeforeDate > Date() } return false @@ -444,29 +461,26 @@ public class AppLockManager: NSObject { } @objc private func updateLockCountdown() { - if let lockDelay = self.lockDelay { - let hours = Int(lockDelay) / 3600 - let minutes = Int(lockDelay) / 60 % 60 - let seconds = Int(lockDelay) % 60 - - if lockDelay > 0 { - self.lockDelay = (lockDelay - 1) - } - + if let date = self.lockedUntilDate { + let interval = Int(date.timeIntervalSinceNow) + let seconds = interval % 60 + let minutes = (interval / 60) % 60 + let hours = (interval / 3600) + let dateFormatted:String? if hours > 0 { dateFormatted = String(format: "%02d:%02d:%02d", hours, minutes, seconds) } else { dateFormatted = String(format: "%02d:%02d", minutes, seconds) } - + let timeoutMessage:String = NSString(format: "Please try again in %@".localized as NSString, dateFormatted!) as String - + performPasscodeViewControllerUpdates { (passcodeViewController) in passcodeViewController.timeoutMessage = timeoutMessage } - - if lockDelay <= 0 { + + if date <= Date() { // Time elapsed, allow entering passcode again self.lockTimer?.invalidate() performPasscodeViewControllerUpdates { (passcodeViewController) in