]> git.ipfire.org Git - thirdparty/wireguard-apple.git/commitdiff
Implement UpdateTunnelConfiguration App Intent
authorAlessio Nossa <alessio.nossa@gmail.com>
Tue, 11 Apr 2023 21:42:09 +0000 (23:42 +0200)
committerAlessio Nossa <alessio.nossa@gmail.com>
Tue, 11 Apr 2023 22:44:02 +0000 (00:44 +0200)
Signed-off-by: Alessio Nossa <alessio.nossa@gmail.com>
Sources/WireguardAppIntents/AppIntents.strings
Sources/WireguardAppIntents/UpdateTunnelConfiguration.swift [new file with mode: 0644]
WireGuard.xcodeproj/project.pbxproj

index 9ebdc8ea3a544471f356b4e40a1ed3a212369297..1c2064ea780e7f226f6ffd105ce24728f9e392c0 100644 (file)
 "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 (file)
index 0000000..51fc605
--- /dev/null
@@ -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<PeerConfiguration>.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<AppIntentsPeer> {
+        // 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")
+        }
+    }
+}
index ad1ef98daab5253ead0a8708a92a13a711593861..119d25a1ff2d1755475df2aa65f96bc639ced3db 100644 (file)
                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 */
                6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.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>"; };
+               A6E361F929D9821100FFF234 /* UpdateTunnelConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateTunnelConfiguration.swift; sourceTree = "<group>"; };
                A6E361FB29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildPeerConfigurationUpdate.swift; sourceTree = "<group>"; };
                A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsOptionsProvider.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
                A625F04C29C4C627005EF23D /* WireguardAppIntents */ = {
                        isa = PBXGroup;
                        children = (
+                               A6E361F929D9821100FFF234 /* UpdateTunnelConfiguration.swift */,
                                A6E361FB29D9AEEA00FFF234 /* BuildPeerConfigurationUpdate.swift */,
                                A625F05029C4C627005EF23D /* GetPeers.swift */,
                                A6E361F729D8758500FFF234 /* AppIntents.strings */,
                                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 */,