summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2022-11-24 17:11:56 +0100
committerAndrej Mihajlov <and@mullvad.net>2022-11-28 10:39:08 +0100
commit4ecef23df44f761292f3cce39f07f3d38c936681 (patch)
tree3c4db9ca3af23a2583f916747ff3fca0f94f469c
parentb8bc88dc42c9d43cab5655fb0071481d24861277 (diff)
downloadmullvadvpn-4ecef23df44f761292f3cce39f07f3d38c936681.tar.xz
mullvadvpn-4ecef23df44f761292f3cce39f07f3d38c936681.zip
Normalize errors returned by migrateStore() and reset store upon failure
-rw-r--r--ios/MullvadVPN/SettingsManager/SettingsManager.swift158
-rw-r--r--ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift5
2 files changed, 124 insertions, 39 deletions
diff --git a/ios/MullvadVPN/SettingsManager/SettingsManager.swift b/ios/MullvadVPN/SettingsManager/SettingsManager.swift
index b047d6b32f..fb6881f731 100644
--- a/ios/MullvadVPN/SettingsManager/SettingsManager.swift
+++ b/ios/MullvadVPN/SettingsManager/SettingsManager.swift
@@ -64,12 +64,15 @@ enum SettingsManager {
let parser = makeParser()
let version = try parser.parseVersion(data: data)
- let currentVersion = SchemaVersion.current.rawValue
+ let currentVersion = SchemaVersion.current
- if version == currentVersion {
+ if version == currentVersion.rawValue {
return try parser.parsePayload(as: TunnelSettingsV2.self, from: data)
} else {
- throw UnsupportedVersionSettings(storedVersion: version, currentVersion: currentVersion)
+ throw UnsupportedSettingsVersionError(
+ storedVersion: version,
+ currentVersion: currentVersion
+ )
}
}
@@ -98,21 +101,38 @@ enum SettingsManager {
// MARK: - Migration
+ /// Migrate settings store if needed.
+ ///
+ /// The error returned in `completion` handler is set to `nil` upon success or when no
+ /// migration is not needed.
+ ///
+ /// The following types of error are expected to be returned by this method:
+ /// `SettingsMigrationError`, `UnsupportedSettingsVersionError`, `ReadSettingsVersionError`.
static func migrateStore(
with restFactory: REST.ProxyFactory,
completion: @escaping (Error?) -> Void
) {
+ let handleCompletion = { (error: Error?) in
+ // Reset store upon failure to migrate settings.
+ if error != nil {
+ self.resetStore()
+ }
+ completion(error)
+ }
+
if let legacySettings = readLegacySettings() {
migrateLegacySettings(
restFactory: restFactory,
legacySettings: legacySettings,
- completion: completion
+ completion: handleCompletion
)
} else {
- migrateModernSettings(completion: completion)
+ migrateModernSettings(completion: handleCompletion)
}
}
+ // MARK: - Private
+
private static func migrateLegacySettings(
restFactory: REST.ProxyFactory,
legacySettings: LegacyTunnelSettings,
@@ -127,22 +147,17 @@ enum SettingsManager {
migration.migrate(with: store, parser: parser) { error in
if let error = error {
- logger.error(
- error: error,
- message: "Failed to migrate from legacy settings to v2."
+ let migrationError = SettingsMigrationError(
+ sourceVersion: .v1,
+ targetVersion: .v2,
+ underlyingError: error
)
- completion(error)
- } else {
- let userDefaults = UserDefaults.standard
-
- logger.debug("Remove legacy settings from keychain.")
- Self.deleteLegacySettings()
+ logger.error(error: migrationError)
- logger.debug("Remove legacy settings from user defaults.")
-
- userDefaults.removeObject(forKey: accountTokenKey)
- userDefaults.removeObject(forKey: accountExpiryKey)
+ completion(migrationError)
+ } else {
+ Self.deleteAllLegacySettings()
completion(nil)
}
@@ -150,22 +165,18 @@ enum SettingsManager {
}
private static func migrateModernSettings(completion: @escaping (Error?) -> Void) {
- let parser = makeParser()
-
do {
+ let parser = makeParser()
let settingsData = try store.read(key: .settings)
let settingsVersion = try parser.parseVersion(data: settingsData)
if settingsVersion != SchemaVersion.current.rawValue {
- let error = UnsupportedVersionSettings(
+ let error = UnsupportedSettingsVersionError(
storedVersion: settingsVersion,
- currentVersion: SchemaVersion.current.rawValue
+ currentVersion: SchemaVersion.current
)
- logger.error(
- error: error,
- message: "Encountered an unknown version."
- )
+ logger.error(error: error, message: "Encountered an unknown version.")
completion(error)
} else {
@@ -174,18 +185,39 @@ enum SettingsManager {
} catch .itemNotFound as KeychainError {
completion(nil)
} catch {
- completion(error)
+ completion(ReadSettingsVersionError(underlyingError: error))
}
}
- // MARK: - Legacy settings
+ /// 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.")
- private static func readLegacySettings() -> LegacyTunnelSettings? {
- let storedAccountNumber = UserDefaults.standard.string(forKey: accountTokenKey)
+ 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.")
+ }
+ }
- guard let storedAccountNumber = storedAccountNumber else {
- logger.debug("Account number is not found in user defaults. Nothing to migrate.")
+ Self.deleteAllLegacySettings()
+ }
+ // MARK: - Legacy settings
+
+ private static func readLegacySettings() -> LegacyTunnelSettings? {
+ guard let storedAccountNumber = UserDefaults.standard.string(forKey: accountTokenKey) else {
+ logger.debug("Legacy account number is not found in user defaults. Nothing to migrate.")
return nil
}
@@ -224,8 +256,6 @@ enum SettingsManager {
return matchingSettings
}
- // MARK: - Legacy settings support
-
private static func findAllLegacySettingsInKeychain() throws -> [LegacyTunnelSettings] {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
@@ -273,7 +303,17 @@ enum SettingsManager {
}
}
- static func deleteLegacySettings() {
+ private static func deleteAllLegacySettings() {
+ logger.debug("Remove legacy settings from keychain.")
+ deleteLegacySettingsFromKeychain()
+
+ logger.debug("Remove legacy settings from user defaults.")
+ let userDefaults = UserDefaults.standard
+ userDefaults.removeObject(forKey: accountTokenKey)
+ userDefaults.removeObject(forKey: accountExpiryKey)
+ }
+
+ private static func deleteLegacySettingsFromKeychain() {
let query: [CFString: Any] = [
kSecClass: kSecClassGenericPassword,
kSecAttrService: keychainServiceName,
@@ -348,11 +388,53 @@ enum SettingsKey: String, CaseIterable {
case lastUsedAccount = "LastUsedAccount"
}
-struct UnsupportedVersionSettings: LocalizedError {
- let storedVersion, currentVersion: Int
+/// An error type describing a failure to read or parse settings version.
+struct ReadSettingsVersionError: LocalizedError, WrappingError {
+ private let inner: Error
+
+ var underlyingError: Error? {
+ return inner
+ }
+
+ var errorDescription: String? {
+ return "Failed to read settings version."
+ }
+
+ init(underlyingError: Error) {
+ inner = underlyingError
+ }
+}
+
+/// An error returned when stored settings version is unknown to the currently running app.
+struct UnsupportedSettingsVersionError: LocalizedError {
+ let storedVersion: Int
+ let currentVersion: SchemaVersion
+
+ var errorDescription: String? {
+ return """
+ Stored settings version was not the same as current version, \
+ stored version: \(storedVersion), current version: \(currentVersion)
+ """
+ }
+}
+
+/// A wrapper type for errors returned by concrete migrations.
+struct SettingsMigrationError: LocalizedError, WrappingError {
+ private let inner: Error
+ let sourceVersion, targetVersion: SchemaVersion
+
+ var underlyingError: Error? {
+ return inner
+ }
var errorDescription: String? {
- return "Stored settings version was not the same as current version, stored version: \(storedVersion), current version: \(currentVersion)"
+ return "Failed to migrate settings from \(sourceVersion) to \(targetVersion)."
+ }
+
+ init(sourceVersion: SchemaVersion, targetVersion: SchemaVersion, underlyingError: Error) {
+ self.sourceVersion = sourceVersion
+ self.targetVersion = targetVersion
+ inner = underlyingError
}
}
diff --git a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
index 6c0ada1415..76b794eba9 100644
--- a/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
+++ b/ios/MullvadVPN/SettingsManager/TunnelSettingsV2.swift
@@ -14,7 +14,10 @@ import class WireGuardKitTypes.PrivateKey
import class WireGuardKitTypes.PublicKey
/// Settings and device state schema versions.
-enum SchemaVersion: Int {
+enum SchemaVersion: Int, Equatable {
+ /// Legacy settings format, stored as `TunnelSettingsV1`.
+ case v1 = 1
+
/// New settings format, stored as `TunnelSettingsV2`.
case v2 = 2