-
Notifications
You must be signed in to change notification settings - Fork 384
/
Copy pathSettingsManager.swift
235 lines (186 loc) · 6.72 KB
/
SettingsManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//
// SettingsManager.swift
// MullvadVPN
//
// Created by pronebird on 29/04/2022.
// Copyright © 2022 Mullvad VPN AB. All rights reserved.
//
import Foundation
import MullvadLogging
import MullvadTypes
private let keychainServiceName = "Mullvad VPN"
private let accountTokenKey = "accountToken"
private let accountExpiryKey = "accountExpiry"
public enum SettingsManager {
private static let logger = Logger(label: "SettingsManager")
#if DEBUG
private static var _store = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)
/// Alternative store used for tests.
internal static var unitTestStore: SettingsStore?
public static var store: SettingsStore {
if let unitTestStore { return unitTestStore }
return _store
}
#else
public static let store: SettingsStore = KeychainSettingsStore(
serviceName: keychainServiceName,
accessGroup: ApplicationConfiguration.securityGroupIdentifier
)
#endif
private static func makeParser() -> SettingsParser {
SettingsParser(decoder: JSONDecoder(), encoder: JSONEncoder())
}
// MARK: - Last used account
public static func getLastUsedAccount() throws -> String {
let data = try store.read(key: .lastUsedAccount)
if let string = String(data: data, encoding: .utf8) {
return string
} else {
throw StringDecodingError(data: data)
}
}
public static func setLastUsedAccount(_ string: String?) throws {
if let string {
guard let data = string.data(using: .utf8) else {
throw StringEncodingError(string: string)
}
try store.write(data, for: .lastUsedAccount)
} else {
do {
try store.delete(key: .lastUsedAccount)
} catch let error as KeychainError where error == .itemNotFound {
return
} catch {
throw error
}
}
}
// MARK: - Should wipe settings
public static func getShouldWipeSettings() -> Bool {
(try? store.read(key: .shouldWipeSettings)) != nil
}
public static func setShouldWipeSettings() {
do {
try store.write(Data(), for: .shouldWipeSettings)
} catch {
logger.error(
error: error,
message: "Failed to set should wipe settings."
)
}
}
// MARK: - Settings
public static func readSettings() throws -> LatestTunnelSettings {
let storedVersion: Int
let data: Data
let parser = makeParser()
do {
data = try store.read(key: .settings)
storedVersion = try parser.parseVersion(data: data)
} catch {
throw ReadSettingsVersionError(underlyingError: error)
}
let currentVersion = SchemaVersion.current
if storedVersion == currentVersion.rawValue {
return try parser.parsePayload(as: LatestTunnelSettings.self, from: data)
} else {
throw UnsupportedSettingsVersionError(
storedVersion: storedVersion,
currentVersion: currentVersion
)
}
}
public static func writeSettings(_ settings: LatestTunnelSettings) throws {
let parser = makeParser()
let data = try parser.producePayload(settings, version: SchemaVersion.current.rawValue)
try store.write(data, for: .settings)
}
// MARK: - Device state
public static func readDeviceState() throws -> DeviceState {
let data = try store.read(key: .deviceState)
let parser = makeParser()
return try parser.parseUnversionedPayload(as: DeviceState.self, from: data)
}
public static func writeDeviceState(_ deviceState: DeviceState) throws {
let parser = makeParser()
let data = try parser.produceUnversionedPayload(deviceState)
try store.write(data, for: .deviceState)
}
/// Removes all legacy settings, device state, tunnel settings and API access methods but keeps
/// the last used account number stored.
public static func resetStore(completely: Bool = false) {
logger.debug("Reset store.")
let keys = completely ? SettingsKey.allCases : [.settings, .deviceState, .apiAccessMethods, .ipOverrides]
keys.forEach { key in
do {
try store.delete(key: key)
} catch {
if (error as? KeychainError) != .itemNotFound {
logger.error(error: error, message: "Failed to delete \(key.rawValue).")
}
}
}
}
// MARK: - Private
private static func checkLatestSettingsVersion() throws {
let settingsVersion: Int
do {
let parser = makeParser()
let settingsData = try store.read(key: .settings)
settingsVersion = try parser.parseVersion(data: settingsData)
} catch .itemNotFound as KeychainError {
return
} catch {
throw ReadSettingsVersionError(underlyingError: error)
}
guard settingsVersion != SchemaVersion.current.rawValue else {
return
}
let error = UnsupportedSettingsVersionError(
storedVersion: settingsVersion,
currentVersion: SchemaVersion.current
)
logger.error(error: error, message: "Encountered an unknown version.")
throw error
}
}
// MARK: - Supporting types
/// An error type describing a failure to read or parse settings version.
public struct ReadSettingsVersionError: LocalizedError, WrappingError {
private let inner: Error
public var underlyingError: Error? {
inner
}
public var errorDescription: String? {
"Failed to read settings version."
}
public init(underlyingError: Error) {
inner = underlyingError
}
}
/// An error returned when stored settings version is unknown to the currently running app.
public struct UnsupportedSettingsVersionError: LocalizedError {
public let storedVersion: Int
public let currentVersion: SchemaVersion
public var errorDescription: String? {
"""
Stored settings version was not the same as current version, \
stored version: \(storedVersion), current version: \(currentVersion)
"""
}
}
public struct StringDecodingError: LocalizedError {
public let data: Data
public var errorDescription: String? {
"Failed to decode string from data."
}
}
public struct StringEncodingError: LocalizedError {
public let string: String
public var errorDescription: String? {
"Failed to encode string into data."
}
}