SimpleTunnel/AddEditConfiguration.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
This file contains the AddEditConfiguration class, which is responsible for controlling a view used to create or edit a VPN configuration. |
*/ |
import UIKit |
import NetworkExtension |
import Security |
import SimpleTunnelServices |
/// A view controller object for a table view containing input fields used to specify configuration parameters for a VPN configuration. |
class AddEditConfiguration: ConfigurationParametersViewController { |
// MARK: Properties |
/// A table view cell containing the text field where the name of the configuration is entered. |
@IBOutlet weak var nameCell: TextFieldCell! |
/// A table view cell containing the text field where the server address of the configuration is entered. |
@IBOutlet weak var serverAddressCell: TextFieldCell! |
/// A table view cell containing the text field where the username of the configuration is entered. |
@IBOutlet weak var usernameCell: TextFieldCell! |
/// A table view cell containing the text field where the password of the configuration is entered. |
@IBOutlet weak var passwordCell: TextFieldCell! |
/// A table view cell containing a switch used to enable and disable Connect On Demand for the configuration. |
@IBOutlet weak var onDemandCell: SwitchCell! |
/// A table view cell containing a switch used to enable and disable proxy settings for the configuration. |
@IBOutlet weak var proxiesCell: SwitchCell! |
/// A table view cell containing a switch used to enable and disable Disconnect On Sleep for the configuration. |
@IBOutlet weak var disconnectOnSleepCell: SwitchCell! |
/// A table view cell that when tapped transitions the app to a view where the Connect On Demand rules are managed. |
@IBOutlet weak var onDemandRulesCell: UITableViewCell! |
/// A table view cell that when tapped transitions the app to a view where the proxy settings are managed. |
@IBOutlet weak var proxySettingsCell: UITableViewCell! |
/// The NEVPNManager object corresponding to the configuration being added or edited. |
var targetManager: NEVPNManager = NEVPNManager.shared() |
// MARK: UIViewController |
/// Handle the event of the view being loaded into memory. |
override func viewDidLoad() { |
super.viewDidLoad() |
// Set up the table view cells |
cells = [ |
nameCell, |
serverAddressCell, |
usernameCell, |
passwordCell, |
onDemandCell, |
proxiesCell, |
disconnectOnSleepCell |
].flatMap { $0 } |
// The switch in proxiesCell controls the display of proxySettingsCell |
proxiesCell.dependentCells = [ proxySettingsCell ] |
proxiesCell.getIndexPath = { |
return self.getIndexPathOfCell(self.proxiesCell) |
} |
proxiesCell.valueChanged = { |
self.updateCellsWithDependentsOfCell(self.proxiesCell) |
} |
// The switch in onDemandCell controls the display of onDemandRulesCell |
onDemandCell.dependentCells = [ onDemandRulesCell ] |
onDemandCell.getIndexPath = { |
return self.getIndexPathOfCell(self.onDemandCell) |
} |
onDemandCell.valueChanged = { |
self.updateCellsWithDependentsOfCell(self.onDemandCell) |
self.targetManager.isOnDemandEnabled = self.onDemandCell.isOn |
} |
disconnectOnSleepCell.valueChanged = { |
self.targetManager.protocolConfiguration?.disconnectOnSleep = self.disconnectOnSleepCell.isOn |
} |
nameCell.valueChanged = { |
self.targetManager.localizedDescription = self.nameCell.textField.text |
} |
serverAddressCell.valueChanged = { |
self.targetManager.protocolConfiguration?.serverAddress = self.serverAddressCell.textField.text |
} |
usernameCell.valueChanged = { |
self.targetManager.protocolConfiguration?.username = self.usernameCell.textField.text |
} |
} |
/// Handle the event of the view being displayed. |
override func viewWillAppear(_ animated: Bool) { |
super.viewWillAppear(animated) |
tableView.reloadData() |
// Set the text fields and switches per the settings in the configuration. |
nameCell.textField.text = targetManager.localizedDescription |
serverAddressCell.textField.text = targetManager.protocolConfiguration?.serverAddress |
usernameCell.textField.text = targetManager.protocolConfiguration?.username |
if let passRef = targetManager.protocolConfiguration?.passwordReference { |
passwordCell.textField.text = getPasswordWithPersistentReference(passRef) |
} |
else { |
passwordCell.textField.text = nil |
} |
disconnectOnSleepCell.isOn = targetManager.protocolConfiguration?.disconnectOnSleep ?? false |
onDemandCell.isOn = targetManager.isOnDemandEnabled |
onDemandRulesCell.detailTextLabel?.text = getDescriptionForListValue(targetManager.onDemandRules, itemDescription: "rule") |
proxiesCell.isOn = targetManager.protocolConfiguration?.proxySettings != nil |
} |
/// Set up the destination view controller of a segue away from this view controller. |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { |
guard let identifier = segue.identifier else { return } |
switch identifier { |
case "edit-proxy-settings": |
// The user tapped on the proxy settings cell. |
guard let controller = segue.destination as? ProxySettingsController else { break } |
if targetManager.protocolConfiguration?.proxySettings == nil { |
targetManager.protocolConfiguration?.proxySettings = NEProxySettings() |
} |
controller.targetConfiguration = targetManager.protocolConfiguration ?? NETunnelProviderProtocol() |
case "edit-on-demand-rules": |
// The user tapped on the Connect On Demand rules cell. |
guard let controller = segue.destination as? OnDemandRuleListController else { break } |
controller.targetManager = targetManager |
default: |
break |
} |
} |
// MARK: Interface |
/// Set the target configuration and the title to display in the view. |
func setTargetManager(_ manager: NEVPNManager?, title: String?) { |
if let newManager = manager { |
// A manager was given, so an existing configuration is being edited. |
targetManager = newManager |
} |
else { |
// No manager was given, create a new configuration. |
let newManager = NETunnelProviderManager() |
newManager.protocolConfiguration = NETunnelProviderProtocol() |
newManager.localizedDescription = "Demo VPN" |
newManager.protocolConfiguration?.serverAddress = "TunnelServer" |
targetManager = newManager |
} |
navigationItem.title = title |
} |
/// Save the configuration to the Network Extension preferences. |
@IBAction func saveTargetManager(_ sender: AnyObject) { |
if !proxiesCell.isOn { |
targetManager.protocolConfiguration?.proxySettings = nil |
} |
targetManager.saveToPreferences { error in |
if let saveError = error { |
simpleTunnelLog("Failed to save the configuration: \(saveError)") |
return |
} |
// Transition back to the configuration list view. |
self.performSegue(withIdentifier: "save-configuration", sender: sender) |
} |
} |
/// Save a password in the keychain. |
func savePassword(_ password: String, inKeychainItem: Data?) -> Data? { |
guard let passwordData = password.data(using: String.Encoding.utf8, allowLossyConversion: false) else { return nil } |
var status = errSecSuccess |
if let persistentReference = inKeychainItem { |
// A persistent reference was given, update the corresponding keychain item. |
let query: [NSObject: AnyObject] = [ |
kSecValuePersistentRef : persistentReference as AnyObject, |
kSecReturnAttributes : kCFBooleanTrue |
] |
var result: AnyObject? |
// Get the current attributes for the item. |
status = SecItemCopyMatching(query as CFDictionary, &result) |
if let attributes = result as? [NSObject: AnyObject] , status == errSecSuccess { |
// Update the attributes with the new data. |
var updateQuery = [NSObject: AnyObject]() |
updateQuery[kSecClass] = kSecClassGenericPassword |
updateQuery[kSecAttrService] = attributes[kSecAttrService] |
var newAttributes = attributes |
newAttributes[kSecValueData] = passwordData as AnyObject? |
status = SecItemUpdate(updateQuery as CFDictionary, newAttributes as CFDictionary) |
if status == errSecSuccess { |
return persistentReference |
} |
} |
} |
if inKeychainItem == nil || status != errSecSuccess { |
// No persistent reference was provided, or the update failed. Add a new keychain item. |
let attributes: [NSObject: AnyObject] = [ |
kSecAttrService : UUID().uuidString as AnyObject, |
kSecValueData : passwordData as AnyObject, |
kSecAttrAccessible : kSecAttrAccessibleAlways, |
kSecClass : kSecClassGenericPassword, |
kSecReturnPersistentRef : kCFBooleanTrue |
] |
var result: AnyObject? |
status = SecItemAdd(attributes as CFDictionary, &result) |
if let newPersistentReference = result as? Data , status == errSecSuccess { |
return newPersistentReference |
} |
} |
return nil |
} |
/// Remove a password from the keychain. |
func removePasswordWithPersistentReference(_ persistentReference: Data) { |
let query: [NSObject: AnyObject] = [ |
kSecClass : kSecClassGenericPassword, |
kSecValuePersistentRef : persistentReference as AnyObject |
] |
let status = SecItemDelete(query as CFDictionary) |
if status != errSecSuccess { |
simpleTunnelLog("Failed to delete a password: \(status)") |
} |
} |
/// Get a password from the keychain. |
func getPasswordWithPersistentReference(_ persistentReference: Data) -> String? { |
var result: String? |
let query: [NSObject: AnyObject] = [ |
kSecClass : kSecClassGenericPassword, |
kSecReturnData : kCFBooleanTrue, |
kSecValuePersistentRef : persistentReference as AnyObject |
] |
var returnValue: AnyObject? |
let status = SecItemCopyMatching(query as CFDictionary, &returnValue) |
if let passwordData = returnValue as? Data , status == errSecSuccess { |
result = NSString(data: passwordData, encoding: String.Encoding.utf8.rawValue) as? String |
} |
return result |
} |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-10-04