If the switch is on, users need to type passphrase of secret key once. The key will be stored in the Keychain. If the switch is off, users have to fill in passphrase for each decryption including show password detail and long press to copy.
414 lines
20 KiB
Swift
414 lines
20 KiB
Swift
//
|
|
// SettingsTableViewController.swift
|
|
// pass
|
|
//
|
|
// Created by Mingshen Sun on 18/1/2017.
|
|
// Copyright © 2017 Bob Sun. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import SVProgressHUD
|
|
import CoreData
|
|
import SwiftyUserDefaults
|
|
import PasscodeLock
|
|
|
|
class SettingsTableViewController: UITableViewController {
|
|
|
|
let touchIDSwitch = UISwitch(frame: CGRect.zero)
|
|
|
|
@IBOutlet weak var pgpKeyTableViewCell: UITableViewCell!
|
|
@IBOutlet weak var touchIDTableViewCell: UITableViewCell!
|
|
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
|
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
|
|
|
|
@IBAction func cancelPGPKey(segue: UIStoryboardSegue) {
|
|
}
|
|
|
|
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
|
|
if let controller = segue.source as? PGPKeySettingTableViewController {
|
|
Defaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
|
|
Defaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
|
|
PasswordStore.shared.pgpKeyPassphrase = controller.pgpPassphrase
|
|
Defaults[.pgpKeySource] = "url"
|
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
do {
|
|
try PasswordStore.shared.initPGP(pgpPublicKeyURL: Defaults[.pgpPublicKeyURL]!,
|
|
pgpPublicKeyLocalPath: Globals.pgpPublicKeyPath,
|
|
pgpPrivateKeyURL: Defaults[.pgpPrivateKeyURL]!,
|
|
pgpPrivateKeyLocalPath: Globals.pgpPrivateKeyPath)
|
|
DispatchQueue.main.async {
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
|
SVProgressHUD.showSuccess(withStatus: "Success")
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
Utils.alert(title: "Rememver to Remove the Key", message: "Remember to remove the key from the server.", controller: self, completion: nil)
|
|
}
|
|
} catch {
|
|
DispatchQueue.main.async {
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
|
Defaults[.pgpKeyID] = nil
|
|
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
}
|
|
}
|
|
}
|
|
|
|
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
|
|
Defaults[.pgpKeySource] = "armor"
|
|
PasswordStore.shared.pgpKeyPassphrase = controller.pgpPassphrase
|
|
if Defaults[.isRememberPassphraseOn] {
|
|
Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: controller.pgpPassphrase!)
|
|
}
|
|
|
|
Defaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
|
Defaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
|
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
|
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
|
do {
|
|
try PasswordStore.shared.initPGP(pgpPublicKeyArmor: controller.armorPublicKeyTextView.text!,
|
|
pgpPublicKeyLocalPath: Globals.pgpPublicKeyPath,
|
|
pgpPrivateKeyArmor: controller.armorPrivateKeyTextView.text!,
|
|
pgpPrivateKeyLocalPath: Globals.pgpPrivateKeyPath)
|
|
DispatchQueue.main.async {
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
|
SVProgressHUD.showSuccess(withStatus: "Success")
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
}
|
|
} catch {
|
|
DispatchQueue.main.async {
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
|
Defaults[.pgpKeyID] = nil
|
|
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@IBAction func cancelGitServerSetting(segue: UIStoryboardSegue) {
|
|
}
|
|
|
|
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
|
|
if let controller = segue.source as? GitServerSettingTableViewController {
|
|
let gitRepostiroyURL = controller.gitRepositoryURLTextField.text!
|
|
let username = controller.usernameTextField.text!
|
|
let password = controller.password
|
|
let auth = controller.authenticationMethod
|
|
|
|
if Defaults[.gitRepositoryURL] == nil ||
|
|
Defaults[.gitRepositoryURL]!.absoluteString != gitRepostiroyURL ||
|
|
auth != Defaults[.gitRepositoryAuthenticationMethod] ||
|
|
username != Defaults[.gitRepositoryUsername] ||
|
|
password != PasswordStore.shared.gitRepositoryPassword ||
|
|
PasswordStore.shared.exists() == false {
|
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
SVProgressHUD.show(withStatus: "Prepare Repository")
|
|
var gitCredential: GitCredential
|
|
if auth == "Password" {
|
|
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: username, password: password!))
|
|
} else {
|
|
gitCredential = GitCredential(
|
|
credential: GitCredential.Credential.ssh(
|
|
userName: username,
|
|
password: Utils.getPasswordFromKeychain(name: "gitRepositorySSHPrivateKeyPassphrase") ?? "",
|
|
publicKeyFile: Globals.sshPublicKeyURL,
|
|
privateKeyFile: Globals.sshPrivateKeyURL,
|
|
passwordNotSetCallback: self.requestSshKeyPassword
|
|
)
|
|
)
|
|
}
|
|
let dispatchQueue = DispatchQueue.global(qos: .userInitiated)
|
|
dispatchQueue.async {
|
|
do {
|
|
try PasswordStore.shared.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!,
|
|
credential: gitCredential,
|
|
transferProgressBlock:{ (git_transfer_progress, stop) in
|
|
DispatchQueue.main.async {
|
|
SVProgressHUD.showProgress(Float(git_transfer_progress.pointee.received_objects)/Float(git_transfer_progress.pointee.total_objects), status: "Clone Remote Repository")
|
|
}
|
|
},
|
|
checkoutProgressBlock: { (path, completedSteps, totalSteps) in
|
|
DispatchQueue.main.async {
|
|
SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "Checkout Master Branch")
|
|
}
|
|
})
|
|
DispatchQueue.main.async {
|
|
PasswordStore.shared.updatePasswordEntityCoreData()
|
|
Defaults[.lastUpdatedTime] = Date()
|
|
NotificationCenter.default.post(Notification(name: Notification.Name("passwordUpdated")))
|
|
Defaults[.gitRepositoryURL] = URL(string: gitRepostiroyURL)
|
|
Defaults[.gitRepositoryUsername] = username
|
|
Defaults[.gitRepositoryAuthenticationMethod] = auth
|
|
Defaults[.gitRepositoryPasswordAttempts] = 0
|
|
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]?.host
|
|
SVProgressHUD.showSuccess(withStatus: "Done")
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
}
|
|
} catch {
|
|
DispatchQueue.main.async {
|
|
print(error)
|
|
SVProgressHUD.showError(withStatus: error.localizedDescription)
|
|
SVProgressHUD.dismiss(withDelay: 1)
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: NSNotification.Name(rawValue: "passwordStoreErased"), object: nil)
|
|
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]?.host
|
|
touchIDSwitch.onTintColor = UIColor(displayP3Red: 0, green: 122.0/255, blue: 1, alpha: 1)
|
|
touchIDTableViewCell.accessoryView = touchIDSwitch
|
|
touchIDSwitch.addTarget(self, action: #selector(touchIDSwitchAction), for: UIControlEvents.valueChanged)
|
|
setPGPKeyTableViewCellDetailText()
|
|
setPasswordRepositoryTableViewCellDetailText()
|
|
setTouchIDSwitch()
|
|
setPasscodeLockRepositoryTableViewCellDetailText()
|
|
}
|
|
|
|
private func setPasscodeLockRepositoryTableViewCellDetailText() {
|
|
if PasscodeLockRepository().hasPasscode {
|
|
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
|
} else {
|
|
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
|
touchIDSwitch.isEnabled = false
|
|
Globals.passcodeConfiguration.isTouchIDAllowed = false
|
|
}
|
|
}
|
|
|
|
private func setPGPKeyTableViewCellDetailText() {
|
|
if Defaults[.pgpKeyID] == nil {
|
|
pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
|
} else {
|
|
pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
|
}
|
|
}
|
|
|
|
private func setPasswordRepositoryTableViewCellDetailText() {
|
|
if Defaults[.gitRepositoryURL] == nil {
|
|
passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set"
|
|
} else {
|
|
passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitRepositoryURL]!.host
|
|
}
|
|
}
|
|
|
|
private func setTouchIDSwitch() {
|
|
if Defaults[.isTouchIDOn] {
|
|
touchIDSwitch.isOn = true
|
|
} else {
|
|
touchIDSwitch.isOn = false
|
|
}
|
|
}
|
|
|
|
func requestSshKeyPassword() -> String {
|
|
let sem = DispatchSemaphore(value: 0)
|
|
var newPassword = ""
|
|
|
|
DispatchQueue.main.async {
|
|
SVProgressHUD.dismiss()
|
|
let alert = UIAlertController(title: "Password", message: "Please fill in the password of your SSH key.", preferredStyle: UIAlertControllerStyle.alert)
|
|
|
|
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
|
newPassword = alert.textFields!.first!.text!
|
|
sem.signal()
|
|
}))
|
|
|
|
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
|
textField.text = PasswordStore.shared.gitRepositoryPassword
|
|
textField.isSecureTextEntry = true
|
|
})
|
|
|
|
self.present(alert, animated: true, completion: nil)
|
|
}
|
|
|
|
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
|
return newPassword
|
|
}
|
|
|
|
func actOnPasswordStoreErasedNotification() {
|
|
setPGPKeyTableViewCellDetailText()
|
|
setPasswordRepositoryTableViewCellDetailText()
|
|
setTouchIDSwitch()
|
|
setPasscodeLockRepositoryTableViewCellDetailText()
|
|
|
|
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
|
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
|
|
if Defaults[.passcodeKey] != nil{
|
|
showPasscodeActionSheet()
|
|
} else {
|
|
setPasscodeLock()
|
|
}
|
|
} else if tableView.cellForRow(at: indexPath) == pgpKeyTableViewCell {
|
|
showPGPKeyActionSheet()
|
|
}
|
|
tableView.deselectRow(at: indexPath, animated: true)
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
}
|
|
|
|
func touchIDSwitchAction(uiSwitch: UISwitch) {
|
|
if uiSwitch.isOn {
|
|
Defaults[.isTouchIDOn] = true
|
|
Globals.passcodeConfiguration.isTouchIDAllowed = true
|
|
} else {
|
|
Defaults[.isTouchIDOn] = false
|
|
Globals.passcodeConfiguration.isTouchIDAllowed = false
|
|
}
|
|
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
|
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
|
}
|
|
|
|
func pgpKeyExists() -> Bool {
|
|
return FileManager.default.fileExists(atPath: Globals.pgpPublicKeyPath) &&
|
|
FileManager.default.fileExists(atPath: Globals.pgpPrivateKeyPath)
|
|
}
|
|
|
|
func showPGPKeyActionSheet() {
|
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
var urlActionTitle = "Download from URL"
|
|
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
|
var fileActionTitle = "Use uploaded keys"
|
|
|
|
if Defaults[.pgpKeySource] == "url" {
|
|
urlActionTitle = "✓ \(urlActionTitle)"
|
|
} else if Defaults[.pgpKeySource] == "armor" {
|
|
armorActionTitle = "✓ \(armorActionTitle)"
|
|
} else if Defaults[.pgpKeySource] == "file" {
|
|
fileActionTitle = "✓ \(fileActionTitle)"
|
|
}
|
|
let urlAction = UIAlertAction(title: urlActionTitle, style: .default) { _ in
|
|
self.performSegue(withIdentifier: "setPGPKeyByURLSegue", sender: self)
|
|
}
|
|
let armorAction = UIAlertAction(title: armorActionTitle, style: .default) { _ in
|
|
self.performSegue(withIdentifier: "setPGPKeyByASCIISegue", sender: self)
|
|
}
|
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
|
optionMenu.addAction(urlAction)
|
|
optionMenu.addAction(armorAction)
|
|
|
|
if (pgpKeyExists()) {
|
|
let fileAction = UIAlertAction(title: fileActionTitle, style: .default) { _ in
|
|
|
|
SVProgressHUD.setDefaultMaskType(.black)
|
|
SVProgressHUD.setDefaultStyle(.light)
|
|
SVProgressHUD.show(withStatus: "Reading PGP key")
|
|
|
|
let alert = UIAlertController(
|
|
title: "PGP Passphrase",
|
|
message: "Please fill in the passphrase for your PGP key.",
|
|
preferredStyle: UIAlertControllerStyle.alert
|
|
)
|
|
|
|
alert.addAction(
|
|
UIAlertAction(
|
|
title: "OK",
|
|
style: UIAlertActionStyle.default,
|
|
handler: {_ in
|
|
Utils.addPasswrodToKeychain(
|
|
name: "pgpKeyPassphrase",
|
|
password: alert.textFields!.first!.text!
|
|
)
|
|
}
|
|
)
|
|
)
|
|
|
|
alert.addTextField(
|
|
configurationHandler: {(textField: UITextField!) in
|
|
textField.text = Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase") ?? ""
|
|
textField.isSecureTextEntry = true
|
|
}
|
|
)
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
try? PasswordStore.shared.initPGP(
|
|
pgpPublicKeyLocalPath: Globals.pgpPublicKeyPath,
|
|
pgpPrivateKeyLocalPath: Globals.pgpPrivateKeyPath
|
|
)
|
|
|
|
let key: PGPKey = PasswordStore.shared.getPgpPrivateKey()
|
|
Defaults[.pgpKeySource] = "file"
|
|
|
|
if (key.isEncrypted) {
|
|
SVProgressHUD.dismiss()
|
|
self.present(alert, animated: true, completion: nil)
|
|
}
|
|
|
|
SVProgressHUD.dismiss()
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = Defaults[.pgpKeyID]
|
|
}
|
|
|
|
}
|
|
|
|
optionMenu.addAction(fileAction)
|
|
}
|
|
|
|
if Defaults[.pgpKeySource] != nil {
|
|
let deleteAction = UIAlertAction(title: "Remove PGP Keys", style: .destructive) { _ in
|
|
Utils.removePGPKeys()
|
|
self.pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
|
}
|
|
optionMenu.addAction(deleteAction)
|
|
}
|
|
optionMenu.addAction(cancelAction)
|
|
optionMenu.popoverPresentationController?.sourceView = pgpKeyTableViewCell
|
|
optionMenu.popoverPresentationController?.sourceRect = pgpKeyTableViewCell.bounds
|
|
self.present(optionMenu, animated: true, completion: nil)
|
|
}
|
|
|
|
func showPasscodeActionSheet() {
|
|
let passcodeChangeViewController = PasscodeLockViewController(state: .change, configuration: Globals.passcodeConfiguration)
|
|
let passcodeRemoveViewController = PasscodeLockViewController(state: .remove, configuration: Globals.passcodeConfiguration)
|
|
|
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
|
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
|
|
passcodeRemoveViewController.successCallback = { _ in
|
|
self?.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
|
self?.touchIDSwitch.isEnabled = false
|
|
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
|
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
|
}
|
|
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
|
}
|
|
|
|
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [weak self] _ in
|
|
self?.present(passcodeChangeViewController, animated: true, completion: nil)
|
|
}
|
|
|
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
|
optionMenu.addAction(removePasscodeAction)
|
|
optionMenu.addAction(changePasscodeAction)
|
|
optionMenu.addAction(cancelAction)
|
|
optionMenu.popoverPresentationController?.sourceView = passcodeTableViewCell
|
|
optionMenu.popoverPresentationController?.sourceRect = passcodeTableViewCell.bounds
|
|
self.present(optionMenu, animated: true, completion: nil)
|
|
}
|
|
|
|
func setPasscodeLock() {
|
|
let passcodeSetViewController = PasscodeLockViewController(state: .set, configuration: Globals.passcodeConfiguration)
|
|
passcodeSetViewController.successCallback = { _ in
|
|
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
|
self.touchIDSwitch.isEnabled = true
|
|
}
|
|
present(passcodeSetViewController, animated: true, completion: nil)
|
|
}
|
|
}
|