Implement UpdateConfiguration AppIntent with Dictionary as input
Signed-off-by: Alessio Nossa <alessio.nossa@gmail.com>
This commit is contained in:
@@ -10,3 +10,21 @@
|
|||||||
"getPeersIntentDescription" = "Get list of public keys of peers in the selected configuration";
|
"getPeersIntentDescription" = "Get list of public keys of peers in the selected configuration";
|
||||||
"getPeersIntentTunnelParameterTitle" = "Tunnel";
|
"getPeersIntentTunnelParameterTitle" = "Tunnel";
|
||||||
"getPeersIntentSummary ${tunnelName}" = "Get peers of ${tunnelName}";
|
"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.";
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -205,6 +205,7 @@
|
|||||||
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
|
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
|
||||||
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandOption.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 */; };
|
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 */; };
|
A625F05529C4C627005EF23D /* GetPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = A625F05029C4C627005EF23D /* GetPeers.swift */; };
|
||||||
A6E361F829D8758500FFF234 /* AppIntents.strings in Resources */ = {isa = PBXBuildFile; fileRef = A6E361F729D8758500FFF234 /* AppIntents.strings */; };
|
A6E361F829D8758500FFF234 /* AppIntents.strings in Resources */ = {isa = PBXBuildFile; fileRef = A6E361F729D8758500FFF234 /* AppIntents.strings */; };
|
||||||
A6E361FE29D9B18C00FFF234 /* TunnelsOptionsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelsOptionsProvider.swift; sourceTree = "<group>"; };
|
||||||
@@ -821,6 +823,7 @@
|
|||||||
A625F04C29C4C627005EF23D /* WireguardAppIntents */ = {
|
A625F04C29C4C627005EF23D /* WireguardAppIntents */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
A625F04E29C4C627005EF23D /* UpdateConfiguration.swift */,
|
||||||
A625F05029C4C627005EF23D /* GetPeers.swift */,
|
A625F05029C4C627005EF23D /* GetPeers.swift */,
|
||||||
A6E361F729D8758500FFF234 /* AppIntents.strings */,
|
A6E361F729D8758500FFF234 /* AppIntents.strings */,
|
||||||
A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */,
|
A6E361FD29D9B18C00FFF234 /* TunnelsOptionsProvider.swift */,
|
||||||
@@ -1435,6 +1438,7 @@
|
|||||||
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */,
|
6F8F0D7722267C57000E8335 /* SSIDOptionEditTableViewController.swift in Sources */,
|
||||||
585B10622577E293004F691E /* DNSServer.swift in Sources */,
|
585B10622577E293004F691E /* DNSServer.swift in Sources */,
|
||||||
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
6FDEF7E62185EFB200D8FBF6 /* QRScanViewController.swift in Sources */,
|
||||||
|
A625F05329C4C627005EF23D /* UpdateConfiguration.swift in Sources */,
|
||||||
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
|
6FDB6D18224CC05A00EE4BC3 /* LogViewController.swift in Sources */,
|
||||||
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
6FFA5D952194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */,
|
||||||
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
5F4541A921C451D100994C13 /* TunnelStatus.swift in Sources */,
|
||||||
|
|||||||
Reference in New Issue
Block a user