Set name and url in Password non-optional

Name and url in Password class shouldn't be optional because we store
them in core data as non-optional. This change also help us to avoid
man unneccessary unwrap.
This commit is contained in:
Bob Sun 2018-11-10 22:38:12 -08:00
parent 5262ca89f7
commit 2abbceb2e9
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
9 changed files with 60 additions and 39 deletions

View file

@ -45,7 +45,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
plainText.append(additionsString) plainText.append(additionsString)
} }
let (name, url) = getNameURL() 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) password!.updatePassword(name: name, url: url, plainText: plainText)
} }
} }

View file

@ -52,7 +52,8 @@ class OTPScannerController: QRScannerController {
private func presentSaveAlert() { private func presentSaveAlert() {
// initialize alert // 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 (title, content) = password.getOtpStrings()!
let alert = UIAlertController(title: "Success", message: "\(title): \(content)", preferredStyle: UIAlertControllerStyle.alert) 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 alert.addAction(UIAlertAction(title: "Save", style: UIAlertActionStyle.default, handler: {[unowned self] (action) -> Void in

View file

@ -85,7 +85,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
navigationItem.largeTitleDisplayMode = .never navigationItem.largeTitleDisplayMode = .never
} }
if let imageData = passwordEntity?.image { if let imageData = passwordEntity?.getImage() {
let image = UIImage(data: imageData as Data) let image = UIImage(data: imageData as Data)
passwordImage = image passwordImage = image
} }
@ -136,7 +136,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
} }
@objc private func decryptThenShowPassword() { @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 Utils.alert(title: "Cannot Show Password", message: "The password does not exist.", controller: self, handler: {(UIAlertAction) -> Void in
self.navigationController!.popViewController(animated: true) self.navigationController!.popViewController(animated: true)
}) })
@ -173,7 +173,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self?.tableView.reloadData() self?.tableView.reloadData()
self?.editUIBarButtonItem.isEnabled = true self?.editUIBarButtonItem.isEnabled = true
if let urlString = self?.password?.urlString { if let urlString = self?.password?.urlString {
if self?.passwordEntity?.image == nil { if self?.passwordEntity?.getImage() == nil {
self?.updatePasswordImage(urlString: urlString) self?.updatePasswordImage(urlString: urlString)
} }
} }
@ -431,13 +431,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
case .name: case .name:
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell let cell = tableView.dequeueReusableCell(withIdentifier: "passwordDetailTitleTableViewCell", for: indexPath) as! PasswordDetailTitleTableViewCell
cell.passwordImageImageView.image = passwordImage ?? #imageLiteral(resourceName: "PasswordImagePlaceHolder") cell.passwordImageImageView.image = passwordImage ?? #imageLiteral(resourceName: "PasswordImagePlaceHolder")
if let passwordName = passwordEntity!.name { let passwordName = passwordEntity!.getName()
if passwordEntity!.synced == false { if passwordEntity!.synced == false {
cell.nameLabel.text = "\(passwordName)" cell.nameLabel.text = "\(passwordName)"
} else { } else {
cell.nameLabel.text = passwordName cell.nameLabel.text = passwordName
} }
}
cell.categoryLabel.text = passwordEntity!.getCategoryText() cell.categoryLabel.text = passwordEntity!.getCategoryText()
cell.selectionStyle = .none cell.selectionStyle = .none
return cell return cell
@ -468,7 +467,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
footerLabel.numberOfLines = 0 footerLabel.numberOfLines = 0
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote) footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
footerLabel.textColor = UIColor.gray 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)" footerLabel.text = "Last Updated: \(dateString)"
view.addSubview(footerLabel) view.addSubview(footerLabel)
return view return view

View file

@ -481,7 +481,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} else if segue.identifier == "addPasswordSegue" { } else if segue.identifier == "addPasswordSegue" {
if let navController = segue.destination as? UINavigationController { if let navController = segue.destination as? UINavigationController {
if let viewController = navController.topViewController as? AddPasswordTableViewController { if let viewController = navController.topViewController as? AddPasswordTableViewController {
if let path = parentPasswordEntity?.path { if let path = parentPasswordEntity?.getPath() {
viewController.defaultDirPrefix = "\(path)/" viewController.defaultDirPrefix = "\(path)/"
} }
} }

View file

@ -17,6 +17,7 @@ public enum AppError: Error {
case PGPPublicKeyNotExistError case PGPPublicKeyNotExistError
case WrongPasswordFilename case WrongPasswordFilename
case YamlLoadError case YamlLoadError
case DecryptionError
case UnknownError case UnknownError
} }
@ -39,6 +40,8 @@ extension AppError: LocalizedError {
return "Cannot write to the password file." return "Cannot write to the password file."
case .YamlLoadError: case .YamlLoadError:
return "Cannot be parsed as a YAML file." return "Cannot be parsed as a YAML file."
case .DecryptionError:
return "Cannot decrypt password."
case .UnknownError: case .UnknownError:
return "Unknown error." return "Unknown error."
} }

View file

@ -62,9 +62,9 @@ public class Password {
private static let URL_KEYWORD = "url" private static let URL_KEYWORD = "url"
private static let UNKNOWN = "unknown" private static let UNKNOWN = "unknown"
public var name = "" public var name: String
public var url: URL? public var url: URL
public var namePath: String { return url?.deletingPathExtension().path ?? "" } public var namePath: String { return url.deletingPathExtension().path }
public var password = "" public var password = ""
public var changed: Int = 0 public var changed: Int = 0
@ -79,11 +79,13 @@ public class Password {
private var otpToken: Token? private var otpToken: Token?
public var otpType: OtpType { return OtpType.from(token: self.otpToken) } 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) 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 || self.url != url {
if self.plainText != plainText { if self.plainText != plainText {
changed = changed|PasswordChange.content.rawValue 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.name = name
self.url = url self.url = url
self.plainText = plainText self.plainText = plainText

View file

@ -37,9 +37,27 @@ extension PasswordEntity {
} }
public func getURL() -> URL? { public func getURL() -> URL? {
if let p = path { if let p = getPath().stringByAddingPercentEncodingForRFC3986() {
return URL(string: p.stringByAddingPercentEncodingForRFC3986()!) return URL(string: p)
} }
return nil 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 ?? ""
}
} }

View file

@ -258,7 +258,7 @@ public class PasswordStore {
public func passwordExisted(password: Password) -> Bool { public func passwordExisted(password: Password) -> Bool {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity") let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do { 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) let count = try context.count(for: passwordEntityFetchRequest)
if count > 0 { if count > 0 {
return true return true
@ -592,7 +592,7 @@ public class PasswordStore {
throw AppError.PasswordDuplicatedError throw AppError.PasswordDuplicatedError
} }
var passwordURL = password.url! var passwordURL = password.url
var previousPathLength = Int.max var previousPathLength = Int.max
var paths: [String] = [] var paths: [String] = []
while passwordURL.path != "." { while passwordURL.path != "." {
@ -610,7 +610,6 @@ public class PasswordStore {
for path in paths { for path in paths {
let isDir = !path.hasSuffix(".gpg") let isDir = !path.hasSuffix(".gpg")
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) { if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
print(passwordEntity.path!)
parentPasswordEntity = passwordEntity parentPasswordEntity = passwordEntity
passwordEntity.synced = false passwordEntity.synced = false
} else { } else {
@ -643,12 +642,12 @@ public class PasswordStore {
} }
public func add(password: Password) throws -> PasswordEntity? { public func add(password: Password) throws -> PasswordEntity? {
try createDirectoryTree(at: password.url!) try createDirectoryTree(at: password.url)
let newPasswordEntity = try addPasswordEntities(password: password) 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 self.encrypt(password: password).write(to: saveURL)
try gitAdd(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.") let _ = try gitCommit(message: "Add password for \(password.url.deletingPathExtension().path) to store using Pass for iOS.")
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
return newPasswordEntity return newPasswordEntity
} }
@ -679,16 +678,16 @@ public class PasswordStore {
print("change path") print("change path")
let deletedFileURL = passwordEntity.getURL()! let deletedFileURL = passwordEntity.getURL()!
// add // add
try createDirectoryTree(at: password.url!) try createDirectoryTree(at: password.url)
newPasswordEntity = try addPasswordEntities(password: password) newPasswordEntity = try addPasswordEntities(password: password)
// mv // mv
try gitMv(from: deletedFileURL.path, to: password.url!.path) try gitMv(from: deletedFileURL.path, to: password.url.path)
// delete // delete
try deleteDirectoryTree(at: deletedFileURL) try deleteDirectoryTree(at: deletedFileURL)
try deletePasswordEntities(passwordEntity: passwordEntity) 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) NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
@ -737,7 +736,7 @@ public class PasswordStore {
let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateMOC.parent = context privateMOC.parent = context
privateMOC.perform { privateMOC.perform {
passwordEntity.image = NSData(data: image) as Data passwordEntity.image = image
do { do {
try privateMOC.save() try privateMOC.save()
self.context.performAndWait { self.context.performAndWait {
@ -835,7 +834,7 @@ public class PasswordStore {
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? { 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) let encryptedData = try Data(contentsOf: encryptedDataPath)
var passphrase = self.pgpKeyPassphrase var passphrase = self.pgpKeyPassphrase
if passphrase == nil { 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 decryptedData = try ObjectivePGP.decrypt(encryptedData, andVerifySignature: false, using: keyring.keys, passphraseForKey: {(_) in passphrase})
let plainText = String(data: decryptedData, encoding: .utf8) ?? "" let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
let escapedPath = passwordEntity.path!.stringByAddingPercentEncodingForRFC3986() ?? "" guard let url = passwordEntity.getURL() else {
return Password(name: passwordEntity.name!, url: URL(string: escapedPath), plainText: plainText) throw AppError.DecryptionError
}
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
} }
public func encrypt(password: Password) throws -> Data { public func encrypt(password: Password) throws -> Data {

View file

@ -27,9 +27,6 @@ class PasswordTest: XCTestCase {
func testUrl() { func testUrl() {
let password1 = getPasswordObjectWith(content: PasswordTest.EMPTY_STRING) let password1 = getPasswordObjectWith(content: PasswordTest.EMPTY_STRING)
XCTAssertEqual(password1.namePath, PasswordTest.PASSWORD_PATH) XCTAssertEqual(password1.namePath, PasswordTest.PASSWORD_PATH)
let password2 = getPasswordObjectWith(content: PasswordTest.EMPTY_STRING, url: nil)
XCTAssertEqual(password2.namePath, PasswordTest.EMPTY_STRING)
} }
func testLooksLikeOTP() { func testLooksLikeOTP() {
@ -325,7 +322,7 @@ class PasswordTest: XCTestCase {
XCTAssertTrue(does(password, contain: noteField)) 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) return Password(name: PasswordTest.PASSWORD_NAME, url: url, plainText: content)
} }