diff options
author | Alessio Nossa <alessio.nossa@gmail.com> | 2023-04-11 18:26:32 +0200 |
---|---|---|
committer | Alessio Nossa <alessio.nossa@gmail.com> | 2023-04-12 00:44:02 +0200 |
commit | ba250fe1a5c57ed3fc2f18560c0ddd72cefafc73 (patch) | |
tree | 8ec24df3a8e7b7304ac4310e91da4fc17bb846f6 | |
parent | WireguardApp: Add async variant of modify tunnel function (diff) | |
download | wireguard-apple-ba250fe1a5c57ed3fc2f18560c0ddd72cefafc73.tar.xz wireguard-apple-ba250fe1a5c57ed3fc2f18560c0ddd72cefafc73.zip |
Implement UpdateConfiguration AppIntent with Dictionary as input
Signed-off-by: Alessio Nossa <alessio.nossa@gmail.com>
-rw-r--r-- | Sources/WireguardAppIntents/AppIntents.strings | 18 | ||||
-rw-r--r-- | Sources/WireguardAppIntents/UpdateConfiguration.swift | 135 | ||||
-rw-r--r-- | WireGuard.xcodeproj/project.pbxproj | 4 |
3 files changed, 157 insertions, 0 deletions
diff --git a/Sources/WireguardAppIntents/AppIntents.strings b/Sources/WireguardAppIntents/AppIntents.strings index e539499..a752acb 100644 --- a/Sources/WireguardAppIntents/AppIntents.strings +++ b/Sources/WireguardAppIntents/AppIntents.strings @@ -10,3 +10,21 @@ "getPeersIntentDescription" = "Get list of public keys of peers in the selected configuration"; "getPeersIntentTunnelParameterTitle" = "Tunnel"; "getPeersIntentSummary ${tunnelName}" = "Get peers of ${tunnelName}"; + +// Update configuration [Dictionary] +"updateConfigurationIntentName" = "Update Tunnel Configuration [Dictionary]"; +"updateConfigurationIntentDescription" = "Update peers configuration. Configuration must be provided as a JSON object that has the peers public keys as dictionary's keys and a nested dictionary with with the fields to update a as velue, like the following example. The fields you can update are: \"Endpoint\". +The fields and the peers you omit will not be modified. + +Example +{ \"Peer1_Public_Key_(Base64)\": { \"Endpoint\": \"1.2.3.4:4321\" }, + \"Peer1_Public_Key_(Base64)\": { \"Endpoint\": \"10.11.12.13:6789\"} } + +In the Shortcuts app, you can pass directly a Dictionary object."; +"updateConfigurationIntentTunnelParameterTitle" = "Tunnel"; +"updateConfigurationIntentConfigurationParameterTitle" = "Configuration"; +"updateConfigurationIntentSummary ${tunnelName}" = "Update ${tunnelName} configuration"; + +"updateConfigurationIntentInvalidConfigurationError" = "The configuration update provided is not in the right format. Make sure you pass configuration as described in Action description."; +"updateConfigurationIntentJsonDecodingError" = "The configuration update provided is not a valid JSON object. Make sure you pass configuration as described in Action description."; +"updateConfigurationIntentMalformedPublicKeyError %@" = "The key \"%1$@\" is not a valid Public Key encoded in Base64 format."; diff --git a/Sources/WireguardAppIntents/UpdateConfiguration.swift b/Sources/WireguardAppIntents/UpdateConfiguration.swift new file mode 100644 index 0000000..686d154 --- /dev/null +++ b/Sources/WireguardAppIntents/UpdateConfiguration.swift @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved. + +import Foundation +import AppIntents + +@available(iOS 16.0, macOS 13.0, watchOS 9.0, tvOS 16.0, *) +struct UpdateConfiguration: AppIntent { + + static var title = LocalizedStringResource("updateConfigurationIntentName", table: "AppIntents") + static var description = IntentDescription( + LocalizedStringResource("updateConfigurationIntentDescription", table: "AppIntents") + ) + + @Parameter( + title: LocalizedStringResource("updateConfigurationIntentTunnelParameterTitle", table: "AppIntents"), + optionsProvider: TunnelsOptionsProvider() + ) + var tunnelName: String + + @Parameter( + title: LocalizedStringResource("updateConfigurationIntentConfigurationParameterTitle", table: "AppIntents"), + default: #"{"Peer Public Key": {"Endpoint":"1.2.3.4:5678"} }"#, + // Multiline not working in iOS 16.4 (FB12099849) + inputOptions: .init(capitalizationType: .none, multiline: true, autocorrect: false, + smartQuotes: false, smartDashes: false) + ) + var configurationsString: String + + @Dependency + var tunnelsManager: TunnelsManager + + func perform() async throws -> some IntentResult { + guard let tunnelContainer = tunnelsManager.tunnel(named: tunnelName) else { + throw UpdateConfigurationIntentError.wrongTunnel(name: tunnelName) + } + + guard let tunnelConfiguration = tunnelContainer.tunnelConfiguration else { + throw UpdateConfigurationIntentError.missingConfiguration + } + + let confugurationsUpdates = try extractConfigurationDictionary(from: configurationsString) + + let newConfiguration = try buildNewConfiguration(from: tunnelConfiguration, configurationUpdates: confugurationsUpdates) + + 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("updateConfigurationIntentSummary \(\.$tunnelName)", table: "AppIntents") { + \.$configurationsString + } + } + + private func extractConfigurationDictionary(from configurationString: String) throws -> [String: [String: String]] { + let configurationsData = Data(configurationsString.utf8) + + var configurations: [String: [String: String]] + do { + let decodedJson = try JSONSerialization.jsonObject(with: configurationsData, options: []) + // Make sure this JSON is in the format we expect + if let configDictionary = decodedJson as? [String: [String: String]] { + configurations = configDictionary + } else { + throw UpdateConfigurationIntentError.invalidConfiguration + } + } catch { + wg_log(.error, message: "Failed to decode configuration data in JSON format for \(tunnelName). \(error.localizedDescription)") + + throw UpdateConfigurationIntentError.jsonDecodingFailure + } + + return configurations + } + + private func buildNewConfiguration(from oldConfiguration: TunnelConfiguration, configurationUpdates: [String: [String: String]]) throws -> TunnelConfiguration { + var peers = oldConfiguration.peers + + for (peerPubKey, valuesToUpdate) in configurationUpdates { + if let peerIndex = peers.firstIndex(where: { $0.publicKey.base64Key == peerPubKey }) { + if let endpointString = valuesToUpdate[kEndpointConfigurationUpdateDictionaryKey] { + if let newEntpoint = Endpoint(from: endpointString) { + peers[peerIndex].endpoint = newEntpoint + } else { + wg_log(.debug, message: "Failed to convert \(endpointString) to Endpoint") + } + } + } else { + wg_log(.debug, message: "Failed to find peer \(peerPubKey) in tunnel with name \(tunnelName). Adding it.") + + guard let pubKeyEncoded = PublicKey(base64Key: peerPubKey) else { + throw UpdateConfigurationIntentError.malformedPublicKey(key: peerPubKey) + } + let newPeerConfig = PeerConfiguration(publicKey: pubKeyEncoded) + peers.append(newPeerConfig) + } + } + + 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, *) +enum UpdateConfigurationIntentError: Swift.Error, CustomLocalizedStringResourceConvertible { + case wrongTunnel(name: String) + case missingConfiguration + case invalidConfiguration + case jsonDecodingFailure + 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 .invalidConfiguration: + return LocalizedStringResource("updateConfigurationIntentInvalidConfigurationError", table: "AppIntents") + case .jsonDecodingFailure: + return LocalizedStringResource("updateConfigurationIntentJsonDecodingError", table: "AppIntents") + case .malformedPublicKey(let malformedKey): + return LocalizedStringResource("updateConfigurationIntentMalformedPublicKeyError \(malformedKey)", table: "AppIntents") + } + } +} diff --git a/WireGuard.xcodeproj/project.pbxproj b/WireGuard.xcodeproj/project.pbxproj index 8c97b5c..2be99b7 100644 --- a/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard.xcodeproj/project.pbxproj @@ -205,6 +205,7 @@ 6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; }; 6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */; }; 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; }; + A625F05329C4C627005EF23D /* UpdateConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = A625F04E29C4C627005EF23D /* UpdateConfiguration.swift */; }; A625F05529C4C627005EF23D /* GetPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A625F05029C4C627005EF23D /* GetPeers.swift */; }; A6E361F829D8758500FFF234 /* AppIntents.strings in Resources */ = {isa = PBXBuildFile; fileRef = A6E361F729D8758500FFF234 /* AppIntents.strings */; }; A6E361FE29D9B18C00FFF234 /* TunnelsOptionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */; }; @@ -441,6 +442,7 @@ 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; }; 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandOption.swift; sourceTree = "<group>"; }; 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; }; + A625F04E29C4C627005EF23D /* UpdateConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateConfiguration.swift; sourceTree = "<group>"; }; A625F05029C4C627005EF23D /* GetPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GetPeers.swift; sourceTree = "<group>"; }; A6E361F729D8758500FFF234 /* AppIntents.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = AppIntents.strings; sourceTree = "<group>"; }; A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsOptionsProvider.swift; sourceTree = "<group>"; }; @@ -821,6 +823,7 @@ A625F04C29C4C627005EF23D /* WireguardAppIntents */ = { isa = PBXGroup; children = ( + A625F04E29C4C627005EF23D /* UpdateConfiguration.swift */, A625F05029C4C627005EF23D /* GetPeers.swift */, A6E361F729D8758500FFF234 /* AppIntents.strings */, A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */, @@ -1435,6 +1438,7 @@ 6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */, 585B10622577E293004F691E /* DNSServer.swift in Sources */, 6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */, + A625F05329C4C627005EF23D /* UpdateConfiguration.swift in Sources */, 6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */, 6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */, 5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */, |