Merge pull request #14 from keeshux/itunes-file-sharing

Enable iTunes File Sharing
This commit is contained in:
Davide De Rosa 2018-10-27 13:35:02 +02:00 committed by GitHub
commit 8961bfe03e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 219 additions and 9 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Attach .ovpn when reporting a connectivity issue, stripped of sensitive data. [#13](https://github.com/keeshux/passepartout-ios/pull/13)
- iTunes File Sharing (skythedesu). [#14](https://github.com/keeshux/passepartout-ios/pull/14)
## 1.0 beta 1107 (2018-10-26)

View File

@ -90,6 +90,7 @@ internal enum StoryboardSegue {
case addProviderSegueIdentifier = "AddProviderSegueIdentifier"
case creditsSegueIdentifier = "CreditsSegueIdentifier"
case disclaimerSegueIdentifier = "DisclaimerSegueIdentifier"
case importHostSegueIdentifier = "ImportHostSegueIdentifier"
case selectProfileSegueIdentifier = "SelectProfileSegueIdentifier"
case versionSegueIdentifier = "VersionSegueIdentifier"
}

View File

@ -43,6 +43,8 @@
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<false/>
<key>UIFileSharingEnabled</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>

View File

@ -0,0 +1,113 @@
//
// ImportedHostsViewController.swift
// Passepartout-iOS
//
// Created by Davide De Rosa on 10/27/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 UIKit
import TunnelKit
import SwiftyBeaver
private let log = SwiftyBeaver.self
class ImportedHostsViewController: UITableViewController {
private lazy var pendingConfigurationURLs = TransientStore.shared.service.pendingConfigurationURLs().sorted { $0.normalizedFilename < $1.normalizedFilename }
private var parsedFile: ParsedFile?
weak var wizardDelegate: WizardDelegate?
override func viewDidLoad() {
super.viewDidLoad()
title = L10n.ImportedHosts.title
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard !pendingConfigurationURLs.isEmpty else {
let alert = Macros.alert(
L10n.ImportedHosts.title,
L10n.Organizer.Alerts.AddHost.message
)
alert.addCancelAction(L10n.Global.ok) {
self.close()
}
present(alert, animated: true, completion: nil)
return
}
}
// MARK: Actions
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
guard let cell = sender as? UITableViewCell, let indexPath = tableView.indexPath(for: cell) else {
return false
}
let url = pendingConfigurationURLs[indexPath.row]
guard let parsedFile = ParsedFile.from(url, withErrorAlertIn: self) else {
if let selectedIP = tableView.indexPathForSelectedRow {
tableView.deselectRow(at: selectedIP, animated: true)
}
return false
}
self.parsedFile = parsedFile
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let wizard = segue.destination as? WizardHostViewController else {
return
}
wizard.parsedFile = parsedFile
wizard.delegate = wizardDelegate
}
@IBAction private func close() {
dismiss(animated: true, completion: nil)
}
}
extension ImportedHostsViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return pendingConfigurationURLs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let url = pendingConfigurationURLs[indexPath.row]
let cell = Cells.setting.dequeue(from: tableView, for: indexPath)
cell.leftText = url.normalizedFilename
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
let url = pendingConfigurationURLs[indexPath.row]
try? FileManager.default.removeItem(at: url)
pendingConfigurationURLs.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .top)
}
}

View File

@ -139,6 +139,8 @@ class OrganizerViewController: UITableViewController, TableModelHost {
providerVC.availableNames = availableProviderNames ?? []
}
vc.delegate = self
} else if let vc = destination as? ImportedHostsViewController {
vc.wizardDelegate = self
}
}
@ -174,12 +176,7 @@ class OrganizerViewController: UITableViewController, TableModelHost {
}
private func addNewHost() {
let alert = Macros.alert(
L10n.Organizer.Sections.Hosts.header,
L10n.Organizer.Alerts.AddHost.message
)
alert.addCancelAction(L10n.Global.ok)
present(alert, animated: true, completion: nil)
perform(segue: StoryboardSegue.Organizer.importHostSegueIdentifier)
}
private func removeProfile(at indexPath: IndexPath) {

View File

@ -161,6 +161,76 @@
</objects>
<point key="canvasLocation" x="-2815" y="-279"/>
</scene>
<!--Imported Hosts View Controller-->
<scene sceneID="0zM-zA-ZTU">
<objects>
<tableViewController id="c0p-pg-Arz" customClass="ImportedHostsViewController" customModule="Passepartout_iOS" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="j8e-Ab-2SM">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="SettingTableViewCell" textLabel="h2k-uQ-JBT" detailTextLabel="Tnk-4u-fB2" style="IBUITableViewCellStyleValue1" id="4dG-dn-eeq" customClass="SettingTableViewCell" customModule="Passepartout_iOS" customModuleProvider="target">
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="4dG-dn-eeq" id="c48-Fg-ve4">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="h2k-uQ-JBT">
<rect key="frame" x="16" y="12" width="33.5" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Detail" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Tnk-4u-fB2">
<rect key="frame" x="315" y="12" width="44" height="20.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="oga-go-FqD" kind="show" id="kwN-PZ-Zg5"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="c0p-pg-Arz" id="Ryi-oH-bPG"/>
<outlet property="delegate" destination="c0p-pg-Arz" id="Ct1-QV-YrO"/>
</connections>
</tableView>
<navigationItem key="navigationItem" id="TpS-QE-QmN">
<barButtonItem key="leftBarButtonItem" systemItem="cancel" id="R3x-hK-EC4">
<connections>
<action selector="close" destination="c0p-pg-Arz" id="yVS-jW-gwZ"/>
</connections>
</barButtonItem>
</navigationItem>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="c40-c8-YA4" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-2502" y="171"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="pgr-Kw-Y4E">
<objects>
<navigationController id="z6E-m6-Op0" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="NoF-OX-w8Z">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="c0p-pg-Arz" kind="relationship" relationship="rootViewController" id="tms-L9-AIy"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="oY5-42-xYk" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-3270" y="171"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="ajI-cA-lhi">
<objects>
@ -175,7 +245,7 @@
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="BvG-Pe-O1S" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1786" y="-356"/>
<point key="canvasLocation" x="-1727" y="-357"/>
</scene>
<!--Organizer View Controller-->
<scene sceneID="H6i-Tl-Gnq">
@ -223,6 +293,7 @@
<connections>
<segue destination="NVA-bQ-iIE" kind="presentation" identifier="AddProviderSegueIdentifier" modalPresentationStyle="formSheet" id="Win-5U-mIc"/>
<segue destination="a3d-vD-Pr7" kind="presentation" identifier="AboutSegueIdentifier" id="fd4-we-46n"/>
<segue destination="z6E-m6-Op0" kind="presentation" identifier="ImportHostSegueIdentifier" modalPresentationStyle="formSheet" id="TZv-OK-8vU"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="bGp-H5-24W" userLabel="First Responder" sceneMemberID="firstResponder"/>
@ -496,5 +567,6 @@
</resources>
<inferredMetricsTieBreakers>
<segue reference="HW6-RJ-VFY"/>
<segue reference="qQl-B5-moM"/>
</inferredMetricsTieBreakers>
</document>

View File

@ -49,6 +49,7 @@
0EA068F4218475F800C320AD /* ParsedFile+Alerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */; };
0EAAD71920E6669A0088754A /* GroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EDE8DED20C93E4C004C739C /* GroupConstants.swift */; };
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */; };
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */; };
0EBBE8F221822B4D00106008 /* ConnectionServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */; };
0EBBE8F321822B4D00106008 /* ConnectionService.json in Resources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F121822B4D00106008 /* ConnectionService.json */; };
0EBBE8F52182361800106008 /* ConnectionService+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */; };
@ -169,6 +170,7 @@
0E8D97E421389276006FB4A0 /* pia.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = pia.json; sourceTree = "<group>"; };
0EA068F3218475F800C320AD /* ParsedFile+Alerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParsedFile+Alerts.swift"; sourceTree = "<group>"; };
0EB60FD92111136E00AD27F3 /* UITextView+Search.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+Search.swift"; sourceTree = "<group>"; };
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportedHostsViewController.swift; sourceTree = "<group>"; };
0EBBE8F021822B4D00106008 /* ConnectionServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionServiceTests.swift; sourceTree = "<group>"; };
0EBBE8F121822B4D00106008 /* ConnectionService.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = ConnectionService.json; sourceTree = "<group>"; };
0EBBE8F42182361700106008 /* ConnectionService+Migration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConnectionService+Migration.swift"; sourceTree = "<group>"; };
@ -344,6 +346,7 @@
isa = PBXGroup;
children = (
0EE3BBB1215ED3A900F30952 /* AboutViewController.swift */,
0EB67D6A2184581E00BA6200 /* ImportedHostsViewController.swift */,
0EFD943F215BED8E00529B64 /* LabelViewController.swift */,
0EBE3A78213C4E5400BFA2F5 /* OrganizerViewController.swift */,
0E3DA370215CB5BF00B40FC9 /* VersionViewController.swift */,
@ -852,6 +855,7 @@
0ED38AEA214054A50004D387 /* OptionViewController.swift in Sources */,
0EFD943E215BE10800529B64 /* IssueReporter.swift in Sources */,
0EB60FDA2111136E00AD27F3 /* UITextView+Search.swift in Sources */,
0EB67D6B2184581E00BA6200 /* ImportedHostsViewController.swift in Sources */,
0E57F63E20C83FC5008323CF /* ServiceViewController.swift in Sources */,
0E39BCF0214B9EF10035E9DE /* WebServices.swift in Sources */,
0EDE8DE720C93945004C739C /* Credentials.swift in Sources */,

View File

@ -43,7 +43,7 @@
"organizer.cells.about.caption" = "About %@";
"organizer.cells.uninstall.caption" = "Delete VPN profile";
"organizer.alerts.exhausted_providers.message" = "You have created profiles for any available network.";
"organizer.alerts.add_host.message" = "Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.";
"organizer.alerts.add_host.message" = "Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing.";
"organizer.alerts.delete_vpn_profile.message" = "Do you really want to delete the VPN profile from the device?";
"account.suggestion_footer.infrastructure.pia" = "Use your website credentials. Your username is usually numeric with a \"p\" prefix.";
@ -58,6 +58,8 @@
"parsed_file.alerts.parsing.message" = "Unable to parse the provided configuration file (%@).";
"parsed_file.alerts.buttons.report" = "Report an issue";
"imported_hosts.title" = "Imported";
"service.welcome.message" = "Welcome to Passepartout!\n\nUse the organizer to add a new profile.";
"service.sections.general.header" = "General";
"service.sections.vpn.header" = "VPN";

View File

@ -24,6 +24,9 @@
//
import Foundation
import SwiftyBeaver
private let log = SwiftyBeaver.self
extension ConnectionService {
func save(configurationURL: URL, for profile: ConnectionProfile) throws -> URL {
@ -46,4 +49,14 @@ extension ConnectionService {
let contextURL = ConnectionService.ProfileKey(profile).contextURL(in: self)
return contextURL.appendingPathComponent(profile.id).appendingPathExtension("ovpn")
}
func pendingConfigurationURLs() -> [URL] {
do {
let list = try FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil, options: [])
return list.filter { $0.pathExtension == "ovpn" }
} catch let e {
log.error("Could not list imported configurations: \(e)")
return []
}
}
}

View File

@ -300,6 +300,11 @@ internal enum L10n {
internal static let ok = L10n.tr("Localizable", "global.ok")
}
internal enum ImportedHosts {
/// Imported
internal static let title = L10n.tr("Localizable", "imported_hosts.title")
}
internal enum IssueReporter {
/// The debug log of your latest connections is crucial to resolve your connectivity issues and is completely anonymous.\n\nThe .ovpn configuration file, if any, is attached stripped of any sensitive data.\n\nPlease double check the email attachments if unsure.
internal static let message = L10n.tr("Localizable", "issue_reporter.message")
@ -338,7 +343,7 @@ internal enum L10n {
internal enum Alerts {
internal enum AddHost {
/// Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.
/// Open an URL to an .ovpn configuration file from Safari, Mail or another app to set up a host profile.\n\nYou can also import an .ovpn with iTunes File Sharing.
internal static let message = L10n.tr("Localizable", "organizer.alerts.add_host.message")
}