From 055ea243a33872b40a513ca5a954123814aeb9d3 Mon Sep 17 00:00:00 2001 From: Bob Sun Date: Tue, 25 Apr 2017 13:01:17 -0700 Subject: [PATCH] Improve edit password to make it consistent with Pass --- .../EditPasswordTableViewController.swift | 2 +- .../GitConfigSettingTableViewController.swift | 1 - .../PasswordDetailTableViewController.swift | 50 +++--- pass/Models/Password.swift | 17 +- pass/Models/PasswordEntity.swift | 7 + pass/Models/PasswordStore.swift | 162 ++++++++++-------- 6 files changed, 136 insertions(+), 103 deletions(-) diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift index fe7f76d..771235e 100644 --- a/pass/Controllers/EditPasswordTableViewController.swift +++ b/pass/Controllers/EditPasswordTableViewController.swift @@ -53,7 +53,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController { if cellContents["additions"]! != "" { plainText = "\(cellContents["password"]!)\n\(cellContents["additions"]!)" } else { - plainText = "\(cellContents["password"]!)\n" + plainText = "\(cellContents["password"]!)" } let name = URL(string: cellContents["name"]!)!.lastPathComponent let url = URL(string: cellContents["name"]!)!.appendingPathExtension("gpg") diff --git a/pass/Controllers/GitConfigSettingTableViewController.swift b/pass/Controllers/GitConfigSettingTableViewController.swift index 3e7daac..3e2f7b7 100644 --- a/pass/Controllers/GitConfigSettingTableViewController.swift +++ b/pass/Controllers/GitConfigSettingTableViewController.swift @@ -22,7 +22,6 @@ class GitConfigSettingTableViewController: UITableViewController { } override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool { - print("test should perform \(identifier)") if identifier == "saveGitConfigSettingSegue" { guard let name = nameTextField.text, !name.isEmpty else { Utils.alert(title: "Cannot Save", message: "Please set name first.", controller: self, completion: nil) diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 15269c9..207e6cb 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -212,28 +212,26 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } @IBAction private func saveEditPassword(segue: UIStoryboardSegue) { - if self.password!.changed { + if self.password!.changed != 0 { SVProgressHUD.show(withStatus: "Saving") - DispatchQueue.global(qos: .userInitiated).async { - do { - self.passwordEntity = try self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!) - } catch { - DispatchQueue.main.async { - Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) - } - } - DispatchQueue.main.async { - self.setTableData() - self.tableView.reloadData() - SVProgressHUD.showSuccess(withStatus: "Success") - SVProgressHUD.dismiss(withDelay: 1) - } + do { + self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: self.password!) + } catch { + Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) } + self.setTableData() + self.tableView.reloadData() + SVProgressHUD.showSuccess(withStatus: "Success") + SVProgressHUD.dismiss(withDelay: 1) } } @IBAction private func deletePassword(segue: UIStoryboardSegue) { - passwordStore.delete(passwordEntity: passwordEntity!) + do { + try passwordStore.delete(passwordEntity: passwordEntity!) + } catch { + Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) + } let _ = navigationController?.popViewController(animated: true) } @@ -387,20 +385,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } // commit the change of HOTP counter - if password!.changed { - DispatchQueue.global(qos: .userInitiated).async { - do { - self.passwordEntity = try self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!) - } catch { - DispatchQueue.main.async { - Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) - } - } - DispatchQueue.main.async { - SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated") - SVProgressHUD.dismiss(withDelay: 1) - } + if password!.changed != 0 { + do { + self.passwordEntity = try self.passwordStore.edit(passwordEntity: self.passwordEntity!, password: self.password!) + } catch { + Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil) } + SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated") + SVProgressHUD.dismiss(withDelay: 1) } } diff --git a/pass/Models/Password.swift b/pass/Models/Password.swift index c6c99e4..2ac0896 100644 --- a/pass/Models/Password.swift +++ b/pass/Models/Password.swift @@ -16,9 +16,15 @@ struct AdditionField { var content: String } +enum PasswordChange: Int { + case path = 0x01 + case content = 0x02 + case none = 0x00 +} + class Password { static let otpKeywords = ["otp_secret", "otp_type", "otp_algorithm", "otp_period", "otp_digits", "otp_counter", "otpauth"] - + var name = "" var url: URL? var namePath: String { @@ -32,7 +38,7 @@ class Password { var password = "" var additions = [String: String]() var additionKeys = [String]() - var changed = false + var changed: Int = 0 var plainText = "" private var firstLineIsOTPField = false @@ -62,8 +68,13 @@ class Password { func updatePassword(name: String, url: URL?, plainText: String) { if self.plainText != plainText || self.url != url { + if self.plainText != plainText { + changed = changed|PasswordChange.content.rawValue + } + if self.url != url { + changed = changed|PasswordChange.path.rawValue + } self.initEverything(name: name, url: url, plainText: plainText) - changed = true } } diff --git a/pass/Models/PasswordEntity.swift b/pass/Models/PasswordEntity.swift index 143d63e..5dddb35 100644 --- a/pass/Models/PasswordEntity.swift +++ b/pass/Models/PasswordEntity.swift @@ -31,4 +31,11 @@ extension PasswordEntity { passwordCategoryArray.reverse() return passwordCategoryArray.joined(separator: " > ") } + + func getURL() -> URL? { + if let p = path { + return URL(string: p) + } + return nil + } } diff --git a/pass/Models/PasswordStore.swift b/pass/Models/PasswordStore.swift index a6f4453..58b3831 100644 --- a/pass/Models/PasswordStore.swift +++ b/pass/Models/PasswordStore.swift @@ -509,43 +509,51 @@ class PasswordStore { func updateRemoteRepo() { } - func createAddCommitInRepository(message: String, path: String) -> GTCommit? { - do { - try storeRepository?.index().addFile(path) - try storeRepository?.index().write() - let newTree = try storeRepository!.index().writeTree() - let headReference = try storeRepository!.headReference() - let commitEnum = try GTEnumerator(repository: storeRepository!) + private func gitAdd(path: String) throws { + if let repo = storeRepository { + try repo.index().addFile(path) + try repo.index().write() + } + } + + private func gitRm(path: String) throws { + if let repo = storeRepository { + var url = storeURL.appendingPathComponent(path) + Utils.removeFileIfExists(at: url) + let fm = FileManager.default + url.deleteLastPathComponent() + var count = try fm.contentsOfDirectory(atPath: url.path).count + while count == 0 { + Utils.removeFileIfExists(atPath: url.path) + url.deleteLastPathComponent() + count = try fm.contentsOfDirectory(atPath: url.path).count + } + try repo.index().removeFile(path) + try repo.index().write() + } + } + + private func gitMv(from: String, to: String) throws { + let fm = FileManager.default + try fm.moveItem(at: storeURL.appendingPathComponent(from), to: storeURL.appendingPathComponent(to)) + try gitAdd(path: to) + try gitRm(path: from) + } + + private func gitCommit(message: String) throws -> GTCommit? { + if let repo = storeRepository { + let newTree = try repo.index().writeTree() + let headReference = try repo.headReference() + let commitEnum = try GTEnumerator(repository: repo) try commitEnum.pushSHA(headReference.targetOID.sha!) let parent = commitEnum.nextObject() as! GTCommit let signature = gitSignatureForNow - let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name) + let commit = try repo.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name) return commit - } catch { - print(error) } return nil } - func createRemoveCommitInRepository(message: String, path: String) -> GTCommit? { - do { - try storeRepository?.index().removeFile(path) - try storeRepository?.index().write() - let newTree = try storeRepository!.index().writeTree() - let headReference = try storeRepository!.headReference() - let commitEnum = try GTEnumerator(repository: storeRepository!) - try commitEnum.pushSHA(headReference.targetOID.sha!) - let parent = commitEnum.nextObject() as! GTCommit - let signature = gitSignatureForNow - let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name) - return commit - } catch { - print(error) - } - return nil - } - - private func getLocalBranch(withName branchName: String) -> GTBranch? { do { let reference = GTBranch.localNamePrefix().appending(branchName) @@ -567,7 +575,11 @@ class PasswordStore { try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock) } - private func addPasswordEntities(password: Password) -> PasswordEntity? { + private func addPasswordEntities(password: Password) throws -> PasswordEntity? { + guard !passwordExisted(password: password) else { + throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot add password: password duplicated."]) + } + var passwordURL = password.url! var paths: [String] = [] while passwordURL.path != "." { @@ -599,63 +611,75 @@ class PasswordStore { private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? { var ret: PasswordEntity? = nil - DispatchQueue.main.sync { - if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity { - passwordEntity.name = name - passwordEntity.path = path - passwordEntity.parent = parent - passwordEntity.synced = synced - passwordEntity.isDir = isDir - do { - try self.context.save() - ret = passwordEntity - } catch { - fatalError("Failed to insert a PasswordEntity: \(error)") - } + if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity { + passwordEntity.name = name + passwordEntity.path = path + passwordEntity.parent = parent + passwordEntity.synced = synced + passwordEntity.isDir = isDir + do { + try self.context.save() + ret = passwordEntity + } catch { + fatalError("Failed to insert a PasswordEntity: \(error)") } } return ret } func add(password: Password) throws -> PasswordEntity? { - guard !passwordExisted(password: password) else { - throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot add password: password duplicated."]) - } - let newPasswordEntity = addPasswordEntities(password: password) - print("new: \(newPasswordEntity!.path!)") + let newPasswordEntity = try addPasswordEntities(password: password) let saveURL = storeURL.appendingPathComponent(password.url!.path) try self.encrypt(password: password).write(to: saveURL) - let _ = createAddCommitInRepository(message: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.", path: password.url!.path) + try gitAdd(path: password.url!.path) + let _ = try gitCommit(message: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.") NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) return newPasswordEntity } - func update(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? { - delete(passwordEntity: passwordEntity) - return try add(password: password) + func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? { + var newPasswordEntity: PasswordEntity? = passwordEntity + + if password.changed&PasswordChange.content.rawValue != 0 { + let saveURL = storeURL.appendingPathComponent(password.url!.path) + try self.encrypt(password: password).write(to: saveURL) + try gitAdd(path: password.url!.path) + let _ = try gitCommit(message: "Edit password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.") + } + guard newPasswordEntity != nil else { + return nil + } + if password.changed&PasswordChange.path.rawValue != 0 { + let oldPasswordURL = newPasswordEntity!.getURL() + try self.deletePasswordEntities(passwordEntity: newPasswordEntity!) + newPasswordEntity = try self.addPasswordEntities(password: password) + try gitMv(from: oldPasswordURL!.path, to: password.url!.path) + let _ = try gitCommit(message: "Rename \(oldPasswordURL!.deletingPathExtension().path) to \(password.url!.deletingPathExtension().path) using Pass for iOS.") + } + return newPasswordEntity } - - public func delete(passwordEntity: PasswordEntity) { - DispatchQueue.main.async { - let _ = self.createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!) - var current: PasswordEntity? = passwordEntity - while current != nil && (current!.children!.count == 0 || !current!.isDir) { - Utils.removeFileIfExists(at: self.storeURL.appendingPathComponent(current!.path!)) - let parent = current!.parent - self.context.delete(current!) - current = parent - do { - try self.context.save() - } catch { - fatalError("Failed to delete a PasswordEntity: \(error)") - } + private func deletePasswordEntities(passwordEntity: PasswordEntity) throws { + var current: PasswordEntity? = passwordEntity + while current != nil && (current!.children!.count == 0 || !current!.isDir) { + let parent = current!.parent + self.context.delete(current!) + current = parent + do { + try self.context.save() + } catch { + fatalError("Failed to delete a PasswordEntity: \(error)") } - NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) - } } + public func delete(passwordEntity: PasswordEntity) throws { + try gitRm(path: passwordEntity.path!) + let _ = try gitCommit(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS.") + try deletePasswordEntities(passwordEntity: passwordEntity) + NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) + } + func saveUpdated(passwordEntity: PasswordEntity) { do { try context.save()