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:
parent
5262ca89f7
commit
2abbceb2e9
9 changed files with 60 additions and 39 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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,12 +431,11 @@ 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
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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."
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue