diff --git a/pass/Base.lproj/Main.storyboard b/pass/Base.lproj/Main.storyboard index 63b58bd..8204f7b 100644 --- a/pass/Base.lproj/Main.storyboard +++ b/pass/Base.lproj/Main.storyboard @@ -319,15 +319,15 @@ - + - + - + @@ -488,6 +488,8 @@ + + diff --git a/pass/Controllers/AboutRepositoryTableViewController.swift b/pass/Controllers/AboutRepositoryTableViewController.swift index 8123a71..3f1bddf 100644 --- a/pass/Controllers/AboutRepositoryTableViewController.swift +++ b/pass/Controllers/AboutRepositoryTableViewController.swift @@ -51,7 +51,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController { DispatchQueue.global(qos: .userInitiated).async { let passwords = self.numberOfPasswordsString() let size = self.sizeOfRepositoryString() - let localCommits = self.numberOfLocalCommitsString() + let localCommits = self.passwordStore.numberOfLocalCommits let lastSynced = self.lastSyncedTimeString() let commits = self.numberOfCommitsString() @@ -86,13 +86,6 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController { return ByteCountFormatter.string(fromByteCount: Int64(self.passwordStore.sizeOfRepositoryByteCount), countStyle: ByteCountFormatter.CountStyle.file) } - private func numberOfLocalCommitsString() -> String { - if let numberOfLocalCommits = passwordStore.numberOfLocalCommits { - return String(numberOfLocalCommits) - } - return AboutRepositoryTableViewController.VALUE_NOT_AVAILABLE - } - private func lastSyncedTimeString() -> String { guard let date = self.passwordStore.lastSyncedTime else { return "SyncAgain?".localize() diff --git a/pass/Controllers/GitServerSettingTableViewController.swift b/pass/Controllers/GitServerSettingTableViewController.swift index b76630f..3509d1a 100644 --- a/pass/Controllers/GitServerSettingTableViewController.swift +++ b/pass/Controllers/GitServerSettingTableViewController.swift @@ -10,6 +10,7 @@ import UIKit import SVProgressHUD import passKit + class GitServerSettingTableViewController: UITableViewController { @IBOutlet weak var gitURLTextField: UITextField! @@ -17,25 +18,50 @@ class GitServerSettingTableViewController: UITableViewController { @IBOutlet weak var branchNameTextField: UITextField! @IBOutlet weak var authSSHKeyCell: UITableViewCell! @IBOutlet weak var authPasswordCell: UITableViewCell! - let passwordStore = PasswordStore.shared - var sshLabel: UILabel? = nil + @IBOutlet weak var gitURLCell: UITableViewCell! + @IBOutlet weak var gitRepositoryURLTabelViewCell: UITableViewCell! + private let passwordStore = PasswordStore.shared + private var sshLabel: UILabel? = nil - var authenticationMethod = SharedDefaults[.gitAuthenticationMethod] ?? "Password" + private var gitAuthenticationMethod: GitAuthenticationMethod { + get { SharedDefaults[.gitAuthenticationMethod] } + set { SharedDefaults[.gitAuthenticationMethod] = newValue } + } + private var gitUrl: URL { + get { SharedDefaults[.gitURL] } + set { SharedDefaults[.gitURL] = newValue } + } + private var gitBranchName: String { + get { SharedDefaults[.gitBranchName] } + set { SharedDefaults[.gitBranchName] = newValue } + } + private var gitUsername: String { + get { SharedDefaults[.gitUsername] } + set { SharedDefaults[.gitUsername] = newValue } + } + private var gitCredential: GitCredential { + get { + switch SharedDefaults[.gitAuthenticationMethod] { + case .password: + return GitCredential(credential: .http(userName: SharedDefaults[.gitUsername])) + case .key: + let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? "" + return GitCredential(credential: .ssh(userName: SharedDefaults[.gitUsername], privateKey: privateKey)) + } + } + } - private func checkAuthenticationMethod(method: String) { + private func checkAuthenticationMethod() { let passwordCheckView = authPasswordCell.viewWithTag(1001)! let sshKeyCheckView = authSSHKeyCell.viewWithTag(1001)! - switch method { - case "Password": + switch self.gitAuthenticationMethod { + case .password: passwordCheckView.isHidden = false sshKeyCheckView.isHidden = true - case "SSH Key": + case .key: passwordCheckView.isHidden = true sshKeyCheckView.isHidden = false - default: - passwordCheckView.isHidden = false - sshKeyCheckView.isHidden = true } } @@ -48,13 +74,11 @@ class GitServerSettingTableViewController: UITableViewController { } override func viewDidLoad() { super.viewDidLoad() - if let url = SharedDefaults[.gitURL] { - gitURLTextField.text = url.absoluteString - } - usernameTextField.text = SharedDefaults[.gitUsername] - branchNameTextField.text = SharedDefaults[.gitBranchName] + gitURLTextField.text = self.gitUrl.absoluteString + usernameTextField.text = self.gitUsername + branchNameTextField.text = self.gitBranchName sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel - checkAuthenticationMethod(method: authenticationMethod) + checkAuthenticationMethod() authSSHKeyCell.accessoryType = .detailButton } @@ -62,9 +86,17 @@ class GitServerSettingTableViewController: UITableViewController { let cell = tableView.cellForRow(at: indexPath) if cell == authSSHKeyCell { showSSHKeyActionSheet() + } else if cell == gitURLCell { + showGitURLFormatHelp() } } + private func showGitURLFormatHelp() { + let alert = UIAlertController(title: "Git URL Format", message: "https://example.com[:port]/project.git\nssh://[user@]server[:port]/project.git\n[user@]server:project.git (no scheme)", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in })) + self.present(alert, animated: true, completion: nil) + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -76,36 +108,15 @@ class GitServerSettingTableViewController: UITableViewController { } private func cloneAndSegueIfSuccess() { - // try to clone - let gitRepostiroyURL = gitURLTextField.text!.trimmed - let username = usernameTextField.text!.trimmed - let branchName = branchNameTextField.text!.trimmed - let auth = authenticationMethod - - SVProgressHUD.setDefaultMaskType(.black) - SVProgressHUD.setDefaultStyle(.light) - SVProgressHUD.show(withStatus: "PrepareRepository".localize()) - var gitCredential: GitCredential - let privateKey: String? = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) - if auth == "Password" || privateKey == nil { - gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: username)) - } else { - gitCredential = GitCredential( - credential: GitCredential.Credential.ssh( - userName: username, - privateKey: privateKey! - ) - ) - } // Remember git credential password/passphrase temporarily, ask whether users want this after a successful clone. SharedDefaults[.isRememberGitCredentialPassphraseOn] = true let group = DispatchGroup() let dispatchQueue = DispatchQueue.global(qos: .userInitiated) dispatchQueue.async(group: group) { do { - try self.passwordStore.cloneRepository(remoteRepoURL: URL(string: gitRepostiroyURL)!, - credential: gitCredential, - branchName: branchName, + try self.passwordStore.cloneRepository(remoteRepoURL: self.gitUrl, + credential: self.gitCredential, + branchName: self.gitBranchName, requestGitPassword: self.requestGitPassword, transferProgressBlock: { (git_transfer_progress, stop) in DispatchQueue.main.async { @@ -113,7 +124,7 @@ class GitServerSettingTableViewController: UITableViewController { } }, checkoutProgressBlock: { (path, completedSteps, totalSteps) in - DispatchQueue.main.async { SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "CheckingOutBranch".localize(branchName)) + DispatchQueue.main.async { SVProgressHUD.showProgress(Float(completedSteps)/Float(totalSteps), status: "CheckingOutBranch".localize(self.gitBranchName)) } }) } catch { @@ -130,10 +141,6 @@ class GitServerSettingTableViewController: UITableViewController { group.notify(queue: dispatchQueue) { NSLog("clone done") DispatchQueue.main.async { - SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL) - SharedDefaults[.gitUsername] = username - SharedDefaults[.gitBranchName] = branchName - SharedDefaults[.gitAuthenticationMethod] = auth SVProgressHUD.dismiss { let savePassphraseAlert = UIAlertController(title: "Done".localize(), message: "WantToSaveGitCredential?".localize(), preferredStyle: UIAlertController.Style.alert) // no @@ -157,24 +164,23 @@ class GitServerSettingTableViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cell = tableView.cellForRow(at: indexPath) if cell == authPasswordCell { - authenticationMethod = "Password" + self.gitAuthenticationMethod = .password } else if cell == authSSHKeyCell { if !AppKeychain.shared.contains(key: SshKey.PRIVATE.getKeychainKey()) { Utils.alert(title: "CannotSelectSshKey".localize(), message: "PleaseSetupSshKeyFirst.".localize(), controller: self, completion: nil) - authenticationMethod = "Password" + gitAuthenticationMethod = .password } else { - authenticationMethod = "SSH Key" + gitAuthenticationMethod = .key } } - checkAuthenticationMethod(method: authenticationMethod) + checkAuthenticationMethod() tableView.deselectRow(at: indexPath, animated: true) } @IBAction func save(_ sender: Any) { - // some sanity checks - guard let gitURL = URL(string: gitURLTextField.text!) else { + guard let gitURLTextFieldText = gitURLTextField.text, let gitURL = URL(string: gitURLTextFieldText) else { Utils.alert(title: "CannotSave".localize(), message: "SetGitRepositoryUrl".localize(), controller: self, completion: nil) return } @@ -184,26 +190,30 @@ class GitServerSettingTableViewController: UITableViewController { return } - switch gitURL.scheme { - case let val where val == "https": - break - case let val where val == "ssh": - guard let sshUsername = gitURL.user, sshUsername.isEmpty == false else { + func checkUsername() { + if gitURL.user == nil && usernameTextField.text == nil { Utils.alert(title: "CannotSave".localize(), message: "CannotFindUsername.".localize(), controller: self, completion: nil) return } - guard let username = usernameTextField.text, username == sshUsername else { + if let urlUsername = gitURL.user, let textFieldUsername = usernameTextField.text, urlUsername != textFieldUsername.trimmed { Utils.alert(title: "CannotSave".localize(), message: "CheckEnteredUsername.".localize(), controller: self, completion: nil) return } - case let val where val == "http": - Utils.alert(title: "CannotSave".localize(), message: "UseHttps.".localize(), controller: self, completion: nil) - return - default: - Utils.alert(title: "CannotSave".localize(), message: "SpecifySchema.".localize(), controller: self, completion: nil) - return } + if let scheme = gitURL.scheme { + switch scheme { + case "ssh", "http", "https", "file": checkUsername() + default: + Utils.alert(title: "CannotSave".localize(), message: "Protocol is not supported", controller: self, completion: nil) + return + } + } + + self.gitUrl = gitURL + self.gitBranchName = branchName + self.gitUsername = (gitURL.user ?? usernameTextField.text ?? "git").trimmed + if passwordStore.repositoryExisted() { let alert = UIAlertController(title: "Overwrite?".localize(), message: "OperationWillOverwriteData.".localize(), preferredStyle: UIAlertController.Style.alert) alert.addAction(UIAlertAction(title: "Overwrite".localize(), style: UIAlertAction.Style.destructive, handler: { _ in @@ -272,7 +282,7 @@ class GitServerSettingTableViewController: UITableViewController { SharedDefaults[.gitSSHKeySource] = nil if let sshLabel = self.sshLabel { sshLabel.isEnabled = false - self.checkAuthenticationMethod(method: "Password".localize()) + self.checkAuthenticationMethod() } } optionMenu.addAction(deleteAction) diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index f98d60c..4263e43 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -42,6 +42,18 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV case unsynced } + private var gitCredential: GitCredential { + get { + switch SharedDefaults[.gitAuthenticationMethod] { + case .password: + return GitCredential(credential: .http(userName: SharedDefaults[.gitUsername])) + case .key: + let privateKey: String = AppKeychain.shared.get(for: SshKey.PRIVATE.getKeychainKey()) ?? "" + return GitCredential(credential: .ssh(userName: SharedDefaults[.gitUsername], privateKey: privateKey)) + } + } + } + private lazy var searchController: UISearchController = { let uiSearchController = UISearchController(searchResultsController: nil) uiSearchController.searchResultsUpdater = self @@ -173,27 +185,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV SVProgressHUD.setDefaultMaskType(.black) SVProgressHUD.setDefaultStyle(.light) SVProgressHUD.show(withStatus: "SyncingPasswordStore".localize()) - var gitCredential: GitCredential - let privateKey: String? = self.keychain.get(for: SshKey.PRIVATE.getKeychainKey()) - if SharedDefaults[.gitAuthenticationMethod] == "Password" || privateKey == nil { - gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: SharedDefaults[.gitUsername]!)) - } else { - gitCredential = GitCredential( - credential: GitCredential.Credential.ssh( - userName: SharedDefaults[.gitUsername]!, - privateKey: privateKey! - ) - ) - } + + DispatchQueue.global(qos: .userInitiated).async { [unowned self] in do { - try self.passwordStore.pullRepository(credential: gitCredential, requestGitPassword: self.requestGitPassword(credential:lastPassword:), transferProgressBlock: {(git_transfer_progress, stop) in + try self.passwordStore.pullRepository(credential: self.gitCredential, requestGitPassword: self.requestGitPassword(credential:lastPassword:), 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: "PullingFromRemoteRepository".localize()) } }) - if self.passwordStore.numberOfLocalCommits ?? 0 > 0 { - try self.passwordStore.pushRepository(credential: gitCredential, requestGitPassword: self.requestGitPassword(credential:lastPassword:), transferProgressBlock: {(current, total, bytes, stop) in + if self.passwordStore.numberOfLocalCommits > 0 { + try self.passwordStore.pushRepository(credential: self.gitCredential, requestGitPassword: self.requestGitPassword(credential:lastPassword:), transferProgressBlock: {(current, total, bytes, stop) in DispatchQueue.main.async { SVProgressHUD.showProgress(Float(current)/Float(total), status: "PushingToRemoteRepository".localize()) } @@ -215,7 +217,6 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV message = message | "UnderlyingError".localize(underlyingError.localizedDescription) if underlyingError.localizedDescription.contains("WrongPassphrase".localize()) { message = message | "RecoverySuggestion.".localize() - gitCredential.delete() } } DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) { @@ -593,8 +594,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV private func reloadTableView(data: [PasswordsTableEntry], label: PasswordLabel = .all, anim: CAAnimation? = nil) { // set navigation item - if let numberOfLocalCommits = passwordStore.numberOfLocalCommits, numberOfLocalCommits != 0 { - navigationController?.tabBarItem.badgeValue = "\(numberOfLocalCommits)" + if passwordStore.numberOfLocalCommits != 0 { + navigationController?.tabBarItem.badgeValue = "\(passwordStore.numberOfLocalCommits)" } else { navigationController?.tabBarItem.badgeValue = nil } diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index e316560..934f106 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -105,7 +105,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele } @IBAction func saveGitServerSetting(segue: UIStoryboardSegue) { - self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host + self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL].host } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -115,7 +115,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele override func viewDidLoad() { super.viewDidLoad() NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil) - self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host + self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL].host setPGPKeyTableViewCellDetailText() setPasswordRepositoryTableViewCellDetailText() setPasscodeLockCell() @@ -139,11 +139,15 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele } private func setPasswordRepositoryTableViewCellDetailText() { - if SharedDefaults[.gitURL] == nil { - passwordRepositoryTableViewCell.detailTextLabel?.text = "NotSet".localize() - } else { - passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]!.host - } + let host: String? = { + let gitURL = SharedDefaults[.gitURL] + if gitURL.scheme == nil { + return URL(string: "scheme://" + gitURL.absoluteString)?.host + } else { + return gitURL.host + } + }() + passwordRepositoryTableViewCell.detailTextLabel?.text = host } @objc func actOnPasswordStoreErasedNotification() { diff --git a/passKit/Helpers/DefaultsKeys.swift b/passKit/Helpers/DefaultsKeys.swift index 675cc01..5e8d830 100644 --- a/passKit/Helpers/DefaultsKeys.swift +++ b/passKit/Helpers/DefaultsKeys.swift @@ -22,9 +22,9 @@ public extension DefaultsKeys { static let gitSSHPrivateKeyArmor = DefaultsKey("gitSSHPrivateKeyArmor") static let passcodeKey = DefaultsKey("passcodeKey") - static let gitURL = DefaultsKey("gitURL") - static let gitAuthenticationMethod = DefaultsKey("gitAuthenticationMethod") - static let gitUsername = DefaultsKey("gitUsername") + static let gitURL = DefaultsKey("gitURL", defaultValue: URL(string: "https://")!) + static let gitAuthenticationMethod = DefaultsKey("gitAuthenticationMethod", defaultValue: GitAuthenticationMethod.password) + static let gitUsername = DefaultsKey("gitUsername", defaultValue: "git") static let gitBranchName = DefaultsKey("gitBranchName", defaultValue: "master") static let gitSSHPrivateKeyURL = DefaultsKey("gitSSHPrivateKeyURL") static let gitSSHKeySource = DefaultsKey("gitSSHKeySource") diff --git a/passKit/Models/GitCredential.swift b/passKit/Models/GitCredential.swift index ee311fb..b190f73 100644 --- a/passKit/Models/GitCredential.swift +++ b/passKit/Models/GitCredential.swift @@ -11,6 +11,10 @@ import UIKit import SwiftyUserDefaults import ObjectiveGit +public enum GitAuthenticationMethod: String, DefaultsSerializable { + case password, key +} + public struct GitCredential { private var credential: Credential private let passwordStore = PasswordStore.shared diff --git a/passKit/Models/PasswordStore.swift b/passKit/Models/PasswordStore.swift index 5f48768..46324c4 100644 --- a/passKit/Models/PasswordStore.swift +++ b/passKit/Models/PasswordStore.swift @@ -89,8 +89,8 @@ public class PasswordStore { return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0 } - public var numberOfLocalCommits: Int? { - return (try? getLocalCommits())?.flatMap { $0.count } + public var numberOfLocalCommits: Int { + return (try? getLocalCommits())?.flatMap { $0.count } ?? 0 } public var lastSyncedTime: Date? {