Move all source files to Sources/ and rename WireGuardKit targets
Signed-off-by: Andrej Mihajlov <and@mullvad.net>
This commit is contained in:
@@ -0,0 +1,297 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018-2019 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Cocoa
|
||||
import WireGuardKit
|
||||
|
||||
protocol TunnelEditViewControllerDelegate: class {
|
||||
func tunnelSaved(tunnel: TunnelContainer)
|
||||
func tunnelEditingCancelled()
|
||||
}
|
||||
|
||||
class TunnelEditViewController: NSViewController {
|
||||
|
||||
let nameRow: EditableKeyValueRow = {
|
||||
let nameRow = EditableKeyValueRow()
|
||||
nameRow.key = tr(format: "macFieldKey (%@)", TunnelViewModel.InterfaceField.name.localizedUIString)
|
||||
return nameRow
|
||||
}()
|
||||
|
||||
let publicKeyRow: KeyValueRow = {
|
||||
let publicKeyRow = KeyValueRow()
|
||||
publicKeyRow.key = tr(format: "macFieldKey (%@)", TunnelViewModel.InterfaceField.publicKey.localizedUIString)
|
||||
return publicKeyRow
|
||||
}()
|
||||
|
||||
let textView: ConfTextView = {
|
||||
let textView = ConfTextView()
|
||||
let minWidth: CGFloat = 120
|
||||
let minHeight: CGFloat = 0
|
||||
textView.minSize = NSSize(width: 0, height: minHeight)
|
||||
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
textView.autoresizingMask = [.width] // Width should be based on superview width
|
||||
textView.isHorizontallyResizable = false // Width shouldn't be based on content
|
||||
textView.isVerticallyResizable = true // Height should be based on content
|
||||
if let textContainer = textView.textContainer {
|
||||
textContainer.size = NSSize(width: minWidth, height: CGFloat.greatestFiniteMagnitude)
|
||||
textContainer.widthTracksTextView = true
|
||||
}
|
||||
NSLayoutConstraint.activate([
|
||||
textView.widthAnchor.constraint(greaterThanOrEqualToConstant: minWidth),
|
||||
textView.heightAnchor.constraint(greaterThanOrEqualToConstant: minHeight)
|
||||
])
|
||||
return textView
|
||||
}()
|
||||
|
||||
let onDemandControlsRow = OnDemandControlsRow()
|
||||
|
||||
let scrollView: NSScrollView = {
|
||||
let scrollView = NSScrollView()
|
||||
scrollView.hasVerticalScroller = true
|
||||
scrollView.autohidesScrollers = true
|
||||
scrollView.borderType = .bezelBorder
|
||||
return scrollView
|
||||
}()
|
||||
|
||||
let excludePrivateIPsCheckbox: NSButton = {
|
||||
let checkbox = NSButton()
|
||||
checkbox.title = tr("tunnelPeerExcludePrivateIPs")
|
||||
checkbox.setButtonType(.switch)
|
||||
checkbox.state = .off
|
||||
return checkbox
|
||||
}()
|
||||
|
||||
let discardButton: NSButton = {
|
||||
let button = NSButton()
|
||||
button.title = tr("macEditDiscard")
|
||||
button.setButtonType(.momentaryPushIn)
|
||||
button.bezelStyle = .rounded
|
||||
return button
|
||||
}()
|
||||
|
||||
let saveButton: NSButton = {
|
||||
let button = NSButton()
|
||||
button.title = tr("macEditSave")
|
||||
button.setButtonType(.momentaryPushIn)
|
||||
button.bezelStyle = .rounded
|
||||
button.keyEquivalent = "s"
|
||||
button.keyEquivalentModifierMask = [.command]
|
||||
return button
|
||||
}()
|
||||
|
||||
let tunnelsManager: TunnelsManager
|
||||
let tunnel: TunnelContainer?
|
||||
var onDemandViewModel: ActivateOnDemandViewModel
|
||||
|
||||
weak var delegate: TunnelEditViewControllerDelegate?
|
||||
|
||||
var privateKeyObservationToken: AnyObject?
|
||||
var hasErrorObservationToken: AnyObject?
|
||||
var singlePeerAllowedIPsObservationToken: AnyObject?
|
||||
|
||||
var dnsServersAddedToAllowedIPs: String?
|
||||
|
||||
init(tunnelsManager: TunnelsManager, tunnel: TunnelContainer?) {
|
||||
self.tunnelsManager = tunnelsManager
|
||||
self.tunnel = tunnel
|
||||
self.onDemandViewModel = tunnel != nil ? ActivateOnDemandViewModel(tunnel: tunnel!) : ActivateOnDemandViewModel()
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
func populateFields() {
|
||||
if let tunnel = tunnel {
|
||||
// Editing an existing tunnel
|
||||
let tunnelConfiguration = tunnel.tunnelConfiguration!
|
||||
nameRow.value = tunnel.name
|
||||
textView.string = tunnelConfiguration.asWgQuickConfig()
|
||||
publicKeyRow.value = tunnelConfiguration.interface.privateKey.publicKey.base64Key
|
||||
textView.privateKeyString = tunnelConfiguration.interface.privateKey.base64Key
|
||||
let singlePeer = tunnelConfiguration.peers.count == 1 ? tunnelConfiguration.peers.first : nil
|
||||
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: singlePeer?.allowedIPs.map { $0.stringRepresentation })
|
||||
dnsServersAddedToAllowedIPs = excludePrivateIPsCheckbox.state == .on ? tunnelConfiguration.interface.dns.map { $0.stringRepresentation }.joined(separator: ", ") : nil
|
||||
} else {
|
||||
// Creating a new tunnel
|
||||
let privateKey = PrivateKey()
|
||||
let bootstrappingText = "[Interface]\nPrivateKey = \(privateKey.base64Key)\n"
|
||||
publicKeyRow.value = privateKey.publicKey.base64Key
|
||||
textView.string = bootstrappingText
|
||||
updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: nil)
|
||||
dnsServersAddedToAllowedIPs = nil
|
||||
}
|
||||
privateKeyObservationToken = textView.observe(\.privateKeyString) { [weak publicKeyRow] textView, _ in
|
||||
if let privateKeyString = textView.privateKeyString,
|
||||
let privateKey = PrivateKey(base64Key: privateKeyString) {
|
||||
publicKeyRow?.value = privateKey.publicKey.base64Key
|
||||
} else {
|
||||
publicKeyRow?.value = ""
|
||||
}
|
||||
}
|
||||
hasErrorObservationToken = textView.observe(\.hasError) { [weak saveButton] textView, _ in
|
||||
saveButton?.isEnabled = !textView.hasError
|
||||
}
|
||||
singlePeerAllowedIPsObservationToken = textView.observe(\.singlePeerAllowedIPs) { [weak self] textView, _ in
|
||||
self?.updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: textView.singlePeerAllowedIPs)
|
||||
}
|
||||
}
|
||||
|
||||
override func loadView() {
|
||||
populateFields()
|
||||
|
||||
scrollView.documentView = textView
|
||||
|
||||
saveButton.target = self
|
||||
saveButton.action = #selector(handleSaveAction)
|
||||
|
||||
discardButton.target = self
|
||||
discardButton.action = #selector(handleDiscardAction)
|
||||
|
||||
excludePrivateIPsCheckbox.target = self
|
||||
excludePrivateIPsCheckbox.action = #selector(excludePrivateIPsCheckboxToggled(sender:))
|
||||
|
||||
onDemandControlsRow.onDemandViewModel = onDemandViewModel
|
||||
|
||||
let margin: CGFloat = 20
|
||||
let internalSpacing: CGFloat = 10
|
||||
|
||||
let editorStackView = NSStackView(views: [nameRow, publicKeyRow, onDemandControlsRow, scrollView])
|
||||
editorStackView.orientation = .vertical
|
||||
editorStackView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
editorStackView.spacing = internalSpacing
|
||||
|
||||
let buttonRowStackView = NSStackView()
|
||||
buttonRowStackView.setViews([discardButton, saveButton], in: .trailing)
|
||||
buttonRowStackView.addView(excludePrivateIPsCheckbox, in: .leading)
|
||||
buttonRowStackView.orientation = .horizontal
|
||||
buttonRowStackView.spacing = internalSpacing
|
||||
|
||||
let containerView = NSStackView(views: [editorStackView, buttonRowStackView])
|
||||
containerView.orientation = .vertical
|
||||
containerView.edgeInsets = NSEdgeInsets(top: margin, left: margin, bottom: margin, right: margin)
|
||||
containerView.setHuggingPriority(.defaultHigh, for: .horizontal)
|
||||
containerView.spacing = internalSpacing
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
containerView.widthAnchor.constraint(greaterThanOrEqualToConstant: 180),
|
||||
containerView.heightAnchor.constraint(greaterThanOrEqualToConstant: 240)
|
||||
])
|
||||
containerView.frame = NSRect(x: 0, y: 0, width: 600, height: 480)
|
||||
|
||||
self.view = containerView
|
||||
}
|
||||
|
||||
func setUserInteractionEnabled(_ enabled: Bool) {
|
||||
view.window?.ignoresMouseEvents = !enabled
|
||||
nameRow.valueLabel.isEditable = enabled
|
||||
textView.isEditable = enabled
|
||||
onDemandControlsRow.onDemandSSIDsField.isEnabled = enabled
|
||||
}
|
||||
|
||||
@objc func handleSaveAction() {
|
||||
let name = nameRow.value
|
||||
guard !name.isEmpty else {
|
||||
ErrorPresenter.showErrorAlert(title: tr("macAlertNameIsEmpty"), message: "", from: self)
|
||||
return
|
||||
}
|
||||
|
||||
onDemandControlsRow.saveToViewModel()
|
||||
let onDemandOption = onDemandViewModel.toOnDemandOption()
|
||||
|
||||
let isTunnelModifiedWithoutChangingName = (tunnel != nil && tunnel!.name == name)
|
||||
guard isTunnelModifiedWithoutChangingName || tunnelsManager.tunnel(named: name) == nil else {
|
||||
ErrorPresenter.showErrorAlert(title: tr(format: "macAlertDuplicateName (%@)", name), message: "", from: self)
|
||||
return
|
||||
}
|
||||
|
||||
var tunnelConfiguration: TunnelConfiguration
|
||||
do {
|
||||
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value)
|
||||
} catch let error as WireGuardAppError {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
} catch {
|
||||
fatalError()
|
||||
}
|
||||
|
||||
if excludePrivateIPsCheckbox.state == .on, tunnelConfiguration.peers.count == 1, let dnsServersAddedToAllowedIPs = dnsServersAddedToAllowedIPs {
|
||||
// Update the DNS servers in the AllowedIPs
|
||||
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
||||
let originalAllowedIPs = tunnelViewModel.peersData[0][.allowedIPs].splitToArray(trimmingCharacters: .whitespacesAndNewlines)
|
||||
let dnsServersInAllowedIPs = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(dnsServersAddedToAllowedIPs.splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||
let dnsServersCurrent = TunnelViewModel.PeerData.normalizedIPAddressRangeStrings(tunnelViewModel.interfaceData[.dns].splitToArray(trimmingCharacters: .whitespacesAndNewlines))
|
||||
let modifiedAllowedIPs = originalAllowedIPs.filter { !dnsServersInAllowedIPs.contains($0) } + dnsServersCurrent
|
||||
tunnelViewModel.peersData[0][.allowedIPs] = modifiedAllowedIPs.joined(separator: ", ")
|
||||
let saveResult = tunnelViewModel.save()
|
||||
if case .saved(let modifiedTunnelConfiguration) = saveResult {
|
||||
tunnelConfiguration = modifiedTunnelConfiguration
|
||||
}
|
||||
}
|
||||
|
||||
setUserInteractionEnabled(false)
|
||||
|
||||
if let tunnel = tunnel {
|
||||
// We're modifying an existing tunnel
|
||||
tunnelsManager.modify(tunnel: tunnel, tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] error in
|
||||
guard let self = self else { return }
|
||||
self.setUserInteractionEnabled(true)
|
||||
if let error = error {
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
return
|
||||
}
|
||||
self.delegate?.tunnelSaved(tunnel: tunnel)
|
||||
self.presentingViewController?.dismiss(self)
|
||||
}
|
||||
} else {
|
||||
// We're creating a new tunnel
|
||||
self.tunnelsManager.add(tunnelConfiguration: tunnelConfiguration, onDemandOption: onDemandOption) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
self.setUserInteractionEnabled(true)
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
ErrorPresenter.showErrorAlert(error: error, from: self)
|
||||
case .success(let tunnel):
|
||||
self.delegate?.tunnelSaved(tunnel: tunnel)
|
||||
self.presentingViewController?.dismiss(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func handleDiscardAction() {
|
||||
delegate?.tunnelEditingCancelled()
|
||||
presentingViewController?.dismiss(self)
|
||||
}
|
||||
|
||||
func updateExcludePrivateIPsVisibility(singlePeerAllowedIPs: [String]?) {
|
||||
let shouldAllowExcludePrivateIPsControl: Bool
|
||||
let excludePrivateIPsValue: Bool
|
||||
if let singlePeerAllowedIPs = singlePeerAllowedIPs {
|
||||
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: true, allowedIPs: Set<String>(singlePeerAllowedIPs))
|
||||
} else {
|
||||
(shouldAllowExcludePrivateIPsControl, excludePrivateIPsValue) = TunnelViewModel.PeerData.excludePrivateIPsFieldStates(isSinglePeer: false, allowedIPs: Set<String>())
|
||||
}
|
||||
excludePrivateIPsCheckbox.isHidden = !shouldAllowExcludePrivateIPsControl
|
||||
excludePrivateIPsCheckbox.state = excludePrivateIPsValue ? .on : .off
|
||||
}
|
||||
|
||||
@objc func excludePrivateIPsCheckboxToggled(sender: AnyObject?) {
|
||||
guard let excludePrivateIPsCheckbox = sender as? NSButton else { return }
|
||||
guard let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: textView.string, called: nameRow.value) else { return }
|
||||
let isOn = excludePrivateIPsCheckbox.state == .on
|
||||
let tunnelViewModel = TunnelViewModel(tunnelConfiguration: tunnelConfiguration)
|
||||
tunnelViewModel.peersData.first?.excludePrivateIPsValueChanged(isOn: isOn, dnsServers: tunnelViewModel.interfaceData[.dns], oldDNSServers: dnsServersAddedToAllowedIPs)
|
||||
if let modifiedConfig = tunnelViewModel.asWgQuickConfig() {
|
||||
textView.setConfText(modifiedConfig)
|
||||
dnsServersAddedToAllowedIPs = isOn ? tunnelViewModel.interfaceData[.dns] : nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelEditViewController {
|
||||
override func cancelOperation(_ sender: Any?) {
|
||||
handleDiscardAction()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user