Provide the 'remember git credential passphrases' option

This commit is contained in:
Yishi Lin 2017-10-08 21:37:58 +08:00
parent c57ae4f88e
commit d0bad8660b
11 changed files with 97 additions and 44 deletions

View file

@ -28,12 +28,21 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
return uiSwitch return uiSwitch
}() }()
let rememberPassphraseSwitch: UISwitch = { let rememberPGPPassphraseSwitch: UISwitch = {
let uiSwitch = UISwitch() let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue uiSwitch.onTintColor = Globals.blue
uiSwitch.sizeToFit() uiSwitch.sizeToFit()
uiSwitch.addTarget(self, action: #selector(rememberPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged) uiSwitch.addTarget(self, action: #selector(rememberPGPPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
uiSwitch.isOn = SharedDefaults[.isRememberPassphraseOn] 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 return uiSwitch
}() }()
@ -58,7 +67,8 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
// section 2 // 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 Folder", .action: "none",],
@ -98,10 +108,14 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
cell.accessoryView = accessoryView cell.accessoryView = accessoryView
cell.selectionStyle = .none cell.selectionStyle = .none
hideOTPSwitch.isOn = SharedDefaults[.isHideOTPOn] hideOTPSwitch.isOn = SharedDefaults[.isHideOTPOn]
case "Remember Passphrase": case "Remember PGP Key Passphrase":
cell.accessoryType = .none cell.accessoryType = .none
cell.selectionStyle = .none cell.selectionStyle = .none
cell.accessoryView = rememberPassphraseSwitch cell.accessoryView = rememberPGPPassphraseSwitch
case "Remember Git Credential Passphrase":
cell.accessoryType = .none
cell.selectionStyle = .none
cell.accessoryView = rememberGitCredentialPassphraseSwitch
case "Show Folder": case "Show Folder":
cell.accessoryType = .none cell.accessoryType = .none
cell.selectionStyle = .none cell.selectionStyle = .none
@ -176,13 +190,21 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
} }
@objc func rememberPassphraseSwitchAction(_ sender: Any?) { @objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
SharedDefaults[.isRememberPassphraseOn] = rememberPassphraseSwitch.isOn SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
if rememberPassphraseSwitch.isOn == false { if rememberPGPPassphraseSwitch.isOn == false {
passwordStore.pgpKeyPassphrase = nil 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?) { @objc func showFolderSwitchAction(_ sender: Any?) {
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil) NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)

View file

@ -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) let dispatchQueue = DispatchQueue.global(qos: .userInitiated)
dispatchQueue.async { dispatchQueue.async {
do { do {
@ -113,9 +115,21 @@ class GitServerSettingTableViewController: UITableViewController {
SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL) SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL)
SharedDefaults[.gitUsername] = username SharedDefaults[.gitUsername] = username
SharedDefaults[.gitAuthenticationMethod] = auth SharedDefaults[.gitAuthenticationMethod] = auth
SVProgressHUD.showSuccess(withStatus: "Done") SVProgressHUD.dismiss()
SVProgressHUD.dismiss(withDelay: 1) let savePassphraseAlert = UIAlertController(title: "Done", message: "Do you want to save the Git credential password/passphrase?", preferredStyle: UIAlertControllerStyle.alert)
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self) // 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 { } catch {
DispatchQueue.main.async { DispatchQueue.main.async {
@ -257,7 +271,7 @@ class GitServerSettingTableViewController: UITableViewController {
case .http: case .http:
message = "Please fill in the password of your Git account." message = "Please fill in the password of your Git account."
case .ssh: 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 { DispatchQueue.main.async {

View file

@ -120,7 +120,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
// no // no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.pgpPassphrase = nil self.pgpPassphrase = nil
SharedDefaults[.isRememberPassphraseOn] = false SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}) })
// yes // 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) 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 alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
SharedDefaults[.isRememberPassphraseOn] = true SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in

View file

@ -45,7 +45,7 @@ class PGPKeySettingTableViewController: UITableViewController {
// no // no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.pgpPassphrase = nil self.pgpPassphrase = nil
SharedDefaults[.isRememberPassphraseOn] = false SharedDefaults[.isRememberPGPPassphraseOn] = false
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
}) })
// yes // 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) 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 alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.pgpPassphrase = alert.textFields?.first?.text self.pgpPassphrase = alert.textFields?.first?.text
SharedDefaults[.isRememberPassphraseOn] = true SharedDefaults[.isRememberPGPPassphraseOn] = true
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self) self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in

View file

@ -134,7 +134,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
let _ = sem.wait(timeout: DispatchTime.distantFuture) let _ = sem.wait(timeout: DispatchTime.distantFuture)
if SharedDefaults[.isRememberPassphraseOn] { if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase self.passwordStore.pgpKeyPassphrase = passphrase
} }
return passphrase return passphrase

View file

@ -174,8 +174,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
DispatchQueue.main.async { DispatchQueue.main.async {
SVProgressHUD.dismiss() SVProgressHUD.dismiss()
self.syncControl.endRefreshing() 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)) { 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)
} }
} }
} }
@ -369,7 +378,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
// bring back // bring back
SVProgressHUD.show(withStatus: "Decrypting") SVProgressHUD.show(withStatus: "Decrypting")
} }
if SharedDefaults[.isRememberPassphraseOn] { if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase self.passwordStore.pgpKeyPassphrase = passphrase
} }
return passphrase return passphrase

View file

@ -33,7 +33,7 @@ class SettingsTableViewController: UITableViewController {
if let controller = segue.source as? PGPKeySettingTableViewController { if let controller = segue.source as? PGPKeySettingTableViewController {
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!) SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
SharedDefaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!) SharedDefaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
if SharedDefaults[.isRememberPassphraseOn] { if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
} }
SharedDefaults[.pgpKeySource] = "url" SharedDefaults[.pgpKeySource] = "url"
@ -61,7 +61,7 @@ class SettingsTableViewController: UITableViewController {
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController { } else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
SharedDefaults[.pgpKeySource] = "armor" SharedDefaults[.pgpKeySource] = "armor"
if SharedDefaults[.isRememberPassphraseOn] { if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
} }
@ -259,7 +259,7 @@ class SettingsTableViewController: UITableViewController {
// no // no
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
self.passwordStore.pgpKeyPassphrase = nil self.passwordStore.pgpKeyPassphrase = nil
SharedDefaults[.isRememberPassphraseOn] = false SharedDefaults[.isRememberPGPPassphraseOn] = false
self.saveImportedPGPKey() self.saveImportedPGPKey()
}) })
// yes // 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) 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 alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
self.passwordStore.pgpKeyPassphrase = alert.textFields?.first?.text self.passwordStore.pgpKeyPassphrase = alert.textFields?.first?.text
SharedDefaults[.isRememberPassphraseOn] = true SharedDefaults[.isRememberPGPPassphraseOn] = true
self.saveImportedPGPKey() self.saveImportedPGPKey()
})) }))
alert.addTextField(configurationHandler: {(textField: UITextField!) in alert.addTextField(configurationHandler: {(textField: UITextField!) in

View file

@ -223,7 +223,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
let _ = sem.wait(timeout: DispatchTime.distantFuture) let _ = sem.wait(timeout: DispatchTime.distantFuture)
if SharedDefaults[.isRememberPassphraseOn] { if SharedDefaults[.isRememberPGPPassphraseOn] {
self.passwordStore.pgpKeyPassphrase = passphrase self.passwordStore.pgpKeyPassphrase = passphrase
} }
return passphrase return passphrase

View file

@ -35,7 +35,8 @@ public extension DefaultsKeys {
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn") static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn") static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
static let isRememberPassphraseOn = DefaultsKey<Bool>("isRememberPassphraseOn") static let isRememberPGPPassphraseOn = DefaultsKey<Bool>("isRememberPGPPassphraseOn")
static let isRememberGitCredentialPassphraseOn = DefaultsKey<Bool>("isRememberGitCredentialPassphraseOn")
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn") static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor") static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")

View file

@ -26,38 +26,39 @@ public struct GitCredential {
public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider { public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
var attempts = 0 var attempts = 0
var lastPassword: String? = nil
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
var credential: GTCredential? = nil var credential: GTCredential? = nil
switch self.credential { switch self.credential {
case let .http(userName): case let .http(userName):
var newPassword = self.passwordStore.gitPassword var lastPassword = self.passwordStore.gitPassword
if newPassword == nil || attempts != 0 { if lastPassword == nil || attempts != 0 {
if let requestedPassword = requestGitPassword(self.credential, lastPassword) { if let requestedPassword = requestGitPassword(self.credential, lastPassword) {
newPassword = requestedPassword if SharedDefaults[.isRememberGitCredentialPassphraseOn] {
self.passwordStore.gitPassword = newPassword self.passwordStore.gitPassword = requestedPassword
}
lastPassword = requestedPassword
} else { } else {
return nil return nil
} }
} }
attempts += 1 attempts += 1
lastPassword = newPassword credential = try? GTCredential(userName: userName, password: lastPassword!)
credential = try? GTCredential(userName: userName, password: newPassword!)
case let .ssh(userName, privateKeyFile): case let .ssh(userName, privateKeyFile):
// remarks: in fact, attempts > 1 never happens even with the wrong passphrase // remarks: in fact, attempts > 1 never happens even with the wrong passphrase
var newPassword = self.passwordStore.gitSSHPrivateKeyPassphrase var lastPassword = self.passwordStore.gitSSHPrivateKeyPassphrase
if newPassword == nil || attempts != 0 { if lastPassword == nil || attempts != 0 {
if let requestedPassword = requestGitPassword(self.credential, lastPassword) { if let requestedPassword = requestGitPassword(self.credential, lastPassword) {
newPassword = requestedPassword if SharedDefaults[.isRememberGitCredentialPassphraseOn] {
self.passwordStore.gitSSHPrivateKeyPassphrase = newPassword self.passwordStore.gitSSHPrivateKeyPassphrase = requestedPassword
}
lastPassword = requestedPassword
} else { } else {
return nil return nil
} }
} }
attempts += 1 attempts += 1
lastPassword = newPassword credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: lastPassword!)
credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: newPassword!)
} }
return credential return credential
} }
@ -66,9 +67,9 @@ public struct GitCredential {
public func delete() { public func delete() {
switch credential { switch credential {
case .http: case .http:
Utils.removeKeychain(name: "gitPassword") self.passwordStore.gitPassword = nil
case .ssh: case .ssh:
Utils.removeKeychain(name: "gitSSHKeyPassphrase") self.passwordStore.gitSSHPrivateKeyPassphrase = nil
} }
} }
} }

View file

@ -120,6 +120,7 @@ public class PasswordStore {
print(Globals.documentPathLegacy) print(Globals.documentPathLegacy)
print(Globals.libraryPathLegacy) print(Globals.libraryPathLegacy)
migrateIfNeeded() migrateIfNeeded()
backwardCompatibility()
do { do {
if fm.fileExists(atPath: storeURL.path) { if fm.fileExists(atPath: storeURL.path) {
@ -166,6 +167,13 @@ public class PasswordStore {
updatePasswordEntityCoreData() updatePasswordEntityCoreData()
} }
private func backwardCompatibility() {
// For the newly-introduced isRememberGitCredentialPassphraseOn (20171008)
if (self.gitPassword != nil || self.gitSSHPrivateKeyPassphrase != nil) && SharedDefaults[.isRememberGitCredentialPassphraseOn] == false {
SharedDefaults[.isRememberGitCredentialPassphraseOn] = true
}
}
enum SSHKeyType { enum SSHKeyType {
case `public`, secret case `public`, secret
} }
@ -328,7 +336,6 @@ public class PasswordStore {
let remote = try GTRemote(name: "origin", in: storeRepository) let remote = try GTRemote(name: "origin", in: storeRepository)
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: transferProgressBlock) try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: transferProgressBlock)
} catch { } catch {
credential.delete()
throw(error) throw(error)
} }
DispatchQueue.main.async { DispatchQueue.main.async {
@ -581,7 +588,6 @@ public class PasswordStore {
try storeRepository.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock) try storeRepository.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
} }
} catch { } catch {
credential.delete()
throw(error) throw(error)
} }
} }
@ -865,7 +871,7 @@ public class PasswordStore {
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath) Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
Defaults.remove(.gitSSHPrivateKeyArmor) Defaults.remove(.gitSSHPrivateKeyArmor)
Defaults.remove(.gitSSHPrivateKeyURL) Defaults.remove(.gitSSHPrivateKeyURL)
Utils.removeKeychain(name: ".gitSSHPrivateKeyPassphrase") self.gitSSHPrivateKeyPassphrase = nil
} }
public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool { public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool {