Purge stale NetworkExtension/keychain data (#728)

- [x] NE managers were not deleted when unable to be decoded to a
profile
- [x] Keychain items were not deleted on profile removal
- [x] Perform clean-up on app launch
- [x] Perform clean-up on app active

Prematurely merged as #727 then reverted, this is the complete PR.
This commit is contained in:
Davide 2024-10-11 17:48:37 +02:00 committed by GitHub
parent 1aa393ee02
commit f2a141a189
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 73 additions and 56 deletions

View File

@ -41,7 +41,7 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "git@github.com:passepartoutvpn/passepartoutkit-source", "location" : "git@github.com:passepartoutvpn/passepartoutkit-source",
"state" : { "state" : {
"revision" : "de9387965025a1f79e060e6bdc862933fe972598" "revision" : "bec0635fe047e09c8b6c894d103ab8dd741b8340"
} }
}, },
{ {

View File

@ -39,6 +39,9 @@ struct PassepartoutApp: App {
private var appDelegate: AppDelegate private var appDelegate: AppDelegate
#endif #endif
@Environment(\.scenePhase)
private var scenePhase
private let context: AppContext = .shared private let context: AppContext = .shared
// private let context: AppContext = .mock(withRegistry: .shared) // private let context: AppContext = .mock(withRegistry: .shared)
@ -47,23 +50,25 @@ struct PassepartoutApp: App {
@StateObject @StateObject
private var theme = Theme() private var theme = Theme()
var body: some Scene {
#if os(iOS) #if os(iOS)
WindowGroup(content: content) var body: some Scene {
WindowGroup(content: contentView)
}
#else #else
Window(appName, id: appName, content: content) var body: some Scene {
Window(appName, id: appName, content: contentView)
.defaultSize(width: 600.0, height: 400.0) .defaultSize(width: 600.0, height: 400.0)
Settings { Settings {
SettingsView(profileManager: context.profileManager) SettingsView(profileManager: context.profileManager)
.frame(minWidth: 300, minHeight: 200) .frame(minWidth: 300, minHeight: 200)
} }
#endif
} }
#endif
} }
private extension PassepartoutApp { private extension PassepartoutApp {
func content() -> some View { func contentView() -> some View {
AppCoordinator( AppCoordinator(
profileManager: context.profileManager, profileManager: context.profileManager,
tunnel: context.tunnel, tunnel: context.tunnel,
@ -77,6 +82,22 @@ private extension PassepartoutApp {
) )
AppUI.configure(with: context) AppUI.configure(with: context)
} }
.onChange(of: scenePhase) {
switch $0 {
case .active:
Task {
do {
pp_log(.app, .notice, "Prepare tunnel and purge stale data")
try await context.tunnel.prepare(purge: true)
} catch {
pp_log(.app, .fault, "Unable to prepare tunnel: \(error)")
}
}
default:
break
}
}
.themeLockScreen() .themeLockScreen()
.withEnvironment(from: context, theme: theme) .withEnvironment(from: context, theme: theme)
} }

View File

@ -28,7 +28,7 @@ let package = Package(
], ],
dependencies: [ dependencies: [
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", from: "0.8.0"),
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "de9387965025a1f79e060e6bdc862933fe972598"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source", revision: "bec0635fe047e09c8b6c894d103ab8dd741b8340"),
// .package(path: "../../../passepartoutkit-source"), // .package(path: "../../../passepartoutkit-source"),
.package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"), .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", from: "0.8.0"),
// .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"), // .package(url: "git@github.com:passepartoutvpn/passepartoutkit-source-openvpn-openssl", revision: "031863a1cd683962a7dfe68e20b91fa820a1ecce"),

View File

@ -46,7 +46,7 @@ public final class InMemoryProfileRepository: ProfileRepository {
} }
public func saveProfile(_ profile: Profile) async throws { public func saveProfile(_ profile: Profile) async throws {
print("Save profile: \(profile.id))") pp_log(.app, .info, "Save profile: \(profile.id))")
if let index = profiles.firstIndex(where: { $0.id == profile.id }) { if let index = profiles.firstIndex(where: { $0.id == profile.id }) {
profiles[index] = profile profiles[index] = profile
} else { } else {
@ -55,7 +55,7 @@ public final class InMemoryProfileRepository: ProfileRepository {
} }
public func removeProfiles(withIds ids: [Profile.ID]) async throws { public func removeProfiles(withIds ids: [Profile.ID]) async throws {
print("Remove profiles: \(ids)") pp_log(.app, .info, "Remove profiles: \(ids)")
profiles = profiles.filter { profiles = profiles.filter {
!ids.contains($0.id) !ids.contains($0.id)
} }

View File

@ -55,20 +55,17 @@ public final class NEProfileRepository: ProfileRepository {
} }
self?.profilesSubject.send(profiles) self?.profilesSubject.send(profiles)
} }
Task {
do {
try await repository.load()
} catch {
pp_log(.app, .fault, "Unable to load NE profiles: \(error)")
}
}
} }
public var profilesPublisher: AnyPublisher<[Profile], Never> { public var profilesPublisher: AnyPublisher<[Profile], Never> {
profilesSubject.eraseToAnyPublisher() profilesSubject.eraseToAnyPublisher()
} }
// unused in app, rely on Tunnel.prepare()
public func loadProfiles(purge: Bool) async throws {
try await repository.load(purge: purge)
}
public func saveProfile(_ profile: Profile) async throws { public func saveProfile(_ profile: Profile) async throws {
try await repository.save(profile, connect: false, title: title) try await repository.save(profile, connect: false, title: title)
} }

View File

@ -30,7 +30,6 @@ import PassepartoutKit
public enum AppUI { public enum AppUI {
public static func configure(with context: AppContext) { public static func configure(with context: AppContext) {
assertMissingModuleImplementations() assertMissingModuleImplementations()
cleanUpOrphanedKeychainEntries()
} }
} }
@ -46,7 +45,4 @@ private extension AppUI {
} }
} }
} }
static func cleanUpOrphanedKeychainEntries() {
}
} }

View File

@ -78,7 +78,6 @@ public final class AppContext: ObservableObject {
subscriptions = [] subscriptions = []
Task { Task {
try await tunnel.prepare()
await iapManager.reloadReceipt() await iapManager.reloadReceipt()
connectionObserver.observeObjects() connectionObserver.observeObjects()
profileManager.observeObjects() profileManager.observeObjects()

View File

@ -193,7 +193,6 @@ private extension OnDemandView {
} }
draft.withSSIDs[$0] = false draft.withSSIDs[$0] = false
} }
// print(">>> withSSIDs (allSSIDs): \(withSSIDs)")
} }
} }
@ -219,7 +218,6 @@ private extension OnDemandView {
} }
draft.withSSIDs[$0] = true draft.withSSIDs[$0] = true
} }
// print(">>> withSSIDs (onSSIDs): \(withSSIDs)")
} }
} }

View File

@ -119,7 +119,7 @@ extension IAPManager {
extension ProfileProcessor { extension ProfileProcessor {
static let shared = ProfileProcessor { static let shared = ProfileProcessor {
sharedProfileTitle($0) ProfileManager.sharedTitle($0)
} processed: { profile in } processed: { profile in
var builder = profile.builder() var builder = profile.builder()
@ -157,45 +157,49 @@ extension Tunnel {
) )
} }
private var localProfileRepository: ProfileRepository { private extension ProfileManager {
let store = CoreDataPersistentStore( static let localProfileRepository: ProfileRepository = {
logger: .default, let store = CoreDataPersistentStore(
containerName: Constants.shared.containers.local, logger: .default,
model: AppData.cdProfilesModel, containerName: Constants.shared.containers.local,
cloudKitIdentifier: nil, model: AppData.cdProfilesModel,
author: nil cloudKitIdentifier: nil,
) author: nil
return AppData.cdProfileRepositoryV3( )
registry: .shared, return AppData.cdProfileRepositoryV3(
coder: CodableProfileCoder(), registry: .shared,
context: store.context, coder: CodableProfileCoder(),
observingResults: false context: store.context,
) { error in observingResults: false
pp_log(.app, .error, "Unable to decode local result: \(error)") ) { error in
return .ignore pp_log(.app, .error, "Unable to decode local result: \(error)")
} return .ignore
}
}()
} }
#else #else
extension Tunnel { extension Tunnel {
static let shared = Tunnel( static let shared = Tunnel(
strategy: NETunnelStrategy(repository: neRepository) strategy: NETunnelStrategy(repository: ProfileManager.neRepository)
) )
} }
private var localProfileRepository: ProfileRepository { private extension ProfileManager {
NEProfileRepository(repository: neRepository) { static let localProfileRepository: ProfileRepository = {
sharedProfileTitle($0) NEProfileRepository(repository: neRepository) {
} ProfileManager.sharedTitle($0)
} }
}()
private var neRepository: NETunnelManagerRepository { static let neRepository: NETunnelManagerRepository = {
NETunnelManagerRepository( NETunnelManagerRepository(
bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId), bundleIdentifier: BundleConfiguration.mainString(for: .tunnelId),
coder: Registry.sharedProtocolCoder, coder: Registry.sharedProtocolCoder,
environment: .shared environment: .shared
) )
}()
} }
#endif #endif
@ -212,8 +216,10 @@ extension ProviderFactory {
// MARK: - // MARK: -
private let sharedProfileTitle: (Profile) -> String = { private extension ProfileManager {
"Passepartout: \($0.name)" static let sharedTitle: (Profile) -> String = {
"Passepartout: \($0.name)"
}
} }
extension CoreDataPersistentStoreLogger where Self == DefaultCoreDataPersistentStoreLogger { extension CoreDataPersistentStoreLogger where Self == DefaultCoreDataPersistentStoreLogger {