passepartout-apple/Passepartout/App/macOS/Scenes/Service/Customization/ProfileCustomizationViewCon...

270 lines
11 KiB
Swift

//
// ProfileCustomizationViewController.swift
// Passepartout
//
// Created by Davide De Rosa on 6/19/19.
// Copyright (c) 2021 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import Cocoa
import PassepartoutCore
import TunnelKit
protocol ProfileCustomization: AnyObject {
var profile: ConnectionProfile? { get set }
var delegate: ProfileCustomizationDelegate? { get set }
}
protocol ProfileCustomizationDelegate: AnyObject {
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithAddress newAddress: String?)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithProtocol newEndpointProtocol: EndpointProtocol?)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdatePreset newPreset: InfrastructurePreset)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateConfiguration newConfiguration: OpenVPN.ConfigurationBuilder)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateTrustedNetworks newTrustedNetworks: TrustedNetworks)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateGateway choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateDNS choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateProxy choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings)
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateMTU choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings)
}
class ProfileCustomizationContainerViewController: NSViewController {
@IBOutlet private weak var buttonOK: NSButton!
@IBOutlet private weak var buttonCancel: NSButton!
fileprivate weak var endpointController: EndpointViewController?
fileprivate weak var dnsController: DNSViewController?
fileprivate weak var proxyController: ProxyViewController?
var profile: ConnectionProfile?
// MARK: Pending (provider)
private var pendingAddress: String?
private var pendingProtocol: EndpointProtocol?
private var pendingPreset: InfrastructurePreset?
// MARK: Pending (host)
private var pendingParameters: OpenVPN.ConfigurationBuilder?
// MARK: Pending
private var pendingTrustedNetworks: TrustedNetworks?
private var pendingChoices: ProfileNetworkChoices?
private let pendingManualNetworkSettings = ProfileNetworkSettings()
override func viewDidLoad() {
super.viewDidLoad()
buttonOK.title = L10n.Core.Global.ok
buttonCancel.title = L10n.Core.Global.cancel
pendingAddress = (profile as? ProviderConnectionProfile)?.customAddress
pendingProtocol = (profile as? ProviderConnectionProfile)?.customProtocol
pendingPreset = (profile as? ProviderConnectionProfile)?.preset
pendingTrustedNetworks = profile?.trustedNetworks
pendingParameters = (profile as? HostConnectionProfile)?.parameters.sessionConfiguration.builder()
pendingChoices = ProfileNetworkChoices.with(profile: profile)
}
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
guard let customVC = segue.destinationController as? ProfileCustomizationViewController else {
return
}
customVC.containerController = self
customVC.profile = profile
}
// MARK: Actions
@IBAction private func commitChanges(_ sender: Any?) {
dnsController?.commitManualSettings()
proxyController?.commitManualSettings()
if let providerProfile = profile as? ProviderConnectionProfile {
if let pending = pendingPreset {
providerProfile.presetId = pending.id
}
} else if let hostProfile = profile as? HostConnectionProfile, let pendingParameters = pendingParameters {
var builder = hostProfile.parameters.builder()
builder.sessionConfiguration = pendingParameters.build()
hostProfile.parameters = builder.build()
}
profile?.customAddress = pendingAddress
profile?.customProtocol = pendingProtocol
profile?.trustedNetworks = pendingTrustedNetworks
if let choices = pendingChoices {
let settings = profile?.manualNetworkSettings ?? ProfileNetworkSettings()
if choices.gateway == .manual {
settings.copyGateway(from: pendingManualNetworkSettings)
}
if choices.dns == .manual {
settings.copyDNS(from: pendingManualNetworkSettings)
}
if choices.proxy == .manual {
settings.copyProxy(from: pendingManualNetworkSettings)
}
if choices.mtu == .manual {
settings.copyMTU(from: pendingManualNetworkSettings)
}
profile?.networkChoices = choices
profile?.manualNetworkSettings = settings
}
TransientStore.shared.serialize(withProfiles: true) // customize
if let profile = profile, TransientStore.shared.service.isActiveProfile(ProfileKey(profile)) {
let vpn = GracefulVPN(service: TransientStore.shared.service)
if vpn.isEnabled {
switch vpn.status {
case .connected, .connecting:
let alert = Macros.warning(
L10n.Core.Configuration.title,
L10n.Core.Configuration.Alerts.Commit.message
)
if alert.presentModally(
withOK: L10n.Core.Configuration.Alerts.Commit.Buttons.reconnect,
cancel: L10n.Core.Configuration.Alerts.Commit.Buttons.skip) {
vpn.reconnect(completionHandler: nil)
} else {
vpn.reinstall(completionHandler: nil)
}
default:
vpn.reinstall(completionHandler: nil)
}
}
}
presentingViewController?.dismiss(self)
}
}
extension ProfileCustomizationContainerViewController: ProfileCustomizationDelegate {
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithAddress newAddress: String?) {
pendingAddress = newAddress
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateEndpointWithProtocol newEndpointProtocol: EndpointProtocol?) {
pendingProtocol = newEndpointProtocol
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdatePreset newPreset: InfrastructurePreset) {
pendingPreset = newPreset
// XXX: commit immediately to update endpoints
(profile as? ProviderConnectionProfile)?.presetId = newPreset.id
endpointController?.reloadEndpoints()
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateConfiguration newConfiguration: OpenVPN.ConfigurationBuilder) {
pendingParameters = newConfiguration
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateTrustedNetworks newTrustedNetworks: TrustedNetworks) {
pendingTrustedNetworks = newTrustedNetworks
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateGateway choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) {
pendingChoices?.gateway = choice
pendingManualNetworkSettings.gatewayPolicies = newSettings.gatewayPolicies
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateDNS choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) {
pendingChoices?.dns = choice
pendingManualNetworkSettings.dnsProtocol = newSettings.dnsProtocol
pendingManualNetworkSettings.dnsHTTPSURL = newSettings.dnsHTTPSURL
pendingManualNetworkSettings.dnsTLSServerName = newSettings.dnsTLSServerName
pendingManualNetworkSettings.dnsServers = newSettings.dnsServers
pendingManualNetworkSettings.dnsSearchDomains = newSettings.dnsSearchDomains
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateProxy choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) {
pendingChoices?.proxy = choice
pendingManualNetworkSettings.proxyAddress = newSettings.proxyAddress
pendingManualNetworkSettings.proxyPort = newSettings.proxyPort
pendingManualNetworkSettings.proxyBypassDomains = newSettings.proxyBypassDomains
}
func profileCustomization(_ profileCustomization: ProfileCustomization, didUpdateMTU choice: NetworkChoice, withManualSettings newSettings: ProfileNetworkSettings) {
pendingChoices?.mtu = choice
pendingManualNetworkSettings.mtuBytes = newSettings.mtuBytes
}
}
//
class ProfileCustomizationViewController: NSTabViewController {
fileprivate weak var containerController: ProfileCustomizationContainerViewController?
fileprivate var profile: ConnectionProfile? {
didSet {
for item in tabViewItems {
guard let custom = item.viewController as? ProfileCustomization else {
continue
}
custom.profile = profile
custom.delegate = containerController
if let vc = custom as? EndpointViewController {
containerController?.endpointController = vc
} else if let vc = custom as? DNSViewController {
containerController?.dnsController = vc
} else if let vc = custom as? ProxyViewController {
containerController?.proxyController = vc
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
let expectedTabs = 7
assert(tabViewItems.count == expectedTabs, "Customization tabs misconfigured (expected \(expectedTabs))")
tabViewItems[0].label = L10n.Core.Endpoint.title
tabViewItems[1].label = L10n.Core.Configuration.title
tabViewItems[2].label = L10n.Core.Service.Sections.Trusted.header
tabViewItems[3].label = L10n.Core.NetworkSettings.Gateway.title
tabViewItems[4].label = L10n.Core.NetworkSettings.Dns.title
tabViewItems[5].label = L10n.Core.NetworkSettings.Proxy.title
tabViewItems[6].label = L10n.Core.NetworkSettings.Mtu.title
}
}