diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2023-04-19 14:27:22 +0200 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2023-04-19 14:27:22 +0200 |
| commit | d39fb86ce5c619ce62f5d4fb8d0a7d18f9ddd452 (patch) | |
| tree | 0c0c586f114c32ae5dcb0a7f4d67ca2ffd5e3ecc | |
| parent | 600223782395a7e1d47b7688d62bff92f2684700 (diff) | |
| parent | 5dc174e47f1b623070042b69c1a116dd019f11de (diff) | |
| download | mullvadvpn-d39fb86ce5c619ce62f5d4fb8d0a7d18f9ddd452.tar.xz mullvadvpn-d39fb86ce5c619ce62f5d4fb8d0a7d18f9ddd452.zip | |
Merge branch 'dont-keep-account-number-and-other-state-ios-72'
| -rw-r--r-- | ios/CHANGELOG.md | 1 | ||||
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 7 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppDelegate.swift | 35 | ||||
| -rw-r--r-- | ios/MullvadVPN/Classes/FirstTimeLaunch.swift | 21 | ||||
| -rw-r--r-- | ios/MullvadVPN/SettingsManager/SettingsManager.swift | 84 |
5 files changed, 123 insertions, 25 deletions
diff --git a/ios/CHANGELOG.md b/ios/CHANGELOG.md index bcaecc10dc..582f230db5 100644 --- a/ios/CHANGELOG.md +++ b/ios/CHANGELOG.md @@ -25,6 +25,7 @@ Line wrap the file at 100 chars. Th ## [Unreleased] ### Added - Add search functionality to location selection view. +- Wipe all settings on app reinstall. ### Changed - Changed key rotation interval from 4 to 14 days. diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index ed8b453b53..83c283dd5c 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -361,6 +361,7 @@ 58FF2C03281BDE02009EF542 /* SettingsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FF2C02281BDE02009EF542 /* SettingsManager.swift */; }; 7A09C98129D99215000C2CAC /* String+FuzzyMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */; }; 7AD2DA1529DC4EB900250737 /* UISearchBar+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */; }; + 7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */; }; E1187ABC289BBB850024E748 /* OutOfTimeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */; }; E1187ABD289BBB850024E748 /* OutOfTimeContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */; }; E158B360285381C60002F069 /* String+AccountFormatting.swift in Sources */ = {isa = PBXBuildFile; fileRef = E158B35F285381C60002F069 /* String+AccountFormatting.swift */; }; @@ -945,6 +946,10 @@ 58FF2C02281BDE02009EF542 /* SettingsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsManager.swift; sourceTree = "<group>"; }; 7A09C98029D99215000C2CAC /* String+FuzzyMatch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+FuzzyMatch.swift"; sourceTree = "<group>"; }; 7AD2DA1429DC4EB900250737 /* UISearchBar+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UISearchBar+Appearance.swift"; sourceTree = "<group>"; }; + 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstTimeLaunch.swift; sourceTree = "<group>"; }; + 7AD8490C29BA1EC500878E53 /* SettingsCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCellFactory.swift; sourceTree = "<group>"; }; + 7AD8490E29BA26B000878E53 /* CellFactoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellFactoryProtocol.swift; sourceTree = "<group>"; }; + 7AD8491029BA316500878E53 /* PreferencesCellFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesCellFactory.swift; sourceTree = "<group>"; }; E1187ABA289BBB850024E748 /* OutOfTimeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeViewController.swift; sourceTree = "<group>"; }; E1187ABB289BBB850024E748 /* OutOfTimeContentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutOfTimeContentView.swift; sourceTree = "<group>"; }; E158B35F285381C60002F069 /* String+AccountFormatting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+AccountFormatting.swift"; sourceTree = "<group>"; }; @@ -1473,6 +1478,7 @@ 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */, 58138E60294871C600684F0C /* DeviceDataThrottling.swift */, 589A454B28DDF5E100565204 /* Swizzle.swift */, + 7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */, ); path = Classes; sourceTree = "<group>"; @@ -2601,6 +2607,7 @@ 587B753B2666467500DEF7E9 /* NotificationBannerView.swift in Sources */, 58B993B12608A34500BA7811 /* LoginContentView.swift in Sources */, 5878A27529093A310096FC88 /* StorePaymentEvent.swift in Sources */, + 7A7AD28D29DC677800480EF1 /* FirstTimeLaunch.swift in Sources */, 58B26E2A2943545A00D5980C /* NotificationManagerDelegate.swift in Sources */, 58A1AA8C23F5584C009F7EA6 /* ConnectionPanelView.swift in Sources */, 5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */, diff --git a/ios/MullvadVPN/AppDelegate.swift b/ios/MullvadVPN/AppDelegate.swift index 126109663f..d5a5d987d9 100644 --- a/ios/MullvadVPN/AppDelegate.swift +++ b/ios/MullvadVPN/AppDelegate.swift @@ -363,6 +363,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD } private func startInitialization(application: UIApplication) { + let wipeSettingsOperation = getWipeSettingsOperation() + let loadTunnelStoreOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in self.tunnelStore.loadPersistentTunnels { error in if let error = error { @@ -395,7 +397,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD migrationUIHandler.showMigrationError(error, completionHandler: finishHandler) } } - migrateSettingsOperation.addDependency(loadTunnelStoreOperation) + migrateSettingsOperation.addDependencies([wipeSettingsOperation, loadTunnelStoreOperation]) let initTunnelManagerOperation = AsyncBlockOperation(dispatchQueue: .main) { operation in self.tunnelManager.loadConfiguration { error in @@ -428,6 +430,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD operationQueue.addOperations( [ + wipeSettingsOperation, loadTunnelStoreOperation, migrateSettingsOperation, initTunnelManagerOperation, @@ -437,6 +440,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD ) } + /// Returns an operation that acts on two conditions: + /// 1. Has the app been launched at least once after install? (`FirstTimeLaunch.hasFinished`) + /// 2. Has the app - at some point in time - been updated from a version compatible with wiping settings? + /// (`SettingsManager.getShouldWipeSettings()`) + /// If (1) is `false` and (2) is `true`, we know that the app has been freshly installed/reinstalled and is + /// compatible, thus triggering a settings wipe. + private func getWipeSettingsOperation() -> AsyncBlockOperation { + return AsyncBlockOperation { + if !FirstTimeLaunch.hasFinished, SettingsManager.getShouldWipeSettings() { + if let deviceState = try? SettingsManager.readDeviceState(), + let accountData = deviceState.accountData, + let deviceData = deviceState.deviceData + { + _ = self.devicesProxy.deleteDevice( + accountNumber: accountData.number, + identifier: deviceData.identifier, + retryStrategy: .noRetry + ) { _ in + // Do nothing. + } + } + + SettingsManager.resetStore(completely: true) + } + + FirstTimeLaunch.setHasFinished() + SettingsManager.setShouldWipeSettings() + } + } + // MARK: - StorePaymentManagerDelegate func storePaymentManager( diff --git a/ios/MullvadVPN/Classes/FirstTimeLaunch.swift b/ios/MullvadVPN/Classes/FirstTimeLaunch.swift new file mode 100644 index 0000000000..efe2168909 --- /dev/null +++ b/ios/MullvadVPN/Classes/FirstTimeLaunch.swift @@ -0,0 +1,21 @@ +// +// FirstTimeLaunch.swift +// MullvadVPN +// +// Created by Jon Petersson on 2023-04-04. +// Copyright © 2023 Mullvad VPN AB. All rights reserved. +// + +import Foundation + +enum FirstTimeLaunch { + private static let userDefaultsKey = "hasFinishedFirstTimeLaunch" + + static var hasFinished: Bool { + return UserDefaults.standard.bool(forKey: userDefaultsKey) + } + + static func setHasFinished() { + UserDefaults.standard.set(true, forKey: userDefaultsKey) + } +} diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift index f24dfa3935..96bed23c45 100644 --- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift +++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift @@ -68,6 +68,23 @@ enum SettingsManager { } } + // MARK: - Should wipe settings + + static func getShouldWipeSettings() -> Bool { + return (try? store.read(key: .shouldWipeSettings)) != nil + } + + static func setShouldWipeSettings() { + do { + try store.write(Data(), for: .shouldWipeSettings) + } catch { + logger.error( + error: error, + message: "Failed to set should wipe settings." + ) + } + } + // MARK: - Settings static func readSettings() throws -> TunnelSettingsV2 { @@ -153,6 +170,48 @@ enum SettingsManager { } } + /// Removes all legacy settings, device state and tunnel settings but keeps the last used + /// account number stored. + static func resetStore(completely: Bool = false) { + logger.debug("Reset store.") + + do { + try store.delete(key: .deviceState) + } catch { + if (error as? KeychainError) != .itemNotFound { + logger.error(error: error, message: "Failed to delete device state.") + } + } + + do { + try store.delete(key: .settings) + } catch { + if (error as? KeychainError) != .itemNotFound { + logger.error(error: error, message: "Failed to delete settings.") + } + } + + if completely { + do { + try store.delete(key: .lastUsedAccount) + } catch { + if (error as? KeychainError) != .itemNotFound { + logger.error(error: error, message: "Failed to delete last used account.") + } + } + + do { + try store.delete(key: .shouldWipeSettings) + } catch { + if (error as? KeychainError) != .itemNotFound { + logger.error(error: error, message: "Failed to delete should wipe settings.") + } + } + } + + Self.deleteAllLegacySettings() + } + // MARK: - Private private static func migrateLegacySettings( @@ -212,30 +271,6 @@ enum SettingsManager { throw error } - /// Removes all legacy settings, device state and tunnel settings but keeps the last used - /// account number stored. - private static func resetStore() { - logger.debug("Reset store.") - - do { - try store.delete(key: .deviceState) - } catch { - if (error as? KeychainError) != .itemNotFound { - logger.error(error: error, message: "Failed to delete device state.") - } - } - - do { - try store.delete(key: .settings) - } catch { - if (error as? KeychainError) != .itemNotFound { - logger.error(error: error, message: "Failed to delete settings.") - } - } - - Self.deleteAllLegacySettings() - } - // MARK: - Legacy settings private static func readLegacySettings() -> LegacyTunnelSettings? { @@ -409,6 +444,7 @@ enum SettingsKey: String, CaseIterable { case settings = "Settings" case deviceState = "DeviceState" case lastUsedAccount = "LastUsedAccount" + case shouldWipeSettings = "shouldWipeSettings" } /// An error type describing a failure to read or parse settings version. |
