Drop v2 migrations (#348)

This commit is contained in:
Davide De Rosa 2023-09-08 22:18:41 +02:00 committed by GitHub
parent 0f84859354
commit a3cfde1950
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 69 additions and 430 deletions

View File

@ -50,6 +50,9 @@
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; };
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
@ -340,6 +343,9 @@
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; };
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
@ -696,9 +702,12 @@
0E3A593F2A54ACC900B3FE40 /* Managers */ = {
isa = PBXGroup;
children = (
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */,
0E9C232F27F47032007D5FC7 /* IntentsManager.swift */,
0E7A8C092A1D410400780F4B /* PersistenceManager.swift */,
0E53249627D26B51002565C3 /* ProductManager.swift */,
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */,
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */,
);
path = Managers;
sourceTree = "<group>";
@ -1466,6 +1475,7 @@
0ED89C1E27DE3F8D008B36D6 /* IntentAddView.swift in Sources */,
0E5468002867AC9A00F74D1C /* MacUtils.swift in Sources */,
0E0F4C6429C84B5A0022E884 /* LockableView.swift in Sources */,
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */,
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */,
@ -1500,6 +1510,7 @@
0E5349BE27C16A4500C71BB3 /* StyledPicker.swift in Sources */,
0E2C172B27CB63F9007E8488 /* Reviewer.swift in Sources */,
0E71ACDD27C0295C00F85C4B /* View+Extensions.swift in Sources */,
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */,
0E021D9C284E68580077EF5D /* CoreContext.swift in Sources */,
0E7A8C0A2A1D410500780F4B /* PersistenceManager.swift in Sources */,
A38D607728AFCFD20005C271 /* SettingsView.swift in Sources */,
@ -1511,6 +1522,7 @@
0EBC075527EBC83800208AD9 /* MailComposerView.swift in Sources */,
0EF0FAF727DD159C007EB181 /* IntentDispatcher.swift in Sources */,
0E0838FD2877334300A34EC0 /* DefaultLightProviderManager.swift in Sources */,
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */,
0E0F4C6629C84CF60022E884 /* LogoView.swift in Sources */,
0E039279281890B100827C10 /* AddHostView.swift in Sources */,
0E5467F72867A57000F74D1C /* MacBridge.swift in Sources */,

View File

@ -224,10 +224,10 @@ extension Constants {
enum Delays {
static let scrolling = 100
// @available(*, deprecated, message: "file importer stops showing again after closing with swipe down")
// @available(*, deprecated, message: "File importer stops showing again after closing with swipe down")
static let xxxPresentFileImporter = 200
// @available(*, deprecated, message: "edited shortcut is outdated in delegate")
// @available(*, deprecated, message: "Edited shortcut is outdated in delegate")
static let xxxReloadEditedShortcut = 200
}

View File

@ -577,7 +577,7 @@ extension View {
// MARK: Hacks
extension View {
@available(*, deprecated, message: "mitigates multiline text truncation (1.0 does not work though)")
@available(*, deprecated, message: "Mitigates multiline text truncation (1.0 does not work though)")
func xxxThemeTruncation() -> some View {
minimumScaleFactor(0.5)
}

View File

@ -0,0 +1,40 @@
//
// DefaultUpgradeManagerStrategy.swift
// Passepartout
//
// Created by Davide De Rosa on 3/20/22.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import PassepartoutLibrary
public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
public init() {
}
public func doMigrateStore(_ store: KeyValueStore, lastVersion: String?) {
if let lastVersion {
pp_log.debug("Upgrade from \(lastVersion)")
} else {
pp_log.debug("Fresh install")
}
}
}

View File

@ -62,7 +62,7 @@ extension PersistenceManager {
}
private extension PersistenceManager {
private enum StoreKey: String, KeyStoreDomainLocation {
enum StoreKey: String, KeyStoreDomainLocation {
case persistenceAuthor
var domain: String {

View File

@ -24,7 +24,7 @@
//
import Foundation
import PassepartoutCore
import PassepartoutLibrary
@MainActor
public final class UpgradeManager: ObservableObject {
@ -48,49 +48,29 @@ public final class UpgradeManager: ObservableObject {
}
public func doMigrations(_ profileManager: ProfileManager) {
strategy.doMigrateStore(store, didMigrate: &didMigrateToV2)
strategy.doMigrateStore(store, lastVersion: lastVersion)
lastVersion = Constants.Global.appVersionNumber
// profileManager.removeAllProfiles() // testing only
guard didMigrateToV2 else {
isDoingMigrations = true
let migrated = strategy.migratedProfilesToV2()
if !migrated.isEmpty {
pp_log.info("Migrating \(migrated.count) profiles")
migrated.forEach {
var profile = $0
if profileManager.isExistingProfile(withName: profile.header.name) {
profile = profile.renamedUniquely(withLastUpdate: true)
}
profileManager.saveProfile(profile, isActive: nil)
}
} else {
pp_log.info("Nothing to migrate!")
}
isDoingMigrations = false
didMigrateToV2 = true
return
}
isDoingMigrations = false
}
}
// MARK: KeyValueStore
extension UpgradeManager {
public internal(set) var didMigrateToV2: Bool {
private extension UpgradeManager {
var lastVersion: String? {
get {
store.value(forLocation: StoreKey.didMigrateToV2) ?? false
store.value(forLocation: StoreKey.lastVersion)
}
set {
store.setValue(newValue, forLocation: StoreKey.didMigrateToV2)
store.setValue(newValue, forLocation: StoreKey.lastVersion)
}
}
}
private extension UpgradeManager {
private enum StoreKey: String, KeyStoreDomainLocation {
case didMigrateToV2
enum StoreKey: String, KeyStoreDomainLocation {
case lastVersion
var domain: String {
"Passepartout.UpgradeManager"

View File

@ -27,7 +27,5 @@ import Foundation
import PassepartoutCore
public protocol UpgradeManagerStrategy {
func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool)
func migratedProfilesToV2() -> [Profile]
func doMigrateStore(_ store: KeyValueStore, lastVersion: String?)
}

View File

@ -213,7 +213,7 @@ extension ProfileManager {
profileRepository.removeProfiles(withIds: ids)
}
@available(*, deprecated, message: "only use for testing")
@available(*, deprecated, message: "Only use for testing")
public func removeAllProfiles() {
let ids = Array(allProfiles.keys)
removeProfiles(withIds: ids)
@ -517,7 +517,7 @@ extension ProfileManager {
}
private extension ProfileManager {
private enum StoreKey: String, KeyStoreDomainLocation {
enum StoreKey: String, KeyStoreDomainLocation {
case activeProfileId
var domain: String {

View File

@ -338,7 +338,7 @@ extension VPNManager {
}
private extension VPNManager {
private enum StoreKey: String, KeyStoreDomainLocation {
enum StoreKey: String, KeyStoreDomainLocation {
case tunnelLogPath
case tunnelLogFormat

View File

@ -1,391 +0,0 @@
//
// DefaultUpgradeManagerStrategy.swift
// Passepartout
//
// Created by Davide De Rosa on 3/20/22.
// Copyright (c) 2023 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import GenericJSON
import PassepartoutCore
import PassepartoutProviders
import PassepartoutVPN
import TunnelKitCore
import TunnelKitManager
import TunnelKitOpenVPNCore
private typealias Map = [String: Any]
private enum UpgradeError: Error {
case json
case missingId
case missingOpenVPNConfiguration
case missingHostname
case missingEndpointProtocols
case missingProviderName
}
public final class DefaultUpgradeManagerStrategy: UpgradeManagerStrategy {
public init() {
}
}
// MARK: Migrate old store
extension DefaultUpgradeManagerStrategy {
private enum LegacyStoreKey: String, KeyStoreLocation, CaseIterable {
case activeProfileId
case launchesOnLogin
case isStatusMenuEnabled
case isShowingFavorites
case confirmsQuit
case logFormat
case tunnelLogFormat
case masksPrivateData
case didHandleSubreddit
case persistenceAuthor
case didMigrateToV2
case other1 = "MasksPrivateData"
case other2 = "DidHandleSubreddit"
case other3 = "Convenience.Reviewer.LastVersion"
case other4 = "didMigrateKeychainContext"
var key: String {
rawValue
}
}
public func doMigrateStore(_ store: KeyValueStore, didMigrate: inout Bool) {
if !didMigrate {
guard let legacyDidMigrateToV2: Bool = store.value(forLocation: LegacyStoreKey.didMigrateToV2) else {
return
}
didMigrate = legacyDidMigrateToV2
}
LegacyStoreKey.allCases.forEach {
store.removeValue(forLocation: $0)
}
}
}
// MARK: Migrate to version 2
extension DefaultUpgradeManagerStrategy {
private var appGroup: String {
"group.com.algoritmico.Passepartout"
}
public func migratedProfilesToV2() -> [Profile] {
var migrated: [Profile] = []
pp_log.info("Migrating data to v2")
let fm = FileManager.default
guard let documents = fm.containerURL(forSecurityApplicationGroupIdentifier: appGroup)?
.appendingPathComponent("Documents") else {
pp_log.info("No data to migrate")
return []
}
let cs = documents.appendingPathComponent("ConnectionService.json")
let hostsFolder = documents.appendingPathComponent("Hosts")
let providersFolder = documents.appendingPathComponent("Providers")
do {
let csJSON = try cs.asJSON()
// pp_log.error(csJSON)
do {
var authUserPassUUIDs: Set<String> = []
for host in try fm.contentsOfDirectory(at: hostsFolder, includingPropertiesForKeys: nil) {
guard host.isFileURL && host.pathExtension == "ovpn" else {
continue
}
do {
let uuid = host.deletingPathExtension().lastPathComponent
let content = try String(contentsOf: host)
if content.contains("auth-user-pass") {
authUserPassUUIDs.insert(uuid)
}
} catch {
pp_log.warning("Unable to read host profile .ovpn: \(host)")
}
}
// print(">>> authUserPassUUIDs: \(authUserPassUUIDs)")
for host in try fm.contentsOfDirectory(at: hostsFolder, includingPropertiesForKeys: nil) {
guard host.isFileURL && host.pathExtension == "json" else {
continue
}
do {
let json = try host.asJSON()
// pp_log.error(json)
let result = try migratedV1Profile(csJSON, hostMap: json, authUserPass: authUserPassUUIDs)
// pp_log.info(result.profile)
// print(">>> Account: \(result.profile.username) -> \(result.password)")
migrated.append(result)
} catch {
pp_log.warning("Unable to migrate host profile: \(host)")
continue
}
}
} catch {
pp_log.warning(error)
}
do {
for provider in try fm.contentsOfDirectory(at: providersFolder, includingPropertiesForKeys: nil) {
guard provider.isFileURL && provider.pathExtension == "json" else {
continue
}
do {
let json = try provider.asJSON()
// pp_log.error(json)
let result = try migratedV1Profile(csJSON, providerMap: json)
// pp_log.info(result.profile)
// print(">>> Account: \(result.profile.username) -> \(result.password)")
migrated.append(result)
} catch {
pp_log.warning("Unable to migrate provider profile: \(provider)")
continue
}
}
} catch {
pp_log.warning(error)
}
} catch {
pp_log.warning(error)
}
return migrated
}
// SHARED
//
// username/password ("username")
// trusted networks ("trustedNetworks")
// network settings ("networkChoices", "manualNetworkSettings")
//
// HOST
//
// provider configuration ("parameters") -- not crucial
// custom endpoint ("parameters"?) -- not crucial
// ovpn configuration ("parameters" -> "sessionConfiguration")
//
private func migratedV1Profile(_ cs: Map, hostMap: Map, authUserPass: Set<String>) throws -> Profile {
guard let oldUUIDString = hostMap["id"] as? String else {
throw UpgradeError.missingId
}
let name = (cs["hostTitles"] as? Map)?[oldUUIDString] as? String ?? oldUUIDString
let header = Profile.Header(name: name) // new UUID
// configuration
guard let params = hostMap["parameters"] as? Map else {
throw UpgradeError.missingOpenVPNConfiguration
}
guard var ovpn = params["sessionConfiguration"] as? Map else {
throw UpgradeError.missingOpenVPNConfiguration
}
guard let hostname = ovpn["hostname"] as? String else {
throw UpgradeError.missingHostname
}
guard let rawEps = ovpn["endpointProtocols"] as? [String] else {
throw UpgradeError.missingEndpointProtocols
}
let eps = rawEps.compactMap(EndpointProtocol.init(rawValue:))
ovpn["remotes"] = eps.map {
[hostname, $0.description].joined(separator: ":")
}
ovpn["authUserPass"] = authUserPass.contains(oldUUIDString)
let cfg = try JSON(ovpn).decode(OpenVPN.Configuration.self)
// keychain
let username = hostMap["username"] as? String ?? ""
let password = migratedV1Password(forProfileId: oldUUIDString, profileType: "host", username: username)
var profile = Profile(header, configuration: cfg)
var account = Profile.Account()
account.username = username
account.password = password
profile.account = account
// shared
profile.onDemand = migratedV1TrustedNetworks(hostMap)
profile.networkSettings = migratedV1NetworkSettings(hostMap)
return profile
}
// HOST
//
// poolId -- not crucial
// presetId
// favoriteGroupIds
//
private func migratedV1Profile(_ cs: Map, providerMap: Map) throws -> Profile {
guard let name = providerMap["name"] as? String else {
throw UpgradeError.missingProviderName
}
let header = Profile.Header(name: name, providerName: name)
var provider = Profile.Provider(name)
// keychain
var account = Profile.Account()
account.username = providerMap["username"] as? String ?? ""
account.password = migratedV1Password(forProfileId: name, profileType: "provider", username: account.username)
// provider configuration
var settings = Profile.Provider.Settings()
if let apiId = providerMap["poolId"] as? String {
settings.serverId = ProviderServer.id(withName: name, vpnProtocol: .openVPN, apiId: apiId)
}
settings.presetId = providerMap["presetId"] as? String
let favoriteGroupIds = providerMap["favoriteGroupIds"] as? [String] ?? []
settings.favoriteLocationIds = Set(favoriteGroupIds.compactMap {
[
name,
$0.replacingOccurrences(of: "/", with: ":")
].joined(separator: ":")
})
settings.account = account
provider.vpnSettings[.openVPN] = settings
var profile = Profile(header, provider: provider)
// shared
profile.onDemand = migratedV1TrustedNetworks(providerMap)
profile.networkSettings = migratedV1NetworkSettings(providerMap)
return profile
}
private func migratedV1Password(forProfileId profileId: String, profileType: String, username: String) -> String {
let keychain = Keychain(group: appGroup)
let passwordContext = [Bundle.main.bundleIdentifier!, profileType, profileId].joined(separator: ".")
do {
return try keychain.password(for: username, context: passwordContext)
} catch {
return ""
}
}
private func migratedV1TrustedNetworks(_ map: Map) -> Profile.OnDemand {
var onDemand = Profile.OnDemand()
onDemand.isEnabled = true
if let trusted = map["trustedNetworks"] as? Map {
onDemand.withMobileNetwork = trusted["includesMobile"] as? Bool ?? false
onDemand.withEthernetNetwork = trusted["includesEthernet"] as? Bool ?? false
onDemand.withSSIDs = trusted["includedWiFis"] as? [String: Bool] ?? [:]
if let rawPolicy = trusted["policy"] as? String, let policy = Profile.OnDemand.Policy(rawValue: rawPolicy) {
onDemand.policy = policy
}
}
return onDemand
}
private func migratedV1NetworkSettings(_ map: Map) -> Profile.NetworkSettings {
var settings = Profile.NetworkSettings()
if let choices = map["networkChoices"] as? Map {
settings.gateway.choice = migratedV1Choice(choices, key: "gateway")
settings.dns.choice = migratedV1Choice(choices, key: "dns")
settings.proxy.choice = migratedV1Choice(choices, key: "proxy")
settings.mtu.choice = migratedV1Choice(choices, key: "mtu")
}
if let manual = map["manualNetworkSettings"] as? Map {
// gateway
settings.gateway.isDefaultIPv4 = (manual["gatewayPolicies"] as? [String])?.contains("IPv4") ?? false
settings.gateway.isDefaultIPv6 = (manual["gatewayPolicies"] as? [String])?.contains("IPv6") ?? false
// dns
(manual["dnsProtocol"] as? String).map {
settings.dns.configurationType = .init(rawValue: $0) ?? .plain
}
settings.dns.dnsServers = manual["dnsServers"] as? [String] ?? []
settings.dns.dnsSearchDomains = manual["dnsSearchDomains"] as? [String] ?? []
(manual["dnsHTTPSURL"] as? String).map {
settings.dns.dnsHTTPSURL = URL(string: $0)
}
settings.dns.dnsTLSServerName = manual["dnsTLSServerName"] as? String
// proxy
settings.proxy.proxyAddress = manual["proxyAddress"] as? String
settings.proxy.proxyPort = manual["proxyPort"] as? UInt16
(manual["proxyAutoConfigurationURL"] as? String).map {
settings.proxy.proxyAutoConfigurationURL = URL(string: $0)
}
settings.proxy.proxyBypassDomains = manual["proxyBypassDomains"] as? [String] ?? []
// mtu
settings.mtu.mtuBytes = manual["mtuBytes"] as? Int ?? 0
}
return settings
}
private func migratedV1Choice(_ map: Map, key: String) -> Network.Choice {
(map[key] as? String) == "manual" ? .manual : .automatic
}
}
private extension URL {
func asJSON() throws -> Map {
let data = try Data(contentsOf: self)
guard let json = try JSONSerialization.jsonObject(with: data) as? Map else {
throw UpgradeError.json
}
return json
}
}