diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 628007e..ee185b7 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 94BA784B85E071D25EE89B59 /* libPods-pass.a in Frameworks */ = {isa = PBXBuildFile; fileRef = ADCE7A5C3CCC67D7D21BB3C4 /* libPods-pass.a */; }; A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; }; A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; }; + A26075721EEC6B8D005DB03E /* SwiftyUserDefaults.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCA049951E3357E000522E8F /* SwiftyUserDefaults.framework */; }; A262A58D1E68749C006B0890 /* Base32.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A262A58C1E68749C006B0890 /* Base32.framework */; }; A26700271EEC466A00176B8A /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A26700261EEC466A00176B8A /* ActionViewController.swift */; }; A267002A1EEC466A00176B8A /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A26700281EEC466A00176B8A /* MainInterface.storyboard */; }; @@ -198,6 +199,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A26075721EEC6B8D005DB03E /* SwiftyUserDefaults.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -738,6 +740,10 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passextension/passextension.entitlements; DEVELOPMENT_TEAM = 4WDM8E95VU; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); INFOPLIST_FILE = passextension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; @@ -755,6 +761,10 @@ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = passextension/passextension.entitlements; DEVELOPMENT_TEAM = 4WDM8E95VU; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Carthage/Build/iOS", + ); INFOPLIST_FILE = passextension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; diff --git a/pass/AppDelegate.swift b/pass/AppDelegate.swift index 5500e98..23b2f09 100644 --- a/pass/AppDelegate.swift +++ b/pass/AppDelegate.swift @@ -113,7 +113,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { error conditions that could cause the creation of the store to fail. */ let container = NSPersistentContainer(name: "pass") - container.loadPersistentStores(completionHandler: { (storeDescription, error) in + let description = NSPersistentStoreDescription(url: Globals.sharedContainerURL) + container.loadPersistentStores(completionHandler: { (description, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. diff --git a/pass/Helpers/DefaultsKeys.swift b/pass/Helpers/DefaultsKeys.swift index fc0a8a1..e198c3c 100644 --- a/pass/Helpers/DefaultsKeys.swift +++ b/pass/Helpers/DefaultsKeys.swift @@ -9,6 +9,8 @@ import Foundation import SwiftyUserDefaults +var Defaults = UserDefaults(suiteName: Globals.groupIdentifier)! + extension DefaultsKeys { static let pgpKeySource = DefaultsKey("pgpKeySource") static let pgpPublicKeyURL = DefaultsKey("pgpPublicKeyURL") diff --git a/pass/Helpers/Globals.swift b/pass/Helpers/Globals.swift index 7bb91db..79ec43b 100644 --- a/pass/Helpers/Globals.swift +++ b/pass/Helpers/Globals.swift @@ -10,15 +10,26 @@ import Foundation import UIKit class Globals { - static let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]; - static let libraryPath = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0]; - static let pgpPublicKeyPath = "\(documentPath)/gpg_key.pub" - static let pgpPrivateKeyPath = "\(documentPath)/gpg_key" - - static let gitSSHPrivateKeyPath = "\(documentPath)/ssh_key" - static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath) - static let repositoryPath = "\(libraryPath)/password-store" + // Legacy paths (not shared) + static let documentPathLegacy = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]; + static let libraryPathLegacy = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0]; + static let pgpPublicKeyPathLegacy = "\(documentPathLegacy)/gpg_key.pub" + static let pgpPrivateKeyPathLegacy = "\(documentPathLegacy)/gpg_key" + static let gitSSHPrivateKeyPathLegacy = "\(documentPathLegacy)/ssh_key" + static let gitSSHPrivateKeyURLLegacy = URL(fileURLWithPath: gitSSHPrivateKeyPathLegacy) + static let repositoryPathLegacy = "\(libraryPathLegacy)/password-store" + + static let groupIdentifier = "group." + Bundle.main.bundleIdentifier! + static let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)! + static let documentPath = sharedContainerURL.appendingPathComponent("Keys").path + static let libraryPath = sharedContainerURL.appendingPathComponent("Repository").path + static let pgpPublicKeyPath = documentPath + "/gpg_key.pub" + static let pgpPrivateKeyPath = documentPath + "/gpg_key" + static let gitSSHPrivateKeyPath = documentPath + "/ssh_key" + static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath) + static let repositoryPath = libraryPath + "/password-store" + static var passcodeConfiguration = PasscodeLockConfiguration() static let passwordDefaultLength = ["Random": (min: 4, max: 64, def: 16), diff --git a/pass/Models/PasswordStore.swift b/pass/Models/PasswordStore.swift index 26e8ab0..ca80ffd 100644 --- a/pass/Models/PasswordStore.swift +++ b/pass/Models/PasswordStore.swift @@ -68,14 +68,40 @@ class PasswordStore { } } - let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext + let fm = FileManager.default + lazy var context: NSManagedObjectContext = { + /* + The persistent container for the application. This implementation + creates and returns a container, having loaded the store for the + application to it. This property is optional since there are legitimate + error conditions that could cause the creation of the store to fail. + */ + let container = NSPersistentContainer(name: "pass") + let description = NSPersistentStoreDescription(url: Globals.sharedContainerURL) + container.loadPersistentStores(completionHandler: { (description, error) in + if let error = error as NSError? { + // Replace this implementation with code to handle the error appropriately. + // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. + + /* + Typical reasons for an error here include: + * The parent directory does not exist, cannot be created, or disallows writing. + * The persistent store is not accessible, due to permissions or data protection when the device is locked. + * The device is out of space. + * The store could not be migrated to the current model version. + Check the error message to determine what the actual problem was. + */ + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + return container.viewContext + }() var numberOfPasswords : Int { return self.fetchPasswordEntityCoreData(withDir: false).count } var sizeOfRepositoryByteCount : UInt64 { - let fm = FileManager.default var size = UInt64(0) do { if fm.fileExists(atPath: self.storeURL.path) { @@ -89,8 +115,11 @@ class PasswordStore { private init() { + // File migration to group + migration() + do { - if FileManager.default.fileExists(atPath: storeURL.path) { + if fm.fileExists(atPath: storeURL.path) { try storeRepository = GTRepository.init(url: storeURL) } try initPGPKeys() @@ -99,6 +128,20 @@ class PasswordStore { } } + private func migration() { + let needMigration = fm.fileExists(atPath: Globals.documentPathLegacy) && !fm.fileExists(atPath: Globals.documentPath) && fm.fileExists(atPath: Globals.libraryPathLegacy) && !fm.fileExists(atPath: Globals.libraryPath) + guard needMigration == true else { + return + } + do { + try fm.copyItem(atPath: Globals.documentPathLegacy, toPath: Globals.documentPath) + try fm.copyItem(atPath: Globals.libraryPathLegacy, toPath: Globals.libraryPath) + } catch { + print("Cannot migrate: \(error)") + } + updatePasswordEntityCoreData() + } + enum SSHKeyType { case `public`, secret } @@ -160,7 +203,6 @@ class PasswordStore { private func importKey(from keyPath: String) -> PGPKey? { - let fm = FileManager.default if fm.fileExists(atPath: keyPath) { if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] { return keys.first @@ -230,7 +272,6 @@ class PasswordStore { let credentialProvider = try credential.credentialProvider() let options = [GTRepositoryCloneOptionsCredentialProvider: credentialProvider] storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock) - let fm = FileManager.default if fm.fileExists(atPath: storeURL.path) { try fm.removeItem(at: storeURL) } @@ -271,7 +312,6 @@ class PasswordStore { private func updatePasswordEntityCoreData() { deleteCoreData(entityName: "PasswordEntity") - let fm = FileManager.default do { var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter{ !$0.hasPrefix(".") @@ -443,8 +483,8 @@ class PasswordStore { throw AppError.RepositoryNotSetError } let url = storeURL.appendingPathComponent(path) - if FileManager.default.fileExists(atPath: url.path) { - try FileManager.default.removeItem(at: url) + if fm.fileExists(atPath: url.path) { + try fm.removeItem(at: url) } try storeRepository.index().removeFile(path) try storeRepository.index().write() @@ -452,7 +492,6 @@ class PasswordStore { private func deleteDirectoryTree(at url: URL) throws { var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path) - let fm = FileManager.default var count = try fm.contentsOfDirectory(atPath: tempURL.path).count while count == 0 { try fm.removeItem(at: tempURL) @@ -463,13 +502,12 @@ class PasswordStore { private func createDirectoryTree(at url: URL) throws { let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path) - try FileManager.default.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil) + try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil) } private func gitMv(from: String, to: String) throws { let fromURL = storeURL.appendingPathComponent(from) let toURL = storeURL.appendingPathComponent(to) - let fm = FileManager.default guard fm.fileExists(atPath: fromURL.path) else { print("\(from) not exist") return @@ -779,8 +817,6 @@ class PasswordStore { return encryptedData } - - func removePGPKeys() { Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath) Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath) @@ -803,12 +839,10 @@ class PasswordStore { } func gitSSHKeyExists() -> Bool { - let fm = FileManager.default return fm.fileExists(atPath: Globals.gitSSHPrivateKeyPath) } func pgpKeyExists() -> Bool { - let fm = FileManager.default return fm.fileExists(atPath: Globals.pgpPublicKeyPath) && fm.fileExists(atPath: Globals.pgpPrivateKeyPath) } }