Support folder operations
Example: - add: a/b/c/d - delete: a/b/c/d - move: a/b/c/d -> a/b/c/d/e
This commit is contained in:
parent
b1ef8a343f
commit
d8ecd1e889
8 changed files with 213 additions and 141 deletions
|
|
@ -42,14 +42,6 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
|
||||
// check "/"
|
||||
guard nameCell.getContent()!.contains("/") == false else {
|
||||
let alertTitle = "Cannot Add Password"
|
||||
let alertMessage = "Illegal character."
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -73,7 +65,9 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
} else {
|
||||
plainText = "\(cellContents["password"]!)"
|
||||
}
|
||||
password = Password(name: cellContents["name"]!, plainText: plainText)
|
||||
let name = URL(string: cellContents["name"]!)!.lastPathComponent
|
||||
let url = URL(string: cellContents["name"]!)!.appendingPathExtension("gpg")
|
||||
password = Password(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import UIKit
|
|||
class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||
override func viewDidLoad() {
|
||||
tableData = [
|
||||
[[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.name]],
|
||||
[[.type: PasswordEditorCellType.textFieldCell, .title: "name", .content: password!.namePath]],
|
||||
[[.type: PasswordEditorCellType.fillPasswordCell, .title: "password", .content: password!.password],
|
||||
[.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"]],
|
||||
[[.type: PasswordEditorCellType.textViewCell, .title: "additions", .content: password!.getAdditionsPlainText()]],
|
||||
|
|
@ -23,15 +23,8 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
if identifier == "saveEditPasswordSegue" {
|
||||
if let nameCell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? ContentTableViewCell {
|
||||
if nameCell.getContent() != password?.name {
|
||||
let alertTitle = "Cannot Save Edit"
|
||||
let alertMessage = "Editing name is not supported."
|
||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self) {
|
||||
nameCell.setContent(content: self.password!.name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
if let _ = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? ContentTableViewCell {
|
||||
// TODO: do some checks here
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
|
@ -54,9 +47,13 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
if cellContents["additions"]! != "" {
|
||||
plainText = "\(cellContents["password"]!)\n\(cellContents["additions"]!)"
|
||||
} else {
|
||||
plainText = "\(cellContents["password"]!)"
|
||||
plainText = "\(cellContents["password"]!)\n"
|
||||
}
|
||||
let name = URL(string: cellContents["name"]!)!.lastPathComponent
|
||||
let url = URL(string: cellContents["name"]!)!.appendingPathExtension("gpg")
|
||||
if password!.plainText != plainText || password!.url!.path != url.path {
|
||||
password!.updatePassword(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
password!.updatePassword(name: cellContents["name"]!, plainText: plainText)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class OTPScannerController: QRScannerController {
|
|||
if accept == true {
|
||||
captureSession?.stopRunning()
|
||||
scannedOTP = scannedString
|
||||
tempPassword = Password(name: "empty", plainText: scannedString)
|
||||
tempPassword = Password(name: "empty", url: nil, plainText: scannedString)
|
||||
// set scannerOutput
|
||||
setupOneTimePasswordMessage()
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import SVProgressHUD
|
|||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
var passwordEntity: PasswordEntity?
|
||||
private var password: Password?
|
||||
private var passwordCategoryText = ""
|
||||
private var passwordImage: UIImage?
|
||||
private var oneTimePasswordIndexPath : IndexPath?
|
||||
private var shouldPopCurrentView = false
|
||||
|
|
@ -77,7 +76,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
||||
|
||||
passwordCategoryText = passwordEntity!.getCategoryText()
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
||||
tapGesture.cancelsTouchesInView = false
|
||||
tableView.addGestureRecognizer(tapGesture)
|
||||
|
|
@ -96,24 +94,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let image = UIImage(data: imageData as Data)
|
||||
passwordImage = image
|
||||
}
|
||||
|
||||
var passphrase = ""
|
||||
if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil {
|
||||
passphrase = self.passwordStore.pgpKeyPassphrase!
|
||||
self.decryptThenShowPassword(passphrase: passphrase)
|
||||
} else {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
self.decryptThenShowPassword(passphrase: passphrase)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
self.decryptThenShowPassword()
|
||||
self.setupOneTimePasswordAutoRefresh()
|
||||
|
||||
// pop the current view because this password might be "discarded"
|
||||
|
|
@ -137,14 +118,33 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
}
|
||||
|
||||
private func decryptThenShowPassword(passphrase: String) {
|
||||
private func requestPGPKeyPassphrase() -> String {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
private func decryptThenShowPassword() {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
// decrypt password
|
||||
do {
|
||||
self.password = try self.passwordEntity!.decrypt(passphrase: passphrase)!
|
||||
self.password = try self.passwordStore.decrypt(passwordEntity: self.passwordEntity!, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Cannot Show Password", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.alert)
|
||||
|
|
@ -215,14 +215,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
if self.password!.changed {
|
||||
SVProgressHUD.show(withStatus: "Saving")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: { progress in
|
||||
do {
|
||||
self.passwordEntity = try self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!)
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(progress, status: "Encrypting")
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
})
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.passwordEntity!.synced = false
|
||||
self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!)
|
||||
self.setTableData()
|
||||
self.tableView.reloadData()
|
||||
SVProgressHUD.showSuccess(withStatus: "Success")
|
||||
|
|
@ -233,7 +233,6 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
}
|
||||
|
||||
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
|
||||
print("delete")
|
||||
passwordStore.delete(passwordEntity: passwordEntity!)
|
||||
let _ = navigationController?.popViewController(animated: true)
|
||||
}
|
||||
|
|
@ -390,10 +389,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
// commit the change of HOTP counter
|
||||
if password!.changed {
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
self.passwordStore.update(passwordEntity: self.passwordEntity!, password: self.password!, progressBlock: {_ in })
|
||||
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.passwordEntity!.synced = false
|
||||
self.passwordStore.saveUpdated(passwordEntity: self.passwordEntity!)
|
||||
SVProgressHUD.showSuccess(withStatus: "Password Copied\nCounter Updated")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
}
|
||||
|
|
@ -435,7 +438,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
cell.nameLabel.text = passwordName
|
||||
}
|
||||
}
|
||||
cell.categoryLabel.text = passwordCategoryText
|
||||
cell.categoryLabel.text = passwordEntity!.getCategoryText()
|
||||
cell.selectionStyle = .none
|
||||
return cell
|
||||
case .main, .addition:
|
||||
|
|
@ -465,7 +468,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
footerLabel.numberOfLines = 0
|
||||
footerLabel.font = UIFont.preferredFont(forTextStyle: .footnote)
|
||||
footerLabel.textColor = UIColor.gray
|
||||
let dateString = self.passwordStore.getLatestUpdateInfo(filename: (passwordEntity?.path)!)
|
||||
let dateString = self.passwordStore.getLatestUpdateInfo(filename: password!.url!.path)
|
||||
footerLabel.text = "Last Updated: \(dateString)"
|
||||
view.addSubview(footerLabel)
|
||||
return view
|
||||
|
|
|
|||
|
|
@ -112,11 +112,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
SVProgressHUD.show(withStatus: "Saving")
|
||||
DispatchQueue.global(qos: .userInitiated).async {
|
||||
do {
|
||||
try self.passwordStore.add(password: controller.password!, progressBlock: { progress in
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.showProgress(progress, status: "Encrypting")
|
||||
}
|
||||
})
|
||||
let _ = try self.passwordStore.add(password: controller.password!)
|
||||
DispatchQueue.main.async {
|
||||
// will trigger reloadTableView() by a notification
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
|
|
@ -323,15 +319,19 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
let password = getPasswordEntry(by: indexPath).passwordEntity!
|
||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||
decryptThenCopyPassword(passwordEntity: password)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private func requestPGPKeyPassphrase() -> String {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var passphrase = ""
|
||||
if Defaults[.isRememberPassphraseOn] && self.passwordStore.pgpKeyPassphrase != nil {
|
||||
passphrase = self.passwordStore.pgpKeyPassphrase!
|
||||
self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase)
|
||||
} else {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Passphrase", message: "Please fill in the passphrase of your PGP secret key.", preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
passphrase = alert.textFields!.first!.text!
|
||||
self.decryptThenCopyPassword(passwordEntity: password, passphrase: passphrase)
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = ""
|
||||
|
|
@ -339,17 +339,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
})
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
}
|
||||
|
||||
private func decryptThenCopyPassword(passwordEntity: PasswordEntity, passphrase: String) {
|
||||
private func decryptThenCopyPassword(passwordEntity: PasswordEntity) {
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.dark)
|
||||
SVProgressHUD.show(withStatus: "Decrypting")
|
||||
DispatchQueue.global(qos: .userInteractive).async {
|
||||
var decryptedPassword: Password?
|
||||
do {
|
||||
decryptedPassword = try passwordEntity.decrypt(passphrase: passphrase)!
|
||||
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase)
|
||||
DispatchQueue.main.async {
|
||||
Utils.copyToPasteboard(textToCopy: decryptedPassword?.password)
|
||||
SVProgressHUD.showSuccess(withStatus: "Password copied, and will be cleared in 45 seconds.")
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@ 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 {
|
||||
get {
|
||||
if url == nil {
|
||||
return ""
|
||||
}
|
||||
return url!.deletingPathExtension().path
|
||||
}
|
||||
}
|
||||
var password = ""
|
||||
var additions = [String: String]()
|
||||
var additionKeys = [String]()
|
||||
|
|
@ -47,19 +56,20 @@ class Password {
|
|||
}
|
||||
}
|
||||
|
||||
init(name: String, plainText: String) {
|
||||
self.initEverything(name: name, plainText: plainText)
|
||||
init(name: String, url: URL?, plainText: String) {
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
|
||||
func updatePassword(name: String, plainText: String) {
|
||||
if self.plainText != plainText {
|
||||
self.initEverything(name: name, plainText: plainText)
|
||||
func updatePassword(name: String, url: URL?, plainText: String) {
|
||||
if self.plainText != plainText || self.url != url {
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
private func initEverything(name: String, plainText: String) {
|
||||
private func initEverything(name: String, url: URL?, plainText: String) {
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.plainText = plainText
|
||||
self.additions.removeAll()
|
||||
self.additionKeys.removeAll()
|
||||
|
|
@ -322,7 +332,7 @@ class Password {
|
|||
if newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
}
|
||||
self.updatePassword(name: self.name, plainText: lines.joined(separator: "\n"))
|
||||
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
||||
|
||||
// get and return the password
|
||||
return self.otpToken?.currentPassword
|
||||
|
|
|
|||
|
|
@ -21,24 +21,6 @@ extension PasswordEntity {
|
|||
}
|
||||
}
|
||||
|
||||
func decrypt(passphrase: String) throws -> Password? {
|
||||
var password: Password?
|
||||
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(path!)")
|
||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
password = Password(name: name!, plainText: plainText)
|
||||
return password
|
||||
}
|
||||
|
||||
func encrypt(password: Password) throws -> Data {
|
||||
name = password.name
|
||||
let plainData = password.getPlainData()
|
||||
let pgp = PasswordStore.shared.pgp
|
||||
let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored])
|
||||
return encryptedData
|
||||
}
|
||||
|
||||
func getCategoryText() -> String {
|
||||
var parentEntity = parent
|
||||
var passwordCategoryArray: [String] = []
|
||||
|
|
|
|||
|
|
@ -284,10 +284,9 @@ class PasswordStore {
|
|||
}
|
||||
|
||||
func passwordExisted(password: Password) -> Bool {
|
||||
print(password.name)
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@", password.name)
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url!.path)
|
||||
let count = try context.count(for: passwordEntityFetchRequest)
|
||||
if count > 0 {
|
||||
return true
|
||||
|
|
@ -300,6 +299,32 @@ class PasswordStore {
|
|||
return true
|
||||
}
|
||||
|
||||
func passwordEntityExisted(path: String) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@", path)
|
||||
let count = try context.count(for: passwordEntityFetchRequest)
|
||||
if count > 0 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
} catch {
|
||||
fatalError("Failed to fetch password entities: \(error)")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getPasswordEntity(by path: String) -> PasswordEntity? {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@", path)
|
||||
return try context.fetch(passwordEntityFetchRequest).first as? PasswordEntity
|
||||
} catch {
|
||||
fatalError("Failed to fetch password entities: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func cloneRepository(remoteRepoURL: URL,
|
||||
credential: GitCredential,
|
||||
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
||||
|
|
@ -511,19 +536,17 @@ class PasswordStore {
|
|||
func updateRemoteRepo() {
|
||||
}
|
||||
|
||||
func createAddCommitInRepository(message: String, fileData: Data, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? {
|
||||
func createAddCommitInRepository(message: String, path: String) -> GTCommit? {
|
||||
do {
|
||||
try storeRepository?.index().add(fileData, withPath: filename)
|
||||
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!)
|
||||
try commitEnum.pushSHA(headReference.targetOID.sha!)
|
||||
let parent = commitEnum.nextObject() as! GTCommit
|
||||
progressBlock(0.5)
|
||||
let signature = gitSignatureForNow
|
||||
let commit = try storeRepository!.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
progressBlock(0.7)
|
||||
return commit
|
||||
} catch {
|
||||
print(error)
|
||||
|
|
@ -571,57 +594,93 @@ class PasswordStore {
|
|||
try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
||||
}
|
||||
|
||||
func add(password: Password, progressBlock: (_ progress: Float) -> Void) throws {
|
||||
progressBlock(0.0)
|
||||
private func addPasswordEntities(password: Password) -> PasswordEntity? {
|
||||
var passwordURL = password.url!
|
||||
var paths: [String] = []
|
||||
while passwordURL.path != "." {
|
||||
paths.append(passwordURL.path)
|
||||
passwordURL = passwordURL.deletingLastPathComponent()
|
||||
}
|
||||
paths.reverse()
|
||||
var parentPasswordEntity: PasswordEntity? = nil
|
||||
for path in paths {
|
||||
if let passwordEntity = getPasswordEntity(by: path) {
|
||||
parentPasswordEntity = passwordEntity
|
||||
} else {
|
||||
if path.hasSuffix(".gpg") {
|
||||
return insertPasswordEntity(name: URL(string: path)!.deletingPathExtension().lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: false)
|
||||
} else {
|
||||
parentPasswordEntity = insertPasswordEntity(name: URL(string: path)!.lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: true)
|
||||
let fm = FileManager.default
|
||||
let saveURL = storeURL.appendingPathComponent(path)
|
||||
do {
|
||||
try fm.createDirectory(at: saveURL, withIntermediateDirectories: false, attributes: nil)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
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 passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
do {
|
||||
let encryptedData = try passwordEntity.encrypt(password: password)
|
||||
progressBlock(0.3)
|
||||
let saveURL = storeURL.appendingPathComponent("\(password.name).gpg")
|
||||
try encryptedData.write(to: saveURL)
|
||||
passwordEntity.name = password.name
|
||||
passwordEntity.path = "\(password.name).gpg"
|
||||
passwordEntity.parent = nil
|
||||
passwordEntity.synced = false
|
||||
passwordEntity.isDir = false
|
||||
try context.save()
|
||||
print(saveURL.path)
|
||||
let _ = createAddCommitInRepository(message: "Add password for \(passwordEntity.nameWithCategory) to store using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
||||
progressBlock(1.0)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
let newPasswordEntity = addPasswordEntities(password: password)
|
||||
print("new: \(newPasswordEntity!.path!)")
|
||||
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)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) {
|
||||
progressBlock(0.0)
|
||||
do {
|
||||
let encryptedData = try passwordEntity.encrypt(password: password)
|
||||
let saveURL = storeURL.appendingPathComponent(passwordEntity.path!)
|
||||
try encryptedData.write(to: saveURL)
|
||||
progressBlock(0.3)
|
||||
let _ = createAddCommitInRepository(message: "Edit password for \(passwordEntity.nameWithCategory) using Pass for iOS.", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
||||
progressBlock(1.0)
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
func update(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
|
||||
delete(passwordEntity: passwordEntity)
|
||||
return try add(password: password)
|
||||
}
|
||||
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) {
|
||||
Utils.removeFileIfExists(at: storeURL.appendingPathComponent(passwordEntity.path!))
|
||||
let _ = createRemoveCommitInRepository(message: "Remove \(passwordEntity.nameWithCategory) from store using Pass for iOS", path: passwordEntity.path!)
|
||||
context.delete(passwordEntity)
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
fatalError("Failed to delete a PasswordEntity: \(error)")
|
||||
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)")
|
||||
}
|
||||
}
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
|
||||
}
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
|
||||
func saveUpdated(passwordEntity: PasswordEntity) {
|
||||
|
|
@ -741,4 +800,27 @@ class PasswordStore {
|
|||
// get a list of local commits
|
||||
return try storeRepository?.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||
var password: Password?
|
||||
let encryptedDataPath = URL(fileURLWithPath: "\(Globals.repositoryPath)/\(passwordEntity.path!)")
|
||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||
var passphrase = self.pgpKeyPassphrase
|
||||
if passphrase == nil {
|
||||
passphrase = requestPGPKeyPassphrase()
|
||||
}
|
||||
let decryptedData = try PasswordStore.shared.pgp.decryptData(encryptedData, passphrase: passphrase)
|
||||
let plainText = String(data: decryptedData, encoding: .utf8) ?? ""
|
||||
password = Password(name: passwordEntity.name!, url: URL(string: passwordEntity.path!), plainText: plainText)
|
||||
return password
|
||||
}
|
||||
|
||||
func encrypt(password: Password) throws -> Data {
|
||||
let plainData = password.getPlainData()
|
||||
let pgp = PasswordStore.shared.pgp
|
||||
let encryptedData = try pgp.encryptData(plainData, usingPublicKey: pgp.getKeysOf(.public)[0], armored: Defaults[.encryptInArmored])
|
||||
return encryptedData
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue