diff --git a/Podfile b/Podfile
index 96091d0..20987bf 100644
--- a/Podfile
+++ b/Podfile
@@ -2,7 +2,7 @@ platform :ios, '10.2'
use_frameworks!
target 'passKit' do
- pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git'
+ pod 'ObjectivePGP', :git => 'https://github.com/krzyzanowskim/ObjectivePGP.git', :tag => '0.9.0'
target 'pass' do
inherit! :search_paths
end
diff --git a/README.md b/README.md
index d10c16b..73a639f 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
# Pass
[](https://github.com/mssun/pass-ios/releases)
-
+
[](https://gitter.im/passforios/passforios)
[](https://travis-ci.org/mssun/passforios)
[](https://www.paypal.me/mssun)
@@ -11,9 +11,14 @@ Pass is an iOS client compatible with [ZX2C4's Pass command line
application](http://www.passwordstore.org/). It is a password manager using
GPG for encryption and Git for version control.
-Pass for iOS is available in the [App Store](https://appsto.re/us/DY13hb.i)
-with the name "Pass - Password Store". If you want to join the iOS beta via
-Testflight, drop an email to `developer@passforios.mssun.me`. Thank you.
+Pass for iOS is available in the App Store with the name "Pass - Password Store".
+
+
+
+
+
+If you want to join the iOS beta via
+TestFlight, drop an email to `developer@passforios.mssun.me`. Thank you.
## Features
diff --git a/badge/app_store_badge.svg b/badge/app_store_badge.svg
new file mode 100644
index 0000000..ac111e5
--- /dev/null
+++ b/badge/app_store_badge.svg
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj
index d011204..24f7730 100644
--- a/pass.xcodeproj/project.pbxproj
+++ b/pass.xcodeproj/project.pbxproj
@@ -874,10 +874,12 @@
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-passKitTests/Pods-passKitTests-frameworks.sh",
"${PODS_ROOT}/ObjectivePGP/Frameworks/ios/ObjectivePGP.framework",
+ "${PODS_ROOT}/ObjectivePGP/Frameworks/ios/ObjectivePGP.framework.dSYM",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectivePGP.framework",
+ "${DWARF_DSYM_FOLDER_PATH}/ObjectivePGP.framework.dSYM",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -910,10 +912,12 @@
inputPaths = (
"${SRCROOT}/Pods/Target Support Files/Pods-pass/Pods-pass-frameworks.sh",
"${PODS_ROOT}/ObjectivePGP/Frameworks/ios/ObjectivePGP.framework",
+ "${PODS_ROOT}/ObjectivePGP/Frameworks/ios/ObjectivePGP.framework.dSYM",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjectivePGP.framework",
+ "${DWARF_DSYM_FOLDER_PATH}/ObjectivePGP.framework.dSYM",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
@@ -1196,7 +1200,7 @@
SWIFT_INCLUDE_PATHS = "";
SWIFT_OBJC_BRIDGING_HEADER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
@@ -1241,7 +1245,7 @@
SKIP_INSTALL = YES;
SWIFT_INCLUDE_PATHS = "";
SWIFT_OBJC_BRIDGING_HEADER = "";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
@@ -1264,7 +1268,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass";
};
@@ -1285,7 +1289,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass";
};
@@ -1319,7 +1323,7 @@
PROVISIONING_PROFILE = "d25c9029-bca6-4b2d-b04e-4abc9d232740";
PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.find-login-action-extension";
SKIP_INSTALL = YES;
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
};
name = Debug;
@@ -1352,7 +1356,7 @@
PROVISIONING_PROFILE = "cbd86628-6f3e-40f3-b518-20d2330db545";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.find-login-action-extension";
SKIP_INSTALL = YES;
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
};
name = Release;
@@ -1368,7 +1372,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass";
};
@@ -1385,7 +1389,7 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests";
PRODUCT_NAME = "$(TARGET_NAME)";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass";
};
@@ -1444,7 +1448,7 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
};
name = Debug;
};
@@ -1493,7 +1497,7 @@
PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios;
SDKROOT = iphoneos;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -1529,7 +1533,7 @@
PROVISIONING_PROFILE = "2e72f4af-b935-4970-9cd3-44d4cc24b646";
PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios";
SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
@@ -1567,7 +1571,7 @@
PROVISIONING_PROFILE = "ee6e841d-ef77-4f00-b534-d7f1fd25dc1d";
PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios";
SWIFT_OBJC_BRIDGING_HEADER = "pass/Helpers/Objective-CBridgingHeader.h";
- SWIFT_SWIFT3_OBJC_INFERENCE = On;
+ SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
diff --git a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme
index a02e528..75208fd 100644
--- a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme
+++ b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme
@@ -1,6 +1,6 @@
-
+
-
+
@@ -439,9 +439,10 @@
+
-
-
+
+
@@ -471,10 +472,11 @@
-
+
+
@@ -487,7 +489,7 @@
-
+
@@ -596,8 +598,8 @@
-
-
+
+
@@ -634,7 +636,7 @@
-
+
@@ -662,10 +664,11 @@
-
+
+
@@ -675,14 +678,13 @@
-
-
+
-
+
@@ -691,7 +693,6 @@
-
@@ -776,8 +777,8 @@
-
-
+
+
@@ -809,7 +810,7 @@
-
+
@@ -838,7 +839,7 @@
-
+
K7PBbkoaJf6mLyVX3EBU
username: passforios-demo@email.com
@@ -853,8 +854,8 @@ Phone Support PIN #: 84719
-
+
@@ -969,7 +970,7 @@ Phone Support PIN #: 84719
-
+
@@ -997,7 +998,7 @@ Phone Support PIN #: 84719
-
+
@@ -1129,7 +1130,7 @@ Phone Support PIN #: 84719
-
+
@@ -1155,7 +1156,7 @@ Phone Support PIN #: 84719
-
+
@@ -1271,7 +1272,7 @@ Phone Support PIN #: 84719
- GnuPG supports a command-line option --armor that that causes output to be generated in an ASCII-armored format similar to unencoded documents rather than the binary format.
+ GnuPG supports the command-line option "-a" that causes output to be generated in an ASCII-armored format similar to unencoded documents rather than the binary format.
@@ -1324,7 +1325,7 @@ Cgo
-
+
@@ -1362,7 +1363,7 @@ Cgo
-
+
@@ -1396,8 +1397,8 @@ Cgo
-
-
+
+
@@ -1517,7 +1518,7 @@ Cgo
-
+
@@ -1549,7 +1550,7 @@ Cgo
-
+
@@ -1651,7 +1652,7 @@ Cgo
-
+
@@ -1680,9 +1681,10 @@ Cgo
-
+
+
@@ -1692,8 +1694,7 @@ Cgo
-
-
+
@@ -1719,9 +1720,10 @@ Cgo
-
+
+
@@ -1732,8 +1734,7 @@ Cgo
-
-
+
@@ -1805,7 +1806,7 @@ Cgo
-
-
+
+
diff --git a/pass/Controllers/AddPasswordTableViewController.swift b/pass/Controllers/AddPasswordTableViewController.swift
index cc82e85..e69cfcf 100644
--- a/pass/Controllers/AddPasswordTableViewController.swift
+++ b/pass/Controllers/AddPasswordTableViewController.swift
@@ -10,8 +10,8 @@ import UIKit
import passKit
class AddPasswordTableViewController: PasswordEditorTableViewController {
- var tempContent: String = ""
let passwordStore = PasswordStore.shared
+ var defaultDirPrefix = ""
override func viewDidLoad() {
tableData = [
@@ -24,6 +24,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
lengthSetting.max > lengthSetting.min {
tableData[1].append([.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"])
}
+ tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix
super.viewDidLoad()
}
@@ -38,10 +39,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
}
// check name
- guard nameCell?.getContent()?.isEmpty == false else {
- let alertTitle = "Cannot Add Password"
- let alertMessage = "Please fill in the name."
- Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
+ guard checkName() == true else {
return false
}
}
@@ -56,9 +54,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
plainText.append("\n")
plainText.append(additionsString)
}
- let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
- let name = URL(string: encodedName)!.lastPathComponent
- let url = URL(string: encodedName)!.appendingPathExtension("gpg")
+ let (name, url) = getNameURL()
password = Password(name: name, url: url, plainText: plainText)
}
}
diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift
index ade537b..1c9cc82 100644
--- a/pass/Controllers/EditPasswordTableViewController.swift
+++ b/pass/Controllers/EditPasswordTableViewController.swift
@@ -27,12 +27,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveEditPasswordSegue" {
- if let name = nameCell?.getContent(),
- let path = name.stringByAddingPercentEncodingForRFC3986(),
- let _ = URL(string: path) {
- return true
- } else {
- Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
+ // check name
+ guard checkName() == true else {
return false
}
}
@@ -47,9 +43,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
plainText.append("\n")
plainText.append(additionsString)
}
- let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
- let name = URL(string: encodedName)!.lastPathComponent
- let url = URL(string: encodedName)!.appendingPathExtension("gpg")
+ let (name, url) = getNameURL()
if password!.plainText != plainText || password!.url!.path != url.path {
password!.updatePassword(name: name, url: url, plainText: plainText)
}
diff --git a/pass/Controllers/GeneralSettingsTableViewController.swift b/pass/Controllers/GeneralSettingsTableViewController.swift
index 051b19e..f0b57de 100644
--- a/pass/Controllers/GeneralSettingsTableViewController.swift
+++ b/pass/Controllers/GeneralSettingsTableViewController.swift
@@ -28,12 +28,21 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
return uiSwitch
}()
- let rememberPassphraseSwitch: UISwitch = {
+ let rememberPGPPassphraseSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
uiSwitch.sizeToFit()
- uiSwitch.addTarget(self, action: #selector(rememberPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
- uiSwitch.isOn = SharedDefaults[.isRememberPassphraseOn]
+ uiSwitch.addTarget(self, action: #selector(rememberPGPPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
+ uiSwitch.isOn = SharedDefaults[.isRememberPGPPassphraseOn]
+ return uiSwitch
+ }()
+
+ let rememberGitCredentialPassphraseSwitch: UISwitch = {
+ let uiSwitch = UISwitch()
+ uiSwitch.onTintColor = Globals.blue
+ uiSwitch.sizeToFit()
+ uiSwitch.addTarget(self, action: #selector(rememberGitCredentialPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
+ uiSwitch.isOn = SharedDefaults[.isRememberGitCredentialPassphraseOn]
return uiSwitch
}()
@@ -58,10 +67,11 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
// section 2
[
- [.title: "Remember Passphrase", .action: "none",],
+ [.title: "Remember PGP Key Passphrase", .action: "none",],
+ [.title: "Remember Git Credential Passphrase", .action: "none",],
],
[
- [.title: "Show Folder", .action: "none",],
+ [.title: "Show Folders", .action: "none",],
[.title: "Hide Unknown Fields", .action: "none",],
[.title: "Hide OTP Fields", .action: "none",],
],
@@ -98,11 +108,15 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
cell.accessoryView = accessoryView
cell.selectionStyle = .none
hideOTPSwitch.isOn = SharedDefaults[.isHideOTPOn]
- case "Remember Passphrase":
+ case "Remember PGP Key Passphrase":
cell.accessoryType = .none
cell.selectionStyle = .none
- cell.accessoryView = rememberPassphraseSwitch
- case "Show Folder":
+ cell.accessoryView = rememberPGPPassphraseSwitch
+ case "Remember Git Credential Passphrase":
+ cell.accessoryType = .none
+ cell.selectionStyle = .none
+ cell.accessoryView = rememberGitCredentialPassphraseSwitch
+ case "Show Folders":
cell.accessoryType = .none
cell.selectionStyle = .none
cell.accessoryView = showFolderSwitch
@@ -176,13 +190,21 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
}
- @objc func rememberPassphraseSwitchAction(_ sender: Any?) {
- SharedDefaults[.isRememberPassphraseOn] = rememberPassphraseSwitch.isOn
- if rememberPassphraseSwitch.isOn == false {
+ @objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
+ SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
+ if rememberPGPPassphraseSwitch.isOn == false {
passwordStore.pgpKeyPassphrase = nil
}
}
+ @objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) {
+ SharedDefaults[.isRememberGitCredentialPassphraseOn] = rememberGitCredentialPassphraseSwitch.isOn
+ if rememberGitCredentialPassphraseSwitch.isOn == false {
+ passwordStore.gitSSHPrivateKeyPassphrase = nil
+ passwordStore.gitPassword = nil
+ }
+ }
+
@objc func showFolderSwitchAction(_ sender: Any?) {
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
diff --git a/pass/Controllers/GitServerSettingTableViewController.swift b/pass/Controllers/GitServerSettingTableViewController.swift
index ab3d76b..8189eb3 100644
--- a/pass/Controllers/GitServerSettingTableViewController.swift
+++ b/pass/Controllers/GitServerSettingTableViewController.swift
@@ -93,6 +93,8 @@ class GitServerSettingTableViewController: UITableViewController {
)
)
}
+ // Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone.
+ SharedDefaults[.isRememberGitCredentialPassphraseOn] = true
let dispatchQueue = DispatchQueue.global(qos: .userInitiated)
dispatchQueue.async {
do {
@@ -113,9 +115,21 @@ class GitServerSettingTableViewController: UITableViewController {
SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL)
SharedDefaults[.gitUsername] = username
SharedDefaults[.gitAuthenticationMethod] = auth
- SVProgressHUD.showSuccess(withStatus: "Done")
- SVProgressHUD.dismiss(withDelay: 1)
- self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
+ SVProgressHUD.dismiss()
+ let savePassphraseAlert = UIAlertController(title: "Done", message: "Do you want to save the Git credential password/passphrase?", preferredStyle: UIAlertControllerStyle.alert)
+ // no
+ savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
+ SharedDefaults[.isRememberGitCredentialPassphraseOn] = false
+ self.passwordStore.gitPassword = nil
+ self.passwordStore.gitSSHPrivateKeyPassphrase = nil
+ self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
+ })
+ // yes
+ savePassphraseAlert.addAction(UIAlertAction(title: "Yes", style: UIAlertActionStyle.destructive) {_ in
+ SharedDefaults[.isRememberGitCredentialPassphraseOn] = true
+ self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
+ })
+ self.present(savePassphraseAlert, animated: true, completion: nil)
}
} catch {
DispatchQueue.main.async {
@@ -257,7 +271,7 @@ class GitServerSettingTableViewController: UITableViewController {
case .http:
message = "Please fill in the password of your Git account."
case .ssh:
- message = "Please fill in the password of your SSH key."
+ message = "Please fill in the passphrase of your SSH key."
}
DispatchQueue.main.async {
diff --git a/pass/Controllers/OTPScannerController.swift b/pass/Controllers/OTPScannerController.swift
index 32bcb08..e8fe13d 100644
--- a/pass/Controllers/OTPScannerController.swift
+++ b/pass/Controllers/OTPScannerController.swift
@@ -14,6 +14,9 @@ class OTPScannerController: QRScannerController {
var scannedOTP: String?
+ @IBAction func pressCancel(_ sender: UIBarButtonItem) {
+ navigationController?.popViewController(animated: true)
+ }
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
diff --git a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift
index 30705b2..d3430ed 100644
--- a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift
+++ b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift
@@ -120,7 +120,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.pgpPassphrase = nil
- SharedDefaults[.isRememberPassphraseOn] = false
+ SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
// yes
@@ -129,7 +129,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
- SharedDefaults[.isRememberPassphraseOn] = true
+ SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
diff --git a/pass/Controllers/PGPKeySettingTableViewController.swift b/pass/Controllers/PGPKeySettingTableViewController.swift
index 364b82a..ae86c7d 100644
--- a/pass/Controllers/PGPKeySettingTableViewController.swift
+++ b/pass/Controllers/PGPKeySettingTableViewController.swift
@@ -45,7 +45,7 @@ class PGPKeySettingTableViewController: UITableViewController {
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.pgpPassphrase = nil
- SharedDefaults[.isRememberPassphraseOn] = false
+ SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})
// yes
@@ -54,7 +54,7 @@ class PGPKeySettingTableViewController: UITableViewController {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text
- SharedDefaults[.isRememberPassphraseOn] = true
+ SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift
index 14d9a4a..4749fd9 100644
--- a/pass/Controllers/PasswordDetailTableViewController.swift
+++ b/pass/Controllers/PasswordDetailTableViewController.swift
@@ -19,12 +19,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
private var shouldPopCurrentView = false
private let passwordStore = PasswordStore.shared
- private let indicator: UIActivityIndicatorView = {
- let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
- indicator.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height * 0.382)
- return indicator
- }()
-
private lazy var editUIBarButtonItem: UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
return uiBarButtonItem
@@ -85,10 +79,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 52
- indicator.startAnimating()
- tableView.addSubview(indicator)
editUIBarButtonItem.isEnabled = false
navigationItem.rightBarButtonItem = editUIBarButtonItem
+ if #available(iOS 11.0, *) {
+ navigationItem.largeTitleDisplayMode = .never
+ }
if let imageData = passwordEntity?.image {
let image = UIImage(data: imageData as Data)
@@ -134,7 +129,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.present(alert, animated: true, completion: nil)
}
let _ = sem.wait(timeout: DispatchTime.distantFuture)
- if SharedDefaults[.isRememberPassphraseOn] {
+ if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase
}
return passphrase
@@ -174,7 +169,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
private func showPassword() {
DispatchQueue.main.async { [weak self] in
- self?.indicator.stopAnimating()
self?.setTableData()
self?.tableView.reloadData()
self?.editUIBarButtonItem.isEnabled = true
@@ -407,7 +401,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
func openLink() {
- guard let urlString = self.password?.getURLString(), let url = URL(string: urlString) else {
+ var urlString = self.password?.getURLString() ?? ""
+ if !urlString.lowercased().starts(with: "https://") && !urlString.lowercased().starts(with: "http://") {
+ urlString = "http://\(urlString)"
+ }
+ guard let url = URL(string: urlString) else {
DispatchQueue.main.async {
Utils.alert(title: "Error", message: "Cannot find a valid URL", controller: self, completion: nil)
}
diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift
index 71defd2..7ed2ed6 100644
--- a/pass/Controllers/PasswordEditorTableViewController.swift
+++ b/pass/Controllers/PasswordEditorTableViewController.swift
@@ -75,6 +75,9 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionFooterHeight = 0;
}
+ override func viewDidLayoutSubviews() {
+ additionsCell?.contentTextView.setContentOffset(.zero, animated: false)
+ }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = tableData[indexPath.section][indexPath.row]
@@ -101,7 +104,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
let minimumLength = lengthSetting?.min ?? 0
let maximumLength = lengthSetting?.max ?? 0
var defaultLength = lengthSetting?.def ?? 0
- if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.characters.count,
+ if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.count,
currentPasswordLength >= minimumLength,
currentPasswordLength <= maximumLength {
defaultLength = currentPasswordLength
@@ -258,4 +261,45 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent()
}
}
+
+ func getNameURL() -> (String, URL) {
+ let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
+ let name = URL(string: encodedName)!.lastPathComponent
+ let url = URL(string: encodedName)!.appendingPathExtension("gpg")
+ return (name, url)
+ }
+
+ func checkName() -> Bool {
+ // the name field should not be empty
+ guard let name = nameCell?.getContent(), name.isEmpty == false else {
+ Utils.alert(title: "Cannot Save", message: "Please fill in the name.", controller: self, completion: nil)
+ return false
+ }
+
+ // the name should not start with /
+ guard name.hasPrefix("/") == false else {
+ Utils.alert(title: "Cannot Save", message: "Please remove the prefix \"/\" from your password name.", controller: self, completion: nil)
+ return false
+ }
+
+ // the name field should be a valid url
+ guard let path = name.stringByAddingPercentEncodingForRFC3986(),
+ var passwordURL = URL(string: path) else {
+ Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
+ return false
+ }
+
+ // check whether we can parse the filename (be consistent with PasswordStore::addPasswordEntities)
+ var previousPathLength = Int.max
+ while passwordURL.path != "." {
+ passwordURL = passwordURL.deletingLastPathComponent()
+ if passwordURL.path != "." && passwordURL.path.count >= previousPathLength {
+ Utils.alert(title: "Cannot Save", message: "Cannot parse the filename. Please check and simplify the password name.", controller: self, completion: nil)
+ return false
+ }
+ previousPathLength = passwordURL.path.count
+ }
+
+ return true
+ }
}
diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift
index b7ca354..c4e18c8 100644
--- a/pass/Controllers/PasswordsViewController.swift
+++ b/pass/Controllers/PasswordsViewController.swift
@@ -39,7 +39,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
uiSearchController.searchResultsUpdater = self
uiSearchController.dimsBackgroundDuringPresentation = false
uiSearchController.searchBar.isTranslucent = false
- uiSearchController.searchBar.backgroundColor = UIColor.gray
uiSearchController.searchBar.sizeToFit()
return uiSearchController
}()
@@ -48,10 +47,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
syncControl.addTarget(self, action: #selector(handleRefresh(_:)), for: UIControlEvents.valueChanged)
return syncControl
}()
- private lazy var searchBarView: UIView = {
- let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 56))
- uiView.addSubview(self.searchController.searchBar)
- return uiView
+ private lazy var searchBarView: UIView? = {
+ guard #available(iOS 11, *) else {
+ let uiView = UIView(frame: CGRect(x: 0, y: 64, width: self.view.bounds.width, height: 56))
+ uiView.addSubview(self.searchController.searchBar)
+ return uiView
+ }
+ return nil
}()
private lazy var backUIBarButtonItem: UIBarButtonItem = {
let backUIBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(self.backAction(_:)))
@@ -172,8 +174,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
DispatchQueue.main.async {
SVProgressHUD.dismiss()
self.syncControl.endRefreshing()
+ let error = error as NSError
+ var message = error.localizedDescription
+ if let underlyingError = error.userInfo[NSUnderlyingErrorKey] as? NSError {
+ message = "\(message)\nUnderlying error: \(underlyingError.localizedDescription)"
+ if underlyingError.localizedDescription.contains("Wrong passphrase") {
+ message = "\(message)\nRecovery suggestion: Wrong credential password/passphrase has been removed, please try again."
+ gitCredential.delete()
+ }
+ }
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
- Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
+ Utils.alert(title: "Error", message: message, controller: self, completion: nil)
}
}
}
@@ -192,14 +203,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
override func viewDidLoad() {
super.viewDidLoad()
-
tabBarController!.delegate = self
searchController.searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
- tableView.contentInset = UIEdgeInsetsMake(56, 0, 0, 0)
definesPresentationContext = true
- view.addSubview(searchBarView)
+ if #available(iOS 11.0, *) {
+ navigationItem.searchController = searchController
+ navigationController?.navigationBar.prefersLargeTitles = true
+ navigationItem.largeTitleDisplayMode = .automatic
+ navigationItem.hidesSearchBarWhenScrolling = false
+ } else {
+ // Fallback on earlier versions
+ tableView.contentInset = UIEdgeInsetsMake(56, 0, 0, 0)
+ view.addSubview(searchBarView!)
+ }
tableView.refreshControl = syncControl
SVProgressHUD.setDefaultMaskType(.black)
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
@@ -223,8 +241,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
- searchBarView.frame = CGRect(x: 0, y: navigationController!.navigationBar.bounds.size.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width, height: 56)
- searchController.searchBar.sizeToFit()
+ guard #available(iOS 11, *) else {
+ searchBarView?.frame = CGRect(x: 0, y: navigationController!.navigationBar.bounds.size.height + UIApplication.shared.statusBarFrame.height, width: UIScreen.main.bounds.width, height: 56)
+ searchController.searchBar.sizeToFit()
+ return
+ }
}
func numberOfSections(in tableView: UITableView) -> Int {
@@ -242,18 +263,16 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath)
+ if entry.passwordEntity!.synced {
+ cell.textLabel?.text = entry.title
+ } else {
+ cell.textLabel?.text = "↻ \(entry.title)"
+ }
if !entry.isDir {
- if entry.passwordEntity!.synced {
- cell.textLabel?.text = entry.title
- } else {
- cell.textLabel?.text = "↻ \(entry.title)"
- }
-
cell.addGestureRecognizer(longPressGestureRecognizer)
cell.accessoryType = .none
cell.detailTextLabel?.text = ""
} else {
- cell.textLabel?.text = "\(entry.title)"
cell.accessoryType = .disclosureIndicator
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .body)
cell.detailTextLabel?.text = "\(entry.passwordEntity?.children?.count ?? 0)"
@@ -351,7 +370,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
// bring back
SVProgressHUD.show(withStatus: "Decrypting")
}
- if SharedDefaults[.isRememberPassphraseOn] {
+ if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase
}
return passphrase
@@ -445,6 +464,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
let passwordEntity = getPasswordEntry(by: selectedIndexPath).passwordEntity!
viewController.passwordEntity = passwordEntity
}
+ } else if segue.identifier == "addPasswordSegue" {
+ if let navController = segue.destination as? UINavigationController {
+ if let viewController = navController.topViewController as? AddPasswordTableViewController {
+ if let path = parentPasswordEntity?.path {
+ viewController.defaultDirPrefix = "\(path)/"
+ }
+ }
+ }
}
}
@@ -478,16 +505,15 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
// set navigation item
- var numberOfLocalCommitsString = ""
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits()
- if numberOfLocalCommits > 0 {
- numberOfLocalCommitsString = " (\(numberOfLocalCommits))"
+ if numberOfLocalCommits == 0 {
+ navigationController?.tabBarItem.badgeValue = nil
+ } else {
+ navigationController?.tabBarItem.badgeValue = "\(numberOfLocalCommits)"
}
if parentPasswordEntity != nil {
- navigationItem.title = "\(parentPasswordEntity!.name!)\(numberOfLocalCommitsString)"
navigationItem.leftBarButtonItem = backUIBarButtonItem
} else {
- navigationItem.title = "Password Store\(numberOfLocalCommitsString)"
navigationItem.leftBarButtonItem = nil
}
diff --git a/pass/Controllers/QRScannerController.swift b/pass/Controllers/QRScannerController.swift
index e496229..3972723 100644
--- a/pass/Controllers/QRScannerController.swift
+++ b/pass/Controllers/QRScannerController.swift
@@ -40,7 +40,7 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
// Initialize the captureSession object.
captureSession = AVCaptureSession()
-
+
// Set the input device on the capture session.
captureSession?.addInput(input)
@@ -90,7 +90,7 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
- func metadataOutput(captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
+ func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
supportedCodeTypes.contains(metadataObj.type),
diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift
index c8b6986..cc24bc1 100644
--- a/pass/Controllers/SettingsTableViewController.swift
+++ b/pass/Controllers/SettingsTableViewController.swift
@@ -33,7 +33,7 @@ class SettingsTableViewController: UITableViewController {
if let controller = segue.source as? PGPKeySettingTableViewController {
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
SharedDefaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
- if SharedDefaults[.isRememberPassphraseOn] {
+ if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
}
SharedDefaults[.pgpKeySource] = "url"
@@ -61,7 +61,7 @@ class SettingsTableViewController: UITableViewController {
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
SharedDefaults[.pgpKeySource] = "armor"
- if SharedDefaults[.isRememberPassphraseOn] {
+ if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
}
@@ -259,7 +259,7 @@ class SettingsTableViewController: UITableViewController {
// no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.passwordStore.pgpKeyPassphrase = nil
- SharedDefaults[.isRememberPassphraseOn] = false
+ SharedDefaults[.isRememberPGPPassphraseOn] = false
self.saveImportedPGPKey()
})
// yes
@@ -268,7 +268,7 @@ class SettingsTableViewController: UITableViewController {
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.passwordStore.pgpKeyPassphrase = alert.textFields?.first?.text
- SharedDefaults[.isRememberPassphraseOn] = true
+ SharedDefaults[.isRememberPGPPassphraseOn] = true
self.saveImportedPGPKey()
}))
alert.addTextField(configurationHandler: {(textField: UITextField!) in
diff --git a/pass/Helpers/Objective-CBridgingHeader.h b/pass/Helpers/Objective-CBridgingHeader.h
index e84e32a..5a04d04 100644
--- a/pass/Helpers/Objective-CBridgingHeader.h
+++ b/pass/Helpers/Objective-CBridgingHeader.h
@@ -9,7 +9,7 @@
#ifndef Objective_CBridgingHeader_h
#define Objective_CBridgingHeader_h
-// #import
-#import
+#import "ObjectivePGP/ObjectivePGP.h"
+#import "ObjectiveGit/ObjectiveGit.h"
#endif /* Objective_CBridgingHeader_h */
diff --git a/pass/Info.plist b/pass/Info.plist
index 0cd043c..dd7b02f 100644
--- a/pass/Info.plist
+++ b/pass/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 0.2.9
+ 0.3.0
CFBundleURLTypes
diff --git a/pass/Views/FillPasswordTableViewCell.swift b/pass/Views/FillPasswordTableViewCell.swift
index 97c702d..0042ba1 100644
--- a/pass/Views/FillPasswordTableViewCell.swift
+++ b/pass/Views/FillPasswordTableViewCell.swift
@@ -25,7 +25,7 @@ class FillPasswordTableViewCell: ContentTableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
- contentTextField.font = UIFont(name: Globals.passwordFonts, size: (contentTextField.font?.pointSize)!)
+ contentTextField.font = Globals.passwordFont
// Force aspect ratio of button images
settingButton.imageView?.contentMode = .scaleAspectFit
diff --git a/pass/Views/FillPasswordTableViewCell.xib b/pass/Views/FillPasswordTableViewCell.xib
index 715a3d3..2ebed2b 100644
--- a/pass/Views/FillPasswordTableViewCell.xib
+++ b/pass/Views/FillPasswordTableViewCell.xib
@@ -1,11 +1,11 @@
-
+
-
+
@@ -16,17 +16,14 @@
-
+
-
+
-
-
-
-
+
@@ -35,7 +32,7 @@
-
+
@@ -46,7 +43,7 @@
-
+
diff --git a/pass/Views/LabelTableViewCell.swift b/pass/Views/LabelTableViewCell.swift
index fd72303..b1d8def 100644
--- a/pass/Views/LabelTableViewCell.swift
+++ b/pass/Views/LabelTableViewCell.swift
@@ -51,7 +51,7 @@ class LabelTableViewCell: UITableViewCell {
contentLabel.text = Globals.passwordDots
}
}
- contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
+ contentLabel.font = Globals.passwordFont
case "hmac-based":
type = .HOTP
if isReveal {
@@ -59,7 +59,7 @@ class LabelTableViewCell: UITableViewCell {
} else {
contentLabel.text = Globals.oneTimePasswordDots
}
- contentLabel.font = UIFont(name: Globals.passwordFonts, size: contentLabel.font.pointSize)
+ contentLabel.font = Globals.passwordFont
case "url":
type = .URL
contentLabel.text = content
diff --git a/pass/Views/LabelTableViewCell.xib b/pass/Views/LabelTableViewCell.xib
index cd316bb..157ca44 100644
--- a/pass/Views/LabelTableViewCell.xib
+++ b/pass/Views/LabelTableViewCell.xib
@@ -1,11 +1,11 @@
-
+
-
+
@@ -16,11 +16,11 @@
-
+
-
+
@@ -29,7 +29,7 @@
-
+
@@ -40,7 +40,7 @@
-
+
diff --git a/pass/Views/TextFieldTableViewCell.xib b/pass/Views/TextFieldTableViewCell.xib
index 35d3feb..00a066d 100644
--- a/pass/Views/TextFieldTableViewCell.xib
+++ b/pass/Views/TextFieldTableViewCell.xib
@@ -1,11 +1,11 @@
-
+
-
+
@@ -20,7 +20,7 @@
-
+
@@ -33,7 +33,7 @@
-
+
diff --git a/pass/Views/TextViewTableViewCell.xib b/pass/Views/TextViewTableViewCell.xib
index 564a3c8..0e13d70 100644
--- a/pass/Views/TextViewTableViewCell.xib
+++ b/pass/Views/TextViewTableViewCell.xib
@@ -1,14 +1,19 @@
-
+
-
+
+
+
+ Menlo-Regular
+
+
@@ -20,7 +25,7 @@
-
+
@@ -32,7 +37,7 @@
-
+
diff --git a/passExtension/Assets.xcassets/AppIcon.appiconset/Contents.json b/passExtension/Assets.xcassets/AppIcon.appiconset/Contents.json
index d98c3c4..2a4a51f 100755
--- a/passExtension/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/passExtension/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -143,6 +143,12 @@
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024@1x.png",
+ "scale" : "1x"
}
],
"info" : {
diff --git a/passExtension/Assets.xcassets/AppIcon.appiconset/Icon-App-1024@1x.png b/passExtension/Assets.xcassets/AppIcon.appiconset/Icon-App-1024@1x.png
new file mode 100644
index 0000000..161d1e0
Binary files /dev/null and b/passExtension/Assets.xcassets/AppIcon.appiconset/Icon-App-1024@1x.png differ
diff --git a/passExtension/Base.lproj/MainInterface.storyboard b/passExtension/Base.lproj/MainInterface.storyboard
index 974a830..4755832 100644
--- a/passExtension/Base.lproj/MainInterface.storyboard
+++ b/passExtension/Base.lproj/MainInterface.storyboard
@@ -1,11 +1,11 @@
-
+
-
+
@@ -13,7 +13,7 @@
-
+
@@ -25,7 +25,7 @@
-
+
@@ -37,18 +37,18 @@
-
+
-
+
-
+
@@ -101,7 +101,7 @@
-
+
diff --git a/passExtension/ExtensionViewController.swift b/passExtension/ExtensionViewController.swift
index 29f6f0b..6e2d67e 100644
--- a/passExtension/ExtensionViewController.swift
+++ b/passExtension/ExtensionViewController.swift
@@ -12,10 +12,14 @@ import passKit
fileprivate class PasswordsTableEntry : NSObject {
var title: String
+ var categoryText: String
+ var categoryArray: [String]
var passwordEntity: PasswordEntity?
- init(title: String, passwordEntity: PasswordEntity?) {
- self.title = title
- self.passwordEntity = passwordEntity
+ init(_ entity: PasswordEntity) {
+ self.title = entity.name!
+ self.categoryText = entity.getCategoryText()
+ self.categoryArray = entity.getCategoryArray()
+ self.passwordEntity = entity
}
}
@@ -46,7 +50,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
var passwordEntities = [PasswordEntity]()
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
passwordsTableEntries = passwordEntities.map {
- PasswordsTableEntry(title: $0.name!, passwordEntity: $0)
+ PasswordsTableEntry($0)
}
}
@@ -141,7 +145,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
cell.accessoryType = .none
cell.detailTextLabel?.font = UIFont.preferredFont(forTextStyle: .footnote)
- cell.detailTextLabel?.text = entry.passwordEntity?.getCategoryText()
+ cell.detailTextLabel?.text = entry.categoryText
return cell
}
@@ -223,7 +227,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
self.present(alert, animated: true, completion: nil)
}
let _ = sem.wait(timeout: DispatchTime.distantFuture)
- if SharedDefaults[.isRememberPassphraseOn] {
+ if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase
}
return passphrase
@@ -241,10 +245,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, searchText.isEmpty == false {
- let searchTextLowerCased = searchText.lowercased()
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
- let entryTitle = entry.title.lowercased()
- return entryTitle.contains(searchTextLowerCased) || searchTextLowerCased.contains(entryTitle)
+ var matched = false
+ matched = matched || entry.title.range(of: searchText, options: .caseInsensitive) != nil
+ matched = matched || searchText.range(of: entry.title, options: .caseInsensitive) != nil
+ entry.categoryArray.forEach({ (category) in
+ matched = matched || category.range(of: searchText, options: .caseInsensitive) != nil
+ matched = matched || searchText.range(of: category, options: .caseInsensitive) != nil
+ })
+ return matched
}
searchActive = true
} else {
diff --git a/passExtension/Info.plist b/passExtension/Info.plist
index f96e05e..5e96e11 100644
--- a/passExtension/Info.plist
+++ b/passExtension/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
XPC!
CFBundleShortVersionString
- 0.2.9
+ 0.3.0
CFBundleVersion
1
NSExtension
diff --git a/passKit/Helpers/AppError.swift b/passKit/Helpers/AppError.swift
index 6190c66..5386ee1 100644
--- a/passKit/Helpers/AppError.swift
+++ b/passKit/Helpers/AppError.swift
@@ -15,6 +15,7 @@ public enum AppError: Error {
case PasswordDuplicatedError
case GitResetError
case PGPPublicKeyNotExistError
+ case WrongPasswordFilename
case UnknownError
}
@@ -33,6 +34,8 @@ extension AppError: LocalizedError {
return "Cannot identify the latest synced commit."
case .PGPPublicKeyNotExistError:
return "PGP public key doesn't exist."
+ case .WrongPasswordFilename:
+ return "Cannot write to the password file."
case .UnknownError:
return "Unknown error."
}
diff --git a/passKit/Helpers/DefaultsKeys.swift b/passKit/Helpers/DefaultsKeys.swift
index 5a4a597..508f395 100644
--- a/passKit/Helpers/DefaultsKeys.swift
+++ b/passKit/Helpers/DefaultsKeys.swift
@@ -35,7 +35,8 @@ public extension DefaultsKeys {
static let isHideUnknownOn = DefaultsKey("isHideUnknownOn")
static let isHideOTPOn = DefaultsKey("isHideOTPOn")
- static let isRememberPassphraseOn = DefaultsKey("isRememberPassphraseOn")
+ static let isRememberPGPPassphraseOn = DefaultsKey("isRememberPGPPassphraseOn")
+ static let isRememberGitCredentialPassphraseOn = DefaultsKey("isRememberGitCredentialPassphraseOn")
static let isShowFolderOn = DefaultsKey("isShowFolderOn")
static let passwordGeneratorFlavor = DefaultsKey("passwordGeneratorFlavor")
diff --git a/passKit/Helpers/Globals.swift b/passKit/Helpers/Globals.swift
index 930ce56..cc7962d 100644
--- a/passKit/Helpers/Globals.swift
+++ b/passKit/Helpers/Globals.swift
@@ -47,11 +47,14 @@ public class Globals {
public static let passwordDots = "••••••••••••"
public static let oneTimePasswordDots = "••••••"
- public static let passwordFonts = "Menlo"
+ public static let passwordFont = UIFont(name: "Courier-Bold", size: UIFont.labelFontSize - 1)
// UI related
public static let red = UIColor(red:1.00, green:0.23, blue:0.19, alpha:1.0)
public static let blue = UIColor(red:0.00, green:0.48, blue:1.00, alpha:1.0)
+ public static let letterColor = UIColor(red:40/255.0, green:42/255.0, blue:54/255.0, alpha:1.0)
+ public static let symbolColor = UIColor(red:200/255.0, green:40/255.0, blue:41/255.0, alpha:1.0)
+ public static let digitColor = UIColor(red:66/255.0, green:113/255.0, blue:174/255.0, alpha:1.0)
public static let tableCellButtonSize = CGFloat(20.0)
private init() { }
diff --git a/passKit/Helpers/Utils.swift b/passKit/Helpers/Utils.swift
index 7767ffb..c97308e 100644
--- a/passKit/Helpers/Utils.swift
+++ b/passKit/Helpers/Utils.swift
@@ -107,9 +107,11 @@ public class Utils {
for (index, element) in plainPassword.unicodeScalars.enumerated() {
var charColor = UIColor.darkText
if NSCharacterSet.decimalDigits.contains(element) {
- charColor = Globals.red
+ charColor = Globals.digitColor
} else if !NSCharacterSet.letters.contains(element) {
- charColor = Globals.blue
+ charColor = Globals.symbolColor
+ } else {
+ charColor = Globals.letterColor
}
attributedPassword.addAttribute(NSAttributedStringKey.foregroundColor, value: charColor, range: NSRange(location: index, length: 1))
}
diff --git a/passKit/Info.plist b/passKit/Info.plist
index b7bfbec..caf06e5 100644
--- a/passKit/Info.plist
+++ b/passKit/Info.plist
@@ -17,7 +17,7 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 1.0
+ 0.3.0
CFBundleVersion
$(CURRENT_PROJECT_VERSION)
NSPrincipalClass
diff --git a/passKit/Models/GitCredential.swift b/passKit/Models/GitCredential.swift
index d71aa51..ddf5741 100644
--- a/passKit/Models/GitCredential.swift
+++ b/passKit/Models/GitCredential.swift
@@ -26,38 +26,39 @@ public struct GitCredential {
public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
var attempts = 0
- var lastPassword: String? = nil
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
var credential: GTCredential? = nil
switch self.credential {
case let .http(userName):
- var newPassword = self.passwordStore.gitPassword
- if newPassword == nil || attempts != 0 {
+ var lastPassword = self.passwordStore.gitPassword
+ if lastPassword == nil || attempts != 0 {
if let requestedPassword = requestGitPassword(self.credential, lastPassword) {
- newPassword = requestedPassword
- self.passwordStore.gitPassword = newPassword
+ if SharedDefaults[.isRememberGitCredentialPassphraseOn] {
+ self.passwordStore.gitPassword = requestedPassword
+ }
+ lastPassword = requestedPassword
} else {
return nil
}
}
attempts += 1
- lastPassword = newPassword
- credential = try? GTCredential(userName: userName, password: newPassword!)
+ credential = try? GTCredential(userName: userName, password: lastPassword!)
case let .ssh(userName, privateKeyFile):
// remarks: in fact, attempts > 1 never happens even with the wrong passphrase
- var newPassword = self.passwordStore.gitSSHPrivateKeyPassphrase
- if newPassword == nil || attempts != 0 {
+ var lastPassword = self.passwordStore.gitSSHPrivateKeyPassphrase
+ if lastPassword == nil || attempts != 0 {
if let requestedPassword = requestGitPassword(self.credential, lastPassword) {
- newPassword = requestedPassword
- self.passwordStore.gitSSHPrivateKeyPassphrase = newPassword
+ if SharedDefaults[.isRememberGitCredentialPassphraseOn] {
+ self.passwordStore.gitSSHPrivateKeyPassphrase = requestedPassword
+ }
+ lastPassword = requestedPassword
} else {
return nil
}
}
attempts += 1
- lastPassword = newPassword
- credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: newPassword!)
+ credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: lastPassword!)
}
return credential
}
@@ -66,9 +67,9 @@ public struct GitCredential {
public func delete() {
switch credential {
case .http:
- Utils.removeKeychain(name: "gitPassword")
+ self.passwordStore.gitPassword = nil
case .ssh:
- Utils.removeKeychain(name: "gitSSHKeyPassphrase")
+ self.passwordStore.gitSSHPrivateKeyPassphrase = nil
}
}
}
diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift
index b1e2426..cae9fee 100644
--- a/passKit/Models/Password.swift
+++ b/passKit/Models/Password.swift
@@ -84,7 +84,7 @@ public class Password {
additions.removeAll()
// split the plain text
- let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
+ let plainTextSplit = plainText.split(maxSplits: 1, omittingEmptySubsequences: false) {
$0 == "\n" || $0 == "\r\n"
}.map(String.init)
@@ -167,7 +167,7 @@ public class Password {
public func getAdditionsPlainText() -> String {
// lines starting from the second
- let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
+ let plainTextSplit = plainText.split(maxSplits: 1, omittingEmptySubsequences: false) {
$0 == "\n" || $0 == "\r\n"
}.map(String.init)
if plainTextSplit.count == 1 {
diff --git a/passKit/Models/PasswordEntity.swift b/passKit/Models/PasswordEntity.swift
index 645dba8..5678ce4 100644
--- a/passKit/Models/PasswordEntity.swift
+++ b/passKit/Models/PasswordEntity.swift
@@ -22,6 +22,10 @@ extension PasswordEntity {
}
public func getCategoryText() -> String {
+ return getCategoryArray().joined(separator: " > ")
+ }
+
+ public func getCategoryArray() -> [String] {
var parentEntity = parent
var passwordCategoryArray: [String] = []
while parentEntity != nil {
@@ -29,7 +33,7 @@ extension PasswordEntity {
parentEntity = parentEntity!.parent
}
passwordCategoryArray.reverse()
- return passwordCategoryArray.joined(separator: " > ")
+ return passwordCategoryArray
}
public func getURL() -> URL? {
diff --git a/passKit/Models/PasswordStore.swift b/passKit/Models/PasswordStore.swift
index 537672a..945cb32 100644
--- a/passKit/Models/PasswordStore.swift
+++ b/passKit/Models/PasswordStore.swift
@@ -21,16 +21,16 @@ public class PasswordStore {
public var storeRepository: GTRepository?
public var pgpKeyID: String?
- public var publicKey: PGPKey? {
+ public var publicKey: Key? {
didSet {
if publicKey != nil {
- pgpKeyID = publicKey!.keyID.shortKeyString
+ pgpKeyID = publicKey!.keyID.shortIdentifier
} else {
pgpKeyID = nil
}
}
}
- public var privateKey: PGPKey?
+ public var privateKey: Key?
public var gitSignatureForNow: GTSignature {
get {
@@ -120,6 +120,7 @@ public class PasswordStore {
print(Globals.documentPathLegacy)
print(Globals.libraryPathLegacy)
migrateIfNeeded()
+ backwardCompatibility()
do {
if fm.fileExists(atPath: storeURL.path) {
@@ -166,6 +167,17 @@ public class PasswordStore {
updatePasswordEntityCoreData()
}
+ private func backwardCompatibility() {
+ // For the newly-introduced isRememberGitCredentialPassphraseOn (20171008)
+ if (self.gitPassword != nil || self.gitSSHPrivateKeyPassphrase != nil) && SharedDefaults[.isRememberGitCredentialPassphraseOn] == false {
+ SharedDefaults[.isRememberGitCredentialPassphraseOn] = true
+ }
+ // For the renamed isRememberPGPPassphraseOn (20171008)
+ if self.pgpKeyPassphrase != nil && SharedDefaults[.isRememberPGPPassphraseOn] == false {
+ SharedDefaults[.isRememberPGPPassphraseOn] = true
+ }
+ }
+
enum SSHKeyType {
case `public`, secret
}
@@ -223,9 +235,10 @@ public class PasswordStore {
}
- private func importKey(from keyPath: String) -> PGPKey? {
+ private func importKey(from keyPath: String) -> Key? {
if fm.fileExists(atPath: keyPath) {
- let keys = pgp.importKeys(fromFile: keyPath)
+ let keys = ObjectivePGP.readKeys(from: keyPath)
+ pgp.import(keys: keys)
if !keys.isEmpty {
return keys.first
}
@@ -233,7 +246,7 @@ public class PasswordStore {
return nil
}
- public func getPgpPrivateKey() -> PGPKey {
+ public func getPgpPrivateKey() -> Key {
return pgp.keys.filter({$0.secretKey != nil})[0]
}
@@ -328,7 +341,6 @@ public class PasswordStore {
let remote = try GTRemote(name: "origin", in: storeRepository)
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: transferProgressBlock)
} catch {
- credential.delete()
throw(error)
}
DispatchQueue.main.async {
@@ -459,17 +471,6 @@ public class PasswordStore {
}
}
- public func getNumberOfUnsyncedPasswords() -> Int {
- let passwordEntityFetchRequest = NSFetchRequest(entityName: "PasswordEntity")
- do {
- passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
- return try context.count(for: passwordEntityFetchRequest)
- } catch {
- fatalError("Failed to fetch unsynced passwords: \(error)")
- }
- }
-
-
public func getLatestUpdateInfo(filename: String) -> String {
guard let storeRepository = storeRepository else {
return "Unknown"
@@ -581,7 +582,6 @@ public class PasswordStore {
try storeRepository.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
}
} catch {
- credential.delete()
throw(error)
}
}
@@ -592,18 +592,26 @@ public class PasswordStore {
}
var passwordURL = password.url!
+ var previousPathLength = Int.max
var paths: [String] = []
while passwordURL.path != "." {
paths.append(passwordURL.path)
passwordURL = passwordURL.deletingLastPathComponent()
+ // better identify errors before saving a new password
+ if passwordURL.path != "." && passwordURL.path.count >= previousPathLength {
+ throw AppError.WrongPasswordFilename
+ }
+ previousPathLength = passwordURL.path.count
}
paths.reverse()
+ print(paths)
var parentPasswordEntity: PasswordEntity? = nil
for path in paths {
let isDir = !path.hasSuffix(".gpg")
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
print(passwordEntity.path!)
parentPasswordEntity = passwordEntity
+ passwordEntity.synced = false
} else {
if !isDir {
return insertPasswordEntity(name: URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!.deletingPathExtension().lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: false)
@@ -661,8 +669,9 @@ public class PasswordStore {
let saveURL = storeURL.appendingPathComponent(passwordEntity.getURL()!.path)
try self.encrypt(password: password).write(to: saveURL)
try gitAdd(path: passwordEntity.getURL()!.path)
- let _ = try gitCommit(message: "Edit password for \(passwordEntity.getURL()!.deletingPathExtension().path.removingPercentEncoding!) to store using Pass for iOS.")
+ let _ = try gitCommit(message: "Edit password for \(passwordEntity.getURL()!.deletingPathExtension().path.removingPercentEncoding!) using Pass for iOS.")
newPasswordEntity = passwordEntity
+ newPasswordEntity?.synced = false
}
if password.changed&PasswordChange.path.rawValue != 0 {
@@ -831,7 +840,7 @@ public class PasswordStore {
if passphrase == nil {
passphrase = requestPGPKeyPassphrase()
}
- let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
+ let decryptedData = try PasswordStore.shared.pgp.decrypt(encryptedData, passphrase: passphrase)
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
let escapedPath = passwordEntity.path!.stringByAddingPercentEncodingForRFC3986() ?? ""
return Password(name: passwordEntity.name!, url: URL(string: escapedPath), plainText: plainText)
@@ -843,7 +852,7 @@ public class PasswordStore {
throw AppError.PGPPublicKeyNotExistError
}
let plainData = password.getPlainData()
- let encryptedData = try pgp.encryptData(plainData, using: Array(publicKey), armored: SharedDefaults[.encryptInArmored])
+ let encryptedData = try pgp.encrypt(plainData, using: Array(publicKey), armored: SharedDefaults[.encryptInArmored])
return encryptedData
}
@@ -865,7 +874,7 @@ public class PasswordStore {
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
Defaults.remove(.gitSSHPrivateKeyArmor)
Defaults.remove(.gitSSHPrivateKeyURL)
- Utils.removeKeychain(name: ".gitSSHPrivateKeyPassphrase")
+ self.gitSSHPrivateKeyPassphrase = nil
}
public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool {