If the switch is on, users need to type passphrase of secret key once. The key will be stored in the Keychain. If the switch is off, users have to fill in passphrase for each decryption including show password detail and long press to copy.
540 lines
23 KiB
Swift
540 lines
23 KiB
Swift
//
|
|
// PasswordStore.swift
|
|
// pass
|
|
//
|
|
// Created by Mingshen Sun on 19/1/2017.
|
|
// Copyright © 2017 Bob Sun. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import CoreData
|
|
import UIKit
|
|
import SwiftyUserDefaults
|
|
import ObjectiveGit
|
|
import SVProgressHUD
|
|
|
|
struct GitCredential {
|
|
|
|
enum Credential {
|
|
case http(userName: String, password: String)
|
|
case ssh(userName: String, password: String, publicKeyFile: URL, privateKeyFile: URL, passwordNotSetCallback: (() -> String)? )
|
|
}
|
|
|
|
var credential: Credential
|
|
|
|
func credentialProvider() throws -> GTCredentialProvider {
|
|
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
|
var credential: GTCredential? = nil
|
|
switch self.credential {
|
|
case let .http(userName, password):
|
|
print(Defaults[.gitRepositoryPasswordAttempts])
|
|
var newPassword: String = password
|
|
if Defaults[.gitRepositoryPasswordAttempts] != 0 {
|
|
let sem = DispatchSemaphore(value: 0)
|
|
DispatchQueue.main.async {
|
|
SVProgressHUD.dismiss()
|
|
if var topController = UIApplication.shared.keyWindow?.rootViewController {
|
|
while let presentedViewController = topController.presentedViewController {
|
|
topController = presentedViewController
|
|
}
|
|
let alert = UIAlertController(title: "Password", message: "Please fill in the password of your Git account.", preferredStyle: UIAlertControllerStyle.alert)
|
|
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
|
newPassword = alert.textFields!.first!.text!
|
|
PasswordStore.shared.gitRepositoryPassword = newPassword
|
|
sem.signal()
|
|
}))
|
|
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
|
Defaults[.gitRepositoryPasswordAttempts] = -1
|
|
sem.signal()
|
|
})
|
|
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
|
textField.text = PasswordStore.shared.gitRepositoryPassword
|
|
textField.isSecureTextEntry = true
|
|
})
|
|
topController.present(alert, animated: true, completion: nil)
|
|
}
|
|
}
|
|
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
|
}
|
|
if Defaults[.gitRepositoryPasswordAttempts] == -1 {
|
|
Defaults[.gitRepositoryPasswordAttempts] = 0
|
|
return nil
|
|
}
|
|
Defaults[.gitRepositoryPasswordAttempts] += 1
|
|
PasswordStore.shared.gitRepositoryPassword = newPassword
|
|
credential = try? GTCredential(userName: userName, password: newPassword)
|
|
case let .ssh(userName, password, publicKeyFile, privateKeyFile, passwordNotSetCallback):
|
|
|
|
var newPassword:String? = password
|
|
|
|
// Check if the private key is encrypted
|
|
let encrypted = try? String(contentsOf: privateKeyFile).contains("ENCRYPTED")
|
|
|
|
// Request password if not already set
|
|
if encrypted! && password == "" {
|
|
newPassword = passwordNotSetCallback!()
|
|
}
|
|
|
|
// Save password for the future
|
|
Utils.addPasswrodToKeychain(name: "gitRepositorySSHPrivateKeyPassphrase", password: newPassword!)
|
|
|
|
// nil is expected in case of empty password
|
|
if newPassword == "" {
|
|
newPassword = nil
|
|
}
|
|
|
|
|
|
credential = try? GTCredential(userName: userName, publicKeyURL: publicKeyFile, privateKeyURL: privateKeyFile, passphrase: newPassword)
|
|
}
|
|
return credential
|
|
}
|
|
}
|
|
}
|
|
|
|
class PasswordStore {
|
|
static let shared = PasswordStore()
|
|
|
|
let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
|
|
let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
|
|
var storeRepository: GTRepository?
|
|
var gitCredential: GitCredential?
|
|
|
|
let pgp: ObjectivePGP = ObjectivePGP()
|
|
|
|
var pgpKeyPassphrase: String? {
|
|
set {
|
|
Utils.addPasswrodToKeychain(name: "pgpKeyPassphrase", password: newValue)
|
|
}
|
|
get {
|
|
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
|
|
}
|
|
}
|
|
var gitRepositoryPassword: String? {
|
|
set {
|
|
Utils.addPasswrodToKeychain(name: "gitRepositoryPassword", password: newValue)
|
|
}
|
|
get {
|
|
return Utils.getPasswordFromKeychain(name: "gitRepositoryPassword")
|
|
}
|
|
}
|
|
|
|
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
|
|
|
|
|
|
private init() {
|
|
do {
|
|
if FileManager.default.fileExists(atPath: storeURL.path) {
|
|
try storeRepository = GTRepository.init(url: storeURL)
|
|
}
|
|
} catch {
|
|
print(error)
|
|
}
|
|
if Defaults[.pgpKeyID] != nil {
|
|
pgp.importKeys(fromFile: Globals.pgpPublicKeyPath, allowDuplicates: false)
|
|
pgp.importKeys(fromFile: Globals.pgpPrivateKeyPath, allowDuplicates: false)
|
|
|
|
}
|
|
if Defaults[.gitRepositoryAuthenticationMethod] == "Password" {
|
|
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitRepositoryUsername]!, password: Utils.getPasswordFromKeychain(name: "gitRepositoryPassword") ?? ""))
|
|
} else if Defaults[.gitRepositoryAuthenticationMethod] == "SSH Key"{
|
|
gitCredential = GitCredential(
|
|
credential: GitCredential.Credential.ssh(
|
|
userName: Defaults[.gitRepositoryUsername]!,
|
|
password: Utils.getPasswordFromKeychain(name: "gitRepositorySSHPrivateKeyPassphrase") ?? "",
|
|
publicKeyFile: Globals.sshPublicKeyURL,
|
|
privateKeyFile: Globals.sshPrivateKeyURL,
|
|
passwordNotSetCallback: nil
|
|
)
|
|
)
|
|
} else {
|
|
gitCredential = nil
|
|
}
|
|
|
|
}
|
|
|
|
func initPGP(pgpPublicKeyLocalPath: String, pgpPrivateKeyLocalPath: String) throws {
|
|
let pgpPublicKeyData = NSData(contentsOfFile: pgpPublicKeyLocalPath)! as Data
|
|
if pgpPublicKeyData.count == 0 {
|
|
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import public key."])
|
|
}
|
|
pgp.importKeys(from: pgpPublicKeyData, allowDuplicates: false)
|
|
if pgp.getKeysOf(.public).count == 0 {
|
|
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import public key."])
|
|
}
|
|
let pgpPrivateKeyData = NSData(contentsOfFile: pgpPrivateKeyLocalPath)! as Data
|
|
if pgpPrivateKeyData.count == 0 {
|
|
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import public key."])
|
|
}
|
|
pgp.importKeys(from: pgpPrivateKeyData, allowDuplicates: false)
|
|
if pgp.getKeysOf(.secret).count == 0 {
|
|
throw NSError(domain: "me.mssun.pass.error", code: 2, userInfo: [NSLocalizedDescriptionKey: "Cannot import seceret key."])
|
|
}
|
|
let key: PGPKey = getPgpPrivateKey()
|
|
Defaults[.pgpKeyID] = key.keyID!.shortKeyString
|
|
if let gpgUser = key.users[0] as? PGPUser {
|
|
Defaults[.pgpKeyUserID] = gpgUser.userID
|
|
}
|
|
}
|
|
|
|
func getPgpPrivateKey() -> PGPKey {
|
|
return pgp.getKeysOf(.secret)[0]
|
|
}
|
|
|
|
func exists() -> Bool {
|
|
let fm = FileManager()
|
|
return fm.fileExists(atPath: Globals.repositoryPath)
|
|
}
|
|
|
|
func initPGP(pgpPublicKeyURL: URL, pgpPublicKeyLocalPath: String, pgpPrivateKeyURL: URL, pgpPrivateKeyLocalPath: String) throws {
|
|
let pgpPublicData = try Data(contentsOf: pgpPublicKeyURL)
|
|
try pgpPublicData.write(to: URL(fileURLWithPath: pgpPublicKeyLocalPath), options: .atomic)
|
|
let pgpPrivateData = try Data(contentsOf: pgpPrivateKeyURL)
|
|
try pgpPrivateData.write(to: URL(fileURLWithPath: pgpPrivateKeyLocalPath), options: .atomic)
|
|
try initPGP(pgpPublicKeyLocalPath: pgpPublicKeyLocalPath, pgpPrivateKeyLocalPath: pgpPrivateKeyLocalPath)
|
|
}
|
|
|
|
func initPGP(pgpPublicKeyArmor: String, pgpPublicKeyLocalPath: String, pgpPrivateKeyArmor: String, pgpPrivateKeyLocalPath: String) throws {
|
|
try pgpPublicKeyArmor.write(toFile: pgpPublicKeyLocalPath, atomically: true, encoding: .ascii)
|
|
try pgpPrivateKeyArmor.write(toFile: pgpPrivateKeyLocalPath, atomically: true, encoding: .ascii)
|
|
try initPGP(pgpPublicKeyLocalPath: pgpPublicKeyLocalPath, pgpPrivateKeyLocalPath: pgpPrivateKeyLocalPath)
|
|
}
|
|
|
|
func cloneRepository(remoteRepoURL: URL,
|
|
credential: GitCredential,
|
|
transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void,
|
|
checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws {
|
|
Utils.removeFileIfExists(at: storeURL)
|
|
Utils.removeFileIfExists(at: tempStoreURL)
|
|
|
|
let credentialProvider = try credential.credentialProvider()
|
|
let options: [String: Any] = [
|
|
GTRepositoryCloneOptionsCredentialProvider: credentialProvider,
|
|
]
|
|
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock, checkoutProgressBlock: checkoutProgressBlock)
|
|
let fm = FileManager.default
|
|
do {
|
|
if fm.fileExists(atPath: storeURL.path) {
|
|
try fm.removeItem(at: storeURL)
|
|
}
|
|
try fm.copyItem(at: tempStoreURL, to: storeURL)
|
|
try fm.removeItem(at: tempStoreURL)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
storeRepository = try GTRepository(url: storeURL)
|
|
gitCredential = credential
|
|
}
|
|
|
|
func pullRepository(transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
|
if gitCredential == nil {
|
|
throw NSError(domain: "me.mssun.pass.error", code: 1, userInfo: [NSLocalizedDescriptionKey: "Git Repository is not set."])
|
|
}
|
|
let credentialProvider = try gitCredential!.credentialProvider()
|
|
let options: [String: Any] = [
|
|
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider
|
|
]
|
|
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
|
try storeRepository?.pull((storeRepository?.currentBranch())!, from: remote, withOptions: options, progress: transferProgressBlock)
|
|
}
|
|
|
|
func updatePasswordEntityCoreData() {
|
|
deleteCoreData(entityName: "PasswordEntity")
|
|
deleteCoreData(entityName: "PasswordCategoryEntity")
|
|
let fm = FileManager.default
|
|
fm.enumerator(atPath: self.storeURL.path)?.forEach({ (e) in
|
|
if let e = e as? String, let url = URL(string: e) {
|
|
if url.pathExtension == "gpg" {
|
|
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
|
let endIndex = url.lastPathComponent.index(url.lastPathComponent.endIndex, offsetBy: -4)
|
|
passwordEntity.name = url.lastPathComponent.substring(to: endIndex)
|
|
passwordEntity.rawPath = "\(url.path)"
|
|
let items = url.path.characters.split(separator: "/").map(String.init)
|
|
for i in 0 ..< items.count - 1 {
|
|
let passwordCategoryEntity = PasswordCategoryEntity(context: context)
|
|
passwordCategoryEntity.category = items[i]
|
|
passwordCategoryEntity.level = Int16(i)
|
|
passwordCategoryEntity.password = passwordEntity
|
|
}
|
|
}
|
|
}
|
|
})
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
print("Error with save: \(error)")
|
|
}
|
|
}
|
|
|
|
func getRecentCommits(count: Int) -> [GTCommit] {
|
|
var commits = [GTCommit]()
|
|
do {
|
|
let enumerator = try GTEnumerator(repository: storeRepository!)
|
|
try enumerator.pushSHA(storeRepository!.headReference().targetOID.sha!)
|
|
for _ in 0 ..< count {
|
|
let commit = try enumerator.nextObject(withSuccess: nil)
|
|
commits.append(commit)
|
|
}
|
|
} catch {
|
|
print(error)
|
|
return commits
|
|
}
|
|
return commits
|
|
}
|
|
|
|
func fetchPasswordEntityCoreData() -> [PasswordEntity] {
|
|
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
|
do {
|
|
let fetchedPasswordEntities = try context.fetch(passwordEntityFetch) as! [PasswordEntity]
|
|
return fetchedPasswordEntities.sorted { $0.name!.caseInsensitiveCompare($1.name!) == .orderedAscending }
|
|
} catch {
|
|
fatalError("Failed to fetch passwords: \(error)")
|
|
}
|
|
}
|
|
|
|
func fetchPasswordCategoryEntityCoreData(password: PasswordEntity) -> [PasswordCategoryEntity] {
|
|
let passwordCategoryEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordCategoryEntity")
|
|
passwordCategoryEntityFetchRequest.predicate = NSPredicate(format: "password = %@", password)
|
|
passwordCategoryEntityFetchRequest.sortDescriptors = [NSSortDescriptor(key: "level", ascending: true)]
|
|
do {
|
|
let passwordCategoryEntities = try context.fetch(passwordCategoryEntityFetchRequest) as! [PasswordCategoryEntity]
|
|
return passwordCategoryEntities
|
|
} catch {
|
|
fatalError("Failed to fetch password categories: \(error)")
|
|
}
|
|
}
|
|
|
|
func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
|
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
|
do {
|
|
let passwordEntities = try context.fetch(passwordEntityFetchRequest) as! [PasswordEntity]
|
|
return passwordEntities
|
|
} catch {
|
|
fatalError("Failed to fetch passwords: \(error)")
|
|
}
|
|
}
|
|
|
|
func setAllSynced() {
|
|
let passwordEntities = fetchUnsyncedPasswords()
|
|
for passwordEntity in passwordEntities {
|
|
passwordEntity.synced = true
|
|
}
|
|
do {
|
|
if context.hasChanges {
|
|
try context.save()
|
|
}
|
|
} catch {
|
|
fatalError("Failed to save: \(error)")
|
|
}
|
|
}
|
|
|
|
func getNumberOfUnsyncedPasswords() -> Int {
|
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
|
do {
|
|
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
|
return try context.count(for: passwordEntityFetchRequest)
|
|
} catch {
|
|
fatalError("Failed to fetch employees: \(error)")
|
|
}
|
|
}
|
|
|
|
func getLatestCommitDate(filename: String) -> String? {
|
|
guard let blameHunks = try? storeRepository?.blame(withFile: filename, options: nil).hunks,
|
|
let latestCommitTime = blameHunks?.map({
|
|
$0.finalSignature?.time?.timeIntervalSince1970 ?? 0
|
|
}).max() else {
|
|
return nil
|
|
}
|
|
let date = Date(timeIntervalSince1970: latestCommitTime)
|
|
let dateString = DateFormatter.localizedString(from: date, dateStyle: DateFormatter.Style.medium, timeStyle: DateFormatter.Style.medium)
|
|
return dateString
|
|
}
|
|
|
|
func updateRemoteRepo() {
|
|
}
|
|
|
|
|
|
func addEntryToGTTree(fileData: Data, filename: String) -> GTTree {
|
|
do {
|
|
let head = try storeRepository!.headReference()
|
|
let branch = GTBranch(reference: head, repository: storeRepository!)
|
|
let headCommit = try branch?.targetCommit()
|
|
|
|
let treeBulider = try GTTreeBuilder(tree: headCommit?.tree, repository: storeRepository!)
|
|
try treeBulider.addEntry(with: fileData, fileName: filename, fileMode: GTFileMode.blob)
|
|
|
|
let newTree = try treeBulider.writeTree()
|
|
return newTree
|
|
} catch {
|
|
fatalError("Failed to add entries to GTTree: \(error)")
|
|
|
|
}
|
|
}
|
|
|
|
func removeEntryFromGTTree(filename: String) -> GTTree {
|
|
do {
|
|
let head = try storeRepository!.headReference()
|
|
let branch = GTBranch(reference: head, repository: storeRepository!)
|
|
let headCommit = try branch?.targetCommit()
|
|
|
|
let treeBulider = try GTTreeBuilder(tree: headCommit?.tree, repository: storeRepository!)
|
|
try treeBulider.removeEntry(withFileName: filename)
|
|
|
|
let newTree = try treeBulider.writeTree()
|
|
return newTree
|
|
} catch {
|
|
fatalError("Failed to remove entries to GTTree: \(error)")
|
|
|
|
}
|
|
}
|
|
|
|
func createAddCommitInRepository(message: String, fileData: Data, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? {
|
|
do {
|
|
let newTree = addEntryToGTTree(fileData: fileData, filename: filename)
|
|
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 commit = try storeRepository!.createCommit(with: newTree, message: message, parents: [parent], updatingReferenceNamed: headReference.name)
|
|
progressBlock(0.7)
|
|
return commit
|
|
} catch {
|
|
print(error)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createRemoveCommitInRepository(message: String, filename: String, progressBlock: (_ progress: Float) -> Void) -> GTCommit? {
|
|
do {
|
|
let newTree = removeEntryFromGTTree(filename: filename)
|
|
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 commit = try storeRepository!.createCommit(with: newTree, message: message, parents: [parent], updatingReferenceNamed: headReference.name)
|
|
progressBlock(0.7)
|
|
return commit
|
|
} catch {
|
|
print(error)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
|
|
private func getLocalBranch(withName branchName: String) -> GTBranch? {
|
|
do {
|
|
let reference = GTBranch.localNamePrefix().appending(branchName)
|
|
let branches = try storeRepository!.branches(withPrefix: reference)
|
|
return branches[0]
|
|
} catch {
|
|
print(error)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func pushRepository(transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
|
let credentialProvider = try gitCredential!.credentialProvider()
|
|
let options: [String: Any] = [
|
|
GTRepositoryRemoteOptionsCredentialProvider: credentialProvider,
|
|
]
|
|
let masterBranch = getLocalBranch(withName: "master")!
|
|
let remote = try GTRemote(name: "origin", in: storeRepository!)
|
|
try storeRepository?.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
|
}
|
|
|
|
func add(password: Password, progressBlock: (_ progress: Float) -> Void) {
|
|
progressBlock(0.0)
|
|
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.rawPath = "\(password.name).gpg"
|
|
passwordEntity.synced = false
|
|
try context.save()
|
|
print(saveURL.path)
|
|
let _ = createAddCommitInRepository(message: "Add new password by pass for iOS", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
|
progressBlock(1.0)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
func update(passwordEntity: PasswordEntity, password: Password, progressBlock: (_ progress: Float) -> Void) {
|
|
do {
|
|
let encryptedData = try passwordEntity.encrypt(password: password)
|
|
let saveURL = storeURL.appendingPathComponent(passwordEntity.rawPath!)
|
|
try encryptedData.write(to: saveURL)
|
|
progressBlock(0.3)
|
|
let _ = createAddCommitInRepository(message: "Update password by pass for iOS", fileData: encryptedData, filename: saveURL.lastPathComponent, progressBlock: progressBlock)
|
|
} catch {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
func saveUpdated(passwordEntity: PasswordEntity) {
|
|
do {
|
|
try context.save()
|
|
} catch {
|
|
fatalError("Failed to save a PasswordEntity: \(error)")
|
|
}
|
|
}
|
|
|
|
func deleteCoreData(entityName: String) {
|
|
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
|
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
|
|
|
|
do {
|
|
try context.execute(deleteRequest)
|
|
try context.save()
|
|
context.reset()
|
|
} catch let error as NSError {
|
|
print(error)
|
|
}
|
|
}
|
|
|
|
func updateImage(passwordEntity: PasswordEntity, image: Data?) {
|
|
if image == nil {
|
|
return
|
|
}
|
|
let privateMOC = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
|
privateMOC.parent = context
|
|
privateMOC.perform {
|
|
passwordEntity.image = NSData(data: image!)
|
|
do {
|
|
try privateMOC.save()
|
|
self.context.performAndWait {
|
|
do {
|
|
try self.context.save()
|
|
} catch {
|
|
fatalError("Failure to save context: \(error)")
|
|
}
|
|
}
|
|
} catch {
|
|
fatalError("Failure to save context: \(error)")
|
|
}
|
|
}
|
|
}
|
|
|
|
func erase() {
|
|
Utils.removeFileIfExists(at: storeURL)
|
|
Utils.removeFileIfExists(at: tempStoreURL)
|
|
|
|
Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath)
|
|
Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath)
|
|
Utils.removeFileIfExists(at: Globals.sshPrivateKeyURL)
|
|
Utils.removeFileIfExists(at: Globals.sshPublicKeyURL)
|
|
|
|
Utils.removeAllKeychain()
|
|
|
|
|
|
deleteCoreData(entityName: "PasswordEntity")
|
|
deleteCoreData(entityName: "PasswordCategoryEntity")
|
|
|
|
Defaults.removeAll()
|
|
storeRepository = nil
|
|
}
|
|
}
|