Files
wireguard-apple-spm/Sources/WireguardAppIntents/UpdateConfiguration.swift
2023-04-12 00:44:02 +02:00

136 lines
5.9 KiB
Swift

// 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")
}
}
}