// // PasswordStore.swift // pass // // Created by Mingshen Sun on 19/1/2017. // Copyright © 2017 Bob Sun. All rights reserved. // import Foundation import Result import CoreData import UIKit import SwiftyUserDefaults import ObjectiveGit struct GitCredential { enum Credential { case http(userName: String, password: String) case ssh(userName: String, password: String, publicKeyFile: URL, privateKeyFile: URL) } var credential: Credential func credentialProvider() throws -> GTCredentialProvider { return GTCredentialProvider { (_, _, _) -> (GTCredential) in let credential: GTCredential? switch self.credential { case let .http(userName, password): credential = try? GTCredential(userName: userName, password: password) case let .ssh(userName, password, publicKeyFile, privateKeyFile): credential = try? GTCredential(userName: userName, publicKeyURL: publicKeyFile, privateKeyURL: privateKeyFile, passphrase: password) } return credential ?? GTCredential() } } } class PasswordStore { static let shared = PasswordStore() let storeURL = URL(fileURLWithPath: "\(Globals.documentPath)/password-store") let tempStoreURL = URL(fileURLWithPath: "\(Globals.documentPath)/password-store-temp") var storeRepository: GTRepository? var gitCredential: GitCredential? let pgp: ObjectivePGP = ObjectivePGP() 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] != "" { pgp.importKeys(fromFile: Globals.secringPath, allowDuplicates: false) } if Defaults[.gitRepositoryAuthenticationMethod] == "Password" { gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitRepositoryUsername], password: Defaults[.gitRepositoryPassword])) } else if Defaults[.gitRepositoryAuthenticationMethod] == "SSH Key"{ gitCredential = GitCredential(credential: GitCredential.Credential.ssh(userName: Defaults[.gitRepositoryUsername], password: Defaults[.gitRepositorySSHPrivateKeyPassphrase]!, publicKeyFile: Globals.sshPublicKeyPath, privateKeyFile: Globals.sshPrivateKeyPath)) } else { gitCredential = nil } } func initPGP(pgpKeyURL: URL, pgpKeyLocalPath: String) throws { let pgpData = try Data(contentsOf: pgpKeyURL) try pgpData.write(to: URL(fileURLWithPath: pgpKeyLocalPath), options: .atomic) pgp.importKeys(fromFile: pgpKeyLocalPath, allowDuplicates: false) let key = pgp.keys[0] Defaults[.pgpKeyID] = key.keyID!.shortKeyString if let gpgUser = key.users[0] as? PGPUser { Defaults[.pgpKeyUserID] = gpgUser.userID } } func cloneRepository(remoteRepoURL: URL, credential: GitCredential, transferProgressBlock: @escaping (UnsafePointer, UnsafeMutablePointer) -> Void, checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws { 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) updatePasswordEntityCoreData() gitCredential = credential } func pullRepository(transferProgressBlock: @escaping (UnsafePointer, UnsafeMutablePointer) -> Void) throws { 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) updatePasswordEntityCoreData() } func updatePasswordEntityCoreData() { deleteCoreData(entityName: "PasswordEntity") deleteCoreData(entityName: "PasswordCategoryEntity") let fm = FileManager.default fm.enumerator(atPath: storeURL.path)?.forEach({ (e) in if let e = e as? String, let url = URL(string: e) { if url.pathExtension == "gpg" { let passwordEntity = PasswordEntity(context: context) let endIndex = url.lastPathComponent.index(url.lastPathComponent.endIndex, offsetBy: -4) passwordEntity.name = url.lastPathComponent.substring(to: endIndex) passwordEntity.rawPath = "password-store/\(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 fetchPasswordEntityCoreData() -> [PasswordEntity] { let passwordEntityFetch = NSFetchRequest(entityName: "PasswordEntity") do { let fetchedPasswordEntities = try context.fetch(passwordEntityFetch) as! [PasswordEntity] return fetchedPasswordEntities.sorted(by: { (p1, p2) -> Bool in return p1.name! < p2.name!; }) } catch { fatalError("Failed to fetch employees: \(error)") } } func fetchPasswordCategoryEntityCoreData(password: PasswordEntity) -> [PasswordCategoryEntity] { let passwordCategoryEntityFetchRequest = NSFetchRequest(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 employees: \(error)") } } func updateRemoteRepo() { } func deleteCoreData(entityName: String) { let deleteFetchRequest = NSFetchRequest(entityName: entityName) let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest) do { try context.execute(deleteRequest) } catch let error as NSError { print(error) } } func erase() { let fm = FileManager.default do { if fm.fileExists(atPath: storeURL.path) { try fm.removeItem(at: storeURL) } if fm.fileExists(atPath: Globals.secringPath) { try fm.removeItem(atPath: Globals.secringPath) } if fm.fileExists(atPath: Globals.sshPrivateKeyPath.path) { try fm.removeItem(at: Globals.sshPrivateKeyPath) } if fm.fileExists(atPath: Globals.sshPublicKeyPath.path) { try fm.removeItem(at: Globals.sshPublicKeyPath) } } catch { print(error) } deleteCoreData(entityName: "PasswordEntity") deleteCoreData(entityName: "PasswordCategoryEntity") Defaults.remove(.pgpKeyURL) Defaults.remove(.pgpKeyPassphrase) Defaults.remove(.pgpKeyID) Defaults.remove(.pgpKeyUserID) Defaults.remove(.gitRepositoryURL) Defaults.remove(.gitRepositoryAuthenticationMethod) Defaults.remove(.gitRepositoryUsername) Defaults.remove(.gitRepositoryPassword) Defaults.remove(.gitRepositorySSHPublicKeyURL) Defaults.remove(.gitRepositorySSHPrivateKeyURL) Defaults.remove(.gitRepositorySSHPrivateKeyPassphrase) Defaults.remove(.lastUpdatedTime) } }