Merge branch 'save-ovpn-to-documents'

This commit is contained in:
Davide De Rosa 2018-10-23 12:57:16 +02:00
commit 50c1984e6c
10 changed files with 159 additions and 7 deletions

View File

@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Tunnel failure reporting in UI. [#8](https://github.com/keeshux/passepartout-ios/pull/8)
- Explicit "Reconnect" button. [#9](https://github.com/keeshux/passepartout-ios/pull/9)
- Option to revert host parameters to original configuration (Nicholas Caito). [#10](https://github.com/keeshux/passepartout-ios/pull/10)
### Fixed

View File

@ -13,6 +13,10 @@
<array/>
<key>CFBundleTypeName</key>
<string>ovpn</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>app.passepartoutvpn.formats.ovpn</string>
@ -87,7 +91,6 @@
</array>
</dict>
</dict>
<dict/>
</array>
</dict>
</plist>

View File

@ -25,6 +25,9 @@
import UIKit
import TunnelKit
import SwiftyBeaver
private let log = SwiftyBeaver.self
class ConfigurationViewController: UIViewController, TableModelHost {
@IBOutlet private weak var tableView: UITableView!
@ -37,15 +40,21 @@ class ConfigurationViewController: UIViewController, TableModelHost {
var isEditable = false
var originalConfigurationURL: URL?
weak var delegate: ConfigurationModificationDelegate?
// MARK: TableModelHost
lazy var model: TableModel<SectionType, RowType> = {
let model: TableModel<SectionType, RowType> = TableModel()
let hasConfigurationURL = isEditable && (originalConfigurationURL != nil)
// sections
model.add(.communication)
if hasConfigurationURL {
model.add(.reset)
}
model.add(.tls)
model.add(.other)
@ -55,12 +64,17 @@ class ConfigurationViewController: UIViewController, TableModelHost {
model.setHeader(L10n.Configuration.Sections.Other.header, for: .other)
// footers
if isEditable {
if hasConfigurationURL {
model.setFooter(L10n.Configuration.Sections.Reset.footer, for: .reset)
} else if isEditable {
model.setFooter(L10n.Configuration.Sections.Communication.Footer.editable, for: .communication)
}
// rows
model.set([.cipher, .digest, .compressionFrame], in: .communication)
if hasConfigurationURL {
model.set([.resetOriginal], in: .reset)
}
model.set([.client, .tlsWrapping], in: .tls)
model.set([.compressionAlgorithm, .keepAlive, .renegSeconds], in: .other)
@ -103,6 +117,26 @@ class ConfigurationViewController: UIViewController, TableModelHost {
// MARK: Actions
private func resetOriginalConfiguration() {
guard let url = originalConfigurationURL else {
log.warning("Resetting with no original configuration set? Bad table model?")
return
}
let originalConfiguration: TunnelKitProvider.Configuration
do {
(_, originalConfiguration) = try TunnelKitProvider.Configuration.parsed(from: url)
} catch let e {
log.warning("Could not parse original configuration: \(e)")
return
}
initialConfiguration = originalConfiguration
configuration = originalConfiguration.builder()
itemRefresh.isEnabled = true // allow for manual reconnection
tableView.reloadData()
delegate?.configurationShouldReinstall()
}
@IBAction private func refresh() {
guard isEditable else {
return
@ -119,7 +153,9 @@ class ConfigurationViewController: UIViewController, TableModelHost {
extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegate {
enum SectionType: Int {
case communication
case reset
case tls
case other
@ -132,6 +168,8 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
case compressionFrame
case resetOriginal
case client
case tlsWrapping
@ -155,6 +193,16 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
return model.footer(for: section)
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard let title = model.header(for: section) else {
return 1.0
}
guard !title.isEmpty else {
return 0.0
}
return UITableView.automaticDimension
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return model.count(for: section)
}
@ -186,6 +234,10 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
cell.leftText = L10n.Configuration.Cells.CompressionFrame.caption
cell.rightText = configuration.compressionFraming.cellDescription
case .resetOriginal:
cell.leftText = L10n.Configuration.Cells.ResetOriginal.caption
cell.applyAction(Theme.current)
case .client:
cell.leftText = L10n.Configuration.Cells.Client.caption
cell.rightText = (configuration.clientCertificate != nil) ? L10n.Configuration.Cells.Client.Value.enabled : L10n.Configuration.Cells.Client.Value.disabled
@ -283,6 +335,10 @@ extension ConfigurationViewController: UITableViewDataSource, UITableViewDelegat
}
navigationController?.pushViewController(vc, animated: true)
case .resetOriginal:
tableView.deselectRow(at: indexPath, animated: true)
resetOriginalConfiguration()
default:
break
}

View File

@ -31,8 +31,12 @@ private let log = SwiftyBeaver.self
class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
private struct ParsedFile {
let filename: String
let url: URL
var filename: String {
return url.deletingPathExtension().lastPathComponent
}
let hostname: String
let configuration: TunnelKitProvider.Configuration
@ -106,7 +110,6 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
func setConfigurationURL(_ url: URL) throws {
log.debug("Parsing configuration URL: \(url)")
let filename = url.deletingPathExtension().lastPathComponent
let hostname: String
let configuration: TunnelKitProvider.Configuration
do {
@ -115,7 +118,7 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
log.error("Could not parse .ovpn configuration file: \(e)")
throw e
}
parsedFile = ParsedFile(filename: filename, hostname: hostname, configuration: configuration)
parsedFile = ParsedFile(url: url, hostname: hostname, configuration: configuration)
}
private func useSuggestedTitle() {
@ -162,6 +165,15 @@ class WizardHostViewController: UITableViewController, TableModelHost, Wizard {
guard let profile = createdProfile else {
fatalError("No profile created?")
}
if let url = parsedFile?.url {
do {
let savedUrl = try ProfileConfigurationFactory.shared.save(url: url, for: profile)
log.debug("Associated .ovpn configuration file to profile '\(profile.id)': \(savedUrl)")
} catch let e {
log.error("Could not associate .ovpn configuration file to profile: \(e)")
}
}
dismiss(animated: true) {
self.delegate?.wizard(didCreate: profile, withCredentials: credentials)
}

View File

@ -155,6 +155,7 @@ class ServiceViewController: UIViewController, TableModelHost {
vc?.title = L10n.Service.Cells.Host.Parameters.caption
vc?.initialConfiguration = uncheckedHostProfile.parameters
vc?.isEditable = true
vc?.originalConfigurationURL = ProfileConfigurationFactory.shared.configurationURL(for: uncheckedHostProfile)
vc?.delegate = self
case .debugLogSegueIdentifier:

View File

@ -20,6 +20,7 @@
0E1D72B4213C118500BA1586 /* ConfigurationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */; };
0E2B494020FCFF990094784C /* Theme+Titles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B493F20FCFF990094784C /* Theme+Titles.swift */; };
0E2B494220FD16540094784C /* TransientStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2B494120FD16540094784C /* TransientStore.swift */; };
0E2D11BA217DBEDE0096822C /* ProfileConfigurationFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */; };
0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCEF214B9EF10035E9DE /* WebServices.swift */; };
0E39BCF3214DA9310035E9DE /* AppConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCF2214DA9310035E9DE /* AppConstants.swift */; };
0E3DA371215CB5BF00B40FC9 /* VersionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */; };
@ -134,6 +135,7 @@
0E1D72B3213C118500BA1586 /* ConfigurationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationViewController.swift; sourceTree = "<group>"; };
0E2B493F20FCFF990094784C /* Theme+Titles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+Titles.swift"; sourceTree = "<group>"; };
0E2B494120FD16540094784C /* TransientStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransientStore.swift; sourceTree = "<group>"; };
0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileConfigurationFactory.swift; sourceTree = "<group>"; };
0E39BCEF214B9EF10035E9DE /* WebServices.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebServices.swift; sourceTree = "<group>"; };
0E39BCF2214DA9310035E9DE /* AppConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConstants.swift; sourceTree = "<group>"; };
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VersionViewController.swift; sourceTree = "<group>"; };
@ -379,6 +381,7 @@
0EC7F20420E24308004EA58E /* DebugLog.swift */,
0ED38AE621404F100004D387 /* EndpointDataSource.swift */,
0E89DFC4213DF7AE00741BA1 /* Preferences.swift */,
0E2D11B9217DBEDE0096822C /* ProfileConfigurationFactory.swift */,
0E89DFC7213E8FC500741BA1 /* TunnelKitProvider+Communication.swift */,
0E2B494120FD16540094784C /* TransientStore.swift */,
0E4C9CB820DB9BC600A0C59C /* TrustedNetworks.swift */,
@ -831,6 +834,7 @@
0ED31C1220CF0ABA0027975F /* Infrastructure.swift in Sources */,
0EC7F20520E24308004EA58E /* DebugLog.swift in Sources */,
0E4FD7E120D3E4C5002221FF /* MockVPNProvider.swift in Sources */,
0E2D11BA217DBEDE0096822C /* ProfileConfigurationFactory.swift in Sources */,
0EBE3A90213C6F4000BFA2F5 /* TrustPolicy.swift in Sources */,
0E6BE13F20CFBAB300A6DD36 /* DebugLogViewController.swift in Sources */,
0E89DFC8213E8FC500741BA1 /* TunnelKitProvider+Communication.swift in Sources */,

View File

@ -126,6 +126,7 @@
"configuration.sections.communication.header" = "Communication";
"configuration.sections.communication.footer.editable" = "Make sure to match server communication parameters, otherwise you will end up with broken connectivity.";
"configuration.sections.reset.footer" = "If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration.";
"configuration.sections.tls.header" = "TLS";
"configuration.sections.other.header" = "Other";
"configuration.cells.cipher.caption" = "Cipher";
@ -135,6 +136,7 @@
"configuration.cells.compression_frame.value.disabled" = "None";
"configuration.cells.compression_frame.value.lzo" = "LZO";
"configuration.cells.compression_frame.value.compress" = "Compress";
"configuration.cells.reset_original.caption" = "Reset configuration";
"configuration.cells.client.caption" = "Client certificate";
"configuration.cells.client.value.enabled" = "Verified";
"configuration.cells.client.value.disabled" = "Not verified";

View File

@ -36,6 +36,8 @@ class AppConstants {
static let serviceFilename = "ConnectionService.json"
static let infrastructureCacheDirectory = "Infrastructures"
static let profileConfigurationsDirectory = "Configurations"
}
class VPN {

View File

@ -0,0 +1,61 @@
//
// ProfileConfigurationFactory.swift
// Passepartout
//
// Created by Davide De Rosa on 10/22/18.
// Copyright (c) 2018 Davide De Rosa. All rights reserved.
//
// https://github.com/keeshux
//
// 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 Foundation
class ProfileConfigurationFactory {
static let shared = ProfileConfigurationFactory(withDirectory: AppConstants.Store.profileConfigurationsDirectory)
private let cachePath: URL
private let configurationsPath: URL
private init(withDirectory directory: String) {
let fm = FileManager.default
cachePath = fm.userURL(for: .cachesDirectory, appending: directory)
configurationsPath = fm.userURL(for: .documentDirectory, appending: directory)
try? fm.createDirectory(at: cachePath, withIntermediateDirectories: false, attributes: nil)
try? fm.createDirectory(at: configurationsPath, withIntermediateDirectories: false, attributes: nil)
}
func save(url: URL, for profile: ConnectionProfile) throws -> URL {
let savedUrl = targetConfigurationURL(for: profile)
try FileManager.default.copyItem(at: url, to: savedUrl)
return savedUrl
}
func configurationURL(for profile: ConnectionProfile) -> URL? {
let url = targetConfigurationURL(for: profile)
guard FileManager.default.fileExists(atPath: url.path) else {
return nil
}
return url
}
private func targetConfigurationURL(for profile: ConnectionProfile) -> URL {
let filename = "\(profile.id).ovpn"
return configurationsPath.appendingPathComponent(filename)
}
}

View File

@ -172,6 +172,11 @@ internal enum L10n {
}
}
internal enum ResetOriginal {
/// Reset configuration
internal static let caption = L10n.tr("Localizable", "configuration.cells.reset_original.caption")
}
internal enum TlsWrapping {
/// Wrapping
internal static let caption = L10n.tr("Localizable", "configuration.cells.tls_wrapping.caption")
@ -204,6 +209,11 @@ internal enum L10n {
internal static let header = L10n.tr("Localizable", "configuration.sections.other.header")
}
internal enum Reset {
/// If you ended up with broken connectivity after changing the communication parameters, tap to revert to the original configuration.
internal static let footer = L10n.tr("Localizable", "configuration.sections.reset.footer")
}
internal enum Tls {
/// TLS
internal static let header = L10n.tr("Localizable", "configuration.sections.tls.header")