diff --git a/pass/Controllers/EditPasswordTableViewController.swift b/pass/Controllers/EditPasswordTableViewController.swift index 015a5fc..8701c8c 100644 --- a/pass/Controllers/EditPasswordTableViewController.swift +++ b/pass/Controllers/EditPasswordTableViewController.swift @@ -45,7 +45,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController { plainText.append(additionsString) } let (name, url) = getNameURL() - if password!.plainText != plainText || password!.url!.path != url.path { + if password!.plainText != plainText || password!.url.path != url.path { password!.updatePassword(name: name, url: url, plainText: plainText) } } diff --git a/pass/Controllers/OTPScannerController.swift b/pass/Controllers/OTPScannerController.swift index e8fe13d..be50ffc 100644 --- a/pass/Controllers/OTPScannerController.swift +++ b/pass/Controllers/OTPScannerController.swift @@ -52,7 +52,8 @@ class OTPScannerController: QRScannerController { private func presentSaveAlert() { // initialize alert - let password = Password(name: "empty", url: nil, plainText: scannedOTP!) + // XXX: use Password class for now, we need to come up a better structure to oranize this + let password = Password(name: "empty", url: URL(string: ".")!, plainText: scannedOTP!) let (title, content) = password.getOtpStrings()! let alert = UIAlertController(title: "Success", message: "\(title): \(content)", preferredStyle: UIAlertControllerStyle.alert) alert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.default, handler: {[unowned self] (action) -> Void in diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 33154e5..546d54e 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -85,7 +85,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni navigationItem.largeTitleDisplayMode = .never } - if let imageData = passwordEntity?.image { + if let imageData = passwordEntity?.getImage() { let image = UIImage(data: imageData as Data) passwordImage = image } @@ -136,7 +136,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } @objc private func decryptThenShowPassword() { - guard let passwordEntity = passwordEntity, passwordEntity.path != nil else { + guard let passwordEntity = passwordEntity else { Utils.alert(title: "Cannot Show Password", message: "The password does not exist.", controller: self, handler: {(UIAlertAction) -> Void in self.navigationController!.popViewController(animated: true) }) @@ -173,7 +173,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni self?.tableView.reloadData() self?.editUIBarButtonItem.isEnabled = true if let urlString = self?.password?.urlString { - if self?.passwordEntity?.image == nil { + if self?.passwordEntity?.getImage() == nil { self?.updatePasswordImage(urlString: urlString) } } @@ -431,12 +431,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni case .name: let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell cell.passwordImageImageView.image = passwordImage ?? #imageLiteral(resourceName: "PasswordImagePlaceHolder") - if let passwordName = passwordEntity!.name { - if passwordEntity!.synced == false { - cell.nameLabel.text = "\(passwordName) ↻" - } else { - cell.nameLabel.text = passwordName - } + let passwordName = passwordEntity!.getName() + if passwordEntity!.synced == false { + cell.nameLabel.text = "\(passwordName) ↻" + } else { + cell.nameLabel.text = passwordName } cell.categoryLabel.text = passwordEntity!.getCategoryText() cell.selectionStyle = .none @@ -468,7 +467,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni footerLabel.numberOfLines = 0 footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote) footerLabel.textColor = UIColor.gray - let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url!.path) + let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url.path) footerLabel.text = "Last Updated: \(dateString)" view.addSubview(footerLabel) return view diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index cc901db..e67f5c4 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -481,7 +481,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV } else if segue.identifier == "addPasswordSegue" { if let navController = segue.destination as? UINavigationController { if let viewController = navController.topViewController as? AddPasswordTableViewController { - if let path = parentPasswordEntity?.path { + if let path = parentPasswordEntity?.getPath() { viewController.defaultDirPrefix = "\(path)/" } } diff --git a/passKit/Helpers/AppError.swift b/passKit/Helpers/AppError.swift index 515c18a..93ab173 100644 --- a/passKit/Helpers/AppError.swift +++ b/passKit/Helpers/AppError.swift @@ -17,6 +17,7 @@ public enum AppError: Error { case PGPPublicKeyNotExistError case WrongPasswordFilename case YamlLoadError + case DecryptionError case UnknownError } @@ -39,6 +40,8 @@ extension AppError: LocalizedError { return "Cannot write to the password file." case .YamlLoadError: return "Cannot be parsed as a YAML file." + case .DecryptionError: + return "Cannot decrypt password." case .UnknownError: return "Unknown error." } diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index 51cac6f..67ca850 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -62,9 +62,9 @@ public class Password { private static let URL_KEYWORD = "url" private static let UNKNOWN = "unknown" - public var name = "" - public var url: URL? - public var namePath: String { return url?.deletingPathExtension().path ?? "" } + public var name: String + public var url: URL + public var namePath: String { return url.deletingPathExtension().path } public var password = "" public var changed: Int = 0 @@ -79,11 +79,13 @@ public class Password { private var otpToken: Token? public var otpType: OtpType { return OtpType.from(token: self.otpToken) } - public init(name: String, url: URL?, plainText: String) { + public init(name: String, url: URL, plainText: String) { + self.name = name + self.url = url self.initEverything(name: name, url: url, plainText: plainText) } - public func updatePassword(name: String, url: URL?, plainText: String) { + public func updatePassword(name: String, url: URL, plainText: String) { if self.plainText != plainText || self.url != url { if self.plainText != plainText { changed = changed|PasswordChange.content.rawValue @@ -95,7 +97,7 @@ public class Password { } } - private func initEverything(name: String, url: URL?, plainText: String) { + private func initEverything(name: String, url: URL, plainText: String) { self.name = name self.url = url self.plainText = plainText diff --git a/passKit/Models/PasswordEntity.swift b/passKit/Models/PasswordEntity.swift index 5678ce4..741fd56 100644 --- a/passKit/Models/PasswordEntity.swift +++ b/passKit/Models/PasswordEntity.swift @@ -37,9 +37,27 @@ extension PasswordEntity { } public func getURL() -> URL? { - if let p = path { - return URL(string: p.stringByAddingPercentEncodingForRFC3986()!) + if let p = getPath().stringByAddingPercentEncodingForRFC3986() { + return URL(string: p) } return nil } + + // XXX: define some getters to get core data, we need to consider + // manually write models instead auto generation. + + public func getImage() -> Data? { + return image + } + + public func getName() -> String { + // unwrap non-optional core data + return name ?? "" + } + + public func getPath() -> String { + // unwrap non-optional core data + return path ?? "" + } + } diff --git a/passKit/Models/PasswordStore.swift b/passKit/Models/PasswordStore.swift index 06dc60e..5267b8e 100644 --- a/passKit/Models/PasswordStore.swift +++ b/passKit/Models/PasswordStore.swift @@ -258,7 +258,7 @@ public class PasswordStore { public func passwordExisted(password: Password) -> Bool { let passwordEntityFetchRequest = NSFetchRequest(entityName: "PasswordEntity") do { - passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url!.path) + passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url.path) let count = try context.count(for: passwordEntityFetchRequest) if count > 0 { return true @@ -592,7 +592,7 @@ public class PasswordStore { throw AppError.PasswordDuplicatedError } - var passwordURL = password.url! + var passwordURL = password.url var previousPathLength = Int.max var paths: [String] = [] while passwordURL.path != "." { @@ -610,7 +610,6 @@ public class PasswordStore { 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 { @@ -643,12 +642,12 @@ public class PasswordStore { } public func add(password: Password) throws -> PasswordEntity? { - try createDirectoryTree(at: password.url!) + try createDirectoryTree(at: password.url) let newPasswordEntity = try addPasswordEntities(password: password) - let saveURL = storeURL.appendingPathComponent(password.url!.path) + 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: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.") + 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 } @@ -679,16 +678,16 @@ public class PasswordStore { print("change path") let deletedFileURL = passwordEntity.getURL()! // add - try createDirectoryTree(at: password.url!) + try createDirectoryTree(at: password.url) newPasswordEntity = try addPasswordEntities(password: password) // mv - try gitMv(from: deletedFileURL.path, to: password.url!.path) + try gitMv(from: deletedFileURL.path, to: password.url.path) // delete try deleteDirectoryTree(at: deletedFileURL) try deletePasswordEntities(passwordEntity: passwordEntity) - let _ = try gitCommit(message: "Rename \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) to \(password.url!.deletingPathExtension().path.removingPercentEncoding!) using Pass for iOS.") + let _ = try gitCommit(message: "Rename \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) to \(password.url.deletingPathExtension().path.removingPercentEncoding!) using Pass for iOS.") } NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) @@ -737,7 +736,7 @@ public class PasswordStore { let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) privateMOC.parent = context privateMOC.perform { - passwordEntity.image = NSData(data: image) as Data + passwordEntity.image = image do { try privateMOC.save() self.context.performAndWait { @@ -835,7 +834,7 @@ public class PasswordStore { public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? { - let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.path!) + let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath()) let encryptedData = try Data(contentsOf: encryptedDataPath) var passphrase = self.pgpKeyPassphrase if passphrase == nil { @@ -843,8 +842,10 @@ public class PasswordStore { } let decryptedData = try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys, passphraseForKey: {(_) in passphrase}) let plainText = String(data: decryptedData, encoding: .utf8) ?? "" - let escapedPath = passwordEntity.path!.stringByAddingPercentEncodingForRFC3986() ?? "" - return Password(name: passwordEntity.name!, url: URL(string: escapedPath), plainText: plainText) + guard let url = passwordEntity.getURL() else { + throw AppError.DecryptionError + } + return Password(name: passwordEntity.getName(), url: url, plainText: plainText) } public func encrypt(password: Password) throws -> Data { diff --git a/passKitTests/PasswordTests.swift b/passKitTests/PasswordTests.swift index 49e8152..7121d38 100644 --- a/passKitTests/PasswordTests.swift +++ b/passKitTests/PasswordTests.swift @@ -27,9 +27,6 @@ class PasswordTest: XCTestCase { func testUrl() { let password1 = getPasswordObjectWith(content: PasswordTest.EMPTY_STRING) XCTAssertEqual(password1.namePath, PasswordTest.PASSWORD_PATH) - - let password2 = getPasswordObjectWith(content: PasswordTest.EMPTY_STRING, url: nil) - XCTAssertEqual(password2.namePath, PasswordTest.EMPTY_STRING) } func testLooksLikeOTP() { @@ -325,7 +322,7 @@ class PasswordTest: XCTestCase { XCTAssertTrue(does(password, contain: noteField)) } - private func getPasswordObjectWith(content: String, url: URL? = PasswordTest.PASSWORD_URL) -> Password { + private func getPasswordObjectWith(content: String, url: URL = PasswordTest.PASSWORD_URL) -> Password { return Password(name: PasswordTest.PASSWORD_NAME, url: url, plainText: content) }