Move codes to an embed framework
- Move bundle/group identifiers to passKit/Global - Fix Core Data - Change Defaults to SharedDefaults
This commit is contained in:
parent
850dc75820
commit
d2ba620ae4
45 changed files with 1062 additions and 523 deletions
|
|
@ -10,6 +10,7 @@ import UIKit
|
|||
import CoreData
|
||||
import PasscodeLock
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
|
@ -20,7 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
|
||||
lazy var passcodeLockPresenter: PasscodeLockPresenter = {
|
||||
let presenter = PasscodeLockPresenter(mainWindow: self.window, configuration: Globals.passcodeConfiguration)
|
||||
let presenter = PasscodeLockPresenter(mainWindow: self.window, configuration: PasscodeLockConfiguration.shared)
|
||||
return presenter
|
||||
}()
|
||||
|
||||
|
|
@ -29,9 +30,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
|
||||
passcodeLockPresenter.present()
|
||||
if let shortcutItem = launchOptions?[UIApplicationLaunchOptionsKey.shortcutItem] as? UIApplicationShortcutItem {
|
||||
var searchType = Bundle.main.bundleIdentifier!
|
||||
searchType.append(".search")
|
||||
if shortcutItem.type == searchType {
|
||||
if shortcutItem.type == Globals.bundleIdentifier + ".search" {
|
||||
self.perform(#selector(postSearchNotification), with: nil, afterDelay: 0.4)
|
||||
}
|
||||
}
|
||||
|
|
@ -44,9 +43,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
}
|
||||
|
||||
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
||||
var searchType = Bundle.main.bundleIdentifier!
|
||||
searchType.append(".search")
|
||||
if shortcutItem.type == searchType {
|
||||
if shortcutItem.type == Globals.bundleIdentifier + ".search" {
|
||||
let tabBarController = self.window!.rootViewController as! UITabBarController
|
||||
tabBarController.selectedIndex = 0
|
||||
let navigationController = tabBarController.selectedViewController as! UINavigationController
|
||||
|
|
@ -112,9 +109,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
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
|
||||
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
|
||||
let managedObjectModel = NSManagedObjectModel(contentsOf: modelURL)
|
||||
let container = NSPersistentContainer(name: "pass", managedObjectModel: managedObjectModel!)
|
||||
container.persistentStoreDescriptions = [NSPersistentStoreDescription(url: Globals.sharedContainerURL.appendingPathComponent("Documents/pass.sqlite"))]
|
||||
container.loadPersistentStores(completionHandler: { (storeDescription, 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.
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
//
|
||||
// AppError.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 30/4/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AppError: Error {
|
||||
case RepositoryNotSetError
|
||||
case RepositoryRemoteMasterNotFoundError
|
||||
case KeyImportError
|
||||
case PasswordDuplicatedError
|
||||
case GitResetError
|
||||
case PGPPublicKeyNotExistError
|
||||
case UnknownError
|
||||
}
|
||||
|
||||
extension AppError: LocalizedError {
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .RepositoryNotSetError:
|
||||
return "Git repository is not set."
|
||||
case .RepositoryRemoteMasterNotFoundError:
|
||||
return "Cannot find remote branch origin/master."
|
||||
case .KeyImportError:
|
||||
return "Cannot import the key."
|
||||
case .PasswordDuplicatedError:
|
||||
return "Cannot add the password: password duplicated."
|
||||
case .GitResetError:
|
||||
return "Cannot identify the latest synced commit."
|
||||
case .PGPPublicKeyNotExistError:
|
||||
return "PGP public key doesn't exist."
|
||||
case .UnknownError:
|
||||
return "Unknown error."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
|
||||
class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class AddPasswordTableViewController: PasswordEditorTableViewController {
|
||||
var tempContent: String = ""
|
||||
|
|
@ -20,7 +20,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
|||
[[.type: PasswordEditorCellType.additionsCell, .title: "additions"]],
|
||||
[[.type: PasswordEditorCellType.scanQRCodeCell]]
|
||||
]
|
||||
if let lengthSetting = Globals.passwordDefaultLength[Defaults[.passwordGeneratorFlavor]],
|
||||
if let lengthSetting = Globals.passwordDefaultLength[SharedDefaults[.passwordGeneratorFlavor]],
|
||||
lengthSetting.max > lengthSetting.min {
|
||||
tableData[1].append([.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class AdvancedSettingsTableViewController: UITableViewController {
|
||||
|
||||
|
|
@ -28,7 +28,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
encryptInASCIIArmoredSwitch.isOn = Defaults[.encryptInArmored]
|
||||
encryptInASCIIArmoredSwitch.isOn = SharedDefaults[.encryptInArmored]
|
||||
encryptInASCIIArmoredTableViewCell.accessoryView = encryptInASCIIArmoredSwitch
|
||||
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
|
||||
setGitSignatureText()
|
||||
|
|
@ -39,7 +39,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
let gitSignatureEmail = passwordStore.gitSignatureForNow.email!
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.systemFont(ofSize: 14)
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.text = "\(gitSignatureName) <\(gitSignatureEmail)>"
|
||||
if Defaults[.gitSignatureName] == nil && Defaults[.gitSignatureEmail] == nil {
|
||||
if SharedDefaults[.gitSignatureName] == nil && SharedDefaults[.gitSignatureEmail] == nil {
|
||||
self.gitSignatureTableViewCell.detailTextLabel?.font = UIFont.systemFont(ofSize: 17)
|
||||
gitSignatureTableViewCell.detailTextLabel?.text = "Not Set"
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
func encryptInASCIIArmoredAction(_ sender: Any?) {
|
||||
Defaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
|
||||
SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
|
||||
}
|
||||
|
||||
@IBAction func cancelGitConfigSetting(segue: UIStoryboardSegue) {
|
||||
|
|
@ -95,8 +95,8 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
|||
if let controller = segue.source as? GitConfigSettingTableViewController {
|
||||
if let gitSignatureName = controller.nameTextField.text,
|
||||
let gitSignatureEmail = controller.emailTextField.text {
|
||||
Defaults[.gitSignatureName] = gitSignatureName.isEmpty ? nil : gitSignatureName
|
||||
Defaults[.gitSignatureEmail] = gitSignatureEmail.isEmpty ? nil : gitSignatureEmail
|
||||
SharedDefaults[.gitSignatureName] = gitSignatureName.isEmpty ? nil : gitSignatureName
|
||||
SharedDefaults[.gitSignatureEmail] = gitSignatureEmail.isEmpty ? nil : gitSignatureEmail
|
||||
}
|
||||
setGitSignatureText()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
import UIKit
|
||||
import SafariServices
|
||||
import MessageUI
|
||||
import passKit
|
||||
|
||||
|
||||
enum CellDataType {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import ObjectiveGit
|
||||
import passKit
|
||||
|
||||
class CommitLogsTableViewController: UITableViewController {
|
||||
var commits: [GTCommit] = []
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||
override func viewDidLoad() {
|
||||
|
|
@ -18,7 +18,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
|||
[[.type: PasswordEditorCellType.scanQRCodeCell],
|
||||
[.type: PasswordEditorCellType.deletePasswordCell]]
|
||||
]
|
||||
if let lengthSetting = Globals.passwordDefaultLength[Defaults[.passwordGeneratorFlavor]],
|
||||
if let lengthSetting = Globals.passwordDefaultLength[SharedDefaults[.passwordGeneratorFlavor]],
|
||||
lengthSetting.max > lengthSetting.min {
|
||||
tableData[1].append([.type: PasswordEditorCellType.passwordLengthCell, .title: "passwordlength"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
|
@ -33,7 +33,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
uiSwitch.onTintColor = Globals.blue
|
||||
uiSwitch.sizeToFit()
|
||||
uiSwitch.addTarget(self, action: #selector(rememberPassphraseSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
||||
uiSwitch.isOn = Defaults[.isRememberPassphraseOn]
|
||||
uiSwitch.isOn = SharedDefaults[.isRememberPassphraseOn]
|
||||
return uiSwitch
|
||||
}()
|
||||
|
||||
|
|
@ -42,7 +42,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
uiSwitch.onTintColor = Globals.blue
|
||||
uiSwitch.sizeToFit()
|
||||
uiSwitch.addTarget(self, action: #selector(showFolderSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
||||
uiSwitch.isOn = Defaults[.isShowFolderOn]
|
||||
uiSwitch.isOn = SharedDefaults[.isShowFolderOn]
|
||||
return uiSwitch
|
||||
}()
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
accessoryView.addSubview(hideUnknownSwitch)
|
||||
cell.accessoryView = accessoryView
|
||||
cell.selectionStyle = .none
|
||||
hideUnknownSwitch.isOn = Defaults[.isHideUnknownOn]
|
||||
hideUnknownSwitch.isOn = SharedDefaults[.isHideUnknownOn]
|
||||
case "Hide OTP Fields":
|
||||
cell.accessoryType = .none
|
||||
let detailButton = UIButton(type: .detailDisclosure)
|
||||
|
|
@ -97,7 +97,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
accessoryView.addSubview(hideOTPSwitch)
|
||||
cell.accessoryView = accessoryView
|
||||
cell.selectionStyle = .none
|
||||
hideOTPSwitch.isOn = Defaults[.isHideOTPOn]
|
||||
hideOTPSwitch.isOn = SharedDefaults[.isHideOTPOn]
|
||||
case "Remember Passphrase":
|
||||
cell.accessoryType = .none
|
||||
cell.selectionStyle = .none
|
||||
|
|
@ -108,7 +108,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
cell.accessoryView = showFolderSwitch
|
||||
case "Password Generator Flavor":
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
cell.detailTextLabel?.text = Defaults[.passwordGeneratorFlavor]
|
||||
cell.detailTextLabel?.text = SharedDefaults[.passwordGeneratorFlavor]
|
||||
default: break
|
||||
}
|
||||
return cell
|
||||
|
|
@ -127,7 +127,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
var randomFlavorActionTitle = ""
|
||||
var appleFlavorActionTitle = ""
|
||||
if Defaults[.passwordGeneratorFlavor] == "Random" {
|
||||
if SharedDefaults[.passwordGeneratorFlavor] == "Random" {
|
||||
randomFlavorActionTitle = "✓ Random String"
|
||||
appleFlavorActionTitle = "Apple's Keychain Style"
|
||||
} else {
|
||||
|
|
@ -135,12 +135,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
appleFlavorActionTitle = "✓ Apple's Keychain Style"
|
||||
}
|
||||
let randomFlavorAction = UIAlertAction(title: randomFlavorActionTitle, style: .default) { _ in
|
||||
Defaults[.passwordGeneratorFlavor] = "Random"
|
||||
SharedDefaults[.passwordGeneratorFlavor] = "Random"
|
||||
sourceCell.detailTextLabel?.text = "Random"
|
||||
}
|
||||
|
||||
let appleFlavorAction = UIAlertAction(title: appleFlavorActionTitle, style: .default) { _ in
|
||||
Defaults[.passwordGeneratorFlavor] = "Apple"
|
||||
SharedDefaults[.passwordGeneratorFlavor] = "Apple"
|
||||
sourceCell.detailTextLabel?.text = "Apple"
|
||||
}
|
||||
|
||||
|
|
@ -167,24 +167,24 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
|||
}
|
||||
|
||||
func hideUnknownSwitchAction(_ sender: Any?) {
|
||||
Defaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
|
||||
SharedDefaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
func hideOTPSwitchAction(_ sender: Any?) {
|
||||
Defaults[.isHideOTPOn] = hideOTPSwitch.isOn
|
||||
SharedDefaults[.isHideOTPOn] = hideOTPSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
func rememberPassphraseSwitchAction(_ sender: Any?) {
|
||||
Defaults[.isRememberPassphraseOn] = rememberPassphraseSwitch.isOn
|
||||
SharedDefaults[.isRememberPassphraseOn] = rememberPassphraseSwitch.isOn
|
||||
if rememberPassphraseSwitch.isOn == false {
|
||||
passwordStore.pgpKeyPassphrase = nil
|
||||
}
|
||||
}
|
||||
|
||||
func showFolderSwitchAction(_ sender: Any?) {
|
||||
Defaults[.isShowFolderOn] = showFolderSwitch.isOn
|
||||
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
|
||||
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class GitConfigSettingTableViewController: UITableViewController {
|
||||
let passwordStore = PasswordStore.shared
|
||||
|
|
@ -22,8 +23,8 @@ class GitConfigSettingTableViewController: UITableViewController {
|
|||
let signature = passwordStore.gitSignatureForNow
|
||||
nameTextField.placeholder = signature.name
|
||||
emailTextField.placeholder = signature.email
|
||||
nameTextField.text = Defaults[.gitSignatureName]
|
||||
emailTextField.text = Defaults[.gitSignatureEmail]
|
||||
nameTextField.text = SharedDefaults[.gitSignatureName]
|
||||
emailTextField.text = SharedDefaults[.gitSignatureEmail]
|
||||
}
|
||||
|
||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||
|
|
@ -73,7 +73,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
armorPrivateKeyTextView.text = Defaults[.gitSSHPrivateKeyArmor]
|
||||
armorPrivateKeyTextView.text = SharedDefaults[.gitSSHPrivateKeyArmor]
|
||||
armorPrivateKeyTextView.delegate = self
|
||||
|
||||
scanPrivateKeyCell?.textLabel?.text = "Scan Private Key QR Codes"
|
||||
|
|
@ -83,13 +83,13 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
|||
}
|
||||
|
||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||
Defaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
||||
SharedDefaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
||||
do {
|
||||
try passwordStore.initGitSSHKey(with: armorPrivateKeyTextView.text, .secret)
|
||||
try passwordStore.initGitSSHKey(with: armorPrivateKeyTextView.text)
|
||||
} catch {
|
||||
Utils.alert(title: "Cannot Save", message: "Cannot Save SSH Key", controller: self, completion: nil)
|
||||
}
|
||||
Defaults[.gitSSHKeySource] = "armor"
|
||||
SharedDefaults[.gitSSHKeySource] = "armor"
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
class GitServerSettingTableViewController: UITableViewController {
|
||||
|
||||
|
|
@ -19,7 +19,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
let passwordStore = PasswordStore.shared
|
||||
var sshLabel: UILabel? = nil
|
||||
|
||||
var authenticationMethod = Defaults[.gitAuthenticationMethod] ?? "Password"
|
||||
var authenticationMethod = SharedDefaults[.gitAuthenticationMethod] ?? "Password"
|
||||
|
||||
private func checkAuthenticationMethod(method: String) {
|
||||
let passwordCheckView = authPasswordCell.viewWithTag(1001)!
|
||||
|
|
@ -47,10 +47,10 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
}
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
if let url = Defaults[.gitURL] {
|
||||
if let url = SharedDefaults[.gitURL] {
|
||||
gitURLTextField.text = url.absoluteString
|
||||
}
|
||||
usernameTextField.text = Defaults[.gitUsername]
|
||||
usernameTextField.text = SharedDefaults[.gitUsername]
|
||||
sshLabel = authSSHKeyCell.subviews[0].subviews[0] as? UILabel
|
||||
checkAuthenticationMethod(method: authenticationMethod)
|
||||
authSSHKeyCell.accessoryType = .detailButton
|
||||
|
|
@ -110,9 +110,9 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
}
|
||||
})
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.gitURL] = URL(string: gitRepostiroyURL)
|
||||
Defaults[.gitUsername] = username
|
||||
Defaults[.gitAuthenticationMethod] = auth
|
||||
SharedDefaults[.gitURL] = URL(string: gitRepostiroyURL)
|
||||
SharedDefaults[.gitUsername] = username
|
||||
SharedDefaults[.gitAuthenticationMethod] = auth
|
||||
SVProgressHUD.showSuccess(withStatus: "Done")
|
||||
SVProgressHUD.dismiss(withDelay: 1)
|
||||
self.performSegue(withIdentifier: "saveGitServerSettingSegue", sender: self)
|
||||
|
|
@ -167,11 +167,11 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
||||
var fileActionTitle = "Use Imported Keys"
|
||||
|
||||
if Defaults[.gitSSHKeySource] == "url" {
|
||||
if SharedDefaults[.gitSSHKeySource] == "url" {
|
||||
urlActionTitle = "✓ \(urlActionTitle)"
|
||||
} else if Defaults[.gitSSHKeySource] == "armor" {
|
||||
} else if SharedDefaults[.gitSSHKeySource] == "armor" {
|
||||
armorActionTitle = "✓ \(armorActionTitle)"
|
||||
} else if Defaults[.gitSSHKeySource] == "file" {
|
||||
} else if SharedDefaults[.gitSSHKeySource] == "file" {
|
||||
fileActionTitle = "✓ \(fileActionTitle)"
|
||||
}
|
||||
let urlAction = UIAlertAction(title: urlActionTitle, style: .default) { _ in
|
||||
|
|
@ -187,7 +187,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
if passwordStore.gitSSHKeyExists() {
|
||||
// might keys updated via iTunes, or downloaded/pasted inside the app
|
||||
let fileAction = UIAlertAction(title: fileActionTitle, style: .default) { _ in
|
||||
Defaults[.gitSSHKeySource] = "file"
|
||||
SharedDefaults[.gitSSHKeySource] = "file"
|
||||
}
|
||||
optionMenu.addAction(fileAction)
|
||||
} else {
|
||||
|
|
@ -199,10 +199,10 @@ class GitServerSettingTableViewController: UITableViewController {
|
|||
optionMenu.addAction(fileAction)
|
||||
}
|
||||
|
||||
if Defaults[.gitSSHKeySource] != nil {
|
||||
if SharedDefaults[.gitSSHKeySource] != nil {
|
||||
let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in
|
||||
self.passwordStore.removeGitSSHKeys()
|
||||
Defaults[.gitSSHKeySource] = nil
|
||||
SharedDefaults[.gitSSHKeySource] = nil
|
||||
if let sshLabel = self.sshLabel {
|
||||
sshLabel.isEnabled = false
|
||||
self.checkAuthenticationMethod(method: "Password")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import AVFoundation
|
||||
import passKit
|
||||
|
||||
class OTPScannerController: QRScannerController {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
||||
@IBOutlet weak var armorPublicKeyTextView: UITextView!
|
||||
|
|
@ -92,8 +92,8 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
armorPublicKeyTextView.text = Defaults[.pgpPublicKeyArmor]
|
||||
armorPrivateKeyTextView.text = Defaults[.pgpPrivateKeyArmor]
|
||||
armorPublicKeyTextView.text = SharedDefaults[.pgpPublicKeyArmor]
|
||||
armorPrivateKeyTextView.text = SharedDefaults[.pgpPrivateKeyArmor]
|
||||
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
||||
|
||||
scanPublicKeyCell?.textLabel?.text = "Scan Public Key QR Codes"
|
||||
|
|
@ -126,7 +126,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
|||
// no
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
self.pgpPassphrase = nil
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
SharedDefaults[.isRememberPassphraseOn] = false
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
})
|
||||
// yes
|
||||
|
|
@ -135,7 +135,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
|||
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
|
||||
self.pgpPassphrase = alert.textFields?.first?.text
|
||||
Defaults[.isRememberPassphraseOn] = true
|
||||
SharedDefaults[.isRememberPassphraseOn] = true
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
class PGPKeySettingTableViewController: UITableViewController {
|
||||
|
||||
|
|
@ -19,8 +19,8 @@ class PGPKeySettingTableViewController: UITableViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
tableView.rowHeight = UITableViewAutomaticDimension
|
||||
pgpPublicKeyURLTextField.text = Defaults[.pgpPublicKeyURL]?.absoluteString
|
||||
pgpPrivateKeyURLTextField.text = Defaults[.pgpPrivateKeyURL]?.absoluteString
|
||||
pgpPublicKeyURLTextField.text = SharedDefaults[.pgpPublicKeyURL]?.absoluteString
|
||||
pgpPrivateKeyURLTextField.text = SharedDefaults[.pgpPrivateKeyURL]?.absoluteString
|
||||
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ class PGPKeySettingTableViewController: UITableViewController {
|
|||
// no
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
self.pgpPassphrase = nil
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
SharedDefaults[.isRememberPassphraseOn] = false
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
})
|
||||
// yes
|
||||
|
|
@ -60,7 +60,7 @@ class PGPKeySettingTableViewController: UITableViewController {
|
|||
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
|
||||
self.pgpPassphrase = alert.textFields?.first?.text
|
||||
Defaults[.isRememberPassphraseOn] = true
|
||||
SharedDefaults[.isRememberPassphraseOn] = true
|
||||
self.performSegue(withIdentifier: "savePGPKeySegue", sender: self)
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
import UIKit
|
||||
import FavIcon
|
||||
import SwiftyUserDefaults
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
class PasswordDetailTableViewController: UITableViewController, UIGestureRecognizerDelegate {
|
||||
var passwordEntity: PasswordEntity?
|
||||
|
|
@ -134,7 +134,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
let _ = sem.wait(timeout: DispatchTime.distantFuture)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
if SharedDefaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
|
|
@ -281,8 +281,8 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
|||
let filteredAdditionKeys = password.additionKeys.filter {
|
||||
$0.lowercased() != "username" &&
|
||||
$0.lowercased() != "password" &&
|
||||
(!$0.hasPrefix("unknown") || !Defaults[.isHideUnknownOn]) &&
|
||||
(!Password.otpKeywords.contains($0) || !Defaults[.isHideOTPOn]) }
|
||||
(!$0.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) &&
|
||||
(!Password.otpKeywords.contains($0) || !SharedDefaults[.isHideOTPOn]) }
|
||||
|
||||
if filteredAdditionKeys.count > 0 {
|
||||
section = TableSection(type: .addition, header: "additions")
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import OneTimePassword
|
||||
import passKit
|
||||
|
||||
enum PasswordEditorCellType {
|
||||
case nameCell, fillPasswordCell, passwordLengthCell, additionsCell, deletePasswordCell, scanQRCodeCell
|
||||
|
|
@ -95,7 +95,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
|||
return fillPasswordCell!
|
||||
case .passwordLengthCell:
|
||||
passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell
|
||||
let lengthSetting = Globals.passwordDefaultLength[Defaults[.passwordGeneratorFlavor]] ??
|
||||
let lengthSetting = Globals.passwordDefaultLength[SharedDefaults[.passwordGeneratorFlavor]] ??
|
||||
Globals.passwordDefaultLength["Random"]
|
||||
passwordLengthCell?.reset(title: "Length",
|
||||
minimumValue: lengthSetting?.min ?? 0,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import SwiftyUserDefaults
|
||||
import PasscodeLock
|
||||
import passKit
|
||||
|
||||
fileprivate class PasswordsTableEntry : NSObject {
|
||||
var title: String
|
||||
|
|
@ -87,7 +86,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
filteredPasswordsTableEntries.removeAll()
|
||||
var passwordEntities = [PasswordEntity]()
|
||||
var passwordAllEntities = [PasswordEntity]()
|
||||
if Defaults[.isShowFolderOn] {
|
||||
if SharedDefaults[.isShowFolderOn] {
|
||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(parent: parent)
|
||||
} else {
|
||||
passwordEntities = self.passwordStore.fetchPasswordEntityCoreData(withDir: false)
|
||||
|
|
@ -139,12 +138,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
SVProgressHUD.show(withStatus: "Sync Password Store")
|
||||
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits()
|
||||
var gitCredential: GitCredential
|
||||
if Defaults[.gitAuthenticationMethod] == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: Defaults[.gitUsername]!, controller: self))
|
||||
if SharedDefaults[.gitAuthenticationMethod] == "Password" {
|
||||
gitCredential = GitCredential(credential: GitCredential.Credential.http(userName: SharedDefaults[.gitUsername]!, controller: self))
|
||||
} else {
|
||||
gitCredential = GitCredential(
|
||||
credential: GitCredential.Credential.ssh(
|
||||
userName: Defaults[.gitUsername]!,
|
||||
userName: SharedDefaults[.gitUsername]!,
|
||||
privateKeyFile: Globals.gitSSHPrivateKeyURL,
|
||||
controller: self
|
||||
)
|
||||
|
|
@ -184,7 +183,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if Defaults[.isShowFolderOn] {
|
||||
if SharedDefaults[.isShowFolderOn] {
|
||||
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
|
||||
} else {
|
||||
searchController.searchBar.scopeButtonTitles = nil
|
||||
|
|
@ -238,7 +237,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
||||
longPressGestureRecognizer.minimumPressDuration = 0.6
|
||||
if Defaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
|
||||
if SharedDefaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||
|
||||
let entry = getPasswordEntry(by: indexPath)
|
||||
|
|
@ -296,7 +295,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
}
|
||||
|
||||
func backAction(_ sender: Any?) {
|
||||
guard Defaults[.isShowFolderOn] else { return }
|
||||
guard SharedDefaults[.isShowFolderOn] else { return }
|
||||
var anim: CATransition? = transitionFromLeft
|
||||
if parentPasswordEntity == nil {
|
||||
anim = nil
|
||||
|
|
@ -363,7 +362,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
|||
// bring back
|
||||
SVProgressHUD.show(withStatus: "Decrypting")
|
||||
}
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
if SharedDefaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = passphrase
|
||||
}
|
||||
return passphrase
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
|
||||
class RawPasswordViewController: UIViewController {
|
||||
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
class SSHKeySettingTableViewController: UITableViewController {
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ class SSHKeySettingTableViewController: UITableViewController {
|
|||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
privateKeyURLTextField.text = Defaults[.gitSSHPrivateKeyURL]?.absoluteString
|
||||
privateKeyURLTextField.text = SharedDefaults[.gitSSHPrivateKeyURL]?.absoluteString
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -27,14 +27,14 @@ class SSHKeySettingTableViewController: UITableViewController {
|
|||
return
|
||||
}
|
||||
|
||||
Defaults[.gitSSHPrivateKeyURL] = privateKeyURL
|
||||
SharedDefaults[.gitSSHPrivateKeyURL] = privateKeyURL
|
||||
|
||||
do {
|
||||
try Data(contentsOf: privateKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPrivateKeyPath), options: .atomic)
|
||||
} catch {
|
||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||
}
|
||||
Defaults[.gitSSHKeySource] = "url"
|
||||
SharedDefaults[.gitSSHKeySource] = "url"
|
||||
self.navigationController!.popViewController(animated: true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@
|
|||
import UIKit
|
||||
import SVProgressHUD
|
||||
import CoreData
|
||||
import SwiftyUserDefaults
|
||||
import PasscodeLock
|
||||
import LocalAuthentication
|
||||
import passKit
|
||||
|
||||
class SettingsTableViewController: UITableViewController {
|
||||
|
||||
|
|
@ -27,26 +27,27 @@ class SettingsTableViewController: UITableViewController {
|
|||
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
||||
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
|
||||
let passwordStore = PasswordStore.shared
|
||||
var passcodeLockConfig = PasscodeLockConfiguration.shared
|
||||
|
||||
@IBAction func cancelPGPKey(segue: UIStoryboardSegue) {
|
||||
}
|
||||
|
||||
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
|
||||
if let controller = segue.source as? PGPKeySettingTableViewController {
|
||||
Defaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
|
||||
Defaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!)
|
||||
SharedDefaults[.pgpPublicKeyURL] = URL(string: controller.pgpPublicKeyURLTextField.text!)
|
||||
if SharedDefaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
}
|
||||
Defaults[.pgpKeySource] = "url"
|
||||
SharedDefaults[.pgpKeySource] = "url"
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
||||
DispatchQueue.global(qos: .userInitiated).async { [unowned self] in
|
||||
do {
|
||||
try self.passwordStore.initPGPKey(from: Defaults[.pgpPublicKeyURL]!, keyType: .public)
|
||||
try self.passwordStore.initPGPKey(from: Defaults[.pgpPrivateKeyURL]!, keyType: .secret)
|
||||
try self.passwordStore.initPGPKey(from: SharedDefaults[.pgpPublicKeyURL]!, keyType: .public)
|
||||
try self.passwordStore.initPGPKey(from: SharedDefaults[.pgpPrivateKeyURL]!, keyType: .secret)
|
||||
DispatchQueue.main.async {
|
||||
self.pgpKeyTableViewCell.detailTextLabel?.text = self.passwordStore.pgpKeyID
|
||||
SVProgressHUD.showSuccess(withStatus: "Success")
|
||||
|
|
@ -62,13 +63,13 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
|
||||
Defaults[.pgpKeySource] = "armor"
|
||||
if Defaults[.isRememberPassphraseOn] {
|
||||
SharedDefaults[.pgpKeySource] = "armor"
|
||||
if SharedDefaults[.isRememberPassphraseOn] {
|
||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||
}
|
||||
|
||||
Defaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
||||
Defaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
|
||||
SharedDefaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
||||
SharedDefaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
|
|
@ -94,7 +95,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
|
||||
private func saveImportedPGPKey() {
|
||||
// load keys
|
||||
Defaults[.pgpKeySource] = "file"
|
||||
SharedDefaults[.pgpKeySource] = "file"
|
||||
|
||||
SVProgressHUD.setDefaultMaskType(.black)
|
||||
SVProgressHUD.setDefaultStyle(.light)
|
||||
|
|
@ -120,7 +121,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
|
@ -138,7 +139,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(SettingsTableViewController.actOnPasswordStoreErasedNotification), name: .passwordStoreErased, object: nil)
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]?.host
|
||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
|
||||
touchIDTableViewCell.accessoryView = touchIDSwitch
|
||||
setPGPKeyTableViewCellDetailText()
|
||||
setPasswordRepositoryTableViewCellDetailText()
|
||||
|
|
@ -170,13 +171,13 @@ class SettingsTableViewController: UITableViewController {
|
|||
private func setPasscodeLockTouchIDCells() {
|
||||
if PasscodeLockRepository().hasPasscode {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
||||
Globals.passcodeConfiguration.isTouchIDAllowed = true
|
||||
touchIDSwitch.isOn = Defaults[.isTouchIDOn]
|
||||
passcodeLockConfig.isTouchIDAllowed = true
|
||||
touchIDSwitch.isOn = SharedDefaults[.isTouchIDOn]
|
||||
} else {
|
||||
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
||||
Globals.passcodeConfiguration.isTouchIDAllowed = false
|
||||
Defaults[.isTouchIDOn] = false
|
||||
touchIDSwitch.isOn = Defaults[.isTouchIDOn]
|
||||
passcodeLockConfig.isTouchIDAllowed = false
|
||||
SharedDefaults[.isTouchIDOn] = false
|
||||
touchIDSwitch.isOn = SharedDefaults[.isTouchIDOn]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,10 +190,10 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
private func setPasswordRepositoryTableViewCellDetailText() {
|
||||
if Defaults[.gitURL] == nil {
|
||||
if SharedDefaults[.gitURL] == nil {
|
||||
passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set"
|
||||
} else {
|
||||
passwordRepositoryTableViewCell.detailTextLabel?.text = Defaults[.gitURL]!.host
|
||||
passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]!.host
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,12 +203,12 @@ class SettingsTableViewController: UITableViewController {
|
|||
setPasscodeLockTouchIDCells()
|
||||
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: passcodeLockConfig)
|
||||
}
|
||||
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
|
||||
if Defaults[.passcodeKey] != nil{
|
||||
if SharedDefaults[.passcodeKey] != nil{
|
||||
showPasscodeActionSheet()
|
||||
} else {
|
||||
setPasscodeLock()
|
||||
|
|
@ -219,17 +220,17 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
func touchIDSwitchAction(uiSwitch: UISwitch) {
|
||||
if !Globals.passcodeConfiguration.isTouchIDAllowed || !isTouchIDEnabled() {
|
||||
if !passcodeLockConfig.isTouchIDAllowed || !isTouchIDEnabled() {
|
||||
// switch off
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
|
||||
uiSwitch.isOn = Defaults[.isTouchIDOn] // false
|
||||
uiSwitch.isOn = SharedDefaults[.isTouchIDOn] // false
|
||||
Utils.alert(title: "Notice", message: "Please enable Touch ID and set the passcode lock first.", controller: self, completion: nil)
|
||||
}
|
||||
} else {
|
||||
Defaults[.isTouchIDOn] = uiSwitch.isOn
|
||||
SharedDefaults[.isTouchIDOn] = uiSwitch.isOn
|
||||
}
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: passcodeLockConfig)
|
||||
}
|
||||
|
||||
func showPGPKeyActionSheet() {
|
||||
|
|
@ -238,11 +239,11 @@ class SettingsTableViewController: UITableViewController {
|
|||
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
||||
var fileActionTitle = "Use Imported Keys"
|
||||
|
||||
if Defaults[.pgpKeySource] == "url" {
|
||||
if SharedDefaults[.pgpKeySource] == "url" {
|
||||
urlActionTitle = "✓ \(urlActionTitle)"
|
||||
} else if Defaults[.pgpKeySource] == "armor" {
|
||||
} else if SharedDefaults[.pgpKeySource] == "armor" {
|
||||
armorActionTitle = "✓ \(armorActionTitle)"
|
||||
} else if Defaults[.pgpKeySource] == "file" {
|
||||
} else if SharedDefaults[.pgpKeySource] == "file" {
|
||||
fileActionTitle = "✓ \(fileActionTitle)"
|
||||
}
|
||||
let urlAction = UIAlertAction(title: urlActionTitle, style: .default) { _ in
|
||||
|
|
@ -262,7 +263,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
// no
|
||||
savePassphraseAlert.addAction(UIAlertAction(title: "No", style: UIAlertActionStyle.default) { _ in
|
||||
self.passwordStore.pgpKeyPassphrase = nil
|
||||
Defaults[.isRememberPassphraseOn] = false
|
||||
SharedDefaults[.isRememberPassphraseOn] = false
|
||||
self.saveImportedPGPKey()
|
||||
})
|
||||
// yes
|
||||
|
|
@ -271,7 +272,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
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
|
||||
self.passwordStore.pgpKeyPassphrase = alert.textFields?.first?.text
|
||||
Defaults[.isRememberPassphraseOn] = true
|
||||
SharedDefaults[.isRememberPassphraseOn] = true
|
||||
self.saveImportedPGPKey()
|
||||
}))
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
|
|
@ -293,7 +294,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
|
||||
if Defaults[.pgpKeySource] != nil {
|
||||
if SharedDefaults[.pgpKeySource] != nil {
|
||||
let deleteAction = UIAlertAction(title: "Remove PGP Keys", style: .destructive) { _ in
|
||||
self.passwordStore.removePGPKeys()
|
||||
self.pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
||||
|
|
@ -307,15 +308,15 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
func showPasscodeActionSheet() {
|
||||
let passcodeChangeViewController = PasscodeLockViewController(state: .change, configuration: Globals.passcodeConfiguration)
|
||||
let passcodeRemoveViewController = PasscodeLockViewController(state: .remove, configuration: Globals.passcodeConfiguration)
|
||||
let passcodeChangeViewController = PasscodeLockViewController(state: .change, configuration: passcodeLockConfig)
|
||||
let passcodeRemoveViewController = PasscodeLockViewController(state: .remove, configuration: passcodeLockConfig)
|
||||
|
||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
|
||||
passcodeRemoveViewController.successCallback = { _ in
|
||||
self?.setPasscodeLockTouchIDCells()
|
||||
let appDelegate = UIApplication.shared.delegate as! AppDelegate
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: Globals.passcodeConfiguration)
|
||||
appDelegate.passcodeLockPresenter = PasscodeLockPresenter(mainWindow: appDelegate.window, configuration: (self?.passcodeLockConfig)!)
|
||||
}
|
||||
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
||||
}
|
||||
|
|
@ -334,7 +335,7 @@ class SettingsTableViewController: UITableViewController {
|
|||
}
|
||||
|
||||
func setPasscodeLock() {
|
||||
let passcodeSetViewController = PasscodeLockViewController(state: .set, configuration: Globals.passcodeConfiguration)
|
||||
let passcodeSetViewController = PasscodeLockViewController(state: .set, configuration: passcodeLockConfig)
|
||||
passcodeSetViewController.successCallback = { _ in
|
||||
self.setPasscodeLockTouchIDCells()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
//
|
||||
// DefaultKeys.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
|
||||
var Defaults = UserDefaults(suiteName: Globals.groupIdentifier)!
|
||||
|
||||
extension DefaultsKeys {
|
||||
static let pgpKeySource = DefaultsKey<String?>("pgpKeySource")
|
||||
static let pgpPublicKeyURL = DefaultsKey<URL?>("pgpPublicKeyURL")
|
||||
static let pgpPrivateKeyURL = DefaultsKey<URL?>("pgpPrivateKeyURL")
|
||||
|
||||
static let pgpPublicKeyArmor = DefaultsKey<String?>("pgpPublicKeyArmor")
|
||||
static let pgpPrivateKeyArmor = DefaultsKey<String?>("pgpPrivateKeyArmor")
|
||||
|
||||
static let gitURL = DefaultsKey<URL?>("gitURL")
|
||||
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
||||
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
||||
static let gitSSHPrivateKeyURL = DefaultsKey<URL?>("gitSSHPrivateKeyURL")
|
||||
static let gitSSHKeySource = DefaultsKey<String?>("gitSSHKeySource")
|
||||
static let gitSSHPrivateKeyArmor = DefaultsKey<String?>("gitSSHPrivateKeyArmor")
|
||||
static let gitSignatureName = DefaultsKey<String?>("gitSignatureName")
|
||||
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
|
||||
|
||||
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
|
||||
|
||||
static let isTouchIDOn = DefaultsKey<Bool>("isTouchIDOn")
|
||||
static let passcodeKey = DefaultsKey<String?>("passcodeKey")
|
||||
|
||||
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
|
||||
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
|
||||
static let isRememberPassphraseOn = DefaultsKey<Bool>("isRememberPassphraseOn")
|
||||
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
|
||||
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")
|
||||
|
||||
static let encryptInArmored = DefaultsKey<Bool>("encryptInArmored")
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
//
|
||||
// Globals.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 21/1/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
class Globals {
|
||||
|
||||
// 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),
|
||||
"Apple": (min: 15, max: 15, def: 15)]
|
||||
|
||||
static let gitSignatureDefaultName = "Pass for iOS"
|
||||
static let gitSignatureDefaultEmail = "user@passforios"
|
||||
|
||||
static let passwordDots = "••••••••••••"
|
||||
static let oneTimePasswordDots = "••••••"
|
||||
static let passwordFonts = "Menlo"
|
||||
|
||||
// UI related
|
||||
static let red = UIColor(red:1.00, green:0.23, blue:0.19, alpha:1.0)
|
||||
static let blue = UIColor(red:0.00, green:0.48, blue:1.00, alpha:1.0)
|
||||
static let tableCellButtonSize = CGFloat(20.0)
|
||||
|
||||
private init() { }
|
||||
}
|
||||
|
||||
extension Bundle {
|
||||
var releaseVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleShortVersionString"] as? String
|
||||
}
|
||||
var buildVersionNumber: String? {
|
||||
return infoDictionary?["CFBundleVersion"] as? String
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// NotificationNames.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Yishi Lin on 17/3/17.
|
||||
// Copyright © 2017 Yishi Lin, Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Notification.Name {
|
||||
static let passwordStoreUpdated = Notification.Name("passwordStoreUpdated")
|
||||
static let passwordStoreErased = Notification.Name("passwordStoreErased")
|
||||
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
|
||||
static let passwordSearch = Notification.Name("passwordSearch")
|
||||
|
||||
static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
|
||||
static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@
|
|||
#ifndef Objective_CBridgingHeader_h
|
||||
#define Objective_CBridgingHeader_h
|
||||
|
||||
#import <ObjectivePGP/ObjectivePGP.h>
|
||||
// #import <ObjectivePGP/ObjectivePGP.h>
|
||||
#import <ObjectiveGit/ObjectiveGit.h>
|
||||
|
||||
#endif /* Objective_CBridgingHeader_h */
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// UIViewControllerExtionsion.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Yishi Lin on 5/4/17.
|
||||
// Copyright © 2017 Yishi Lin. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
private var kAssociationKeyNextField: UInt8 = 0
|
||||
|
||||
extension UITextField {
|
||||
@IBOutlet var nextField: UITextField? {
|
||||
get {
|
||||
return objc_getAssociatedObject(self, &kAssociationKeyNextField) as? UITextField
|
||||
}
|
||||
set(newField) {
|
||||
objc_setAssociatedObject(self, &kAssociationKeyNextField, newField, .OBJC_ASSOCIATION_RETAIN)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension UIViewController {
|
||||
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||
if textField.nextField != nil {
|
||||
textField.nextField?.becomeFirstResponder()
|
||||
} else {
|
||||
textField.resignFirstResponder()
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
@ -1,227 +0,0 @@
|
|||
//
|
||||
// Utils.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 8/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
import KeychainAccess
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
|
||||
class Utils {
|
||||
static func removeFileIfExists(atPath path: String) {
|
||||
let fm = FileManager.default
|
||||
do {
|
||||
if fm.fileExists(atPath: path) {
|
||||
try fm.removeItem(atPath: path)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
static func removeFileIfExists(at url: URL) {
|
||||
removeFileIfExists(atPath: url.path)
|
||||
}
|
||||
|
||||
static func getLastSyncedTimeString() -> String {
|
||||
guard let lastSyncedTime = Defaults[.lastSyncedTime] else {
|
||||
return "Oops! Sync again?"
|
||||
}
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateStyle = .medium
|
||||
formatter.timeStyle = .short
|
||||
return formatter.string(from: lastSyncedTime)
|
||||
}
|
||||
|
||||
static func generatePassword(length: Int) -> String{
|
||||
switch Defaults[.passwordGeneratorFlavor] {
|
||||
case "Random":
|
||||
return randomString(length: length)
|
||||
case "Apple":
|
||||
return Keychain.generatePassword()
|
||||
default:
|
||||
return randomString(length: length)
|
||||
}
|
||||
}
|
||||
|
||||
static func randomString(length: Int) -> String {
|
||||
|
||||
let letters : NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*_+-="
|
||||
let len = UInt32(letters.length)
|
||||
|
||||
var randomString = ""
|
||||
|
||||
for _ in 0 ..< length {
|
||||
let rand = arc4random_uniform(len)
|
||||
var nextChar = letters.character(at: Int(rand))
|
||||
randomString += NSString(characters: &nextChar, length: 1) as String
|
||||
}
|
||||
|
||||
return randomString
|
||||
}
|
||||
|
||||
static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))
|
||||
controller.present(alert, animated: true, completion: completion)
|
||||
}
|
||||
|
||||
static func getPasswordFromKeychain(name: String) -> String? {
|
||||
let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
|
||||
do {
|
||||
return try keychain.getString(name)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
static func addPasswordToKeychain(name: String, password: String?) {
|
||||
let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
|
||||
keychain[name] = password
|
||||
}
|
||||
static func removeKeychain(name: String) {
|
||||
let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
|
||||
do {
|
||||
try keychain.remove(name)
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
static func removeAllKeychain() {
|
||||
let keychain = Keychain(service: Bundle.main.bundleIdentifier!)
|
||||
do {
|
||||
try keychain.removeAll()
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
static func copyToPasteboard(textToCopy: String?, expirationTime: Double = 45) {
|
||||
guard textToCopy != nil else {
|
||||
return
|
||||
}
|
||||
UIPasteboard.general.string = textToCopy
|
||||
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime.now() + expirationTime) {
|
||||
let pasteboardString: String? = UIPasteboard.general.string
|
||||
if textToCopy == pasteboardString {
|
||||
UIPasteboard.general.string = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
static func attributedPassword(plainPassword: String) -> NSAttributedString{
|
||||
let attributedPassword = NSMutableAttributedString.init(string: plainPassword)
|
||||
// draw all digits in the password into red
|
||||
// draw all punctuation characters in the password into blue
|
||||
for (index, element) in plainPassword.unicodeScalars.enumerated() {
|
||||
if NSCharacterSet.decimalDigits.contains(element) {
|
||||
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1))
|
||||
} else if !NSCharacterSet.letters.contains(element) {
|
||||
attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1))
|
||||
}
|
||||
}
|
||||
return attributedPassword
|
||||
}
|
||||
static func initDefaultKeys() {
|
||||
if Defaults[.passwordGeneratorFlavor] == "" {
|
||||
Defaults[.passwordGeneratorFlavor] = "Random"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
||||
extension FileManager {
|
||||
|
||||
/// This method calculates the accumulated size of a directory on the volume in bytes.
|
||||
///
|
||||
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
|
||||
/// accumulating the overall sum on the way. The resulting value is roughly equivalent with the amount of bytes
|
||||
/// that would become available on the volume if the directory would be deleted.
|
||||
///
|
||||
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
||||
/// directories, hard links, ...).
|
||||
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
||||
|
||||
// We'll sum up content size here:
|
||||
var accumulatedSize = UInt64(0)
|
||||
|
||||
// prefetching some properties during traversal will speed up things a bit.
|
||||
let prefetchedProperties = [
|
||||
URLResourceKey.isRegularFileKey,
|
||||
URLResourceKey.fileAllocatedSizeKey,
|
||||
URLResourceKey.totalFileAllocatedSizeKey,
|
||||
]
|
||||
|
||||
// The error handler simply signals errors to outside code.
|
||||
var errorDidOccur: Error?
|
||||
let errorHandler: (URL, Error) -> Bool = { _, error in
|
||||
errorDidOccur = error
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// We have to enumerate all directory contents, including subdirectories.
|
||||
let enumerator = self.enumerator(at: directoryURL,
|
||||
includingPropertiesForKeys: prefetchedProperties,
|
||||
options: FileManager.DirectoryEnumerationOptions(),
|
||||
errorHandler: errorHandler)
|
||||
precondition(enumerator != nil)
|
||||
|
||||
// Start the traversal:
|
||||
for item in enumerator! {
|
||||
let contentItemURL = item as! NSURL
|
||||
|
||||
// Bail out on errors from the errorHandler.
|
||||
if let error = errorDidOccur { throw error }
|
||||
|
||||
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
|
||||
var value: AnyObject?
|
||||
try contentItemURL.getResourceValue(&value, forKey: key)
|
||||
return value as? NSNumber
|
||||
}
|
||||
|
||||
// Get the type of this item, making sure we only sum up sizes of regular files.
|
||||
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
guard isRegularFile.boolValue else {
|
||||
continue
|
||||
}
|
||||
|
||||
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
|
||||
// This includes metadata, compression (on file system level) and block size.
|
||||
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
|
||||
|
||||
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
|
||||
// This value should always be available.
|
||||
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
|
||||
|
||||
guard let size = fileSize else {
|
||||
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
|
||||
}
|
||||
|
||||
// We're good, add up the value.
|
||||
accumulatedSize += size.uint64Value
|
||||
}
|
||||
|
||||
// Bail out on errors from the errorHandler.
|
||||
if let error = errorDidOccur { throw error }
|
||||
|
||||
// We finally got it.
|
||||
return accumulatedSize
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func stringByAddingPercentEncodingForRFC3986() -> String? {
|
||||
let unreserved = "-._~/?"
|
||||
var allowed = CharacterSet.alphanumerics
|
||||
allowed.insert(charactersIn: unreserved)
|
||||
return addingPercentEncoding(withAllowedCharacters: allowed)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +0,0 @@
|
|||
//
|
||||
// GitCredential.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 30/4/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftyUserDefaults
|
||||
import ObjectiveGit
|
||||
import SVProgressHUD
|
||||
|
||||
struct GitCredential {
|
||||
var credential: Credential
|
||||
|
||||
enum Credential {
|
||||
case http(userName: String, controller: UIViewController)
|
||||
case ssh(userName: String, privateKeyFile: URL, controller: UIViewController)
|
||||
}
|
||||
|
||||
init(credential: Credential) {
|
||||
self.credential = credential
|
||||
}
|
||||
|
||||
func credentialProvider() throws -> GTCredentialProvider {
|
||||
var attempts = 0
|
||||
var lastPassword: String? = nil
|
||||
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||
var credential: GTCredential? = nil
|
||||
|
||||
switch self.credential {
|
||||
case let .http(userName, controller):
|
||||
var newPassword = Utils.getPasswordFromKeychain(name: "gitPassword")
|
||||
if newPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = self.requestGitPassword(controller, lastPassword) {
|
||||
newPassword = requestedPassword
|
||||
Utils.addPasswordToKeychain(name: "gitPassword", password: newPassword)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
attempts += 1
|
||||
lastPassword = newPassword
|
||||
credential = try? GTCredential(userName: userName, password: newPassword!)
|
||||
case let .ssh(userName, privateKeyFile, controller):
|
||||
var newPassword = Utils.getPasswordFromKeychain(name: "gitSSHKeyPassphrase")
|
||||
if newPassword == nil || attempts != 0 {
|
||||
if let requestedPassword = self.requestGitPassword(controller, lastPassword) {
|
||||
newPassword = requestedPassword
|
||||
Utils.addPasswordToKeychain(name: "gitSSHKeyPassphrase", password: newPassword)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
attempts += 1
|
||||
lastPassword = newPassword
|
||||
credential = try? GTCredential(userName: userName, publicKeyURL: nil, privateKeyURL: privateKeyFile, passphrase: newPassword!)
|
||||
print(privateKeyFile)
|
||||
}
|
||||
return credential
|
||||
}
|
||||
}
|
||||
|
||||
func delete() {
|
||||
switch credential {
|
||||
case .http:
|
||||
Utils.removeKeychain(name: "gitPassword")
|
||||
case .ssh:
|
||||
Utils.removeKeychain(name: "gitSSHKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
private func requestGitPassword(_ controller: UIViewController, _ lastPassword: String?) -> String? {
|
||||
let sem = DispatchSemaphore(value: 0)
|
||||
var password: String?
|
||||
var message = ""
|
||||
switch credential {
|
||||
case .http:
|
||||
message = "Please fill in the password of your Git account."
|
||||
case .ssh:
|
||||
message = "Please fill in the password of your SSH key."
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
SVProgressHUD.dismiss()
|
||||
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||
alert.addTextField(configurationHandler: {(textField: UITextField!) in
|
||||
textField.text = lastPassword ?? ""
|
||||
textField.isSecureTextEntry = true
|
||||
})
|
||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
|
||||
password = alert.textFields!.first!.text
|
||||
sem.signal()
|
||||
}))
|
||||
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
|
||||
password = nil
|
||||
sem.signal()
|
||||
})
|
||||
controller.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
let _ = sem.wait(timeout: .distantFuture)
|
||||
return password
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -8,13 +8,15 @@
|
|||
|
||||
import Foundation
|
||||
import PasscodeLock
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
struct PasscodeLockConfiguration: PasscodeLockConfigurationType {
|
||||
|
||||
static let shared = PasscodeLockConfiguration()
|
||||
|
||||
let repository: PasscodeRepositoryType
|
||||
let passcodeLength = 4
|
||||
var isTouchIDAllowed = Defaults[.isTouchIDOn]
|
||||
var isTouchIDAllowed = SharedDefaults[.isTouchIDOn]
|
||||
let shouldRequestTouchIDImmediately = true
|
||||
let maximumInccorectPasscodeAttempts = 3
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
import PasscodeLock
|
||||
import SwiftyUserDefaults
|
||||
import passKit
|
||||
|
||||
public class PasscodeLockRepository: PasscodeRepositoryType {
|
||||
private let passcodeKey = "passcode.lock.passcode"
|
||||
|
|
@ -23,11 +23,11 @@ public class PasscodeLockRepository: PasscodeRepositoryType {
|
|||
}
|
||||
|
||||
private var passcode: String? {
|
||||
return Defaults[.passcodeKey]
|
||||
return SharedDefaults[.passcodeKey]
|
||||
}
|
||||
|
||||
public func save(passcode: String) {
|
||||
Defaults[.passcodeKey] = passcode
|
||||
SharedDefaults[.passcodeKey] = passcode
|
||||
}
|
||||
|
||||
public func check(passcode: String) -> Bool {
|
||||
|
|
@ -35,6 +35,6 @@ public class PasscodeLockRepository: PasscodeRepositoryType {
|
|||
}
|
||||
|
||||
public func delete() {
|
||||
Defaults[.passcodeKey] = nil
|
||||
SharedDefaults[.passcodeKey] = nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,356 +0,0 @@
|
|||
//
|
||||
// Password.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 2/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
import OneTimePassword
|
||||
import Base32
|
||||
|
||||
struct AdditionField {
|
||||
var title: String
|
||||
var content: String
|
||||
}
|
||||
|
||||
enum PasswordChange: Int {
|
||||
case path = 0x01
|
||||
case content = 0x02
|
||||
case none = 0x00
|
||||
}
|
||||
|
||||
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]()
|
||||
var changed: Int = 0
|
||||
var plainText = ""
|
||||
|
||||
private var firstLineIsOTPField = false
|
||||
private var otpToken: Token?
|
||||
|
||||
enum OtpType {
|
||||
case totp, hotp, none
|
||||
}
|
||||
|
||||
var otpType: OtpType {
|
||||
get {
|
||||
guard let token = self.otpToken else {
|
||||
return OtpType.none
|
||||
}
|
||||
switch token.generator.factor {
|
||||
case .counter:
|
||||
return OtpType.hotp
|
||||
case .timer:
|
||||
return OtpType.totp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init(name: String, url: URL?, plainText: String) {
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
|
||||
func updatePassword(name: String, url: URL?, plainText: String) {
|
||||
if self.plainText != plainText || self.url != url {
|
||||
if self.plainText != plainText {
|
||||
changed = changed|PasswordChange.content.rawValue
|
||||
}
|
||||
if self.url != url {
|
||||
changed = changed|PasswordChange.path.rawValue
|
||||
}
|
||||
self.initEverything(name: name, url: url, plainText: plainText)
|
||||
}
|
||||
}
|
||||
|
||||
private func initEverything(name: String, url: URL?, plainText: String) {
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.plainText = plainText
|
||||
self.additions.removeAll()
|
||||
self.additionKeys.removeAll()
|
||||
|
||||
// get password and additional fields
|
||||
let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
|
||||
$0 == "\n" || $0 == "\r\n"
|
||||
}.map(String.init)
|
||||
self.password = plainTextSplit.first ?? ""
|
||||
if plainTextSplit.count == 2 {
|
||||
(self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1])
|
||||
}
|
||||
|
||||
// check whether the first line of the plainText looks like an otp entry
|
||||
let (key, value) = Password.getKeyValuePair(from: self.password)
|
||||
if Password.otpKeywords.contains(key ?? "") {
|
||||
firstLineIsOTPField = true
|
||||
self.additions[key!] = value
|
||||
self.additionKeys.insert(key!, at: 0)
|
||||
} else {
|
||||
firstLineIsOTPField = false
|
||||
}
|
||||
|
||||
// construct the otp token
|
||||
self.updateOtpToken()
|
||||
}
|
||||
|
||||
func getUsername() -> String? {
|
||||
return getAdditionValue(withKey: "Username") ?? getAdditionValue(withKey: "username")
|
||||
}
|
||||
|
||||
func getURLString() -> String? {
|
||||
return getAdditionValue(withKey: "URL") ?? getAdditionValue(withKey: "url") ?? getAdditionValue(withKey: "Url")
|
||||
}
|
||||
|
||||
// return a key-value pair from the line
|
||||
// key might be nil, if there is no ":" in the line
|
||||
static private func getKeyValuePair(from line: String) -> (key: String?, value: String) {
|
||||
let items = line.components(separatedBy: ": ").map{String($0).trimmingCharacters(in: .whitespaces)}
|
||||
var key : String? = nil
|
||||
var value = ""
|
||||
if items.count == 1 || (items[0].isEmpty && items[1].isEmpty) {
|
||||
// no ": " found, or empty on both sides of ": "
|
||||
value = line
|
||||
// otpauth special case
|
||||
if value.hasPrefix("otpauth://") {
|
||||
key = "otpauth"
|
||||
}
|
||||
} else {
|
||||
if !items[0].isEmpty {
|
||||
key = items[0]
|
||||
}
|
||||
value = items[1]
|
||||
}
|
||||
return (key, value)
|
||||
}
|
||||
|
||||
static private func getAdditionFields(from additionFieldsPlainText: String) -> ([String: String], [String]){
|
||||
var additions = [String: String]()
|
||||
var additionKeys = [String]()
|
||||
var unknownIndex = 0
|
||||
|
||||
additionFieldsPlainText.enumerateLines() { line, _ in
|
||||
if line == "" {
|
||||
return
|
||||
}
|
||||
var (key, value) = getKeyValuePair(from: line)
|
||||
if key == nil {
|
||||
unknownIndex += 1
|
||||
key = "unknown \(unknownIndex)"
|
||||
}
|
||||
additions[key!] = value
|
||||
additionKeys.append(key!)
|
||||
}
|
||||
|
||||
return (additions, additionKeys)
|
||||
}
|
||||
|
||||
func getAdditionsPlainText() -> String {
|
||||
// lines starting from the second
|
||||
let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) {
|
||||
$0 == "\n" || $0 == "\r\n"
|
||||
}.map(String.init)
|
||||
if plainTextSplit.count == 1 {
|
||||
return ""
|
||||
} else {
|
||||
return plainTextSplit[1]
|
||||
}
|
||||
}
|
||||
|
||||
private func getPlainText() -> String {
|
||||
return self.plainText
|
||||
}
|
||||
|
||||
func getPlainData() -> Data {
|
||||
return getPlainText().data(using: .utf8)!
|
||||
}
|
||||
|
||||
private func getAdditionValue(withKey key: String) -> String? {
|
||||
return self.additions[key]
|
||||
}
|
||||
|
||||
/*
|
||||
Set otpType and otpToken, if we are able to construct a valid token.
|
||||
|
||||
Example of TOTP otpauth
|
||||
(Key Uri Format: https://github.com/google/google-authenticator/wiki/Key-Uri-Format)
|
||||
otpauth://totp/totp-secret?secret=AAAAAAAAAAAAAAAA&issuer=totp-secret
|
||||
|
||||
Example of TOTP fields [Legacy, lower priority]
|
||||
otp_secret: secretsecretsecretsecretsecretsecret
|
||||
otp_type: totp
|
||||
otp_algorithm: sha1 (default: sha1, optional)
|
||||
otp_period: 30 (default: 30, optional)
|
||||
otp_digits: 6 (default: 6, optional)
|
||||
|
||||
Example of HOTP fields [Legacy, lower priority]
|
||||
otp_secret: secretsecretsecretsecretsecretsecret
|
||||
otp_type: hotp
|
||||
otp_counter: 1
|
||||
otp_digits: 6 (default: 6, optional)
|
||||
|
||||
*/
|
||||
private func updateOtpToken() {
|
||||
self.otpToken = nil
|
||||
|
||||
// get otpauth, if we are able to generate a token, return
|
||||
if var otpauthString = getAdditionValue(withKey: "otpauth") {
|
||||
if !otpauthString.hasPrefix("otpauth:") {
|
||||
otpauthString = "otpauth:\(otpauthString)"
|
||||
}
|
||||
if let otpauthUrl = URL(string: otpauthString),
|
||||
let token = Token(url: otpauthUrl) {
|
||||
self.otpToken = token
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// get secret data
|
||||
guard let secretString = getAdditionValue(withKey: "otp_secret"),
|
||||
let secretData = MF_Base32Codec.data(fromBase32String: secretString),
|
||||
!secretData.isEmpty else {
|
||||
// print("Missing / Invalid otp secret")
|
||||
return
|
||||
}
|
||||
|
||||
// get type
|
||||
guard let type = getAdditionValue(withKey: "otp_type")?.lowercased(),
|
||||
(type == "totp" || type == "hotp") else {
|
||||
// print("Missing / Invalid otp type")
|
||||
return
|
||||
}
|
||||
|
||||
// get algorithm (optional)
|
||||
var algorithm = Generator.Algorithm.sha1
|
||||
if let algoString = getAdditionValue(withKey: "otp_algorithm") {
|
||||
switch algoString.lowercased() {
|
||||
case "sha256":
|
||||
algorithm = .sha256
|
||||
case "sha512":
|
||||
algorithm = .sha512
|
||||
default:
|
||||
algorithm = .sha1
|
||||
}
|
||||
}
|
||||
|
||||
// construct the token
|
||||
if type == "totp" {
|
||||
// HOTP
|
||||
// default: 6 digits, 30 seconds
|
||||
guard let digits = Int(getAdditionValue(withKey: "otp_digits") ?? "6"),
|
||||
let period = Double(getAdditionValue(withKey: "otp_period") ?? "30.0") else {
|
||||
let alertMessage = "Invalid otp_digits or otp_period."
|
||||
print(alertMessage)
|
||||
return
|
||||
}
|
||||
guard let generator = Generator(
|
||||
factor: .timer(period: period),
|
||||
secret: secretData,
|
||||
algorithm: algorithm,
|
||||
digits: digits) else {
|
||||
let alertMessage = "Invalid OTP generator parameters."
|
||||
print(alertMessage)
|
||||
return
|
||||
}
|
||||
self.otpToken = Token(name: self.name, issuer: "", generator: generator)
|
||||
} else {
|
||||
// HOTP
|
||||
// default: 6 digits
|
||||
guard let digits = Int(getAdditionValue(withKey: "otp_digits") ?? "6"),
|
||||
let counter = UInt64(getAdditionValue(withKey: "otp_counter") ?? "") else {
|
||||
let alertMessage = "Invalid otp_digits or otp_counter."
|
||||
print(alertMessage)
|
||||
return
|
||||
}
|
||||
guard let generator = Generator(
|
||||
factor: .counter(counter),
|
||||
secret: secretData,
|
||||
algorithm: algorithm,
|
||||
digits: digits) else {
|
||||
let alertMessage = "Invalid OTP generator parameters."
|
||||
print(alertMessage)
|
||||
return
|
||||
}
|
||||
self.otpToken = Token(name: self.name, issuer: "", generator: generator)
|
||||
}
|
||||
}
|
||||
|
||||
// return the description and the password strings
|
||||
func getOtpStrings() -> (description: String, otp: String)? {
|
||||
guard let token = self.otpToken else {
|
||||
return nil
|
||||
}
|
||||
var description : String
|
||||
switch token.generator.factor {
|
||||
case .counter:
|
||||
// htop
|
||||
description = "HMAC-based"
|
||||
case .timer(let period):
|
||||
// totp
|
||||
let timeSinceEpoch = Date().timeIntervalSince1970
|
||||
let validTime = Int(period - timeSinceEpoch.truncatingRemainder(dividingBy: period))
|
||||
description = "time-based (expiring in \(validTime)s)"
|
||||
}
|
||||
let otp = self.otpToken?.currentPassword ?? "error"
|
||||
return (description, otp)
|
||||
}
|
||||
|
||||
// return the password strings
|
||||
func getOtp() -> String? {
|
||||
if let otp = self.otpToken?.currentPassword {
|
||||
return otp
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// return the password strings
|
||||
// it is guaranteed that it is a HOTP password when we call this
|
||||
func getNextHotp() -> String? {
|
||||
// increase the counter
|
||||
otpToken = otpToken?.updatedToken()
|
||||
|
||||
// replace old HOTP settings with the new otpauth
|
||||
var newOtpauth = try! otpToken?.toURL().absoluteString
|
||||
newOtpauth?.append("&secret=")
|
||||
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
|
||||
|
||||
var lines : [String] = []
|
||||
self.plainText.enumerateLines() { line, _ in
|
||||
let (key, _) = Password.getKeyValuePair(from: line)
|
||||
if !Password.otpKeywords.contains(key ?? "") {
|
||||
lines.append(line)
|
||||
} else if key == "otpauth" && newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
// set to nil to prevent duplication
|
||||
newOtpauth = nil
|
||||
}
|
||||
}
|
||||
if newOtpauth != nil {
|
||||
lines.append(newOtpauth!)
|
||||
}
|
||||
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
||||
|
||||
// get and return the password
|
||||
return self.otpToken?.currentPassword
|
||||
}
|
||||
|
||||
static func LooksLikeOTP(line: String) -> Bool {
|
||||
let (key, _) = getKeyValuePair(from: line)
|
||||
return Password.otpKeywords.contains(key ?? "")
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// PasswordEntity.swift
|
||||
// pass
|
||||
//
|
||||
// Created by Mingshen Sun on 11/2/2017.
|
||||
// Copyright © 2017 Bob Sun. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftyUserDefaults
|
||||
|
||||
extension PasswordEntity {
|
||||
|
||||
var nameWithCategory: String {
|
||||
get {
|
||||
if let p = path, p.hasSuffix(".gpg") {
|
||||
return p.substring(to: p.index(p.endIndex, offsetBy: -4))
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getCategoryText() -> String {
|
||||
var parentEntity = parent
|
||||
var passwordCategoryArray: [String] = []
|
||||
while parentEntity != nil {
|
||||
passwordCategoryArray.append(parentEntity!.name!)
|
||||
parentEntity = parentEntity!.parent
|
||||
}
|
||||
passwordCategoryArray.reverse()
|
||||
return passwordCategoryArray.joined(separator: " > ")
|
||||
}
|
||||
|
||||
func getURL() -> URL? {
|
||||
if let p = path {
|
||||
return URL(string: p.stringByAddingPercentEncodingForRFC3986()!)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -1,848 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
class PasswordStore {
|
||||
static let shared = PasswordStore()
|
||||
let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
|
||||
let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
|
||||
|
||||
var storeRepository: GTRepository?
|
||||
var pgpKeyID: String?
|
||||
var publicKey: PGPKey? {
|
||||
didSet {
|
||||
if publicKey != nil {
|
||||
pgpKeyID = publicKey!.keyID!.shortKeyString
|
||||
} else {
|
||||
pgpKeyID = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
var privateKey: PGPKey?
|
||||
|
||||
var gitSignatureForNow: GTSignature {
|
||||
get {
|
||||
let gitSignatureName = Defaults[.gitSignatureName] ?? Globals.gitSignatureDefaultName
|
||||
let gitSignatureEmail = Defaults[.gitSignatureEmail] ?? Globals.gitSignatureDefaultEmail
|
||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())!
|
||||
}
|
||||
}
|
||||
|
||||
var pgp: ObjectivePGP = ObjectivePGP()
|
||||
|
||||
var pgpKeyPassphrase: String? {
|
||||
set {
|
||||
Utils.addPasswordToKeychain(name: "pgpKeyPassphrase", password: newValue)
|
||||
}
|
||||
get {
|
||||
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
var gitPassword: String? {
|
||||
set {
|
||||
Utils.addPasswordToKeychain(name: "gitPassword", password: newValue)
|
||||
}
|
||||
get {
|
||||
return Utils.getPasswordFromKeychain(name: "gitPassword")
|
||||
}
|
||||
}
|
||||
|
||||
var gitSSHPrivateKeyPassphrase: String? {
|
||||
set {
|
||||
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue)
|
||||
}
|
||||
get {
|
||||
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase")
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
var size = UInt64(0)
|
||||
do {
|
||||
if fm.fileExists(atPath: self.storeURL.path) {
|
||||
size = try fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
|
||||
private init() {
|
||||
// File migration to group
|
||||
migration()
|
||||
|
||||
do {
|
||||
if fm.fileExists(atPath: storeURL.path) {
|
||||
try storeRepository = GTRepository.init(url: storeURL)
|
||||
}
|
||||
try initPGPKeys()
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
public func initGitSSHKey(with armorKey: String, _ keyType: SSHKeyType) throws {
|
||||
guard keyType == .secret else {
|
||||
return
|
||||
}
|
||||
let keyPath = Globals.gitSSHPrivateKeyPath
|
||||
try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii)
|
||||
}
|
||||
|
||||
public func initPGPKeys() throws {
|
||||
try initPGPKey(.public)
|
||||
try initPGPKey(.secret)
|
||||
}
|
||||
|
||||
public func initPGPKey(_ keyType: PGPKeyType) throws {
|
||||
switch keyType {
|
||||
case .public:
|
||||
let keyPath = Globals.pgpPublicKeyPath
|
||||
self.publicKey = importKey(from: keyPath)
|
||||
if self.publicKey == nil {
|
||||
throw AppError.KeyImportError
|
||||
}
|
||||
case .secret:
|
||||
let keyPath = Globals.pgpPrivateKeyPath
|
||||
self.privateKey = importKey(from: keyPath)
|
||||
if self.privateKey == nil {
|
||||
throw AppError.KeyImportError
|
||||
}
|
||||
default:
|
||||
throw AppError.UnknownError
|
||||
}
|
||||
}
|
||||
|
||||
public func initPGPKey(from url: URL, keyType: PGPKeyType) throws {
|
||||
var pgpKeyLocalPath = ""
|
||||
if keyType == .public {
|
||||
pgpKeyLocalPath = Globals.pgpPublicKeyPath
|
||||
} else {
|
||||
pgpKeyLocalPath = Globals.pgpPrivateKeyPath
|
||||
}
|
||||
let pgpKeyData = try Data(contentsOf: url)
|
||||
try pgpKeyData.write(to: URL(fileURLWithPath: pgpKeyLocalPath), options: .atomic)
|
||||
try initPGPKey(keyType)
|
||||
}
|
||||
|
||||
public func initPGPKey(with armorKey: String, keyType: PGPKeyType) throws {
|
||||
var pgpKeyLocalPath = ""
|
||||
if keyType == .public {
|
||||
pgpKeyLocalPath = Globals.pgpPublicKeyPath
|
||||
} else {
|
||||
pgpKeyLocalPath = Globals.pgpPrivateKeyPath
|
||||
}
|
||||
try armorKey.write(toFile: pgpKeyLocalPath, atomically: true, encoding: .ascii)
|
||||
try initPGPKey(keyType)
|
||||
}
|
||||
|
||||
|
||||
private func importKey(from keyPath: String) -> PGPKey? {
|
||||
if fm.fileExists(atPath: keyPath) {
|
||||
if let keys = pgp.importKeys(fromFile: keyPath, allowDuplicates: false) as? [PGPKey] {
|
||||
return keys.first
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getPgpPrivateKey() -> PGPKey {
|
||||
return pgp.getKeysOf(.secret)[0]
|
||||
}
|
||||
|
||||
func repositoryExisted() -> Bool {
|
||||
let fm = FileManager()
|
||||
return fm.fileExists(atPath: Globals.repositoryPath)
|
||||
}
|
||||
|
||||
func passwordExisted(password: Password) -> Bool {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "name = %@ and path = %@", password.name, password.url!.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 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, isDir: Bool) -> PasswordEntity? {
|
||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "path = %@ and isDir = %@", path, isDir.description)
|
||||
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,
|
||||
checkoutProgressBlock: @escaping (String?, UInt, UInt) -> Void) throws {
|
||||
Utils.removeFileIfExists(at: storeURL)
|
||||
Utils.removeFileIfExists(at: tempStoreURL)
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options = [GTRepositoryCloneOptionsCredentialProvider: credentialProvider]
|
||||
storeRepository = try GTRepository.clone(from: remoteRepoURL, toWorkingDirectory: tempStoreURL, options: options, transferProgressBlock:transferProgressBlock)
|
||||
if fm.fileExists(atPath: storeURL.path) {
|
||||
try fm.removeItem(at: storeURL)
|
||||
}
|
||||
try fm.copyItem(at: tempStoreURL, to: storeURL)
|
||||
try fm.removeItem(at: tempStoreURL)
|
||||
storeRepository = try GTRepository(url: storeURL)
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func pullRepository(credential: GitCredential, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options = [GTRepositoryRemoteOptionsCredentialProvider: credentialProvider]
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository)
|
||||
try storeRepository.pull(storeRepository.currentBranch(), from: remote, withOptions: options, progress: transferProgressBlock)
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
Defaults[.lastSyncedTime] = Date()
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func updatePasswordEntityCoreData() {
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
do {
|
||||
var q = try fm.contentsOfDirectory(atPath: self.storeURL.path).filter{
|
||||
!$0.hasPrefix(".")
|
||||
}.map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = filename.substring(to: filename.index(filename.endIndex, offsetBy: -4))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = filename
|
||||
passwordEntity.parent = nil
|
||||
return passwordEntity
|
||||
}
|
||||
while q.count > 0 {
|
||||
let e = q.first!
|
||||
q.remove(at: 0)
|
||||
guard !e.name!.hasPrefix(".") else {
|
||||
continue
|
||||
}
|
||||
var isDirectory: ObjCBool = false
|
||||
let filePath = storeURL.appendingPathComponent(e.path!).path
|
||||
if fm.fileExists(atPath: filePath, isDirectory: &isDirectory) {
|
||||
if isDirectory.boolValue {
|
||||
e.isDir = true
|
||||
let files = try fm.contentsOfDirectory(atPath: filePath).map { (filename) -> PasswordEntity in
|
||||
let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: context) as! PasswordEntity
|
||||
if filename.hasSuffix(".gpg") {
|
||||
passwordEntity.name = filename.substring(to: filename.index(filename.endIndex, offsetBy: -4))
|
||||
} else {
|
||||
passwordEntity.name = filename
|
||||
}
|
||||
passwordEntity.path = "\(e.path!)/\(filename)"
|
||||
passwordEntity.parent = e
|
||||
return passwordEntity
|
||||
}
|
||||
q += files
|
||||
} else {
|
||||
e.isDir = false
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
do {
|
||||
try context.save()
|
||||
} catch {
|
||||
print("Error with save: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
func getRecentCommits(count: Int) throws -> [GTCommit] {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return []
|
||||
}
|
||||
var commits = [GTCommit]()
|
||||
let enumerator = try GTEnumerator(repository: storeRepository)
|
||||
if let sha = try storeRepository.headReference().targetOID.sha {
|
||||
try enumerator.pushSHA(sha)
|
||||
}
|
||||
for _ in 0 ..< count {
|
||||
let commit = try enumerator.nextObject(withSuccess: nil)
|
||||
commits.append(commit)
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
passwordEntityFetch.predicate = NSPredicate(format: "parent = %@", parent ?? 0)
|
||||
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 fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||
do {
|
||||
if !withDir {
|
||||
passwordEntityFetch.predicate = NSPredicate(format: "isDir = false")
|
||||
}
|
||||
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 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 unsynced passwords: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func getLatestUpdateInfo(filename: String) -> String {
|
||||
guard let storeRepository = storeRepository else {
|
||||
return "Unknown"
|
||||
}
|
||||
guard let blameHunks = try? storeRepository.blame(withFile: filename, options: nil).hunks,
|
||||
let latestCommitTime = blameHunks.map({
|
||||
$0.finalSignature?.time?.timeIntervalSince1970 ?? 0
|
||||
}).max() else {
|
||||
return "Unknown"
|
||||
}
|
||||
let lastCommitDate = Date(timeIntervalSince1970: latestCommitTime)
|
||||
let currentDate = Date()
|
||||
var autoFormattedDifference: String
|
||||
if currentDate.timeIntervalSince(lastCommitDate) <= 60 {
|
||||
autoFormattedDifference = "Just now"
|
||||
} else {
|
||||
let diffDate = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: lastCommitDate, to: currentDate)
|
||||
let dateComponentsFormatter = DateComponentsFormatter()
|
||||
dateComponentsFormatter.unitsStyle = .full
|
||||
dateComponentsFormatter.maximumUnitCount = 2
|
||||
dateComponentsFormatter.includesApproximationPhrase = true
|
||||
autoFormattedDifference = dateComponentsFormatter.string(from: diffDate)!.appending(" ago")
|
||||
}
|
||||
return autoFormattedDifference
|
||||
}
|
||||
|
||||
func updateRemoteRepo() {
|
||||
}
|
||||
|
||||
private func gitAdd(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
try storeRepository.index().addFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
private func gitRm(path: String) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
let url = storeURL.appendingPathComponent(path)
|
||||
if fm.fileExists(atPath: url.path) {
|
||||
try fm.removeItem(at: url)
|
||||
}
|
||||
try storeRepository.index().removeFile(path)
|
||||
try storeRepository.index().write()
|
||||
}
|
||||
|
||||
private func deleteDirectoryTree(at url: URL) throws {
|
||||
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
while count == 0 {
|
||||
try fm.removeItem(at: tempURL)
|
||||
tempURL.deleteLastPathComponent()
|
||||
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||
}
|
||||
}
|
||||
|
||||
private func createDirectoryTree(at url: URL) throws {
|
||||
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||
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)
|
||||
guard fm.fileExists(atPath: fromURL.path) else {
|
||||
print("\(from) not exist")
|
||||
return
|
||||
}
|
||||
try fm.moveItem(at: fromURL, to: toURL)
|
||||
try gitAdd(path: to)
|
||||
try gitRm(path: from)
|
||||
}
|
||||
|
||||
private func gitCommit(message: String) throws -> GTCommit? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
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
|
||||
let signature = gitSignatureForNow
|
||||
let commit = try storeRepository.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||
return commit
|
||||
}
|
||||
|
||||
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
let reference = GTBranch.localNamePrefix().appending(branchName)
|
||||
let branches = try storeRepository.branches(withPrefix: reference)
|
||||
return branches.first
|
||||
}
|
||||
|
||||
func pushRepository(credential: GitCredential, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
do {
|
||||
let credentialProvider = try credential.credentialProvider()
|
||||
let options = [GTRepositoryRemoteOptionsCredentialProvider: credentialProvider]
|
||||
if let masterBranch = try getLocalBranch(withName: "master") {
|
||||
let remote = try GTRemote(name: "origin", in: storeRepository)
|
||||
try storeRepository.push(masterBranch, to: remote, withOptions: options, progress: transferProgressBlock)
|
||||
}
|
||||
} catch {
|
||||
credential.delete()
|
||||
throw(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
||||
guard !passwordExisted(password: password) else {
|
||||
throw AppError.PasswordDuplicatedError
|
||||
}
|
||||
|
||||
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 {
|
||||
let isDir = !path.hasSuffix(".gpg")
|
||||
if let passwordEntity = getPasswordEntity(by: path, isDir: isDir) {
|
||||
print(passwordEntity.path!)
|
||||
parentPasswordEntity = passwordEntity
|
||||
} else {
|
||||
if !isDir {
|
||||
return insertPasswordEntity(name: URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!.deletingPathExtension().lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: false)
|
||||
} else {
|
||||
parentPasswordEntity = insertPasswordEntity(name: URL(string: path.stringByAddingPercentEncodingForRFC3986()!)!.lastPathComponent, path: path, parent: parentPasswordEntity, synced: false, isDir: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? {
|
||||
var ret: PasswordEntity? = nil
|
||||
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? {
|
||||
try createDirectoryTree(at: password.url!)
|
||||
let newPasswordEntity = try addPasswordEntities(password: password)
|
||||
let saveURL = storeURL.appendingPathComponent(password.url!.path)
|
||||
try self.encrypt(password: password).write(to: saveURL)
|
||||
try gitAdd(path: password.url!.path)
|
||||
let _ = try gitCommit(message: "Add password for \(password.url!.deletingPathExtension().path) to store using Pass for iOS.")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
public func delete(passwordEntity: PasswordEntity) throws {
|
||||
let deletedFileURL = passwordEntity.getURL()!
|
||||
try deleteDirectoryTree(at: passwordEntity.getURL()!)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
try gitRm(path: deletedFileURL.path)
|
||||
let _ = try gitCommit(message: "Remove \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) from store using Pass for iOS.")
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
}
|
||||
|
||||
func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
|
||||
var newPasswordEntity: PasswordEntity? = passwordEntity
|
||||
|
||||
if password.changed&PasswordChange.content.rawValue != 0 {
|
||||
print("chagne content")
|
||||
let saveURL = storeURL.appendingPathComponent(passwordEntity.getURL()!.path)
|
||||
try self.encrypt(password: password).write(to: saveURL)
|
||||
try gitAdd(path: passwordEntity.getURL()!.path)
|
||||
let _ = try gitCommit(message: "Edit password for \(passwordEntity.getURL()!.deletingPathExtension().path.removingPercentEncoding!) to store using Pass for iOS.")
|
||||
newPasswordEntity = passwordEntity
|
||||
}
|
||||
|
||||
if password.changed&PasswordChange.path.rawValue != 0 {
|
||||
print("change path")
|
||||
let deletedFileURL = passwordEntity.getURL()!
|
||||
// add
|
||||
try createDirectoryTree(at: password.url!)
|
||||
newPasswordEntity = try addPasswordEntities(password: password)
|
||||
|
||||
// mv
|
||||
try gitMv(from: deletedFileURL.path, to: password.url!.path)
|
||||
|
||||
// delete
|
||||
try deleteDirectoryTree(at: deletedFileURL)
|
||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||
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)
|
||||
return newPasswordEntity
|
||||
}
|
||||
|
||||
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
||||
var current: PasswordEntity? = passwordEntity
|
||||
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
|
||||
let parent = current!.parent
|
||||
self.context.delete(current!)
|
||||
current = parent
|
||||
do {
|
||||
try self.context.save()
|
||||
} catch {
|
||||
fatalError("Failed to delete a PasswordEntity: \(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?) {
|
||||
guard let image = image else {
|
||||
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() {
|
||||
publicKey = nil
|
||||
privateKey = nil
|
||||
Utils.removeFileIfExists(at: storeURL)
|
||||
Utils.removeFileIfExists(at: tempStoreURL)
|
||||
|
||||
Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath)
|
||||
Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath)
|
||||
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
|
||||
|
||||
Utils.removeAllKeychain()
|
||||
|
||||
deleteCoreData(entityName: "PasswordEntity")
|
||||
|
||||
Defaults.removeAll()
|
||||
storeRepository = nil
|
||||
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreErased, object: nil)
|
||||
}
|
||||
|
||||
// return the number of discarded commits
|
||||
func reset() throws -> Int {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
// get a list of local commits
|
||||
if let localCommits = try getLocalCommits(),
|
||||
localCommits.count > 0 {
|
||||
// get the oldest local commit
|
||||
guard let firstLocalCommit = localCommits.last,
|
||||
firstLocalCommit.parents.count == 1,
|
||||
let newHead = firstLocalCommit.parents.first else {
|
||||
throw AppError.GitResetError
|
||||
}
|
||||
try storeRepository.reset(to: newHead, resetType: .hard)
|
||||
self.setAllSynced()
|
||||
self.updatePasswordEntityCoreData()
|
||||
|
||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
||||
return localCommits.count
|
||||
} else {
|
||||
return 0 // no new commit
|
||||
}
|
||||
}
|
||||
|
||||
func numberOfLocalCommits() -> Int {
|
||||
do {
|
||||
if let localCommits = try getLocalCommits() {
|
||||
return localCommits.count
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} catch {
|
||||
print(error)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
private func getLocalCommits() throws -> [GTCommit]? {
|
||||
guard let storeRepository = storeRepository else {
|
||||
throw AppError.RepositoryNotSetError
|
||||
}
|
||||
// get the remote origin/master branch
|
||||
guard let index = try storeRepository.remoteBranches().index(where: { $0.shortName == "master" }) else {
|
||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
||||
}
|
||||
let remoteMasterBranch = try storeRepository.remoteBranches()[index]
|
||||
|
||||
// check oid before calling localCommitsRelative
|
||||
guard remoteMasterBranch.oid != nil else {
|
||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
||||
}
|
||||
|
||||
// get a list of local commits
|
||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
||||
}
|
||||
|
||||
|
||||
|
||||
func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||
let encryptedDataPath = storeURL.appendingPathComponent(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) ?? ""
|
||||
let escapedPath = passwordEntity.path!.stringByAddingPercentEncodingForRFC3986() ?? ""
|
||||
return Password(name: passwordEntity.name!, url: URL(string: escapedPath), plainText: plainText)
|
||||
}
|
||||
|
||||
func encrypt(password: Password) throws -> Data {
|
||||
guard let publicKey = pgp.getKeysOf(.public).first else {
|
||||
throw AppError.PGPPublicKeyNotExistError
|
||||
}
|
||||
let plainData = password.getPlainData()
|
||||
let encryptedData = try pgp.encryptData(plainData, usingPublicKey: publicKey, armored: Defaults[.encryptInArmored])
|
||||
return encryptedData
|
||||
}
|
||||
|
||||
func removePGPKeys() {
|
||||
Utils.removeFileIfExists(atPath: Globals.pgpPublicKeyPath)
|
||||
Utils.removeFileIfExists(atPath: Globals.pgpPrivateKeyPath)
|
||||
Defaults.remove(.pgpKeySource)
|
||||
Defaults.remove(.pgpPublicKeyArmor)
|
||||
Defaults.remove(.pgpPrivateKeyArmor)
|
||||
Defaults.remove(.pgpPrivateKeyURL)
|
||||
Defaults.remove(.pgpPublicKeyURL)
|
||||
Utils.removeKeychain(name: ".pgpKeyPassphrase")
|
||||
pgp = ObjectivePGP()
|
||||
publicKey = nil
|
||||
privateKey = nil
|
||||
}
|
||||
|
||||
func removeGitSSHKeys() {
|
||||
Utils.removeFileIfExists(atPath: Globals.gitSSHPrivateKeyPath)
|
||||
Defaults.remove(.gitSSHPrivateKeyArmor)
|
||||
Defaults.remove(.gitSSHPrivateKeyURL)
|
||||
Utils.removeKeychain(name: ".gitSSHPrivateKeyPassphrase")
|
||||
}
|
||||
|
||||
func gitSSHKeyExists() -> Bool {
|
||||
return fm.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
|
||||
}
|
||||
|
||||
func pgpKeyExists() -> Bool {
|
||||
return fm.fileExists(atPath: Globals.pgpPublicKeyPath) && fm.fileExists(atPath: Globals.pgpPrivateKeyPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import passKit
|
||||
|
||||
protocol FillPasswordTableViewCellDelegate {
|
||||
func generateAndCopyPassword()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import SVProgressHUD
|
||||
import passKit
|
||||
|
||||
struct LabelTableViewCellData {
|
||||
var title: String
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12124.1" systemVersion="16E175b" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="PasswordEntity" representedClassName="PasswordEntity" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="image" optional="YES" attributeType="Binary" allowsExternalBinaryDataStorage="YES" syncable="YES"/>
|
||||
<attribute name="isDir" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" syncable="YES"/>
|
||||
<attribute name="name" attributeType="String" syncable="YES"/>
|
||||
<attribute name="path" attributeType="String" syncable="YES"/>
|
||||
<attribute name="synced" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES" syncable="YES"/>
|
||||
<relationship name="children" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="PasswordEntity" inverseName="parent" inverseEntity="PasswordEntity" syncable="YES"/>
|
||||
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PasswordEntity" inverseName="children" inverseEntity="PasswordEntity" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="PasswordEntity" positionX="36" positionY="81" width="128" height="150"/>
|
||||
</elements>
|
||||
</model>
|
||||
Loading…
Add table
Add a link
Reference in a new issue