From ee035362ece471b5a23e1d4b2aec862e5fe5ca83 Mon Sep 17 00:00:00 2001 From: Alessio Nossa Date: Tue, 11 Apr 2023 23:42:09 +0200 Subject: Implement UpdateTunnelConfiguration App Intent Signed-off-by: Alessio Nossa --- Sources/WireguardAppIntents/AppIntents.strings | 12 ++ .../UpdateTunnelConfiguration.swift | 134 +++++++++++++++++++++ WireGuard.xcodeproj/project.pbxproj | 4 + 3 files changed, 150 insertions(+) create mode 100644 Sources/WireguardAppIntents/UpdateTunnelConfiguration.swift diff --git a/Sources/WireguardAppIntents/AppIntents.strings b/Sources/WireguardAppIntents/AppIntents.strings index 9ebdc8e..1c2064e 100644 --- a/Sources/WireguardAppIntents/AppIntents.strings +++ b/Sources/WireguardAppIntents/AppIntents.strings @@ -11,6 +11,18 @@ "getPeersIntentTunnelParameterTitle" = "Tunnel"; "getPeersIntentSummary ${tunnelName}" = "Get peers of ${tunnelName}"; +// Tunnel Configuration Update +"updateTunnelConfigurationIntentName" = "Update Tunnel Configuration"; +"updateTunnelConfigurationDescription" = "Update peers configuration of the selected tunnel."; +"updateTunnelConfigurationIntentTunnelParameterTitle" = "Tunnel"; +"updateTunnelConfigurationIntentPeersParameterTitle" = "Peers"; +"updateTunnelConfigurationIntentMergeParameterTitle" = "Merge configuration"; +"updateTunnelConfigurationIntentSummary ${tunnelName}" = "Update ${tunnelName} configuration"; + +"updateTunnelConfigurationIntentPeerOptionsUnavailableError" = "Use the output of \"Build Peer Configuration\" action to update tunnel configuration."; +"updateTunnelConfigurationIntentMissingPeerParameterError" = "Peer parameter value is missing"; +"updateTunnelConfigurationIntentMalformedPublicKeyError %@" = "The key \"%1$@\" is not a valid Public Key encoded in Base64 format."; + // Build Peer Configuration "buildPeerConfigurationUpdateIntentName" = "Build Peer Configuration"; "buildPeerConfigurationUpdateIntentDescription" = ""; diff --git a/Sources/WireguardAppIntents/UpdateTunnelConfiguration.swift b/Sources/WireguardAppIntents/UpdateTunnelConfiguration.swift new file mode 100644 index 0000000..51fc605 --- /dev/null +++ b/Sources/WireguardAppIntents/UpdateTunnelConfiguration.swift @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import AppIntents + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +struct UpdateTunnelConfiguration: AppIntent { + + static var title = LocalizedStringResource("updateTunnelConfigurationIntentName", table: "AppIntents") + static var description = IntentDescription( + LocalizedStringResource("updateTunnelConfigurationDescription", table: "AppIntents") + ) + + @Parameter( + title: LocalizedStringResource("updateTunnelConfigurationIntentTunnelParameterTitle", table: "AppIntents"), + optionsProvider: TunnelsOptionsProvider() + ) + var tunnelName: String + + @Parameter( + title: LocalizedStringResource("updateTunnelConfigurationIntentPeersParameterTitle", table: "AppIntents"), + optionsProvider: AppIntentsPeerOptionsProvider() + ) + var peers: [AppIntentsPeer]? + + @Parameter( + title: LocalizedStringResource("updateTunnelConfigurationIntentMergeParameterTitle", table: "AppIntents"), + default: true + ) + var mergeConfiguration: Bool + + @Dependency + var tunnelsManager: TunnelsManager + + func perform() async throws -> some IntentResult { + guard let peers else { throw AppIntentConfigurationUpdateError.missingPeerParameter } + + guard let tunnelContainer = tunnelsManager.tunnel(named: tunnelName) else { + throw AppIntentConfigurationUpdateError.wrongTunnel(name: tunnelName) + } + + guard let tunnelConfiguration = tunnelContainer.tunnelConfiguration else { + throw AppIntentConfigurationUpdateError.missingConfiguration + } + + let newConfiguration = try buildNewConfiguration(from: tunnelConfiguration, peersUpdates: peers, mergeChanges: mergeConfiguration) + + do { + try await tunnelsManager.modify(tunnel: tunnelContainer, tunnelConfiguration: newConfiguration, onDemandOption: tunnelContainer.onDemandOption) + } catch { + wg_log(.error, message: error.localizedDescription) + throw error + } + + wg_log(.debug, message: "Updated configuration of tunnel \(tunnelName)") + + return .result() + } + + static var parameterSummary: some ParameterSummary { + Summary("updateTunnelConfigurationIntentSummary \(\.$tunnelName)", table: "AppIntents") { + \.$peers + \.$mergeConfiguration + } + } + + private func buildNewConfiguration(from oldConfiguration: TunnelConfiguration, peersUpdates: [AppIntentsPeer], mergeChanges: Bool) throws -> TunnelConfiguration { + var peers = oldConfiguration.peers + + for peerUpdate in peersUpdates { + let peerIndex: Array.Index + if let foundIndex = peers.firstIndex(where: { $0.publicKey.base64Key == peerUpdate.publicKey }) { + peerIndex = foundIndex + if mergeChanges == false { + peers[peerIndex] = PeerConfiguration(publicKey: peers[peerIndex].publicKey) + } + } else { + wg_log(.debug, message: "Failed to find peer \(peerUpdate.publicKey) in tunnel with name \(tunnelName). Adding it.") + + guard let pubKeyEncoded = PublicKey(base64Key: peerUpdate.publicKey) else { + throw AppIntentConfigurationUpdateError.malformedPublicKey(key: peerUpdate.publicKey) + } + let newPeerConfig = PeerConfiguration(publicKey: pubKeyEncoded) + peerIndex = peers.endIndex + peers.append(newPeerConfig) + } + + if let endpointString = peerUpdate.endpoint { + if let newEntpoint = Endpoint(from: endpointString) { + peers[peerIndex].endpoint = newEntpoint + } else { + wg_log(.debug, message: "Failed to convert \(endpointString) to Endpoint") + } + } + } + + let newConfiguration = TunnelConfiguration(name: oldConfiguration.name, interface: oldConfiguration.interface, peers: peers) + return newConfiguration + } +} + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +struct AppIntentsPeerOptionsProvider: DynamicOptionsProvider { + + func results() async throws -> ItemCollection { + // The error thrown here is not displayed correctly to the user. A Feedback + // has been opened (FB12098463). + throw AppIntentConfigurationUpdateError.peerOptionsUnavailable + } +} + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +enum AppIntentConfigurationUpdateError: Swift.Error, CustomLocalizedStringResourceConvertible { + case wrongTunnel(name: String) + case missingConfiguration + case peerOptionsUnavailable + case missingPeerParameter + case malformedPublicKey(key: String) + + var localizedStringResource: LocalizedStringResource { + switch self { + case .wrongTunnel(let name): + return LocalizedStringResource("wireguardAppIntentsWrongTunnelError \(name)", table: "AppIntents") + case .missingConfiguration: + return LocalizedStringResource("wireguardAppIntentsMissingConfigurationError", table: "AppIntents") + case .peerOptionsUnavailable: + return LocalizedStringResource("updateTunnelConfigurationIntentPeerOptionsUnavailableError", table: "AppIntents") + case .missingPeerParameter: + return LocalizedStringResource("updateTunnelConfigurationIntentMissingPeerParameterError", table: "AppIntents") + case .malformedPublicKey(let malformedKey): + return LocalizedStringResource("updateTunnelConfigurationIntentMalformedPublicKeyError \(malformedKey)", table: "AppIntents") + } + } +} diff --git a/WireGuard.xcodeproj/project.pbxproj b/WireGuard.xcodeproj/project.pbxproj index ad1ef98..119d25a 100644 --- a/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard.xcodeproj/project.pbxproj @@ -207,6 +207,7 @@ 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; }; A625F05529C4C627005EF23D /* GetPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A625F05029C4C627005EF23D /* GetPeers.swift */; }; A6E361F829D8758500FFF234 /* AppIntents.strings in Resources */ = {isa = PBXBuildFile; fileRef = A6E361F729D8758500FFF234 /* AppIntents.strings */; }; + A6E361FA29D9821200FFF234 /* UpdateTunnelConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E361F929D9821100FFF234 /* UpdateTunnelConfiguration.swift */; }; A6E361FC29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E361FB29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift */; }; A6E361FE29D9B18C00FFF234 /* TunnelsOptionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */; }; /* End PBXBuildFile section */ @@ -444,6 +445,7 @@ 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = ""; }; A625F05029C4C627005EF23D /* GetPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPeers.swift; sourceTree = ""; }; A6E361F729D8758500FFF234 /* AppIntents.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = AppIntents.strings; sourceTree = ""; }; + A6E361F929D9821100FFF234 /* UpdateTunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTunnelConfiguration.swift; sourceTree = ""; }; A6E361FB29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildPeerConfigurationUpdate.swift; sourceTree = ""; }; A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsOptionsProvider.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -823,6 +825,7 @@ A625F04C29C4C627005EF23D /* WireguardAppIntents */ = { isa = PBXGroup; children = ( + A6E361F929D9821100FFF234 /* UpdateTunnelConfiguration.swift */, A6E361FB29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift */, A625F05029C4C627005EF23D /* GetPeers.swift */, A6E361F729D8758500FFF234 /* AppIntents.strings */, @@ -1427,6 +1430,7 @@ 6FF3527221C2616C0008484E /* ringlogger.c in Sources */, 6F0F44CB222D55FD00B0FF04 /* EditableTextCell.swift in Sources */, 585B105E2577E293004F691E /* PeerConfiguration.swift in Sources */, + A6E361FA29D9821200FFF234 /* UpdateTunnelConfiguration.swift in Sources */, 6FF3527321C2616C0008484E /* Logger.swift in Sources */, 6F7774E421718281006A79B3 /* TunnelsListTableViewController.swift in Sources */, 585B108E2577E294004F691E /* x25519.c in Sources */, -- cgit v1.2.3-59-g8ed1b