lint: delete trailing whitespaces
This commit is contained in:
parent
2ba6917710
commit
ed387069a4
59 changed files with 624 additions and 623 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
<img src="icon/icon_round.png" width="76"/>
|
<img src="icon/icon_round.png" width="76"/>
|
||||||
|
|
||||||
# Pass
|
# Pass
|
||||||
[](https://github.com/mssun/passforios/releases)
|
[](https://github.com/mssun/passforios/releases)
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
theme: jekyll-theme-minimal
|
theme: jekyll-theme-minimal
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
let presenter = PasscodeLockPresenter(mainWindow: self.window)
|
let presenter = PasscodeLockPresenter(mainWindow: self.window)
|
||||||
return presenter
|
return presenter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||||
// Override point for customization after application launch.
|
// Override point for customization after application launch.
|
||||||
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
|
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
|
||||||
|
|
@ -35,18 +35,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||||
if let _ = window?.rootViewController as? PasscodeLockViewController {
|
if let _ = window?.rootViewController as? PasscodeLockViewController {
|
||||||
window?.frame = UIScreen.main.bounds
|
window?.frame = UIScreen.main.bounds
|
||||||
}
|
}
|
||||||
return .all
|
return .all
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func postSearchNotification() {
|
@objc func postSearchNotification() {
|
||||||
NotificationCenter.default.post(name: .passwordSearch, object: nil)
|
NotificationCenter.default.post(name: .passwordSearch, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
|
||||||
if shortcutItem.type == Globals.bundleIdentifier + ".search" {
|
if shortcutItem.type == Globals.bundleIdentifier + ".search" {
|
||||||
let tabBarController = self.window!.rootViewController as! UITabBarController
|
let tabBarController = self.window!.rootViewController as! UITabBarController
|
||||||
|
|
@ -60,7 +60,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
func applicationWillResignActive(_ application: UIApplication) {
|
func applicationWillResignActive(_ application: UIApplication) {
|
||||||
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
|
||||||
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
|
||||||
|
|
||||||
// Display a blur effect view
|
// Display a blur effect view
|
||||||
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
|
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
|
||||||
let blurEffectView = UIVisualEffectView(effect: blurEffect)
|
let blurEffectView = UIVisualEffectView(effect: blurEffect)
|
||||||
|
|
@ -68,7 +68,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
blurEffectView.tag = ViewTag.blur.rawValue
|
blurEffectView.tag = ViewTag.blur.rawValue
|
||||||
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
self.window?.addSubview(blurEffectView)
|
self.window?.addSubview(blurEffectView)
|
||||||
|
|
||||||
// Display the Pass icon in the middle of the screen
|
// Display the Pass icon in the middle of the screen
|
||||||
let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary
|
let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary
|
||||||
let primaryIconsDictionary = iconsDictionary?["CFBundlePrimaryIcon"] as? NSDictionary
|
let primaryIconsDictionary = iconsDictionary?["CFBundlePrimaryIcon"] as? NSDictionary
|
||||||
|
|
@ -85,7 +85,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||||
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
|
||||||
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func applicationWillEnterForeground(_ application: UIApplication) {
|
func applicationWillEnterForeground(_ application: UIApplication) {
|
||||||
|
|
@ -95,7 +95,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
|
|
||||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||||
|
|
||||||
self.window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview()
|
self.window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview()
|
||||||
self.window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview()
|
self.window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview()
|
||||||
}
|
}
|
||||||
|
|
@ -107,7 +107,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Core Data stack
|
// MARK: - Core Data stack
|
||||||
|
|
||||||
lazy var persistentContainer: NSPersistentContainer = {
|
lazy var persistentContainer: NSPersistentContainer = {
|
||||||
/*
|
/*
|
||||||
The persistent container for the application. This implementation
|
The persistent container for the application. This implementation
|
||||||
|
|
@ -126,7 +126,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
if let error = error as NSError? {
|
if let error = error as NSError? {
|
||||||
// Replace this implementation with code to handle the error appropriately.
|
// 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.
|
// 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:
|
Typical reasons for an error here include:
|
||||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||||
|
|
@ -140,9 +140,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
})
|
})
|
||||||
return container
|
return container
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// MARK: - Core Data Saving support
|
// MARK: - Core Data Saving support
|
||||||
|
|
||||||
func saveContext () {
|
func saveContext () {
|
||||||
let context = persistentContainer.viewContext
|
let context = persistentContainer.viewContext
|
||||||
if context.hasChanges {
|
if context.hasChanges {
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import UIKit
|
||||||
import passKit
|
import passKit
|
||||||
|
|
||||||
class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
|
|
||||||
private var needRefresh = false
|
private var needRefresh = false
|
||||||
private var indicator: UIActivityIndicatorView = {
|
private var indicator: UIActivityIndicatorView = {
|
||||||
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
||||||
|
|
@ -23,13 +23,13 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
|
|
||||||
indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)
|
indicator.center = CGPoint(x: view.bounds.midX, y: view.bounds.height * 0.382)
|
||||||
tableView.addSubview(indicator)
|
tableView.addSubview(indicator)
|
||||||
|
|
||||||
setTableData()
|
setTableData()
|
||||||
|
|
||||||
// all password store updates (including erase, discard) will trigger the refresh
|
// all password store updates (including erase, discard) will trigger the refresh
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(setNeedRefresh), name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(setNeedRefresh), name: .passwordStoreUpdated, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
if needRefresh {
|
if needRefresh {
|
||||||
|
|
@ -37,14 +37,14 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
needRefresh = false
|
needRefresh = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setTableData() {
|
private func setTableData() {
|
||||||
|
|
||||||
// clear current contents (if any)
|
// clear current contents (if any)
|
||||||
self.tableData.removeAll(keepingCapacity: true)
|
self.tableData.removeAll(keepingCapacity: true)
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
indicator.startAnimating()
|
indicator.startAnimating()
|
||||||
|
|
||||||
// reload the table
|
// reload the table
|
||||||
DispatchQueue.global(qos: .userInitiated).async {
|
DispatchQueue.global(qos: .userInitiated).async {
|
||||||
let passwords = self.numberOfPasswordsString()
|
let passwords = self.numberOfPasswordsString()
|
||||||
|
|
@ -52,7 +52,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
let localCommits = self.numberOfLocalCommitsString()
|
let localCommits = self.numberOfLocalCommitsString()
|
||||||
let lastSynced = self.lastSyncedTimeString()
|
let lastSynced = self.lastSyncedTimeString()
|
||||||
let commits = self.numberOfPasswordsString()
|
let commits = self.numberOfPasswordsString()
|
||||||
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
|
@ -97,7 +97,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
|
||||||
formatter.timeStyle = .short
|
formatter.timeStyle = .short
|
||||||
return formatter.string(from: date)
|
return formatter.string(from: date)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func setNeedRefresh() {
|
@objc func setNeedRefresh() {
|
||||||
needRefresh = true
|
needRefresh = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,21 +9,21 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class AboutTableViewController: BasicStaticTableViewController {
|
class AboutTableViewController: BasicStaticTableViewController {
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
tableData = [
|
tableData = [
|
||||||
// section 0
|
// section 0
|
||||||
[[.title: "Website", .action: "link", .link: "https://github.com/mssun/pass-ios.git"],
|
[[.title: "Website", .action: "link", .link: "https://github.com/mssun/pass-ios.git"],
|
||||||
[.title: "Help", .action: "link", .link: "https://github.com/mssun/passforios/wiki"],
|
[.title: "Help", .action: "link", .link: "https://github.com/mssun/passforios/wiki"],
|
||||||
[.title: "Contact Developer", .action: "link", .link: "mailto:developer@passforios.mssun.me?subject=Pass%20for%20iOS"],],
|
[.title: "Contact Developer", .action: "link", .link: "mailto:developer@passforios.mssun.me?subject=Pass%20for%20iOS"],],
|
||||||
|
|
||||||
// section 1,
|
// section 1,
|
||||||
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
|
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
|
||||||
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
|
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
|
||||||
]
|
]
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||||
if section == tableData.count - 1 {
|
if section == tableData.count - 1 {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -38,7 +38,7 @@ class AboutTableViewController: BasicStaticTableViewController {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
if section == 1 {
|
if section == 1 {
|
||||||
return "Acknowledgements".uppercased()
|
return "Acknowledgements".uppercased()
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
||||||
tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix
|
tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||||
if identifier == "saveAddPasswordSegue" {
|
if identifier == "saveAddPasswordSegue" {
|
||||||
// check PGP key
|
// check PGP key
|
||||||
|
|
@ -37,7 +37,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
||||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check name
|
// check name
|
||||||
guard checkName() == true else {
|
guard checkName() == true else {
|
||||||
return false
|
return false
|
||||||
|
|
@ -45,7 +45,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
super.prepare(for: segue, sender: sender)
|
super.prepare(for: segue, sender: sender)
|
||||||
if segue.identifier == "saveAddPasswordSegue" {
|
if segue.identifier == "saveAddPasswordSegue" {
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
||||||
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
|
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
|
||||||
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
|
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
let encryptInASCIIArmoredSwitch: UISwitch = {
|
let encryptInASCIIArmoredSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Globals.blue
|
uiSwitch.onTintColor = Globals.blue
|
||||||
|
|
@ -33,7 +33,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
||||||
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
|
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
|
||||||
setGitSignatureText()
|
setGitSignatureText()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setGitSignatureText() {
|
private func setGitSignatureText() {
|
||||||
let gitSignatureName = passwordStore.gitSignatureForNow.name!
|
let gitSignatureName = passwordStore.gitSignatureForNow.name!
|
||||||
let gitSignatureEmail = passwordStore.gitSignatureForNow.email!
|
let gitSignatureEmail = passwordStore.gitSignatureForNow.email!
|
||||||
|
|
@ -77,17 +77,17 @@ class AdvancedSettingsTableViewController: UITableViewController {
|
||||||
} catch {
|
} catch {
|
||||||
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}))
|
}))
|
||||||
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
|
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func encryptInASCIIArmoredAction(_ sender: Any?) {
|
@objc func encryptInASCIIArmoredAction(_ sender: Any?) {
|
||||||
SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
|
SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) {
|
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) {
|
||||||
if let controller = segue.source as? GitConfigSettingTableViewController {
|
if let controller = segue.source as? GitConfigSettingTableViewController {
|
||||||
if let gitSignatureName = controller.nameTextField.text,
|
if let gitSignatureName = controller.nameTextField.text,
|
||||||
|
|
|
||||||
|
|
@ -27,18 +27,18 @@ enum CellDataKey {
|
||||||
class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate {
|
class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate {
|
||||||
var tableData = [[Dictionary<CellDataKey, Any>]]()
|
var tableData = [[Dictionary<CellDataKey, Any>]]()
|
||||||
var navigationItemTitle: String?
|
var navigationItemTitle: String?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
if navigationItemTitle != nil {
|
if navigationItemTitle != nil {
|
||||||
navigationItem.title = navigationItemTitle
|
navigationItem.title = navigationItemTitle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
return tableData.count
|
return tableData.count
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return tableData[section].count
|
return tableData[section].count
|
||||||
}
|
}
|
||||||
|
|
@ -47,13 +47,13 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
||||||
super.didReceiveMemoryWarning()
|
super.didReceiveMemoryWarning()
|
||||||
// Dispose of any resources that can be recreated.
|
// Dispose of any resources that can be recreated.
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
|
|
||||||
let cellData = tableData[indexPath.section][indexPath.row]
|
let cellData = tableData[indexPath.section][indexPath.row]
|
||||||
let cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle
|
let cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle
|
||||||
var cell: UITableViewCell?
|
var cell: UITableViewCell?
|
||||||
|
|
||||||
switch cellDataStyle ?? .defaultStyle {
|
switch cellDataStyle ?? .defaultStyle {
|
||||||
case .value1:
|
case .value1:
|
||||||
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
|
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
|
||||||
|
|
@ -61,7 +61,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
||||||
default:
|
default:
|
||||||
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
|
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let detailText = cellData[CellDataKey.detailText] as? String {
|
if let detailText = cellData[CellDataKey.detailText] as? String {
|
||||||
cell?.detailTextLabel?.text = detailText
|
cell?.detailTextLabel?.text = detailText
|
||||||
}
|
}
|
||||||
|
|
@ -71,17 +71,17 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
||||||
cell?.accessoryType = .disclosureIndicator
|
cell?.accessoryType = .disclosureIndicator
|
||||||
cell?.selectionStyle = .default
|
cell?.selectionStyle = .default
|
||||||
}
|
}
|
||||||
|
|
||||||
cell?.textLabel?.text = cellData[CellDataKey.title] as? String
|
cell?.textLabel?.text = cellData[CellDataKey.title] as? String
|
||||||
return cell ?? UITableViewCell()
|
return cell ?? UITableViewCell()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||||
let cellData = tableData[indexPath.section][indexPath.row]
|
let cellData = tableData[indexPath.section][indexPath.row]
|
||||||
let selector = cellData[CellDataKey.detailDisclosureAction] as? Selector
|
let selector = cellData[CellDataKey.detailDisclosureAction] as? Selector
|
||||||
perform(selector, with: cellData[CellDataKey.detailDisclosureData])
|
perform(selector, with: cellData[CellDataKey.detailDisclosureData])
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
let cellData = tableData[indexPath.section][indexPath.row]
|
let cellData = tableData[indexPath.section][indexPath.row]
|
||||||
|
|
@ -116,7 +116,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendEmail(toRecipients recipients: [String], subject: String) {
|
func sendEmail(toRecipients recipients: [String], subject: String) {
|
||||||
let mailVC = MFMailComposeViewController()
|
let mailVC = MFMailComposeViewController()
|
||||||
mailVC.mailComposeDelegate = self
|
mailVC.mailComposeDelegate = self
|
||||||
|
|
@ -125,7 +125,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
|
||||||
mailVC.setMessageBody("", isHTML: false)
|
mailVC.setMessageBody("", isHTML: false)
|
||||||
self.present(mailVC, animated: true, completion: nil)
|
self.present(mailVC, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
controller.dismiss(animated: true)
|
controller.dismiss(animated: true)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import passKit
|
||||||
class CommitLogsTableViewController: UITableViewController {
|
class CommitLogsTableViewController: UITableViewController {
|
||||||
var commits: [GTCommit] = []
|
var commits: [GTCommit] = []
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(updateCommitLogs), name: .passwordStoreUpdated, object: nil)
|
||||||
|
|
@ -25,14 +25,14 @@ class CommitLogsTableViewController: UITableViewController {
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return commits.count
|
return commits.count
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = DateFormatter.Style.medium
|
formatter.dateStyle = DateFormatter.Style.medium
|
||||||
formatter.timeStyle = .medium
|
formatter.timeStyle = .medium
|
||||||
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
|
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
|
||||||
|
|
||||||
let author = cell.contentView.viewWithTag(200) as? UILabel
|
let author = cell.contentView.viewWithTag(200) as? UILabel
|
||||||
let dateLabel = cell.contentView.viewWithTag(201) as? UILabel
|
let dateLabel = cell.contentView.viewWithTag(201) as? UILabel
|
||||||
let messageLabel = cell.contentView.viewWithTag(202) as? UILabel
|
let messageLabel = cell.contentView.viewWithTag(202) as? UILabel
|
||||||
|
|
@ -41,12 +41,12 @@ class CommitLogsTableViewController: UITableViewController {
|
||||||
messageLabel?.text = commits[indexPath.row].message?.trimmed
|
messageLabel?.text = commits[indexPath.row].message?.trimmed
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func updateCommitLogs() {
|
@objc func updateCommitLogs() {
|
||||||
commits = getCommitLogs()
|
commits = getCommitLogs()
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getCommitLogs() -> [GTCommit] {
|
private func getCommitLogs() -> [GTCommit] {
|
||||||
do {
|
do {
|
||||||
return try passwordStore.getRecentCommits(count: 20)
|
return try passwordStore.getRecentCommits(count: 20)
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||||
tableData[1].append([.type: PasswordEditorCellType.memorablePasswordGeneratorCell])
|
tableData[1].append([.type: PasswordEditorCellType.memorablePasswordGeneratorCell])
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||||
if identifier == "saveEditPasswordSegue" {
|
if identifier == "saveEditPasswordSegue" {
|
||||||
// check name
|
// check name
|
||||||
|
|
@ -34,7 +34,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
super.prepare(for: segue, sender: sender)
|
super.prepare(for: segue, sender: sender)
|
||||||
if segue.identifier == "saveEditPasswordSegue" {
|
if segue.identifier == "saveEditPasswordSegue" {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
uiSwitch.addTarget(self, action: #selector(hideUnknownSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
uiSwitch.addTarget(self, action: #selector(hideUnknownSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
||||||
return uiSwitch
|
return uiSwitch
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let hideOTPSwitch: UISwitch = {
|
let hideOTPSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Globals.blue
|
uiSwitch.onTintColor = Globals.blue
|
||||||
|
|
@ -27,7 +27,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
uiSwitch.addTarget(self, action: #selector(hideOTPSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
uiSwitch.addTarget(self, action: #selector(hideOTPSwitchAction(_:)), for: UIControlEvents.valueChanged)
|
||||||
return uiSwitch
|
return uiSwitch
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let rememberPGPPassphraseSwitch: UISwitch = {
|
let rememberPGPPassphraseSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Globals.blue
|
uiSwitch.onTintColor = Globals.blue
|
||||||
|
|
@ -36,7 +36,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
uiSwitch.isOn = SharedDefaults[.isRememberPGPPassphraseOn]
|
uiSwitch.isOn = SharedDefaults[.isRememberPGPPassphraseOn]
|
||||||
return uiSwitch
|
return uiSwitch
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let rememberGitCredentialPassphraseSwitch: UISwitch = {
|
let rememberGitCredentialPassphraseSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Globals.blue
|
uiSwitch.onTintColor = Globals.blue
|
||||||
|
|
@ -45,7 +45,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
uiSwitch.isOn = SharedDefaults[.isRememberGitCredentialPassphraseOn]
|
uiSwitch.isOn = SharedDefaults[.isRememberGitCredentialPassphraseOn]
|
||||||
return uiSwitch
|
return uiSwitch
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let showFolderSwitch: UISwitch = {
|
let showFolderSwitch: UISwitch = {
|
||||||
let uiSwitch = UISwitch()
|
let uiSwitch = UISwitch()
|
||||||
uiSwitch.onTintColor = Globals.blue
|
uiSwitch.onTintColor = Globals.blue
|
||||||
|
|
@ -59,12 +59,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
tableData = [
|
tableData = [
|
||||||
// section 0
|
// section 0
|
||||||
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],
|
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],
|
||||||
|
|
||||||
// section 1
|
// section 1
|
||||||
[
|
[
|
||||||
[.title: "Password Generator Flavor", .action: "none", .style: CellDataStyle.value1],
|
[.title: "Password Generator Flavor", .action: "none", .style: CellDataStyle.value1],
|
||||||
],
|
],
|
||||||
|
|
||||||
// section 2
|
// section 2
|
||||||
[
|
[
|
||||||
[.title: "Remember PGP Key Passphrase", .action: "none",],
|
[.title: "Remember PGP Key Passphrase", .action: "none",],
|
||||||
|
|
@ -78,9 +78,9 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
|
|
||||||
]
|
]
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
let cell = super.tableView(tableView, cellForRowAt: indexPath)
|
||||||
switch cell.textLabel!.text! {
|
switch cell.textLabel!.text! {
|
||||||
|
|
@ -127,7 +127,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
}
|
}
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
super.tableView(tableView, didSelectRowAt: indexPath)
|
super.tableView(tableView, didSelectRowAt: indexPath)
|
||||||
let cell = tableView.cellForRow(at: indexPath)!
|
let cell = tableView.cellForRow(at: indexPath)!
|
||||||
|
|
@ -136,7 +136,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
showPasswordGeneratorFlavorActionSheet(sourceCell: cell)
|
showPasswordGeneratorFlavorActionSheet(sourceCell: cell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell) {
|
func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell) {
|
||||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
var randomFlavorActionTitle = ""
|
var randomFlavorActionTitle = ""
|
||||||
|
|
@ -152,12 +152,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
SharedDefaults[.passwordGeneratorFlavor] = "Random"
|
SharedDefaults[.passwordGeneratorFlavor] = "Random"
|
||||||
sourceCell.detailTextLabel?.text = "Random"
|
sourceCell.detailTextLabel?.text = "Random"
|
||||||
}
|
}
|
||||||
|
|
||||||
let appleFlavorAction = UIAlertAction(title: appleFlavorActionTitle, style: .default) { _ in
|
let appleFlavorAction = UIAlertAction(title: appleFlavorActionTitle, style: .default) { _ in
|
||||||
SharedDefaults[.passwordGeneratorFlavor] = "Apple"
|
SharedDefaults[.passwordGeneratorFlavor] = "Apple"
|
||||||
sourceCell.detailTextLabel?.text = "Apple"
|
sourceCell.detailTextLabel?.text = "Apple"
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
optionMenu.addAction(randomFlavorAction)
|
optionMenu.addAction(randomFlavorAction)
|
||||||
optionMenu.addAction(appleFlavorAction)
|
optionMenu.addAction(appleFlavorAction)
|
||||||
|
|
@ -166,37 +166,37 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
optionMenu.popoverPresentationController?.sourceRect = sourceCell.bounds
|
optionMenu.popoverPresentationController?.sourceRect = sourceCell.bounds
|
||||||
self.present(optionMenu, animated: true, completion: nil)
|
self.present(optionMenu, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapHideUnknownSwitchDetailButton(_ sender: Any?) {
|
@objc func tapHideUnknownSwitchDetailButton(_ sender: Any?) {
|
||||||
let alertMessage = "Only \"key: value\" format in additional fields is supported. Unsupported fields will be given \"unknown\" keys. Turn on this switch to hide unsupported fields."
|
let alertMessage = "Only \"key: value\" format in additional fields is supported. Unsupported fields will be given \"unknown\" keys. Turn on this switch to hide unsupported fields."
|
||||||
let alertTitle = "Hide Unknown Fields"
|
let alertTitle = "Hide Unknown Fields"
|
||||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tapHideOTPSwitchDetailButton(_ sender: Any?) {
|
@objc func tapHideOTPSwitchDetailButton(_ sender: Any?) {
|
||||||
let keywordsString = Constants.OTP_KEYWORDS.joined(separator: ",")
|
let keywordsString = Constants.OTP_KEYWORDS.joined(separator: ",")
|
||||||
let alertMessage = "Turn on this switch to hide the fields related to one time passwords (i.e., \(keywordsString))."
|
let alertMessage = "Turn on this switch to hide the fields related to one time passwords (i.e., \(keywordsString))."
|
||||||
let alertTitle = "Hide One Time Password Fields"
|
let alertTitle = "Hide One Time Password Fields"
|
||||||
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func hideUnknownSwitchAction(_ sender: Any?) {
|
@objc func hideUnknownSwitchAction(_ sender: Any?) {
|
||||||
SharedDefaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
|
SharedDefaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
|
||||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func hideOTPSwitchAction(_ sender: Any?) {
|
@objc func hideOTPSwitchAction(_ sender: Any?) {
|
||||||
SharedDefaults[.isHideOTPOn] = hideOTPSwitch.isOn
|
SharedDefaults[.isHideOTPOn] = hideOTPSwitch.isOn
|
||||||
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
|
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
|
||||||
SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
|
SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
|
||||||
if rememberPGPPassphraseSwitch.isOn == false {
|
if rememberPGPPassphraseSwitch.isOn == false {
|
||||||
passwordStore.pgpKeyPassphrase = nil
|
passwordStore.pgpKeyPassphrase = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) {
|
@objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) {
|
||||||
SharedDefaults[.isRememberGitCredentialPassphraseOn] = rememberGitCredentialPassphraseSwitch.isOn
|
SharedDefaults[.isRememberGitCredentialPassphraseOn] = rememberGitCredentialPassphraseSwitch.isOn
|
||||||
if rememberGitCredentialPassphraseSwitch.isOn == false {
|
if rememberGitCredentialPassphraseSwitch.isOn == false {
|
||||||
|
|
@ -204,10 +204,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
|
||||||
passwordStore.gitPassword = nil
|
passwordStore.gitPassword = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func showFolderSwitchAction(_ sender: Any?) {
|
@objc func showFolderSwitchAction(_ sender: Any?) {
|
||||||
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
|
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
|
||||||
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,21 +12,21 @@ import passKit
|
||||||
|
|
||||||
class GitConfigSettingTableViewController: UITableViewController {
|
class GitConfigSettingTableViewController: UITableViewController {
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
@IBOutlet weak var nameTextField: UITextField!
|
@IBOutlet weak var nameTextField: UITextField!
|
||||||
@IBOutlet weak var emailTextField: UITextField!
|
@IBOutlet weak var emailTextField: UITextField!
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
tableView.rowHeight = UITableViewAutomaticDimension
|
tableView.rowHeight = UITableViewAutomaticDimension
|
||||||
|
|
||||||
let signature = passwordStore.gitSignatureForNow
|
let signature = passwordStore.gitSignatureForNow
|
||||||
nameTextField.placeholder = signature.name
|
nameTextField.placeholder = signature.name
|
||||||
emailTextField.placeholder = signature.email
|
emailTextField.placeholder = signature.email
|
||||||
nameTextField.text = SharedDefaults[.gitSignatureName]
|
nameTextField.text = SharedDefaults[.gitSignatureName]
|
||||||
emailTextField.text = SharedDefaults[.gitSignatureEmail]
|
emailTextField.text = SharedDefaults[.gitSignatureEmail]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||||
if identifier == "saveGitConfigSettingSegue" {
|
if identifier == "saveGitConfigSettingSegue" {
|
||||||
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!
|
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ import passKit
|
||||||
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
|
||||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||||
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
||||||
|
|
||||||
var gitSSHPrivateKeyPassphrase: String?
|
var gitSSHPrivateKeyPassphrase: String?
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
class ScannedSSHKey {
|
class ScannedSSHKey {
|
||||||
static let maxNumberOfGif = 100
|
static let maxNumberOfGif = 100
|
||||||
var numberOfSegments = 0
|
var numberOfSegments = 0
|
||||||
|
|
@ -24,7 +24,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
var message = ""
|
var message = ""
|
||||||
var hasStarted = false
|
var hasStarted = false
|
||||||
var isDone = false
|
var isDone = false
|
||||||
|
|
||||||
func reset() {
|
func reset() {
|
||||||
numberOfSegments = 0
|
numberOfSegments = 0
|
||||||
previousSegment = ""
|
previousSegment = ""
|
||||||
|
|
@ -33,14 +33,14 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
hasStarted = false
|
hasStarted = false
|
||||||
isDone = false
|
isDone = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSegment(segment: String) {
|
func addSegment(segment: String) {
|
||||||
// skip duplicated segments
|
// skip duplicated segments
|
||||||
guard segment != previousSegment else {
|
guard segment != previousSegment else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
previousSegment = segment
|
previousSegment = segment
|
||||||
|
|
||||||
// check whether we have found the first block
|
// check whether we have found the first block
|
||||||
if hasStarted == false {
|
if hasStarted == false {
|
||||||
hasStarted = segment.contains("-----BEGIN")
|
hasStarted = segment.contains("-----BEGIN")
|
||||||
|
|
@ -48,38 +48,38 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
guard hasStarted == true else {
|
guard hasStarted == true else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the number of segments
|
// check the number of segments
|
||||||
numberOfSegments = numberOfSegments + 1
|
numberOfSegments = numberOfSegments + 1
|
||||||
guard numberOfSegments <= ScannedSSHKey.maxNumberOfGif else {
|
guard numberOfSegments <= ScannedSSHKey.maxNumberOfGif else {
|
||||||
key = "Too many QR codes"
|
key = "Too many QR codes"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// update full text and check whether we are done
|
// update full text and check whether we are done
|
||||||
key.append(segment)
|
key.append(segment)
|
||||||
if let index1 = key.range(of: "-----END")?.lowerBound,
|
if let index1 = key.range(of: "-----END")?.lowerBound,
|
||||||
let _ = key.suffix(from: index1).range(of: "KEY-----")?.lowerBound {
|
let _ = key.suffix(from: index1).range(of: "KEY-----")?.lowerBound {
|
||||||
isDone = true
|
isDone = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// update message
|
// update message
|
||||||
message = "\(numberOfSegments) scanned QR codes."
|
message = "\(numberOfSegments) scanned QR codes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var scanned = ScannedSSHKey()
|
var scanned = ScannedSSHKey()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
armorPrivateKeyTextView.text = SharedDefaults[.gitSSHPrivateKeyArmor]
|
armorPrivateKeyTextView.text = SharedDefaults[.gitSSHPrivateKeyArmor]
|
||||||
armorPrivateKeyTextView.delegate = self
|
armorPrivateKeyTextView.delegate = self
|
||||||
|
|
||||||
scanPrivateKeyCell?.textLabel?.text = "Scan Private Key QR Codes"
|
scanPrivateKeyCell?.textLabel?.text = "Scan Private Key QR Codes"
|
||||||
scanPrivateKeyCell?.textLabel?.textColor = Globals.blue
|
scanPrivateKeyCell?.textLabel?.textColor = Globals.blue
|
||||||
scanPrivateKeyCell?.selectionStyle = .default
|
scanPrivateKeyCell?.selectionStyle = .default
|
||||||
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func doneButtonTapped(_ sender: Any) {
|
@IBAction func doneButtonTapped(_ sender: Any) {
|
||||||
SharedDefaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
SharedDefaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
|
||||||
do {
|
do {
|
||||||
|
|
@ -90,7 +90,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
SharedDefaults[.gitSSHKeySource] = "armor"
|
SharedDefaults[.gitSSHKeySource] = "armor"
|
||||||
self.navigationController!.popViewController(animated: true)
|
self.navigationController!.popViewController(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
if text == UIPasteboard.general.string {
|
if text == UIPasteboard.general.string {
|
||||||
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
||||||
|
|
@ -98,7 +98,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let selectedCell = tableView.cellForRow(at: indexPath)
|
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||||
if selectedCell == scanPrivateKeyCell {
|
if selectedCell == scanPrivateKeyCell {
|
||||||
|
|
@ -107,8 +107,8 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
}
|
}
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||||
scanned.addSegment(segment: line)
|
scanned.addSegment(segment: line)
|
||||||
|
|
@ -118,12 +118,12 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
return (accept: false, message: scanned.message)
|
return (accept: false, message: scanned.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func handleScannedOutput(line: String) {
|
func handleScannedOutput(line: String) {
|
||||||
armorPrivateKeyTextView.text = scanned.key
|
armorPrivateKeyTextView.text = scanned.key
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
if segue.identifier == "showSSHScannerSegue" {
|
if segue.identifier == "showSSHScannerSegue" {
|
||||||
if let navController = segue.destination as? UINavigationController {
|
if let navController = segue.destination as? UINavigationController {
|
||||||
|
|
@ -135,9 +135,9 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func cancelSSHScanner(segue: UIStoryboardSegue) {
|
@IBAction private func cancelSSHScanner(segue: UIStoryboardSegue) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
sshKeyCheckView.isHidden = true
|
sshKeyCheckView.isHidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
// Grey out ssh option if ssh_key is not present
|
// Grey out ssh option if ssh_key is not present
|
||||||
|
|
@ -55,30 +55,30 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
checkAuthenticationMethod(method: authenticationMethod)
|
checkAuthenticationMethod(method: authenticationMethod)
|
||||||
authSSHKeyCell.accessoryType = .detailButton
|
authSSHKeyCell.accessoryType = .detailButton
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||||
let cell = tableView.cellForRow(at: indexPath)
|
let cell = tableView.cellForRow(at: indexPath)
|
||||||
if cell == authSSHKeyCell {
|
if cell == authSSHKeyCell {
|
||||||
showSSHKeyActionSheet()
|
showSSHKeyActionSheet()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillDisappear(_ animated: Bool) {
|
override func viewWillDisappear(_ animated: Bool) {
|
||||||
super.viewWillDisappear(animated)
|
super.viewWillDisappear(animated)
|
||||||
view.endEditing(true)
|
view.endEditing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func cloneAndSegueIfSuccess() {
|
private func cloneAndSegueIfSuccess() {
|
||||||
// try to clone
|
// try to clone
|
||||||
let gitRepostiroyURL = gitURLTextField.text!.trimmed
|
let gitRepostiroyURL = gitURLTextField.text!.trimmed
|
||||||
let username = usernameTextField.text!
|
let username = usernameTextField.text!
|
||||||
let auth = authenticationMethod
|
let auth = authenticationMethod
|
||||||
|
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
SVProgressHUD.setDefaultStyle(.light)
|
SVProgressHUD.setDefaultStyle(.light)
|
||||||
SVProgressHUD.show(withStatus: "Prepare Repository")
|
SVProgressHUD.show(withStatus: "Prepare Repository")
|
||||||
|
|
@ -160,15 +160,15 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
checkAuthenticationMethod(method: authenticationMethod)
|
checkAuthenticationMethod(method: authenticationMethod)
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func save(_ sender: Any) {
|
@IBAction func save(_ sender: Any) {
|
||||||
|
|
||||||
// some sanity checks
|
// some sanity checks
|
||||||
guard let gitURL = URL(string: gitURLTextField.text!) else {
|
guard let gitURL = URL(string: gitURLTextField.text!) else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please set the Git repository URL.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please set the Git repository URL.", controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch gitURL.scheme {
|
switch gitURL.scheme {
|
||||||
case let val where val == "https":
|
case let val where val == "https":
|
||||||
break
|
break
|
||||||
|
|
@ -188,7 +188,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please specify the scheme of the Git repository URL (https or ssh).", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please specify the scheme of the Git repository URL (https or ssh).", controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if passwordStore.repositoryExisted() {
|
if passwordStore.repositoryExisted() {
|
||||||
let alert = UIAlertController(title: "Overwrite?", message: "This operation will overwrite your current password store data (repository). Data on your remote server will not be affected.", preferredStyle: UIAlertControllerStyle.alert)
|
let alert = UIAlertController(title: "Overwrite?", message: "This operation will overwrite your current password store data (repository). Data on your remote server will not be affected.", preferredStyle: UIAlertControllerStyle.alert)
|
||||||
alert.addAction(UIAlertAction(title: "Overwrite", style: UIAlertActionStyle.destructive, handler: { _ in
|
alert.addAction(UIAlertAction(title: "Overwrite", style: UIAlertActionStyle.destructive, handler: { _ in
|
||||||
|
|
@ -202,13 +202,13 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
cloneAndSegueIfSuccess()
|
cloneAndSegueIfSuccess()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func showSSHKeyActionSheet() {
|
func showSSHKeyActionSheet() {
|
||||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
var urlActionTitle = "Download from URL"
|
var urlActionTitle = "Download from URL"
|
||||||
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
||||||
var fileActionTitle = "iTunes File Sharing"
|
var fileActionTitle = "iTunes File Sharing"
|
||||||
|
|
||||||
if SharedDefaults[.gitSSHKeySource] == "url" {
|
if SharedDefaults[.gitSSHKeySource] == "url" {
|
||||||
urlActionTitle = "✓ \(urlActionTitle)"
|
urlActionTitle = "✓ \(urlActionTitle)"
|
||||||
} else if SharedDefaults[.gitSSHKeySource] == "armor" {
|
} else if SharedDefaults[.gitSSHKeySource] == "armor" {
|
||||||
|
|
@ -225,7 +225,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
optionMenu.addAction(urlAction)
|
optionMenu.addAction(urlAction)
|
||||||
optionMenu.addAction(armorAction)
|
optionMenu.addAction(armorAction)
|
||||||
|
|
||||||
if passwordStore.gitSSHKeyExists(inFileSharing: true) {
|
if passwordStore.gitSSHKeyExists(inFileSharing: true) {
|
||||||
// might keys updated via iTunes, or downloaded/pasted inside the app
|
// might keys updated via iTunes, or downloaded/pasted inside the app
|
||||||
fileActionTitle.append(" (Import)")
|
fileActionTitle.append(" (Import)")
|
||||||
|
|
@ -249,7 +249,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
optionMenu.addAction(fileAction)
|
optionMenu.addAction(fileAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
if SharedDefaults[.gitSSHKeySource] != nil {
|
if SharedDefaults[.gitSSHKeySource] != nil {
|
||||||
let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in
|
let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in
|
||||||
self.passwordStore.removeGitSSHKeys()
|
self.passwordStore.removeGitSSHKeys()
|
||||||
|
|
@ -266,7 +266,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
optionMenu.popoverPresentationController?.sourceRect = authSSHKeyCell.bounds
|
optionMenu.popoverPresentationController?.sourceRect = authSSHKeyCell.bounds
|
||||||
self.present(optionMenu, animated: true, completion: nil)
|
self.present(optionMenu, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
|
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var password: String?
|
var password: String?
|
||||||
|
|
@ -277,7 +277,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
case .ssh:
|
case .ssh:
|
||||||
message = "Please fill in the passphrase of your SSH key."
|
message = "Please fill in the passphrase of your SSH key."
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
SVProgressHUD.dismiss()
|
SVProgressHUD.dismiss()
|
||||||
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||||
|
|
@ -295,7 +295,7 @@ class GitServerSettingTableViewController: UITableViewController {
|
||||||
})
|
})
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = sem.wait(timeout: .distantFuture)
|
let _ = sem.wait(timeout: .distantFuture)
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
||||||
"https://github.com/SVProgressHUD/SVProgressHUD",
|
"https://github.com/SVProgressHUD/SVProgressHUD",
|
||||||
"https://github.com/SVProgressHUD/SVProgressHUD/blob/master/LICENSE.txt"],
|
"https://github.com/SVProgressHUD/SVProgressHUD/blob/master/LICENSE.txt"],
|
||||||
]
|
]
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
tableData.append([])
|
tableData.append([])
|
||||||
for item in openSourceComponents {
|
for item in openSourceComponents {
|
||||||
|
|
@ -43,7 +43,7 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
|
||||||
}
|
}
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func actOnDetailDisclosureButton(_ sender: Any?) {
|
@objc func actOnDetailDisclosureButton(_ sender: Any?) {
|
||||||
if let link = sender as? String {
|
if let link = sender as? String {
|
||||||
let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false)
|
let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false)
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,10 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
|
||||||
@IBOutlet weak var scanPublicKeyCell: UITableViewCell!
|
@IBOutlet weak var scanPublicKeyCell: UITableViewCell!
|
||||||
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
|
||||||
|
|
||||||
var pgpPassphrase: String?
|
var pgpPassphrase: String?
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
class ScannedPGPKey {
|
class ScannedPGPKey {
|
||||||
static let maxNumberOfGif = 100
|
static let maxNumberOfGif = 100
|
||||||
enum KeyType {
|
enum KeyType {
|
||||||
|
|
@ -30,7 +30,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
var message = ""
|
var message = ""
|
||||||
var hasStarted = false
|
var hasStarted = false
|
||||||
var isDone = false
|
var isDone = false
|
||||||
|
|
||||||
func reset(keytype: KeyType) {
|
func reset(keytype: KeyType) {
|
||||||
self.keyType = keytype
|
self.keyType = keytype
|
||||||
numberOfSegments = 0
|
numberOfSegments = 0
|
||||||
|
|
@ -40,14 +40,14 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
hasStarted = false
|
hasStarted = false
|
||||||
isDone = false
|
isDone = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSegment(segment: String) {
|
func addSegment(segment: String) {
|
||||||
// skip duplicated segments
|
// skip duplicated segments
|
||||||
guard segment != previousSegment else {
|
guard segment != previousSegment else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
previousSegment = segment
|
previousSegment = segment
|
||||||
|
|
||||||
// check whether we have found the first block
|
// check whether we have found the first block
|
||||||
if hasStarted == false {
|
if hasStarted == false {
|
||||||
let findPublic = segment.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
let findPublic = segment.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
|
||||||
|
|
@ -68,32 +68,32 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
guard hasStarted == true else {
|
guard hasStarted == true else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// check the number of segments
|
// check the number of segments
|
||||||
numberOfSegments = numberOfSegments + 1
|
numberOfSegments = numberOfSegments + 1
|
||||||
guard numberOfSegments <= ScannedPGPKey.maxNumberOfGif else {
|
guard numberOfSegments <= ScannedPGPKey.maxNumberOfGif else {
|
||||||
key = "Too many QR codes"
|
key = "Too many QR codes"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// update full text and check whether we are done
|
// update full text and check whether we are done
|
||||||
key.append(segment)
|
key.append(segment)
|
||||||
if key.contains("-----END PGP PUBLIC KEY BLOCK-----") || key.contains("-----END PGP PRIVATE KEY BLOCK-----") {
|
if key.contains("-----END PGP PUBLIC KEY BLOCK-----") || key.contains("-----END PGP PRIVATE KEY BLOCK-----") {
|
||||||
isDone = true
|
isDone = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// update message
|
// update message
|
||||||
message = "\(numberOfSegments) scanned QR codes."
|
message = "\(numberOfSegments) scanned QR codes."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var scanned = ScannedPGPKey()
|
var scanned = ScannedPGPKey()
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
armorPublicKeyTextView.text = SharedDefaults[.pgpPublicKeyArmor]
|
armorPublicKeyTextView.text = SharedDefaults[.pgpPublicKeyArmor]
|
||||||
armorPrivateKeyTextView.text = SharedDefaults[.pgpPrivateKeyArmor]
|
armorPrivateKeyTextView.text = SharedDefaults[.pgpPrivateKeyArmor]
|
||||||
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
||||||
|
|
||||||
scanPublicKeyCell?.textLabel?.text = "Scan Public Key QR Codes"
|
scanPublicKeyCell?.textLabel?.text = "Scan Public Key QR Codes"
|
||||||
scanPublicKeyCell?.textLabel?.textColor = Globals.blue
|
scanPublicKeyCell?.textLabel?.textColor = Globals.blue
|
||||||
scanPublicKeyCell?.selectionStyle = .default
|
scanPublicKeyCell?.selectionStyle = .default
|
||||||
|
|
@ -104,7 +104,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
scanPrivateKeyCell?.selectionStyle = .default
|
scanPrivateKeyCell?.selectionStyle = .default
|
||||||
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func save(_ sender: Any) {
|
@IBAction func save(_ sender: Any) {
|
||||||
guard armorPublicKeyTextView.text.isEmpty == false else {
|
guard armorPublicKeyTextView.text.isEmpty == false else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please set public key first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please set public key first.", controller: self, completion: nil)
|
||||||
|
|
@ -138,7 +138,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
})
|
})
|
||||||
self.present(savePassphraseAlert, animated: true, completion: nil)
|
self.present(savePassphraseAlert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
|
||||||
if text == UIPasteboard.general.string {
|
if text == UIPasteboard.general.string {
|
||||||
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
// user pastes something, do the copy here again and clear the pasteboard in 45s
|
||||||
|
|
@ -146,7 +146,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let selectedCell = tableView.cellForRow(at: indexPath)
|
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||||
if selectedCell == scanPublicKeyCell {
|
if selectedCell == scanPublicKeyCell {
|
||||||
|
|
@ -158,7 +158,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
}
|
}
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||||
scanned.addSegment(segment: line)
|
scanned.addSegment(segment: line)
|
||||||
|
|
@ -168,7 +168,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
return (accept: false, message: scanned.message)
|
return (accept: false, message: scanned.message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func handleScannedOutput(line: String) {
|
func handleScannedOutput(line: String) {
|
||||||
switch scanned.keyType {
|
switch scanned.keyType {
|
||||||
|
|
@ -178,7 +178,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
|
||||||
armorPrivateKeyTextView.text = scanned.key
|
armorPrivateKeyTextView.text = scanned.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
if segue.identifier == "showPGPScannerSegue" {
|
if segue.identifier == "showPGPScannerSegue" {
|
||||||
if let navController = segue.destination as? UINavigationController {
|
if let navController = segue.destination as? UINavigationController {
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ class PGPKeySettingTableViewController: UITableViewController {
|
||||||
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
|
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
|
||||||
var pgpPassphrase: String?
|
var pgpPassphrase: String?
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
tableView.rowHeight = UITableViewAutomaticDimension
|
tableView.rowHeight = UITableViewAutomaticDimension
|
||||||
|
|
@ -23,7 +23,7 @@ class PGPKeySettingTableViewController: UITableViewController {
|
||||||
pgpPrivateKeyURLTextField.text = SharedDefaults[.pgpPrivateKeyURL]?.absoluteString
|
pgpPrivateKeyURLTextField.text = SharedDefaults[.pgpPrivateKeyURL]?.absoluteString
|
||||||
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
pgpPassphrase = passwordStore.pgpKeyPassphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
private func validatePGPKeyURL(input: String?) -> Bool {
|
private func validatePGPKeyURL(input: String?) -> Bool {
|
||||||
guard let path = input, let url = URL(string: path) else {
|
guard let path = input, let url = URL(string: path) else {
|
||||||
Utils.alert(title: "Cannot Save PGP Key", message: "Please set PGP Key URL first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save PGP Key", message: "Please set PGP Key URL first.", controller: self, completion: nil)
|
||||||
|
|
@ -35,7 +35,7 @@ class PGPKeySettingTableViewController: UITableViewController {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func save(_ sender: Any) {
|
@IBAction func save(_ sender: Any) {
|
||||||
guard validatePGPKeyURL(input: pgpPublicKeyURLTextField.text) == true,
|
guard validatePGPKeyURL(input: pgpPublicKeyURLTextField.text) == true,
|
||||||
validatePGPKeyURL(input: pgpPrivateKeyURLTextField.text) == true else {
|
validatePGPKeyURL(input: pgpPrivateKeyURLTextField.text) == true else {
|
||||||
|
|
@ -65,6 +65,6 @@ class PGPKeySettingTableViewController: UITableViewController {
|
||||||
})
|
})
|
||||||
self.present(savePassphraseAlert, animated: true, completion: nil)
|
self.present(savePassphraseAlert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
private var oneTimePasswordIndexPath : IndexPath?
|
private var oneTimePasswordIndexPath : IndexPath?
|
||||||
private var shouldPopCurrentView = false
|
private var shouldPopCurrentView = false
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
private lazy var editUIBarButtonItem: UIBarButtonItem = {
|
private lazy var editUIBarButtonItem: UIBarButtonItem = {
|
||||||
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
|
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
|
||||||
return uiBarButtonItem
|
return uiBarButtonItem
|
||||||
|
|
@ -31,18 +31,18 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
title = ""
|
title = ""
|
||||||
content = ""
|
content = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
init(title: String) {
|
init(title: String) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.content = ""
|
self.content = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
init(title: String, content: String) {
|
init(title: String, content: String) {
|
||||||
self.title = title
|
self.title = title
|
||||||
self.content = content
|
self.content = content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct TableSection {
|
private struct TableSection {
|
||||||
var type: PasswordDetailTableViewControllerSectionType
|
var type: PasswordDetailTableViewControllerSectionType
|
||||||
var header: String?
|
var header: String?
|
||||||
|
|
@ -52,56 +52,56 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
header = nil
|
header = nil
|
||||||
item = [TableCell]()
|
item = [TableCell]()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(type: PasswordDetailTableViewControllerSectionType, header: String) {
|
init(type: PasswordDetailTableViewControllerSectionType, header: String) {
|
||||||
self.init(type: type)
|
self.init(type: type)
|
||||||
self.header = header
|
self.header = header
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private var tableData = Array<TableSection>()
|
private var tableData = Array<TableSection>()
|
||||||
|
|
||||||
private enum PasswordDetailTableViewControllerSectionType {
|
private enum PasswordDetailTableViewControllerSectionType {
|
||||||
case name, main, addition, misc
|
case name, main, addition, misc
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
|
||||||
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
|
||||||
|
|
||||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
|
||||||
tapGesture.cancelsTouchesInView = false
|
tapGesture.cancelsTouchesInView = false
|
||||||
tableView.addGestureRecognizer(tapGesture)
|
tableView.addGestureRecognizer(tapGesture)
|
||||||
tapGesture.delegate = self
|
tapGesture.delegate = self
|
||||||
|
|
||||||
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 44, 0);
|
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 44, 0);
|
||||||
tableView.rowHeight = UITableViewAutomaticDimension
|
tableView.rowHeight = UITableViewAutomaticDimension
|
||||||
tableView.estimatedRowHeight = 52
|
tableView.estimatedRowHeight = 52
|
||||||
|
|
||||||
editUIBarButtonItem.isEnabled = false
|
editUIBarButtonItem.isEnabled = false
|
||||||
navigationItem.rightBarButtonItem = editUIBarButtonItem
|
navigationItem.rightBarButtonItem = editUIBarButtonItem
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
navigationItem.largeTitleDisplayMode = .never
|
navigationItem.largeTitleDisplayMode = .never
|
||||||
}
|
}
|
||||||
|
|
||||||
if let imageData = passwordEntity?.getImage() {
|
if let imageData = passwordEntity?.getImage() {
|
||||||
let image = UIImage(data: imageData as Data)
|
let image = UIImage(data: imageData as Data)
|
||||||
passwordImage = image
|
passwordImage = image
|
||||||
}
|
}
|
||||||
self.decryptThenShowPassword()
|
self.decryptThenShowPassword()
|
||||||
self.setupOneTimePasswordAutoRefresh()
|
self.setupOneTimePasswordAutoRefresh()
|
||||||
|
|
||||||
// pop the current view because this password might be "discarded"
|
// pop the current view because this password might be "discarded"
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
|
||||||
|
|
||||||
// reset the data table if some password (maybe another one) has been updated
|
// reset the data table if some password (maybe another one) has been updated
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordStoreUpdated, object: nil)
|
||||||
|
|
||||||
// reset the data table if the disaply settings have been changed
|
// reset the data table if the disaply settings have been changed
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordDetailDisplaySettingChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordDetailDisplaySettingChanged, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
if self.shouldPopCurrentView {
|
if self.shouldPopCurrentView {
|
||||||
|
|
@ -112,7 +112,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestPGPKeyPassphrase() -> String {
|
private func requestPGPKeyPassphrase() -> String {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var passphrase = ""
|
var passphrase = ""
|
||||||
|
|
@ -134,7 +134,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
return passphrase
|
return passphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func decryptThenShowPassword() {
|
@objc private func decryptThenShowPassword() {
|
||||||
guard let passwordEntity = passwordEntity else {
|
guard let passwordEntity = passwordEntity else {
|
||||||
Utils.alert(title: "Cannot Show Password", message: "The password does not exist.", controller: self, handler: {(UIAlertAction) -> Void in
|
Utils.alert(title: "Cannot Show Password", message: "The password does not exist.", controller: self, handler: {(UIAlertAction) -> Void in
|
||||||
|
|
@ -166,7 +166,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
self.showPassword()
|
self.showPassword()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func showPassword() {
|
private func showPassword() {
|
||||||
DispatchQueue.main.async { [weak self] in
|
DispatchQueue.main.async { [weak self] in
|
||||||
self?.setTableData()
|
self?.setTableData()
|
||||||
|
|
@ -179,7 +179,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupOneTimePasswordAutoRefresh() {
|
private func setupOneTimePasswordAutoRefresh() {
|
||||||
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
|
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
|
||||||
[weak self] timer in
|
[weak self] timer in
|
||||||
|
|
@ -204,19 +204,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func pressEdit(_ sender: Any?) {
|
@objc private func pressEdit(_ sender: Any?) {
|
||||||
performSegue(withIdentifier: "editPasswordSegue", sender: self)
|
performSegue(withIdentifier: "editPasswordSegue", sender: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func setShouldPopCurrentView() {
|
@objc private func setShouldPopCurrentView() {
|
||||||
self.shouldPopCurrentView = true
|
self.shouldPopCurrentView = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
|
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
|
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
|
||||||
if self.password!.changed != 0 {
|
if self.password!.changed != 0 {
|
||||||
SVProgressHUD.show(withStatus: "Saving")
|
SVProgressHUD.show(withStatus: "Saving")
|
||||||
|
|
@ -231,7 +231,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
SVProgressHUD.dismiss(withDelay: 1)
|
SVProgressHUD.dismiss(withDelay: 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
|
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
|
||||||
do {
|
do {
|
||||||
try passwordStore.delete(passwordEntity: passwordEntity!)
|
try passwordStore.delete(passwordEntity: passwordEntity!)
|
||||||
|
|
@ -243,7 +243,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
|
|
||||||
private func setTableData() {
|
private func setTableData() {
|
||||||
self.tableData = Array<TableSection>()
|
self.tableData = Array<TableSection>()
|
||||||
|
|
||||||
// name section
|
// name section
|
||||||
var section = TableSection(type: .name)
|
var section = TableSection(type: .name)
|
||||||
section.item.append(TableCell())
|
section.item.append(TableCell())
|
||||||
|
|
@ -261,9 +261,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
section.item.append(TableCell(title: "password", content: password.password))
|
section.item.append(TableCell(title: "password", content: password.password))
|
||||||
tableData.append(section)
|
tableData.append(section)
|
||||||
|
|
||||||
|
|
||||||
// addition section
|
// addition section
|
||||||
|
|
||||||
// show one time password
|
// show one time password
|
||||||
if password.otpType != .none {
|
if password.otpType != .none {
|
||||||
if let (title, otp) = self.password?.getOtpStrings() {
|
if let (title, otp) = self.password?.getOtpStrings() {
|
||||||
|
|
@ -273,7 +273,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
oneTimePasswordIndexPath = IndexPath(row: 0, section: tableData.count - 1)
|
oneTimePasswordIndexPath = IndexPath(row: 0, section: tableData.count - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// show additional information
|
// show additional information
|
||||||
let filteredAdditionKeys = password.getFilteredAdditions()
|
let filteredAdditionKeys = password.getFilteredAdditions()
|
||||||
if filteredAdditionKeys.count > 0 {
|
if filteredAdditionKeys.count > 0 {
|
||||||
|
|
@ -283,14 +283,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
})
|
})
|
||||||
tableData.append(section)
|
tableData.append(section)
|
||||||
}
|
}
|
||||||
|
|
||||||
// misc section
|
// misc section
|
||||||
section = TableSection(type: .misc)
|
section = TableSection(type: .misc)
|
||||||
section.item.append(TableCell(title: "Show Raw"))
|
section.item.append(TableCell(title: "Show Raw"))
|
||||||
tableData.append(section)
|
tableData.append(section)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
if segue.identifier == "editPasswordSegue" {
|
if segue.identifier == "editPasswordSegue" {
|
||||||
if let controller = segue.destination as? UINavigationController {
|
if let controller = segue.destination as? UINavigationController {
|
||||||
|
|
@ -306,7 +306,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePasswordImage(urlString: String) {
|
private func updatePasswordImage(urlString: String) {
|
||||||
var newUrlString = urlString
|
var newUrlString = urlString
|
||||||
if urlString.lowercased().hasPrefix("http://") {
|
if urlString.lowercased().hasPrefix("http://") {
|
||||||
|
|
@ -321,7 +321,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
// if a url does not start with http or https, try to add https
|
// if a url does not start with http or https, try to add https
|
||||||
newUrlString = "https://\(urlString)"
|
newUrlString = "https://\(urlString)"
|
||||||
}
|
}
|
||||||
|
|
||||||
try? FavIcon.downloadPreferred(newUrlString) { [weak self] result in
|
try? FavIcon.downloadPreferred(newUrlString) { [weak self] result in
|
||||||
if case let .success(image) = result {
|
if case let .success(image) = result {
|
||||||
let indexPath = IndexPath(row: 0, section: 0)
|
let indexPath = IndexPath(row: 0, section: 0)
|
||||||
|
|
@ -334,7 +334,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func tapMenu(recognizer: UITapGestureRecognizer) {
|
@objc private func tapMenu(recognizer: UITapGestureRecognizer) {
|
||||||
if recognizer.state == UIGestureRecognizerState.ended {
|
if recognizer.state == UIGestureRecognizerState.ended {
|
||||||
let tapLocation = recognizer.location(in: self.tableView)
|
let tapLocation = recognizer.location(in: self.tableView)
|
||||||
|
|
@ -353,17 +353,17 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
|
||||||
if touch.view!.isKind(of: UIButton.classForCoder()) {
|
if touch.view!.isKind(of: UIButton.classForCoder()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func back(segue:UIStoryboardSegue) {
|
@IBAction func back(segue:UIStoryboardSegue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNextHOTP() {
|
func getNextHOTP() {
|
||||||
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
|
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
|
|
@ -371,12 +371,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy HOTP to pasteboard (will update counter)
|
// copy HOTP to pasteboard (will update counter)
|
||||||
if let plainPassword = password!.getNextHotp() {
|
if let plainPassword = password!.getNextHotp() {
|
||||||
SecurePasteboard.shared.copy(textToCopy: plainPassword)
|
SecurePasteboard.shared.copy(textToCopy: plainPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
// commit the change of HOTP counter
|
// commit the change of HOTP counter
|
||||||
if password!.changed != 0 {
|
if password!.changed != 0 {
|
||||||
do {
|
do {
|
||||||
|
|
@ -465,11 +465,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
detailTextLabel.textColor = .gray
|
detailTextLabel.textColor = .gray
|
||||||
detailTextLabel.text = "\(numberOfHiddenFields) hidden field\(numberOfHiddenFields > 1 ? "s" : "")"
|
detailTextLabel.text = "\(numberOfHiddenFields) hidden field\(numberOfHiddenFields > 1 ? "s" : "")"
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
return tableData[section].header
|
return tableData[section].header
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
|
||||||
if section == tableData.count - 1 {
|
if section == tableData.count - 1 {
|
||||||
let view = UIView()
|
let view = UIView()
|
||||||
|
|
@ -484,13 +484,13 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
|
override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
|
||||||
if action == #selector(copy(_:)) {
|
if action == #selector(copy(_:)) {
|
||||||
SecurePasteboard.shared.copy(textToCopy: tableData[indexPath.section].item[indexPath.row].content)
|
SecurePasteboard.shared.copy(textToCopy: tableData[indexPath.section].item[indexPath.row].content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
|
override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
|
||||||
let section = tableData[indexPath.section]
|
let section = tableData[indexPath.section]
|
||||||
switch(section.type) {
|
switch(section.type) {
|
||||||
|
|
@ -500,11 +500,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
|
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let section = tableData[indexPath.section]
|
let section = tableData[indexPath.section]
|
||||||
if section.type == .misc {
|
if section.type == .misc {
|
||||||
|
|
|
||||||
|
|
@ -20,21 +20,21 @@ enum PasswordEditorCellKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate, UITextFieldDelegate, UITextViewDelegate, SFSafariViewControllerDelegate {
|
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate, UITextFieldDelegate, UITextViewDelegate, SFSafariViewControllerDelegate {
|
||||||
|
|
||||||
var tableData = [
|
var tableData = [
|
||||||
[Dictionary<PasswordEditorCellKey, Any>]
|
[Dictionary<PasswordEditorCellKey, Any>]
|
||||||
]()
|
]()
|
||||||
var password: Password?
|
var password: Password?
|
||||||
|
|
||||||
private var navigationItemTitle: String?
|
private var navigationItemTitle: String?
|
||||||
|
|
||||||
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
|
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
|
||||||
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
|
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
|
||||||
private let nameSection = 0
|
private let nameSection = 0
|
||||||
private let passwordSection = 1
|
private let passwordSection = 1
|
||||||
private let additionsSection = 2
|
private let additionsSection = 2
|
||||||
private var hidePasswordSettings = true
|
private var hidePasswordSettings = true
|
||||||
|
|
||||||
var nameCell: TextFieldTableViewCell?
|
var nameCell: TextFieldTableViewCell?
|
||||||
var fillPasswordCell: FillPasswordTableViewCell?
|
var fillPasswordCell: FillPasswordTableViewCell?
|
||||||
private var passwordLengthCell: SliderTableViewCell?
|
private var passwordLengthCell: SliderTableViewCell?
|
||||||
|
|
@ -42,39 +42,39 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
private var deletePasswordCell: UITableViewCell?
|
private var deletePasswordCell: UITableViewCell?
|
||||||
private var scanQRCodeCell: UITableViewCell?
|
private var scanQRCodeCell: UITableViewCell?
|
||||||
private var memorablePasswordGeneratorCell: UITableViewCell?
|
private var memorablePasswordGeneratorCell: UITableViewCell?
|
||||||
|
|
||||||
override func loadView() {
|
override func loadView() {
|
||||||
super.loadView()
|
super.loadView()
|
||||||
|
|
||||||
deletePasswordCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
deletePasswordCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
||||||
deletePasswordCell!.textLabel?.text = "Delete Password"
|
deletePasswordCell!.textLabel?.text = "Delete Password"
|
||||||
deletePasswordCell!.textLabel?.textColor = Globals.red
|
deletePasswordCell!.textLabel?.textColor = Globals.red
|
||||||
deletePasswordCell?.selectionStyle = .default
|
deletePasswordCell?.selectionStyle = .default
|
||||||
|
|
||||||
scanQRCodeCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
scanQRCodeCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
||||||
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
|
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
|
||||||
scanQRCodeCell?.textLabel?.textColor = Globals.blue
|
scanQRCodeCell?.textLabel?.textColor = Globals.blue
|
||||||
scanQRCodeCell?.selectionStyle = .default
|
scanQRCodeCell?.selectionStyle = .default
|
||||||
scanQRCodeCell?.accessoryType = .disclosureIndicator
|
scanQRCodeCell?.accessoryType = .disclosureIndicator
|
||||||
|
|
||||||
memorablePasswordGeneratorCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
memorablePasswordGeneratorCell = UITableViewCell(style: .default, reuseIdentifier: "default")
|
||||||
memorablePasswordGeneratorCell?.textLabel?.text = "Get a Memorable One: xkpasswd"
|
memorablePasswordGeneratorCell?.textLabel?.text = "Get a Memorable One: xkpasswd"
|
||||||
memorablePasswordGeneratorCell?.textLabel?.textColor = Globals.blue
|
memorablePasswordGeneratorCell?.textLabel?.textColor = Globals.blue
|
||||||
memorablePasswordGeneratorCell?.selectionStyle = .default
|
memorablePasswordGeneratorCell?.selectionStyle = .default
|
||||||
memorablePasswordGeneratorCell?.accessoryType = .disclosureIndicator
|
memorablePasswordGeneratorCell?.accessoryType = .disclosureIndicator
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
if navigationItemTitle != nil {
|
if navigationItemTitle != nil {
|
||||||
navigationItem.title = navigationItemTitle
|
navigationItem.title = navigationItemTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
|
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
|
||||||
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
|
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
|
||||||
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
|
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
|
||||||
tableView.register(UINib(nibName: "SliderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordLengthCell")
|
tableView.register(UINib(nibName: "SliderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordLengthCell")
|
||||||
|
|
||||||
tableView.rowHeight = UITableViewAutomaticDimension
|
tableView.rowHeight = UITableViewAutomaticDimension
|
||||||
tableView.estimatedRowHeight = 48
|
tableView.estimatedRowHeight = 48
|
||||||
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
|
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
|
||||||
|
|
@ -83,10 +83,10 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
override func viewDidLayoutSubviews() {
|
override func viewDidLayoutSubviews() {
|
||||||
additionsCell?.contentTextView.setContentOffset(.zero, animated: false)
|
additionsCell?.contentTextView.setContentOffset(.zero, animated: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cellData = tableData[indexPath.section][indexPath.row]
|
let cellData = tableData[indexPath.section][indexPath.row]
|
||||||
|
|
||||||
switch cellData[PasswordEditorCellKey.type] as! PasswordEditorCellType {
|
switch cellData[PasswordEditorCellKey.type] as! PasswordEditorCellType {
|
||||||
case .nameCell:
|
case .nameCell:
|
||||||
nameCell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as? TextFieldTableViewCell
|
nameCell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as? TextFieldTableViewCell
|
||||||
|
|
@ -132,7 +132,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
return scanQRCodeCell!
|
return scanQRCodeCell!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||||
return 44
|
return 44
|
||||||
}
|
}
|
||||||
|
|
@ -153,11 +153,11 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
return sectionHeaderTitles[section]
|
return sectionHeaderTitles[section]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
|
||||||
return sectionFooterTitles[section]
|
return sectionFooterTitles[section]
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let selectedCell = tableView.cellForRow(at: indexPath)
|
let selectedCell = tableView.cellForRow(at: indexPath)
|
||||||
if selectedCell == deletePasswordCell {
|
if selectedCell == deletePasswordCell {
|
||||||
|
|
@ -175,12 +175,12 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
|
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
|
||||||
vc.delegate = self
|
vc.delegate = self
|
||||||
present(vc, animated: true)
|
present(vc, animated: true)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate password, copy to pasteboard, and set the cell
|
// generate password, copy to pasteboard, and set the cell
|
||||||
// check whether the current password looks like an OTP field
|
// check whether the current password looks like an OTP field
|
||||||
func generateAndCopyPassword() {
|
func generateAndCopyPassword() {
|
||||||
|
|
@ -195,23 +195,23 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
self.generateAndCopyPasswordNoOtpCheck()
|
self.generateAndCopyPasswordNoOtpCheck()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the password, don't care whether the original line is otp
|
// generate the password, don't care whether the original line is otp
|
||||||
func generateAndCopyPasswordNoOtpCheck() {
|
func generateAndCopyPasswordNoOtpCheck() {
|
||||||
// show password settings (e.g., the length slider)
|
// show password settings (e.g., the length slider)
|
||||||
showPasswordSettings()
|
showPasswordSettings()
|
||||||
|
|
||||||
let length = passwordLengthCell?.roundedValue ?? 0
|
let length = passwordLengthCell?.roundedValue ?? 0
|
||||||
let plainPassword = PasswordGeneratorFlavour.from(SharedDefaults[.passwordGeneratorFlavor]).generatePassword(length: length)
|
let plainPassword = PasswordGeneratorFlavour.from(SharedDefaults[.passwordGeneratorFlavor]).generatePassword(length: length)
|
||||||
SecurePasteboard.shared.copy(textToCopy: plainPassword)
|
SecurePasteboard.shared.copy(textToCopy: plainPassword)
|
||||||
|
|
||||||
// update tableData so to make sure reloadData() works correctly
|
// update tableData so to make sure reloadData() works correctly
|
||||||
tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword
|
tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword
|
||||||
|
|
||||||
// update cell manually, no need to call reloadData()
|
// update cell manually, no need to call reloadData()
|
||||||
fillPasswordCell?.setContent(content: plainPassword)
|
fillPasswordCell?.setContent(content: plainPassword)
|
||||||
}
|
}
|
||||||
|
|
||||||
// show password settings (e.g., the length slider)
|
// show password settings (e.g., the length slider)
|
||||||
func showPasswordSettings() {
|
func showPasswordSettings() {
|
||||||
if hidePasswordSettings == true {
|
if hidePasswordSettings == true {
|
||||||
|
|
@ -219,13 +219,13 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
tableView.reloadSections([passwordSection], with: .fade)
|
tableView.reloadSections([passwordSection], with: .fade)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// show/hide password settings (e.g., the length slider)
|
// show/hide password settings (e.g., the length slider)
|
||||||
func showHidePasswordSettings() {
|
func showHidePasswordSettings() {
|
||||||
hidePasswordSettings = !hidePasswordSettings
|
hidePasswordSettings = !hidePasswordSettings
|
||||||
tableView.reloadSections([passwordSection], with: .fade)
|
tableView.reloadSections([passwordSection], with: .fade)
|
||||||
}
|
}
|
||||||
|
|
||||||
func insertScannedOTPFields(_ otpauth: String) {
|
func insertScannedOTPFields(_ otpauth: String) {
|
||||||
// update tableData
|
// update tableData
|
||||||
var additionsString = ""
|
var additionsString = ""
|
||||||
|
|
@ -235,11 +235,11 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
additionsString = otpauth
|
additionsString = otpauth
|
||||||
}
|
}
|
||||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsString
|
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsString
|
||||||
|
|
||||||
// reload the additions cell
|
// reload the additions cell
|
||||||
additionsCell?.setContent(content: additionsString)
|
additionsCell?.setContent(content: additionsString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
|
||||||
if let url = URL(string: line), let _ = Token(url: url) {
|
if let url = URL(string: line), let _ = Token(url: url) {
|
||||||
|
|
@ -248,12 +248,12 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
return (accept: false, message: "Invalid token URL")
|
return (accept: false, message: "Invalid token URL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - QRScannerControllerDelegate Methods
|
// MARK: - QRScannerControllerDelegate Methods
|
||||||
func handleScannedOutput(line: String) {
|
func handleScannedOutput(line: String) {
|
||||||
insertScannedOTPFields(line)
|
insertScannedOTPFields(line)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
if segue.identifier == "showQRScannerSegue" {
|
if segue.identifier == "showQRScannerSegue" {
|
||||||
if let navController = segue.destination as? UINavigationController {
|
if let navController = segue.destination as? UINavigationController {
|
||||||
|
|
@ -265,7 +265,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update tableData so to make sure reloadData() works correctly
|
// update tableData so to make sure reloadData() works correctly
|
||||||
func textFieldDidEndEditing(_ textField: UITextField) {
|
func textFieldDidEndEditing(_ textField: UITextField) {
|
||||||
if textField == nameCell?.contentTextField {
|
if textField == nameCell?.contentTextField {
|
||||||
|
|
@ -277,48 +277,48 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// update tableData so to make sure reloadData() works correctly
|
// update tableData so to make sure reloadData() works correctly
|
||||||
func textViewDidEndEditing(_ textView: UITextView) {
|
func textViewDidEndEditing(_ textView: UITextView) {
|
||||||
if textView == additionsCell?.contentTextView {
|
if textView == additionsCell?.contentTextView {
|
||||||
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent()
|
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func textFieldDidBeginEditing(_ textField: UITextField) {
|
func textFieldDidBeginEditing(_ textField: UITextField) {
|
||||||
if textField == fillPasswordCell?.contentTextField {
|
if textField == fillPasswordCell?.contentTextField {
|
||||||
// show password generation settings automatically
|
// show password generation settings automatically
|
||||||
showPasswordSettings()
|
showPasswordSettings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getNameURL() -> (String, URL) {
|
func getNameURL() -> (String, URL) {
|
||||||
let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
|
let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
|
||||||
let name = URL(string: encodedName)!.lastPathComponent
|
let name = URL(string: encodedName)!.lastPathComponent
|
||||||
let url = URL(string: encodedName)!.appendingPathExtension("gpg")
|
let url = URL(string: encodedName)!.appendingPathExtension("gpg")
|
||||||
return (name, url)
|
return (name, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkName() -> Bool {
|
func checkName() -> Bool {
|
||||||
// the name field should not be empty
|
// the name field should not be empty
|
||||||
guard let name = nameCell?.getContent(), name.isEmpty == false else {
|
guard let name = nameCell?.getContent(), name.isEmpty == false else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please fill in the name.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please fill in the name.", controller: self, completion: nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// the name should not start with /
|
// the name should not start with /
|
||||||
guard name.hasPrefix("/") == false else {
|
guard name.hasPrefix("/") == false else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please remove the prefix \"/\" from your password name.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please remove the prefix \"/\" from your password name.", controller: self, completion: nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// the name field should be a valid url
|
// the name field should be a valid url
|
||||||
guard let path = name.stringByAddingPercentEncodingForRFC3986(),
|
guard let path = name.stringByAddingPercentEncodingForRFC3986(),
|
||||||
var passwordURL = URL(string: path) else {
|
var passwordURL = URL(string: path) else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// check whether we can parse the filename (be consistent with PasswordStore::addPasswordEntities)
|
// check whether we can parse the filename (be consistent with PasswordStore::addPasswordEntities)
|
||||||
var previousPathLength = Int.max
|
var previousPathLength = Int.max
|
||||||
while passwordURL.path != "." {
|
while passwordURL.path != "." {
|
||||||
|
|
@ -329,10 +329,10 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
}
|
}
|
||||||
previousPathLength = passwordURL.path.count
|
previousPathLength = passwordURL.path.count
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
||||||
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter({ !$0.isEmpty })
|
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter({ !$0.isEmpty })
|
||||||
if copiedLinesSplit?.count ?? 0 > 0 {
|
if copiedLinesSplit?.count ?? 0 > 0 {
|
||||||
|
|
@ -353,5 +353,5 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
||||||
private var parentPasswordEntity: PasswordEntity? = nil
|
private var parentPasswordEntity: PasswordEntity? = nil
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
private var tapTabBarTime: TimeInterval = 0
|
private var tapTabBarTime: TimeInterval = 0
|
||||||
|
|
||||||
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
|
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
|
||||||
|
|
||||||
private var searchActive : Bool = false
|
private var searchActive : Bool = false
|
||||||
|
|
||||||
private lazy var searchController: UISearchController = {
|
private lazy var searchController: UISearchController = {
|
||||||
let uiSearchController = UISearchController(searchResultsController: nil)
|
let uiSearchController = UISearchController(searchResultsController: nil)
|
||||||
uiSearchController.searchResultsUpdater = self
|
uiSearchController.searchResultsUpdater = self
|
||||||
|
|
@ -59,7 +59,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
let backUIBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(self.backAction(_:)))
|
let backUIBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: self, action: #selector(self.backAction(_:)))
|
||||||
return backUIBarButtonItem
|
return backUIBarButtonItem
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var transitionFromRight: CATransition = {
|
private lazy var transitionFromRight: CATransition = {
|
||||||
let transition = CATransition()
|
let transition = CATransition()
|
||||||
transition.type = kCATransitionPush
|
transition.type = kCATransitionPush
|
||||||
|
|
@ -69,7 +69,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
transition.subtype = kCATransitionFromRight
|
transition.subtype = kCATransitionFromRight
|
||||||
return transition
|
return transition
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var transitionFromLeft: CATransition = {
|
private lazy var transitionFromLeft: CATransition = {
|
||||||
let transition = CATransition()
|
let transition = CATransition()
|
||||||
transition.type = kCATransitionPush
|
transition.type = kCATransitionPush
|
||||||
|
|
@ -81,7 +81,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
private func initPasswordsTableEntries(parent: PasswordEntity?) {
|
||||||
passwordsTableEntries.removeAll()
|
passwordsTableEntries.removeAll()
|
||||||
passwordsTableAllEntries.removeAll()
|
passwordsTableAllEntries.removeAll()
|
||||||
|
|
@ -102,9 +102,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
parentPasswordEntity = parent
|
parentPasswordEntity = parent
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
|
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
|
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
|
||||||
if let controller = segue.source as? AddPasswordTableViewController {
|
if let controller = segue.source as? AddPasswordTableViewController {
|
||||||
|
|
@ -127,7 +127,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncPasswords() {
|
private func syncPasswords() {
|
||||||
guard passwordStore.repositoryExisted() else {
|
guard passwordStore.repositoryExisted() else {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
|
||||||
|
|
@ -190,17 +190,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidAppear(_ animated: Bool) {
|
override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
if SharedDefaults[.isShowFolderOn] {
|
if SharedDefaults[.isShowFolderOn] {
|
||||||
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
|
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
|
||||||
} else {
|
} else {
|
||||||
searchController.searchBar.scopeButtonTitles = nil
|
searchController.searchBar.scopeButtonTitles = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
searchController.searchBar.delegate = self
|
searchController.searchBar.delegate = self
|
||||||
|
|
@ -220,22 +220,22 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
tableView.refreshControl = syncControl
|
tableView.refreshControl = syncControl
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
||||||
|
|
||||||
// initialize the password table
|
// initialize the password table
|
||||||
reloadTableView(parent: nil)
|
reloadTableView(parent: nil)
|
||||||
|
|
||||||
// reset the data table if some password (maybe another one) has been updated
|
// reset the data table if some password (maybe another one) has been updated
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordStoreUpdated, object: nil)
|
||||||
// reset the data table if the disaply settings have been changed
|
// reset the data table if the disaply settings have been changed
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordDisplaySettingChanged, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(actOnSearchNotification), name: .passwordSearch, object: nil)
|
||||||
|
|
||||||
// listen to the swipe back guesture
|
// listen to the swipe back guesture
|
||||||
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
|
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
|
||||||
swipeRight.direction = UISwipeGestureRecognizerDirection.right
|
swipeRight.direction = UISwipeGestureRecognizerDirection.right
|
||||||
self.view.addGestureRecognizer(swipeRight)
|
self.view.addGestureRecognizer(swipeRight)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
tabBarController!.delegate = self
|
tabBarController!.delegate = self
|
||||||
|
|
@ -243,7 +243,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
tableView.deselectRow(at: path, animated: false)
|
tableView.deselectRow(at: path, animated: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillLayoutSubviews() {
|
override func viewWillLayoutSubviews() {
|
||||||
super.viewWillLayoutSubviews()
|
super.viewWillLayoutSubviews()
|
||||||
guard #available(iOS 11, *) else {
|
guard #available(iOS 11, *) else {
|
||||||
|
|
@ -252,21 +252,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfSections(in tableView: UITableView) -> Int {
|
func numberOfSections(in tableView: UITableView) -> Int {
|
||||||
return sections.count
|
return sections.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return sections[section].entries.count
|
return sections[section].entries.count
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
|
||||||
longPressGestureRecognizer.minimumPressDuration = 0.6
|
longPressGestureRecognizer.minimumPressDuration = 0.6
|
||||||
if SharedDefaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
|
if SharedDefaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
|
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
if entry.passwordEntity!.synced {
|
if entry.passwordEntity!.synced {
|
||||||
cell.textLabel?.text = entry.title
|
cell.textLabel?.text = entry.title
|
||||||
|
|
@ -299,11 +299,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
||||||
return sections[indexPath.section].entries[indexPath.row]
|
return sections[indexPath.section].entries[indexPath.row]
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
if !entry.isDir {
|
if !entry.isDir {
|
||||||
|
|
@ -318,7 +318,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
reloadTableView(parent: entry.passwordEntity, anim: transitionFromRight)
|
reloadTableView(parent: entry.passwordEntity, anim: transitionFromRight)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
|
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
|
||||||
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
|
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
|
||||||
// swipe right -> swipe back
|
// swipe right -> swipe back
|
||||||
|
|
@ -327,7 +327,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func backAction(_ sender: Any?) {
|
@objc func backAction(_ sender: Any?) {
|
||||||
guard SharedDefaults[.isShowFolderOn] else { return }
|
guard SharedDefaults[.isShowFolderOn] else { return }
|
||||||
var anim: CATransition? = transitionFromLeft
|
var anim: CATransition? = transitionFromLeft
|
||||||
|
|
@ -336,7 +336,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
|
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
|
||||||
if gesture.state == UIGestureRecognizerState.began {
|
if gesture.state == UIGestureRecognizerState.began {
|
||||||
let touchPoint = gesture.location(in: tableView)
|
let touchPoint = gesture.location(in: tableView)
|
||||||
|
|
@ -345,7 +345,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||||
return sections[section].title
|
return sections[section].title
|
||||||
}
|
}
|
||||||
|
|
@ -357,11 +357,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
||||||
decryptThenCopyPassword(from: indexPath)
|
decryptThenCopyPassword(from: indexPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestPGPKeyPassphrase() -> String {
|
private func requestPGPKeyPassphrase() -> String {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var passphrase = ""
|
var passphrase = ""
|
||||||
|
|
@ -389,7 +389,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
return passphrase
|
return passphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
private func decryptThenCopyPassword(from indexPath: IndexPath) {
|
private func decryptThenCopyPassword(from indexPath: IndexPath) {
|
||||||
guard self.passwordStore.privateKey != nil else {
|
guard self.passwordStore.privateKey != nil else {
|
||||||
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
||||||
|
|
@ -418,39 +418,39 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func generateSections(item: [PasswordsTableEntry]) {
|
private func generateSections(item: [PasswordsTableEntry]) {
|
||||||
let collation = UILocalizedIndexedCollation.current()
|
let collation = UILocalizedIndexedCollation.current()
|
||||||
let sectionTitles = collation.sectionIndexTitles
|
let sectionTitles = collation.sectionIndexTitles
|
||||||
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
|
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
|
||||||
|
|
||||||
// initialize all sections
|
// initialize all sections
|
||||||
for i in 0..<sectionTitles.count {
|
for i in 0..<sectionTitles.count {
|
||||||
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
|
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// put entries into sections
|
// put entries into sections
|
||||||
for entry in item {
|
for entry in item {
|
||||||
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
||||||
newSections[sectionNumber].entries.append(entry)
|
newSections[sectionNumber].entries.append(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort each list and set sectionTitles
|
// sort each list and set sectionTitles
|
||||||
for i in 0..<sectionTitles.count {
|
for i in 0..<sectionTitles.count {
|
||||||
let entriesToSort = newSections[i].entries
|
let entriesToSort = newSections[i].entries
|
||||||
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
|
||||||
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
|
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
|
||||||
}
|
}
|
||||||
|
|
||||||
// only keep non-empty sections
|
// only keep non-empty sections
|
||||||
sections = newSections.filter {$0.entries.count > 0}
|
sections = newSections.filter {$0.entries.count > 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func actOnSearchNotification() {
|
@objc func actOnSearchNotification() {
|
||||||
searchController.searchBar.becomeFirstResponder()
|
searchController.searchBar.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
|
||||||
if identifier == "showPasswordDetail" {
|
if identifier == "showPasswordDetail" {
|
||||||
guard self.passwordStore.privateKey != nil else {
|
guard self.passwordStore.privateKey != nil else {
|
||||||
|
|
@ -487,7 +487,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
func filterContentForSearchText(searchText: String, scope: String = "All") {
|
||||||
switch scope {
|
switch scope {
|
||||||
case "All":
|
case "All":
|
||||||
|
|
@ -512,10 +512,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
|
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
|
||||||
// set navigation item
|
// set navigation item
|
||||||
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits
|
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits
|
||||||
|
|
@ -529,7 +529,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
} else {
|
} else {
|
||||||
navigationItem.leftBarButtonItem = nil
|
navigationItem.leftBarButtonItem = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the password table
|
// set the password table
|
||||||
generateSections(item: data)
|
generateSections(item: data)
|
||||||
if anim != nil {
|
if anim != nil {
|
||||||
|
|
@ -537,7 +537,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
tableView.reloadData()
|
tableView.reloadData()
|
||||||
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
|
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
|
||||||
|
|
||||||
// set the sync control title
|
// set the sync control title
|
||||||
let atribbutedTitle = "Last Synced: \(lastSyncedTimeString())"
|
let atribbutedTitle = "Last Synced: \(lastSyncedTimeString())"
|
||||||
syncControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
syncControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
|
||||||
|
|
@ -552,12 +552,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
formatter.timeStyle = .short
|
formatter.timeStyle = .short
|
||||||
return formatter.string(from: date)
|
return formatter.string(from: date)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func reloadTableView(parent: PasswordEntity?, anim: CAAnimation? = nil) {
|
private func reloadTableView(parent: PasswordEntity?, anim: CAAnimation? = nil) {
|
||||||
initPasswordsTableEntries(parent: parent)
|
initPasswordsTableEntries(parent: parent)
|
||||||
reloadTableView(data: passwordsTableEntries, anim: anim)
|
reloadTableView(data: passwordsTableEntries, anim: anim)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func actOnReloadTableViewRelatedNotification() {
|
@objc func actOnReloadTableViewRelatedNotification() {
|
||||||
DispatchQueue.main.async { [weak weakSelf = self] in
|
DispatchQueue.main.async { [weak weakSelf = self] in
|
||||||
guard let strongSelf = weakSelf else { return }
|
guard let strongSelf = weakSelf else { return }
|
||||||
|
|
@ -565,11 +565,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
strongSelf.reloadTableView(data: strongSelf.passwordsTableEntries)
|
strongSelf.reloadTableView(data: strongSelf.passwordsTableEntries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func handleRefresh(_ syncControl: UIRefreshControl) {
|
@objc func handleRefresh(_ syncControl: UIRefreshControl) {
|
||||||
syncPasswords()
|
syncPasswords()
|
||||||
}
|
}
|
||||||
|
|
||||||
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
||||||
if viewController == self.navigationController {
|
if viewController == self.navigationController {
|
||||||
let currentTime = Date().timeIntervalSince1970
|
let currentTime = Date().timeIntervalSince1970
|
||||||
|
|
@ -586,14 +586,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
backAction(self)
|
backAction(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
|
||||||
// update the default search scope
|
// update the default search scope
|
||||||
SharedDefaults[.isSearchDefaultAll] = searchController.searchBar.scopeButtonTitles![selectedScope] == "All"
|
SharedDefaults[.isSearchDefaultAll] = searchController.searchBar.scopeButtonTitles![selectedScope] == "All"
|
||||||
updateSearchResults(for: searchController)
|
updateSearchResults(for: searchController)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
|
||||||
// set the default search scope to "all"
|
// set the default search scope to "all"
|
||||||
if SharedDefaults[.isShowFolderOn] && SharedDefaults[.isSearchDefaultAll] {
|
if SharedDefaults[.isShowFolderOn] && SharedDefaults[.isSearchDefaultAll] {
|
||||||
|
|
@ -603,14 +603,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
|
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
|
||||||
// set the default search scope to "current"
|
// set the default search scope to "current"
|
||||||
searchController.searchBar.selectedScopeButtonIndex = 0
|
searchController.searchBar.selectedScopeButtonIndex = 0
|
||||||
updateSearchResults(for: searchController)
|
updateSearchResults(for: searchController)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
|
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var password: String?
|
var password: String?
|
||||||
|
|
@ -621,7 +621,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
case .ssh:
|
case .ssh:
|
||||||
message = "Please fill in the password of your SSH key."
|
message = "Please fill in the password of your SSH key."
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
SVProgressHUD.dismiss()
|
SVProgressHUD.dismiss()
|
||||||
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||||
|
|
@ -639,7 +639,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
})
|
})
|
||||||
self.present(alert, animated: true, completion: nil)
|
self.present(alert, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = sem.wait(timeout: .distantFuture)
|
let _ = sem.wait(timeout: .distantFuture)
|
||||||
return password
|
return password
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,90 +17,90 @@ protocol QRScannerControllerDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
|
||||||
|
|
||||||
@IBOutlet weak var scannerOutput: UILabel!
|
@IBOutlet weak var scannerOutput: UILabel!
|
||||||
|
|
||||||
var captureSession: AVCaptureSession?
|
var captureSession: AVCaptureSession?
|
||||||
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
|
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
|
||||||
var qrCodeFrameView: UIView?
|
var qrCodeFrameView: UIView?
|
||||||
|
|
||||||
let supportedCodeTypes = [AVMetadataObject.ObjectType.qr]
|
let supportedCodeTypes = [AVMetadataObject.ObjectType.qr]
|
||||||
|
|
||||||
var delegate: QRScannerControllerDelegate?
|
var delegate: QRScannerControllerDelegate?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
|
if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
|
||||||
presentCameraSettings()
|
presentCameraSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
|
// Get an instance of the AVCaptureDevice class to initialize a device object and provide the video as the media type parameter.
|
||||||
let captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
|
let captureDevice = AVCaptureDevice.default(for: AVMediaType.video)
|
||||||
do {
|
do {
|
||||||
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
|
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
|
||||||
let input = try AVCaptureDeviceInput(device: captureDevice!)
|
let input = try AVCaptureDeviceInput(device: captureDevice!)
|
||||||
|
|
||||||
// Initialize the captureSession object.
|
// Initialize the captureSession object.
|
||||||
captureSession = AVCaptureSession()
|
captureSession = AVCaptureSession()
|
||||||
|
|
||||||
// Set the input device on the capture session.
|
// Set the input device on the capture session.
|
||||||
captureSession?.addInput(input)
|
captureSession?.addInput(input)
|
||||||
|
|
||||||
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
|
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
|
||||||
let captureMetadataOutput = AVCaptureMetadataOutput()
|
let captureMetadataOutput = AVCaptureMetadataOutput()
|
||||||
captureSession?.addOutput(captureMetadataOutput)
|
captureSession?.addOutput(captureMetadataOutput)
|
||||||
|
|
||||||
// Set delegate and use the default dispatch queue to execute the call back
|
// Set delegate and use the default dispatch queue to execute the call back
|
||||||
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
|
||||||
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
|
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
|
||||||
|
|
||||||
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
|
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
|
||||||
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
|
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
|
||||||
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
|
||||||
videoPreviewLayer?.frame = view.layer.bounds
|
videoPreviewLayer?.frame = view.layer.bounds
|
||||||
view.layer.addSublayer(videoPreviewLayer!)
|
view.layer.addSublayer(videoPreviewLayer!)
|
||||||
|
|
||||||
// Start video capture.
|
// Start video capture.
|
||||||
captureSession?.startRunning()
|
captureSession?.startRunning()
|
||||||
|
|
||||||
// Move the message label to the front
|
// Move the message label to the front
|
||||||
scannerOutput.layer.cornerRadius = 10
|
scannerOutput.layer.cornerRadius = 10
|
||||||
scannerOutput.text = "No QR code detected"
|
scannerOutput.text = "No QR code detected"
|
||||||
view.bringSubview(toFront: scannerOutput)
|
view.bringSubview(toFront: scannerOutput)
|
||||||
|
|
||||||
// Initialize QR Code Frame to highlight the QR code
|
// Initialize QR Code Frame to highlight the QR code
|
||||||
qrCodeFrameView = UIView()
|
qrCodeFrameView = UIView()
|
||||||
|
|
||||||
if let qrCodeFrameView = qrCodeFrameView {
|
if let qrCodeFrameView = qrCodeFrameView {
|
||||||
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
|
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
|
||||||
qrCodeFrameView.layer.borderWidth = 2
|
qrCodeFrameView.layer.borderWidth = 2
|
||||||
view.addSubview(qrCodeFrameView)
|
view.addSubview(qrCodeFrameView)
|
||||||
view.bringSubview(toFront: qrCodeFrameView)
|
view.bringSubview(toFront: qrCodeFrameView)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
scannerOutput.text = error.localizedDescription
|
scannerOutput.text = error.localizedDescription
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func didReceiveMemoryWarning() {
|
override func didReceiveMemoryWarning() {
|
||||||
super.didReceiveMemoryWarning()
|
super.didReceiveMemoryWarning()
|
||||||
// Dispose of any resources that can be recreated.
|
// Dispose of any resources that can be recreated.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
|
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
|
||||||
|
|
||||||
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
|
||||||
|
|
||||||
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
|
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
|
||||||
supportedCodeTypes.contains(metadataObj.type),
|
supportedCodeTypes.contains(metadataObj.type),
|
||||||
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
|
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
|
||||||
|
|
||||||
// draw a bounds on the found QR code
|
// draw a bounds on the found QR code
|
||||||
qrCodeFrameView?.frame = barCodeObject.bounds
|
qrCodeFrameView?.frame = barCodeObject.bounds
|
||||||
|
|
||||||
// check whether it is a valid result
|
// check whether it is a valid result
|
||||||
if let scanned = metadataObj.stringValue {
|
if let scanned = metadataObj.stringValue {
|
||||||
if let (accept, message) = delegate?.checkScannedOutput(line: scanned) {
|
if let (accept, message) = delegate?.checkScannedOutput(line: scanned) {
|
||||||
|
|
@ -121,13 +121,13 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
|
||||||
} else {
|
} else {
|
||||||
scannerOutput.text = "No string value"
|
scannerOutput.text = "No string value"
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qrCodeFrameView?.frame = CGRect.zero
|
qrCodeFrameView?.frame = CGRect.zero
|
||||||
scannerOutput.text = "No QR code detected"
|
scannerOutput.text = "No QR code detected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentCameraSettings() {
|
func presentCameraSettings() {
|
||||||
let alertController = UIAlertController(title: "Error",
|
let alertController = UIAlertController(title: "Error",
|
||||||
message: "Camera access denied.\nWARNING: Toggle the camera permission resets the app! Save your changes.",
|
message: "Camera access denied.\nWARNING: Toggle the camera permission resets the app! Save your changes.",
|
||||||
|
|
@ -140,7 +140,7 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
present(alertController, animated: true)
|
present(alertController, animated: true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class RawPasswordViewController: UIViewController {
|
||||||
|
|
||||||
@IBOutlet weak var rawPasswordTextView: UITextView!
|
@IBOutlet weak var rawPasswordTextView: UITextView!
|
||||||
var password: Password?
|
var password: Password?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
navigationItem.title = password?.name
|
navigationItem.title = password?.name
|
||||||
|
|
|
||||||
|
|
@ -14,21 +14,21 @@ class SSHKeySettingTableViewController: UITableViewController {
|
||||||
|
|
||||||
@IBOutlet weak var privateKeyURLTextField: UITextField!
|
@IBOutlet weak var privateKeyURLTextField: UITextField!
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
privateKeyURLTextField.text = SharedDefaults[.gitSSHPrivateKeyURL]?.absoluteString
|
privateKeyURLTextField.text = SharedDefaults[.gitSSHPrivateKeyURL]?.absoluteString
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@IBAction func doneButtonTapped(_ sender: UIButton) {
|
@IBAction func doneButtonTapped(_ sender: UIButton) {
|
||||||
guard let privateKeyURL = URL(string: privateKeyURLTextField.text!.trimmed) else {
|
guard let privateKeyURL = URL(string: privateKeyURLTextField.text!.trimmed) else {
|
||||||
Utils.alert(title: "Cannot Save", message: "Please set Private Key URL first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Save", message: "Please set Private Key URL first.", controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedDefaults[.gitSSHPrivateKeyURL] = privateKeyURL
|
SharedDefaults[.gitSSHPrivateKeyURL] = privateKeyURL
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try Data(contentsOf: privateKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPrivateKeyPath), options: .atomic)
|
try Data(contentsOf: privateKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPrivateKeyPath), options: .atomic)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ class SettingsSplitViewController: UISplitViewController, UISplitViewControllerD
|
||||||
self.delegate = self
|
self.delegate = self
|
||||||
self.preferredDisplayMode = .allVisible
|
self.preferredDisplayMode = .allVisible
|
||||||
}
|
}
|
||||||
|
|
||||||
func splitViewController(
|
func splitViewController(
|
||||||
_ splitViewController: UISplitViewController,
|
_ splitViewController: UISplitViewController,
|
||||||
collapseSecondary secondaryViewController: UIViewController,
|
collapseSecondary secondaryViewController: UIViewController,
|
||||||
|
|
|
||||||
|
|
@ -16,14 +16,14 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
|
||||||
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
|
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
|
||||||
var setPasscodeLockAlert: UIAlertController?
|
var setPasscodeLockAlert: UIAlertController?
|
||||||
|
|
||||||
let passwordStore = PasswordStore.shared
|
let passwordStore = PasswordStore.shared
|
||||||
var passcodeLock = PasscodeLock.shared
|
var passcodeLock = PasscodeLock.shared
|
||||||
|
|
||||||
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
|
||||||
navigationController?.popViewController(animated: true)
|
navigationController?.popViewController(animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
|
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
|
||||||
if let controller = segue.source as? PGPKeySettingTableViewController {
|
if let controller = segue.source as? PGPKeySettingTableViewController {
|
||||||
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!.trimmed)
|
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!.trimmed)
|
||||||
|
|
@ -32,7 +32,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
|
||||||
}
|
}
|
||||||
SharedDefaults[.pgpKeySource] = "url"
|
SharedDefaults[.pgpKeySource] = "url"
|
||||||
|
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
SVProgressHUD.setDefaultStyle(.light)
|
SVProgressHUD.setDefaultStyle(.light)
|
||||||
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
||||||
|
|
@ -53,7 +53,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
|
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
|
||||||
SharedDefaults[.pgpKeySource] = "armor"
|
SharedDefaults[.pgpKeySource] = "armor"
|
||||||
if SharedDefaults[.isRememberPGPPassphraseOn] {
|
if SharedDefaults[.isRememberPGPPassphraseOn] {
|
||||||
|
|
@ -62,7 +62,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
|
|
||||||
SharedDefaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
SharedDefaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
|
||||||
SharedDefaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
|
SharedDefaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
|
||||||
|
|
||||||
SVProgressHUD.setDefaultMaskType(.black)
|
SVProgressHUD.setDefaultMaskType(.black)
|
||||||
SVProgressHUD.setDefaultStyle(.light)
|
SVProgressHUD.setDefaultStyle(.light)
|
||||||
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
SVProgressHUD.show(withStatus: "Fetching PGP Key")
|
||||||
|
|
@ -84,7 +84,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func saveImportedPGPKey() {
|
private func saveImportedPGPKey() {
|
||||||
// load keys
|
// load keys
|
||||||
SharedDefaults[.pgpKeySource] = "file"
|
SharedDefaults[.pgpKeySource] = "file"
|
||||||
|
|
@ -108,11 +108,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
|
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
|
||||||
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
|
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
return super.tableView(tableView, numberOfRowsInSection: section)
|
return super.tableView(tableView, numberOfRowsInSection: section)
|
||||||
}
|
}
|
||||||
|
|
@ -125,12 +125,12 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
setPasswordRepositoryTableViewCellDetailText()
|
setPasswordRepositoryTableViewCellDetailText()
|
||||||
setPasscodeLockCell()
|
setPasscodeLockCell()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(true)
|
super.viewWillAppear(true)
|
||||||
tabBarController!.delegate = self
|
tabBarController!.delegate = self
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setPasscodeLockCell() {
|
private func setPasscodeLockCell() {
|
||||||
if passcodeLock.hasPasscode {
|
if passcodeLock.hasPasscode {
|
||||||
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
self.passcodeTableViewCell.detailTextLabel?.text = "On"
|
||||||
|
|
@ -138,7 +138,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setPGPKeyTableViewCellDetailText() {
|
private func setPGPKeyTableViewCellDetailText() {
|
||||||
if let pgpKeyID = self.passwordStore.pgpKeyID {
|
if let pgpKeyID = self.passwordStore.pgpKeyID {
|
||||||
pgpKeyTableViewCell.detailTextLabel?.text = pgpKeyID
|
pgpKeyTableViewCell.detailTextLabel?.text = pgpKeyID
|
||||||
|
|
@ -146,7 +146,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setPasswordRepositoryTableViewCellDetailText() {
|
private func setPasswordRepositoryTableViewCellDetailText() {
|
||||||
if SharedDefaults[.gitURL] == nil {
|
if SharedDefaults[.gitURL] == nil {
|
||||||
passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set"
|
passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set"
|
||||||
|
|
@ -154,13 +154,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]!.host
|
passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]!.host
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func actOnPasswordStoreErasedNotification() {
|
@objc func actOnPasswordStoreErasedNotification() {
|
||||||
setPGPKeyTableViewCellDetailText()
|
setPGPKeyTableViewCellDetailText()
|
||||||
setPasswordRepositoryTableViewCellDetailText()
|
setPasswordRepositoryTableViewCellDetailText()
|
||||||
setPasscodeLockCell()
|
setPasscodeLockCell()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
|
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
|
||||||
if SharedDefaults[.passcodeKey] != nil{
|
if SharedDefaults[.passcodeKey] != nil{
|
||||||
|
|
@ -173,13 +173,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
tableView.deselectRow(at: indexPath, animated: true)
|
tableView.deselectRow(at: indexPath, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showPGPKeyActionSheet() {
|
func showPGPKeyActionSheet() {
|
||||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
var urlActionTitle = "Download from URL"
|
var urlActionTitle = "Download from URL"
|
||||||
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
var armorActionTitle = "ASCII-Armor Encrypted Key"
|
||||||
var fileActionTitle = "iTunes File Sharing"
|
var fileActionTitle = "iTunes File Sharing"
|
||||||
|
|
||||||
if SharedDefaults[.pgpKeySource] == "url" {
|
if SharedDefaults[.pgpKeySource] == "url" {
|
||||||
urlActionTitle = "✓ \(urlActionTitle)"
|
urlActionTitle = "✓ \(urlActionTitle)"
|
||||||
} else if SharedDefaults[.pgpKeySource] == "armor" {
|
} else if SharedDefaults[.pgpKeySource] == "armor" {
|
||||||
|
|
@ -235,8 +235,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
optionMenu.addAction(fileAction)
|
optionMenu.addAction(fileAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if SharedDefaults[.pgpKeySource] != nil {
|
if SharedDefaults[.pgpKeySource] != nil {
|
||||||
let deleteAction = UIAlertAction(title: "Remove PGP Keys", style: .destructive) { _ in
|
let deleteAction = UIAlertAction(title: "Remove PGP Keys", style: .destructive) { _ in
|
||||||
self.passwordStore.removePGPKeys()
|
self.passwordStore.removePGPKeys()
|
||||||
|
|
@ -249,12 +249,12 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
optionMenu.popoverPresentationController?.sourceRect = pgpKeyTableViewCell.bounds
|
optionMenu.popoverPresentationController?.sourceRect = pgpKeyTableViewCell.bounds
|
||||||
self.present(optionMenu, animated: true, completion: nil)
|
self.present(optionMenu, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func showPasscodeActionSheet() {
|
func showPasscodeActionSheet() {
|
||||||
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
|
||||||
let passcodeRemoveViewController = PasscodeLockViewController()
|
let passcodeRemoveViewController = PasscodeLockViewController()
|
||||||
|
|
||||||
|
|
||||||
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
|
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
|
||||||
passcodeRemoveViewController.successCallback = {
|
passcodeRemoveViewController.successCallback = {
|
||||||
self?.passcodeLock.delete()
|
self?.passcodeLock.delete()
|
||||||
|
|
@ -262,11 +262,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [weak self] _ in
|
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [weak self] _ in
|
||||||
self?.setPasscodeLock()
|
self?.setPasscodeLock()
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
optionMenu.addAction(removePasscodeAction)
|
optionMenu.addAction(removePasscodeAction)
|
||||||
optionMenu.addAction(changePasscodeAction)
|
optionMenu.addAction(changePasscodeAction)
|
||||||
|
|
@ -289,7 +289,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setPasscodeLock() {
|
func setPasscodeLock() {
|
||||||
// prepare the alert for setting the passcode
|
// prepare the alert for setting the passcode
|
||||||
setPasscodeLockAlert = UIAlertController(title: "Set passcode", message: "Fill in your passcode for Pass (at least 4 characters)", preferredStyle: .alert)
|
setPasscodeLockAlert = UIAlertController(title: "Set passcode", message: "Fill in your passcode for Pass (at least 4 characters)", preferredStyle: .alert)
|
||||||
|
|
@ -303,7 +303,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
textField.isSecureTextEntry = true
|
textField.isSecureTextEntry = true
|
||||||
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
|
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
|
||||||
})
|
})
|
||||||
|
|
||||||
// save action
|
// save action
|
||||||
let saveAction = UIAlertAction(title: "Save", style: .default) { (action:UIAlertAction) -> Void in
|
let saveAction = UIAlertAction(title: "Save", style: .default) { (action:UIAlertAction) -> Void in
|
||||||
let passcode: String = self.setPasscodeLockAlert!.textFields![0].text!
|
let passcode: String = self.setPasscodeLockAlert!.textFields![0].text!
|
||||||
|
|
@ -312,10 +312,10 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
|
||||||
self.setPasscodeLockCell()
|
self.setPasscodeLockCell()
|
||||||
}
|
}
|
||||||
saveAction.isEnabled = false // disable the Save button by default
|
saveAction.isEnabled = false // disable the Save button by default
|
||||||
|
|
||||||
// cancel action
|
// cancel action
|
||||||
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
|
||||||
|
|
||||||
// present
|
// present
|
||||||
setPasscodeLockAlert?.addAction(saveAction)
|
setPasscodeLockAlert?.addAction(saveAction)
|
||||||
setPasscodeLockAlert?.addAction(cancelAction)
|
setPasscodeLockAlert?.addAction(cancelAction)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
|
||||||
["FlatIcon",
|
["FlatIcon",
|
||||||
"https://www.flaticon.com"],
|
"https://www.flaticon.com"],
|
||||||
]
|
]
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
tableData.append([])
|
tableData.append([])
|
||||||
for item in openSourceComponents {
|
for item in openSourceComponents {
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,33 @@ import UIKit
|
||||||
class SecurePasteboard {
|
class SecurePasteboard {
|
||||||
public static let shared = SecurePasteboard()
|
public static let shared = SecurePasteboard()
|
||||||
private var backgroundTaskID = UIBackgroundTaskInvalid
|
private var backgroundTaskID = UIBackgroundTaskInvalid
|
||||||
|
|
||||||
func copy(textToCopy: String?, expirationTime: Double = 45) {
|
func copy(textToCopy: String?, expirationTime: Double = 45) {
|
||||||
// copy to the pasteboard
|
// copy to the pasteboard
|
||||||
UIPasteboard.general.string = textToCopy ?? ""
|
UIPasteboard.general.string = textToCopy ?? ""
|
||||||
|
|
||||||
// clean the pasteboard after expirationTime
|
// clean the pasteboard after expirationTime
|
||||||
guard expirationTime > 0 else {
|
guard expirationTime > 0 else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// exit the existing background task, if any
|
// exit the existing background task, if any
|
||||||
if backgroundTaskID != UIBackgroundTaskInvalid {
|
if backgroundTaskID != UIBackgroundTaskInvalid {
|
||||||
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
||||||
self.backgroundTaskID = UIBackgroundTaskInvalid
|
self.backgroundTaskID = UIBackgroundTaskInvalid
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
|
backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
|
||||||
UIPasteboard.general.string = ""
|
UIPasteboard.general.string = ""
|
||||||
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
||||||
self?.backgroundTaskID = UIBackgroundTaskInvalid
|
self?.backgroundTaskID = UIBackgroundTaskInvalid
|
||||||
})
|
})
|
||||||
|
|
||||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + expirationTime) { [weak self] in
|
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + expirationTime) { [weak self] in
|
||||||
UIPasteboard.general.string = ""
|
UIPasteboard.general.string = ""
|
||||||
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
|
||||||
self?.backgroundTaskID = UIBackgroundTaskInvalid
|
self?.backgroundTaskID = UIBackgroundTaskInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ class ContentTableViewCell: UITableViewCell {
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getContent() -> String? {
|
func getContent() -> String? {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setContent(content: String?) { }
|
func setContent(content: String?) { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ class FillPasswordTableViewCell: ContentTableViewCell {
|
||||||
|
|
||||||
@IBOutlet weak var contentTextField: UITextField!
|
@IBOutlet weak var contentTextField: UITextField!
|
||||||
var delegate: FillPasswordTableViewCellDelegate?
|
var delegate: FillPasswordTableViewCellDelegate?
|
||||||
|
|
||||||
@IBOutlet weak var settingButton: UIButton!
|
@IBOutlet weak var settingButton: UIButton!
|
||||||
@IBOutlet weak var generateButton: UIButton!
|
@IBOutlet weak var generateButton: UIButton!
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
// Initialization code
|
// Initialization code
|
||||||
contentTextField.font = Globals.passwordFont
|
contentTextField.font = Globals.passwordFont
|
||||||
|
|
||||||
// Force aspect ratio of button images
|
// Force aspect ratio of button images
|
||||||
settingButton.imageView?.contentMode = .scaleAspectFit
|
settingButton.imageView?.contentMode = .scaleAspectFit
|
||||||
generateButton.imageView?.contentMode = .scaleAspectFit
|
generateButton.imageView?.contentMode = .scaleAspectFit
|
||||||
|
|
@ -37,24 +37,24 @@ class FillPasswordTableViewCell: ContentTableViewCell {
|
||||||
|
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func generatePassword(_ sender: UIButton) {
|
@IBAction func generatePassword(_ sender: UIButton) {
|
||||||
self.delegate?.generateAndCopyPassword()
|
self.delegate?.generateAndCopyPassword()
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func showHidePasswordSettings() {
|
@IBAction func showHidePasswordSettings() {
|
||||||
self.delegate?.showHidePasswordSettings()
|
self.delegate?.showHidePasswordSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
// re-color
|
// re-color
|
||||||
@IBAction func textFieldDidChange(_ sender: UITextField) {
|
@IBAction func textFieldDidChange(_ sender: UITextField) {
|
||||||
contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
|
contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func getContent() -> String? {
|
override func getContent() -> String? {
|
||||||
return contentTextField.attributedText?.string
|
return contentTextField.attributedText?.string
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setContent(content: String?) {
|
override func setContent(content: String?) {
|
||||||
contentTextField.attributedText = Utils.attributedPassword(plainPassword: content ?? "")
|
contentTextField.attributedText = Utils.attributedPassword(plainPassword: content ?? "")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,19 +19,19 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@IBOutlet weak var contentLabel: UILabel!
|
@IBOutlet weak var contentLabel: UILabel!
|
||||||
@IBOutlet weak var titleLabel: UILabel!
|
@IBOutlet weak var titleLabel: UILabel!
|
||||||
|
|
||||||
private enum CellType {
|
private enum CellType {
|
||||||
case password, URL, HOTP, other
|
case password, URL, HOTP, other
|
||||||
}
|
}
|
||||||
|
|
||||||
private var type = CellType.other
|
private var type = CellType.other
|
||||||
private var isReveal = false
|
private var isReveal = false
|
||||||
|
|
||||||
weak var delegatePasswordTableView : PasswordDetailTableViewController?
|
weak var delegatePasswordTableView : PasswordDetailTableViewController?
|
||||||
|
|
||||||
private var passwordDisplayButton: UIButton?
|
private var passwordDisplayButton: UIButton?
|
||||||
private var buttons: UIView?
|
private var buttons: UIView?
|
||||||
|
|
||||||
var cellData: LabelTableViewCellData? {
|
var cellData: LabelTableViewCellData? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let title = cellData?.title, let content = cellData?.content else {
|
guard let title = cellData?.title, let content = cellData?.content else {
|
||||||
|
|
@ -72,7 +72,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
updateButtons()
|
updateButtons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override var canBecomeFirstResponder: Bool {
|
override var canBecomeFirstResponder: Bool {
|
||||||
get {
|
get {
|
||||||
return true
|
return true
|
||||||
|
|
@ -103,7 +103,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
override func copy(_ sender: Any?) {
|
override func copy(_ sender: Any?) {
|
||||||
SecurePasteboard.shared.copy(textToCopy: cellData?.content)
|
SecurePasteboard.shared.copy(textToCopy: cellData?.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func revealPassword(_ sender: Any?) {
|
@objc func revealPassword(_ sender: Any?) {
|
||||||
let plainPassword = cellData?.content ?? ""
|
let plainPassword = cellData?.content ?? ""
|
||||||
if type == .password {
|
if type == .password {
|
||||||
|
|
@ -114,7 +114,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
isReveal = true
|
isReveal = true
|
||||||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
|
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func concealPassword(_ sender: Any?) {
|
@objc func concealPassword(_ sender: Any?) {
|
||||||
if type == .password {
|
if type == .password {
|
||||||
if cellData?.content.isEmpty == false {
|
if cellData?.content.isEmpty == false {
|
||||||
|
|
@ -128,7 +128,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
isReveal = false
|
isReveal = false
|
||||||
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func reversePasswordDisplay(_ sender: Any?) {
|
@objc func reversePasswordDisplay(_ sender: Any?) {
|
||||||
if isReveal {
|
if isReveal {
|
||||||
// conceal
|
// conceal
|
||||||
|
|
@ -143,21 +143,21 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
// if isURLCell, passwordTableView should not be nil
|
// if isURLCell, passwordTableView should not be nil
|
||||||
delegatePasswordTableView!.openLink(to: cellData?.content)
|
delegatePasswordTableView!.openLink(to: cellData?.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func getNextHOTP(_ sender: Any?) {
|
@objc func getNextHOTP(_ sender: Any?) {
|
||||||
// if isHOTPCell, passwordTableView should not be nil
|
// if isHOTPCell, passwordTableView should not be nil
|
||||||
delegatePasswordTableView!.getNextHOTP()
|
delegatePasswordTableView!.getNextHOTP()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateButtons() {
|
private func updateButtons() {
|
||||||
// total width and height of a button
|
// total width and height of a button
|
||||||
let height = min(self.bounds.height, 36.0)
|
let height = min(self.bounds.height, 36.0)
|
||||||
let width = max(height * 0.8, Globals.tableCellButtonSize)
|
let width = max(height * 0.8, Globals.tableCellButtonSize)
|
||||||
|
|
||||||
// margins (between button boundary and icon)
|
// margins (between button boundary and icon)
|
||||||
let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0)
|
let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0)
|
||||||
let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
|
let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
|
||||||
|
|
||||||
switch type {
|
switch type {
|
||||||
case .password:
|
case .password:
|
||||||
if let content = cellData?.content, content != "" {
|
if let content = cellData?.content, content != "" {
|
||||||
|
|
@ -178,7 +178,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
nextButton.imageView?.contentMode = .scaleAspectFit
|
nextButton.imageView?.contentMode = .scaleAspectFit
|
||||||
nextButton.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
nextButton.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
||||||
nextButton.addTarget(self, action: #selector(getNextHOTP), for: UIControlEvents.touchUpInside)
|
nextButton.addTarget(self, action: #selector(getNextHOTP), for: UIControlEvents.touchUpInside)
|
||||||
|
|
||||||
// password button
|
// password button
|
||||||
passwordDisplayButton = UIButton(type: .system)
|
passwordDisplayButton = UIButton(type: .system)
|
||||||
passwordDisplayButton!.frame = CGRect(x: width, y: 0, width: width, height: height)
|
passwordDisplayButton!.frame = CGRect(x: width, y: 0, width: width, height: height)
|
||||||
|
|
@ -187,7 +187,7 @@ class LabelTableViewCell: UITableViewCell {
|
||||||
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
|
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
|
||||||
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
|
||||||
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
|
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
|
||||||
|
|
||||||
buttons = UIView()
|
buttons = UIView()
|
||||||
buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height)
|
buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height)
|
||||||
buttons!.addSubview(nextButton)
|
buttons!.addSubview(nextButton)
|
||||||
|
|
|
||||||
|
|
@ -22,5 +22,5 @@ class PasswordDetailTitleTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,15 +18,15 @@ class SliderTableViewCell: ContentTableViewCell {
|
||||||
@IBOutlet weak var titleLabel: UILabel!
|
@IBOutlet weak var titleLabel: UILabel!
|
||||||
@IBOutlet weak var valueLabel: UILabel!
|
@IBOutlet weak var valueLabel: UILabel!
|
||||||
@IBOutlet weak var slider: UISlider!
|
@IBOutlet weak var slider: UISlider!
|
||||||
|
|
||||||
var delegate: UITableViewController?
|
var delegate: UITableViewController?
|
||||||
|
|
||||||
var roundedValue: Int {
|
var roundedValue: Int {
|
||||||
get {
|
get {
|
||||||
return Int(valueLabel.text!)!
|
return Int(valueLabel.text!)!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
// Initialization code
|
// Initialization code
|
||||||
|
|
@ -37,7 +37,7 @@ class SliderTableViewCell: ContentTableViewCell {
|
||||||
|
|
||||||
// Configure the view for the selected state
|
// Configure the view for the selected state
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func handleSliderValueChange(_ sender: UISlider) {
|
@IBAction func handleSliderValueChange(_ sender: UISlider) {
|
||||||
let oldRoundedValue = self.roundedValue
|
let oldRoundedValue = self.roundedValue
|
||||||
let newRoundedValue = Int(sender.value)
|
let newRoundedValue = Int(sender.value)
|
||||||
|
|
@ -51,14 +51,14 @@ class SliderTableViewCell: ContentTableViewCell {
|
||||||
delegate.generateAndCopyPassword()
|
delegate.generateAndCopyPassword()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) {
|
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) {
|
||||||
titleLabel.text = title
|
titleLabel.text = title
|
||||||
slider.minimumValue = Float(minimumValue)
|
slider.minimumValue = Float(minimumValue)
|
||||||
slider.maximumValue = Float(maximumValue)
|
slider.maximumValue = Float(maximumValue)
|
||||||
slider.value = Float(defaultValue)
|
slider.value = Float(defaultValue)
|
||||||
valueLabel.text = String(defaultValue)
|
valueLabel.text = String(defaultValue)
|
||||||
|
|
||||||
// "not editable"
|
// "not editable"
|
||||||
if minimumValue == maximumValue {
|
if minimumValue == maximumValue {
|
||||||
titleLabel.textColor = UIColor.gray
|
titleLabel.textColor = UIColor.gray
|
||||||
|
|
@ -66,5 +66,5 @@ class SliderTableViewCell: ContentTableViewCell {
|
||||||
slider.isUserInteractionEnabled = false
|
slider.isUserInteractionEnabled = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@ import UIKit
|
||||||
class TextFieldTableViewCell: ContentTableViewCell {
|
class TextFieldTableViewCell: ContentTableViewCell {
|
||||||
|
|
||||||
@IBOutlet weak var contentTextField: UITextField!
|
@IBOutlet weak var contentTextField: UITextField!
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func getContent() -> String? {
|
override func getContent() -> String? {
|
||||||
return contentTextField.text
|
return contentTextField.text
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ class TextViewTableViewCell: ContentTableViewCell {
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func getContent() -> String? {
|
override func getContent() -> String? {
|
||||||
return contentTextView.text
|
return contentTextView.text
|
||||||
}
|
}
|
||||||
|
|
||||||
override func setContent(content: String?) {
|
override func setContent(content: String?) {
|
||||||
contentTextView.text = content
|
contentTextView.text = content
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class TitleTextFieldTableViewCell: UITableViewCell {
|
||||||
|
|
||||||
@IBOutlet weak var titleLabel: UILabel!
|
@IBOutlet weak var titleLabel: UILabel!
|
||||||
@IBOutlet weak var contentTextField: UITextField!
|
@IBOutlet weak var contentTextField: UITextField!
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
// Initialization code
|
// Initialization code
|
||||||
|
|
@ -20,7 +20,7 @@ class TitleTextFieldTableViewCell: UITableViewCell {
|
||||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
|
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
|
||||||
titleLabel.addGestureRecognizer(tapGestureRecognizer)
|
titleLabel.addGestureRecognizer(tapGestureRecognizer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func tap(_ sender: Any?) {
|
@objc func tap(_ sender: Any?) {
|
||||||
contentTextField.becomeFirstResponder()
|
contentTextField.becomeFirstResponder()
|
||||||
}
|
}
|
||||||
|
|
@ -28,5 +28,5 @@ class TitleTextFieldTableViewCell: UITableViewCell {
|
||||||
override func setSelected(_ selected: Bool, animated: Bool) {
|
override func setSelected(_ selected: Bool, animated: Bool) {
|
||||||
super.setSelected(selected, animated: animated)
|
super.setSelected(selected, animated: animated)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,18 @@ fileprivate class PasswordsTableEntry : NSObject {
|
||||||
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
|
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
|
||||||
@IBOutlet weak var searchBar: UISearchBar!
|
@IBOutlet weak var searchBar: UISearchBar!
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
private var searchActive = false
|
private var searchActive = false
|
||||||
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
||||||
|
|
||||||
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
||||||
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
||||||
return passcodelock
|
return passcodelock
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Prepare your UI to list available credentials for the user to choose from. The items in
|
Prepare your UI to list available credentials for the user to choose from. The items in
|
||||||
'serviceIdentifiers' describe the service the user is logging in to, so your extension can
|
'serviceIdentifiers' describe the service the user is logging in to, so your extension can
|
||||||
|
|
@ -50,14 +50,14 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
searchBarSearchButtonClicked(searchBar)
|
searchBarSearchButtonClicked(searchBar)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the domain
|
// get the domain
|
||||||
var identifier = serviceIdentifiers[0].identifier
|
var identifier = serviceIdentifiers[0].identifier
|
||||||
if !identifier.hasPrefix("http://") && !identifier.hasPrefix("https://") {
|
if !identifier.hasPrefix("http://") && !identifier.hasPrefix("https://") {
|
||||||
identifier = "http://" + identifier
|
identifier = "http://" + identifier
|
||||||
}
|
}
|
||||||
let url = URL(string: identifier)?.host ?? ""
|
let url = URL(string: identifier)?.host ?? ""
|
||||||
|
|
||||||
// "click" search
|
// "click" search
|
||||||
searchBar.text = url
|
searchBar.text = url
|
||||||
searchBar.becomeFirstResponder()
|
searchBar.becomeFirstResponder()
|
||||||
|
|
@ -102,20 +102,20 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
passcodelock.presentPasscodeLockIfNeeded(self)
|
passcodelock.presentPasscodeLockIfNeeded(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
|
|
||||||
// prepare
|
// prepare
|
||||||
searchBar.delegate = self
|
searchBar.delegate = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
||||||
|
|
||||||
// initialize table entries
|
// initialize table entries
|
||||||
initPasswordsTableEntries()
|
initPasswordsTableEntries()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func initPasswordsTableEntries() {
|
private func initPasswordsTableEntries() {
|
||||||
passwordsTableEntries.removeAll()
|
passwordsTableEntries.removeAll()
|
||||||
filteredPasswordsTableEntries.removeAll()
|
filteredPasswordsTableEntries.removeAll()
|
||||||
|
|
@ -125,7 +125,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
PasswordsTableEntry($0)
|
PasswordsTableEntry($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// define cell contents, and set long press action
|
// define cell contents, and set long press action
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
|
|
@ -140,16 +140,16 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
cell.detailTextLabel?.text = entry.categoryText
|
cell.detailTextLabel?.text = entry.categoryText
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
// select row -> extension returns (with username and password)
|
// select row -> extension returns (with username and password)
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
|
|
||||||
guard self.passwordStore.privateKey != nil else {
|
guard self.passwordStore.privateKey != nil else {
|
||||||
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity!
|
let passwordEntity = entry.passwordEntity!
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
|
|
@ -171,18 +171,18 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
if searchActive {
|
if searchActive {
|
||||||
return filteredPasswordsTableEntries.count
|
return filteredPasswordsTableEntries.count
|
||||||
}
|
}
|
||||||
return passwordsTableEntries.count;
|
return passwordsTableEntries.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestPGPKeyPassphrase() -> String {
|
private func requestPGPKeyPassphrase() -> String {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var passphrase = ""
|
var passphrase = ""
|
||||||
|
|
@ -204,13 +204,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
}
|
}
|
||||||
return passphrase
|
return passphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||||
searchBar.text = ""
|
searchBar.text = ""
|
||||||
searchActive = false
|
searchActive = false
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
||||||
|
|
@ -229,11 +229,11 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
|
||||||
}
|
}
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||||
searchBarSearchButtonClicked(searchBar)
|
searchBarSearchButtonClicked(searchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
||||||
if searchActive {
|
if searchActive {
|
||||||
return filteredPasswordsTableEntries[indexPath.row]
|
return filteredPasswordsTableEntries[indexPath.row]
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ class PasscodeExtensionDisplay {
|
||||||
private var isPasscodePresented = false
|
private var isPasscodePresented = false
|
||||||
private let passcodeLockVC: PasscodeLockViewControllerForExtension
|
private let passcodeLockVC: PasscodeLockViewControllerForExtension
|
||||||
private let extensionContext: ASCredentialProviderExtensionContext?
|
private let extensionContext: ASCredentialProviderExtensionContext?
|
||||||
|
|
||||||
public init(extensionContext: ASCredentialProviderExtensionContext?) {
|
public init(extensionContext: ASCredentialProviderExtensionContext?) {
|
||||||
self.extensionContext = extensionContext
|
self.extensionContext = extensionContext
|
||||||
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||||
|
|
@ -40,7 +40,7 @@ class PasscodeExtensionDisplay {
|
||||||
}
|
}
|
||||||
passcodeLockVC.setCancellable(true)
|
passcodeLockVC.setCancellable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// present the passcode lock view if passcode is set and the view controller is not presented
|
// present the passcode lock view if passcode is set and the view controller is not presented
|
||||||
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
||||||
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
||||||
|
|
@ -49,7 +49,7 @@ class PasscodeExtensionDisplay {
|
||||||
isPasscodePresented = true
|
isPasscodePresented = true
|
||||||
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dismiss(animated: Bool = true) {
|
public func dismiss(animated: Bool = true) {
|
||||||
isPasscodePresented = false
|
isPasscodePresented = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,24 +26,24 @@ fileprivate class PasswordsTableEntry : NSObject {
|
||||||
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
|
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
|
||||||
@IBOutlet weak var searchBar: UISearchBar!
|
@IBOutlet weak var searchBar: UISearchBar!
|
||||||
@IBOutlet weak var tableView: UITableView!
|
@IBOutlet weak var tableView: UITableView!
|
||||||
|
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
private var searchActive = false
|
private var searchActive = false
|
||||||
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
private var passwordsTableEntries: [PasswordsTableEntry] = []
|
||||||
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
case findLogin, fillBrowser, unknown
|
case findLogin, fillBrowser, unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
private var extensionAction = Action.unknown
|
private var extensionAction = Action.unknown
|
||||||
|
|
||||||
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
private lazy var passcodelock: PasscodeExtensionDisplay = {
|
||||||
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
|
||||||
return passcodelock
|
return passcodelock
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private func initPasswordsTableEntries() {
|
private func initPasswordsTableEntries() {
|
||||||
passwordsTableEntries.removeAll()
|
passwordsTableEntries.removeAll()
|
||||||
filteredPasswordsTableEntries.removeAll()
|
filteredPasswordsTableEntries.removeAll()
|
||||||
|
|
@ -53,12 +53,12 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
PasswordsTableEntry($0)
|
PasswordsTableEntry($0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewWillAppear(_ animated: Bool) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
passcodelock.presentPasscodeLockIfNeeded(self)
|
passcodelock.presentPasscodeLockIfNeeded(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
// prepare
|
// prepare
|
||||||
|
|
@ -66,15 +66,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
tableView.dataSource = self
|
tableView.dataSource = self
|
||||||
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
|
||||||
|
|
||||||
// initialize table entries
|
// initialize table entries
|
||||||
initPasswordsTableEntries()
|
initPasswordsTableEntries()
|
||||||
|
|
||||||
// get the provider
|
// get the provider
|
||||||
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
|
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for extensionItem in extensionItems {
|
for extensionItem in extensionItems {
|
||||||
if let itemProviders = extensionItem.attachments as? [NSItemProvider] {
|
if let itemProviders = extensionItem.attachments as? [NSItemProvider] {
|
||||||
for provider in itemProviders {
|
for provider in itemProviders {
|
||||||
|
|
@ -133,7 +133,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// define cell contents, and set long press action
|
// define cell contents, and set long press action
|
||||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
|
||||||
|
|
@ -148,16 +148,16 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
cell.detailTextLabel?.text = entry.categoryText
|
cell.detailTextLabel?.text = entry.categoryText
|
||||||
return cell
|
return cell
|
||||||
}
|
}
|
||||||
|
|
||||||
// select row -> extension returns (with username and password)
|
// select row -> extension returns (with username and password)
|
||||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||||
let entry = getPasswordEntry(by: indexPath)
|
let entry = getPasswordEntry(by: indexPath)
|
||||||
|
|
||||||
guard self.passwordStore.privateKey != nil else {
|
guard self.passwordStore.privateKey != nil else {
|
||||||
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
Utils.alert(title: "Cannot Copy Password", message: "PGP Key is not set. Please set your PGP Key first.", controller: self, completion: nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let passwordEntity = entry.passwordEntity!
|
let passwordEntity = entry.passwordEntity!
|
||||||
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
|
||||||
DispatchQueue.global(qos: .userInteractive).async {
|
DispatchQueue.global(qos: .userInteractive).async {
|
||||||
|
|
@ -197,19 +197,19 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||||
if searchActive{
|
if searchActive{
|
||||||
return filteredPasswordsTableEntries.count
|
return filteredPasswordsTableEntries.count
|
||||||
}
|
}
|
||||||
return passwordsTableEntries.count;
|
return passwordsTableEntries.count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private func requestPGPKeyPassphrase() -> String {
|
private func requestPGPKeyPassphrase() -> String {
|
||||||
let sem = DispatchSemaphore(value: 0)
|
let sem = DispatchSemaphore(value: 0)
|
||||||
var passphrase = ""
|
var passphrase = ""
|
||||||
|
|
@ -231,17 +231,17 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
return passphrase
|
return passphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
@IBAction func cancelExtension(_ sender: Any) {
|
@IBAction func cancelExtension(_ sender: Any) {
|
||||||
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
|
||||||
searchBar.text = ""
|
searchBar.text = ""
|
||||||
searchActive = false
|
searchActive = false
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
|
||||||
if let searchText = searchBar.text, searchText.isEmpty == false {
|
if let searchText = searchBar.text, searchText.isEmpty == false {
|
||||||
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
|
||||||
|
|
@ -260,11 +260,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
|
||||||
}
|
}
|
||||||
self.tableView.reloadData()
|
self.tableView.reloadData()
|
||||||
}
|
}
|
||||||
|
|
||||||
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
|
||||||
searchBarSearchButtonClicked(searchBar)
|
searchBarSearchButtonClicked(searchBar)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
|
||||||
if searchActive {
|
if searchActive {
|
||||||
return filteredPasswordsTableEntries[indexPath.row]
|
return filteredPasswordsTableEntries[indexPath.row]
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ class OnePasswordExtensionKey {
|
||||||
static let returnedFieldsKey = "returned_fields"
|
static let returnedFieldsKey = "returned_fields"
|
||||||
static let oldPasswordKey = "old_password"
|
static let oldPasswordKey = "old_password"
|
||||||
static let passwordGeneratorOptionsKey = "password_generator_options"
|
static let passwordGeneratorOptionsKey = "password_generator_options"
|
||||||
|
|
||||||
// Password Generator options - Used to set the 1Password Password Generator options when saving a new Login or when changing the password for for an existing Login
|
// Password Generator options - Used to set the 1Password Password Generator options when saving a new Login or when changing the password for for an existing Login
|
||||||
static let generatedPasswordMinLengthKey = "password_min_length"
|
static let generatedPasswordMinLengthKey = "password_min_length"
|
||||||
static let generatedPasswordMaxLengthKey = "password_max_length"
|
static let generatedPasswordMaxLengthKey = "password_max_length"
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ class PasscodeExtensionDisplay {
|
||||||
private var isPasscodePresented = false
|
private var isPasscodePresented = false
|
||||||
private let passcodeLockVC: PasscodeLockViewControllerForExtension
|
private let passcodeLockVC: PasscodeLockViewControllerForExtension
|
||||||
private let extensionContext: NSExtensionContext?
|
private let extensionContext: NSExtensionContext?
|
||||||
|
|
||||||
public init(extensionContext: NSExtensionContext?) {
|
public init(extensionContext: NSExtensionContext?) {
|
||||||
self.extensionContext = extensionContext
|
self.extensionContext = extensionContext
|
||||||
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
|
||||||
|
|
@ -39,7 +39,7 @@ class PasscodeExtensionDisplay {
|
||||||
}
|
}
|
||||||
passcodeLockVC.setCancellable(true)
|
passcodeLockVC.setCancellable(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// present the passcode lock view if passcode is set and the view controller is not presented
|
// present the passcode lock view if passcode is set and the view controller is not presented
|
||||||
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
|
||||||
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
|
||||||
|
|
@ -48,7 +48,7 @@ class PasscodeExtensionDisplay {
|
||||||
isPasscodePresented = true
|
isPasscodePresented = true
|
||||||
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dismiss(animated: Bool = true) {
|
public func dismiss(animated: Bool = true) {
|
||||||
isPasscodePresented = false
|
isPasscodePresented = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ run: function(arguments) {
|
||||||
arguments.completionFunction({"url_string": url, "error": error});
|
arguments.completionFunction({"url_string": url, "error": error});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
finalize: function(arguments) {
|
finalize: function(arguments) {
|
||||||
if (arguments["password"]) {
|
if (arguments["password"]) {
|
||||||
var passwordElement = document.querySelector("input[type=password]")
|
var passwordElement = document.querySelector("input[type=password]")
|
||||||
|
|
@ -21,7 +21,7 @@ finalize: function(arguments) {
|
||||||
passwordElement.value = arguments["password"]
|
passwordElement.value = arguments["password"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arguments["username"]) {
|
if (arguments["username"]) {
|
||||||
var usernameElement = document.querySelector("input[type=email], input[type=text]")
|
var usernameElement = document.querySelector("input[type=email], input[type=text]")
|
||||||
if (usernameElement) {
|
if (usernameElement) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
open class PasscodeLockPresenter {
|
open class PasscodeLockPresenter {
|
||||||
|
|
||||||
fileprivate var mainWindow: UIWindow?
|
fileprivate var mainWindow: UIWindow?
|
||||||
fileprivate var passcodeLockWindow: UIWindow?
|
fileprivate var passcodeLockWindow: UIWindow?
|
||||||
|
|
||||||
|
|
@ -21,16 +21,16 @@ open class PasscodeLockPresenter {
|
||||||
|
|
||||||
open func present(windowLevel: CGFloat?) {
|
open func present(windowLevel: CGFloat?) {
|
||||||
guard PasscodeLock.shared.hasPasscode else { return }
|
guard PasscodeLock.shared.hasPasscode else { return }
|
||||||
|
|
||||||
// dismiss the original window
|
// dismiss the original window
|
||||||
dismiss()
|
dismiss()
|
||||||
|
|
||||||
// new window
|
// new window
|
||||||
mainWindow?.endEditing(true)
|
mainWindow?.endEditing(true)
|
||||||
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
|
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
|
||||||
moveWindowsToFront(windowLevel: windowLevel)
|
moveWindowsToFront(windowLevel: windowLevel)
|
||||||
passcodeLockWindow?.isHidden = false
|
passcodeLockWindow?.isHidden = false
|
||||||
|
|
||||||
// new vc
|
// new vc
|
||||||
let passcodeLockVC = PasscodeLockViewController()
|
let passcodeLockVC = PasscodeLockViewController()
|
||||||
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback
|
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback
|
||||||
|
|
|
||||||
|
|
@ -12,23 +12,23 @@ import UIKit
|
||||||
import LocalAuthentication
|
import LocalAuthentication
|
||||||
|
|
||||||
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
|
|
||||||
open var dismissCompletionCallback: (()->Void)?
|
open var dismissCompletionCallback: (()->Void)?
|
||||||
open var successCallback: (()->Void)?
|
open var successCallback: (()->Void)?
|
||||||
open var cancelCallback: (()->Void)?
|
open var cancelCallback: (()->Void)?
|
||||||
|
|
||||||
weak var passcodeLabel: UILabel?
|
weak var passcodeLabel: UILabel?
|
||||||
weak var passcodeWrongAttemptsLabel: UILabel?
|
weak var passcodeWrongAttemptsLabel: UILabel?
|
||||||
weak var passcodeTextField: UITextField?
|
weak var passcodeTextField: UITextField?
|
||||||
weak var biometryAuthButton: UIButton?
|
weak var biometryAuthButton: UIButton?
|
||||||
open weak var cancelButton: UIButton?
|
open weak var cancelButton: UIButton?
|
||||||
|
|
||||||
var passcodeFailedAttempts = 0
|
var passcodeFailedAttempts = 0
|
||||||
var isCancellable: Bool = false
|
var isCancellable: Bool = false
|
||||||
|
|
||||||
open override func loadView() {
|
open override func loadView() {
|
||||||
super.loadView()
|
super.loadView()
|
||||||
|
|
||||||
let passcodeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
let passcodeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
||||||
passcodeLabel.text = "Enter passcode for Pass"
|
passcodeLabel.text = "Enter passcode for Pass"
|
||||||
passcodeLabel.font = UIFont.boldSystemFont(ofSize: 18)
|
passcodeLabel.font = UIFont.boldSystemFont(ofSize: 18)
|
||||||
|
|
@ -37,7 +37,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
passcodeLabel.translatesAutoresizingMaskIntoConstraints = false
|
passcodeLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.view.addSubview(passcodeLabel)
|
self.view.addSubview(passcodeLabel)
|
||||||
self.passcodeLabel = passcodeLabel
|
self.passcodeLabel = passcodeLabel
|
||||||
|
|
||||||
let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
||||||
passcodeWrongAttemptsLabel.text = ""
|
passcodeWrongAttemptsLabel.text = ""
|
||||||
passcodeWrongAttemptsLabel.textColor = UIColor.red
|
passcodeWrongAttemptsLabel.textColor = UIColor.red
|
||||||
|
|
@ -45,7 +45,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
passcodeWrongAttemptsLabel.translatesAutoresizingMaskIntoConstraints = false
|
passcodeWrongAttemptsLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.view.addSubview(passcodeWrongAttemptsLabel)
|
self.view.addSubview(passcodeWrongAttemptsLabel)
|
||||||
self.passcodeWrongAttemptsLabel = passcodeWrongAttemptsLabel
|
self.passcodeWrongAttemptsLabel = passcodeWrongAttemptsLabel
|
||||||
|
|
||||||
let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
|
||||||
passcodeTextField.borderStyle = UITextBorderStyle.roundedRect
|
passcodeTextField.borderStyle = UITextBorderStyle.roundedRect
|
||||||
passcodeTextField.placeholder = "passcode"
|
passcodeTextField.placeholder = "passcode"
|
||||||
|
|
@ -57,7 +57,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
|
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.view.addSubview(passcodeTextField)
|
self.view.addSubview(passcodeTextField)
|
||||||
self.passcodeTextField = passcodeTextField
|
self.passcodeTextField = passcodeTextField
|
||||||
|
|
||||||
let biometryAuthButton = UIButton(type: .custom)
|
let biometryAuthButton = UIButton(type: .custom)
|
||||||
biometryAuthButton.setTitle("", for: .normal)
|
biometryAuthButton.setTitle("", for: .normal)
|
||||||
biometryAuthButton.setTitleColor(Globals.blue, for: .normal)
|
biometryAuthButton.setTitleColor(Globals.blue, for: .normal)
|
||||||
|
|
@ -66,7 +66,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
|
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
|
||||||
self.view.addSubview(biometryAuthButton)
|
self.view.addSubview(biometryAuthButton)
|
||||||
self.biometryAuthButton = biometryAuthButton
|
self.biometryAuthButton = biometryAuthButton
|
||||||
|
|
||||||
let myContext = LAContext()
|
let myContext = LAContext()
|
||||||
var authError: NSError?
|
var authError: NSError?
|
||||||
if #available(iOS 8.0, macOS 10.12.1, *) {
|
if #available(iOS 8.0, macOS 10.12.1, *) {
|
||||||
|
|
@ -81,7 +81,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
biometryAuthButton.isHidden = false
|
biometryAuthButton.isHidden = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelButton = UIButton(type: .custom)
|
let cancelButton = UIButton(type: .custom)
|
||||||
cancelButton.setTitle("Cancel", for: .normal)
|
cancelButton.setTitle("Cancel", for: .normal)
|
||||||
cancelButton.setTitleColor(Globals.blue, for: .normal)
|
cancelButton.setTitleColor(Globals.blue, for: .normal)
|
||||||
|
|
@ -91,7 +91,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
|
cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
|
||||||
self.view.addSubview(cancelButton)
|
self.view.addSubview(cancelButton)
|
||||||
self.cancelButton = cancelButton
|
self.cancelButton = cancelButton
|
||||||
|
|
||||||
NSLayoutConstraint.activate([
|
NSLayoutConstraint.activate([
|
||||||
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
|
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
|
||||||
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
|
||||||
|
|
@ -118,13 +118,13 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
|
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
|
||||||
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20)
|
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20)
|
||||||
])
|
])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func viewDidLoad() {
|
open override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
}
|
}
|
||||||
|
|
||||||
open override func viewDidAppear(_ animated: Bool) {
|
open override func viewDidAppear(_ animated: Bool) {
|
||||||
super.viewDidAppear(animated)
|
super.viewDidAppear(animated)
|
||||||
if let biometryAuthButton = biometryAuthButton {
|
if let biometryAuthButton = biometryAuthButton {
|
||||||
|
|
@ -137,7 +137,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.passcodeTextField?.text = ""
|
self.passcodeTextField?.text = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// pop
|
// pop
|
||||||
if presentingViewController?.presentedViewController == self {
|
if presentingViewController?.presentedViewController == self {
|
||||||
// if presented as modal
|
// if presented as modal
|
||||||
|
|
@ -160,16 +160,16 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
passcodeWrongAttemptsLabel?.text = ""
|
passcodeWrongAttemptsLabel?.text = ""
|
||||||
dismissPasscodeLock(completionHandler: successCallback)
|
dismissPasscodeLock(completionHandler: successCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func passcodeLockDidCancel() {
|
@objc func passcodeLockDidCancel() {
|
||||||
dismissPasscodeLock(completionHandler: cancelCallback)
|
dismissPasscodeLock(completionHandler: cancelCallback)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
|
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
|
||||||
let myContext = LAContext()
|
let myContext = LAContext()
|
||||||
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
let myLocalizedReasonString = "Authentication is needed to access Pass."
|
||||||
var authError: NSError?
|
var authError: NSError?
|
||||||
|
|
||||||
if #available(iOS 8.0, *) {
|
if #available(iOS 8.0, *) {
|
||||||
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
|
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
|
||||||
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
|
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
|
||||||
|
|
@ -183,7 +183,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
|
||||||
if textField == passcodeTextField {
|
if textField == passcodeTextField {
|
||||||
if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||||
|
|
@ -198,13 +198,13 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
|
||||||
textField.resignFirstResponder()
|
textField.resignFirstResponder()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
|
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
|
||||||
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
|
||||||
self.passcodeLockDidSucceed()
|
self.passcodeLockDidSucceed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setCancellable(_ isCancellable: Bool) {
|
public func setCancellable(_ isCancellable: Bool) {
|
||||||
self.isCancellable = isCancellable
|
self.isCancellable = isCancellable
|
||||||
cancelButton?.isHidden = !isCancellable
|
cancelButton?.isHidden = !isCancellable
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ public extension DefaultsKeys {
|
||||||
static let pgpKeySource = DefaultsKey<String?>("pgpKeySource")
|
static let pgpKeySource = DefaultsKey<String?>("pgpKeySource")
|
||||||
static let pgpPublicKeyURL = DefaultsKey<URL?>("pgpPublicKeyURL")
|
static let pgpPublicKeyURL = DefaultsKey<URL?>("pgpPublicKeyURL")
|
||||||
static let pgpPrivateKeyURL = DefaultsKey<URL?>("pgpPrivateKeyURL")
|
static let pgpPrivateKeyURL = DefaultsKey<URL?>("pgpPrivateKeyURL")
|
||||||
|
|
||||||
static let pgpPublicKeyArmor = DefaultsKey<String?>("pgpPublicKeyArmor")
|
static let pgpPublicKeyArmor = DefaultsKey<String?>("pgpPublicKeyArmor")
|
||||||
static let pgpPrivateKeyArmor = DefaultsKey<String?>("pgpPrivateKeyArmor")
|
static let pgpPrivateKeyArmor = DefaultsKey<String?>("pgpPrivateKeyArmor")
|
||||||
|
|
||||||
static let gitURL = DefaultsKey<URL?>("gitURL")
|
static let gitURL = DefaultsKey<URL?>("gitURL")
|
||||||
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
|
||||||
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
static let gitUsername = DefaultsKey<String?>("gitUsername")
|
||||||
|
|
@ -29,10 +29,10 @@ public extension DefaultsKeys {
|
||||||
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
|
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
|
||||||
|
|
||||||
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
|
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
|
||||||
|
|
||||||
static let isTouchIDOn = DefaultsKey<Bool>("isTouchIDOn")
|
static let isTouchIDOn = DefaultsKey<Bool>("isTouchIDOn")
|
||||||
static let passcodeKey = DefaultsKey<String?>("passcodeKey")
|
static let passcodeKey = DefaultsKey<String?>("passcodeKey")
|
||||||
|
|
||||||
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
|
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
|
||||||
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
|
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
|
||||||
static let isRememberPGPPassphraseOn = DefaultsKey<Bool>("isRememberPGPPassphraseOn")
|
static let isRememberPGPPassphraseOn = DefaultsKey<Bool>("isRememberPGPPassphraseOn")
|
||||||
|
|
@ -40,6 +40,6 @@ public extension DefaultsKeys {
|
||||||
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
|
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
|
||||||
static let isSearchDefaultAll = DefaultsKey<Bool>("isSearchDefaultAll")
|
static let isSearchDefaultAll = DefaultsKey<Bool>("isSearchDefaultAll")
|
||||||
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")
|
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")
|
||||||
|
|
||||||
static let encryptInArmored = DefaultsKey<Bool>("encryptInArmored")
|
static let encryptInArmored = DefaultsKey<Bool>("encryptInArmored")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
|
|
||||||
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
|
||||||
public extension FileManager {
|
public extension FileManager {
|
||||||
|
|
||||||
/// This method calculates the accumulated size of a directory on the volume in bytes.
|
/// 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,
|
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
|
||||||
|
|
@ -20,73 +20,73 @@ public extension FileManager {
|
||||||
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
/// - note: There are a couple of oddities that are not taken into account (like symbolic links, meta data of
|
||||||
/// directories, hard links, ...).
|
/// directories, hard links, ...).
|
||||||
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
|
||||||
|
|
||||||
// We'll sum up content size here:
|
// We'll sum up content size here:
|
||||||
var accumulatedSize = UInt64(0)
|
var accumulatedSize = UInt64(0)
|
||||||
|
|
||||||
// prefetching some properties during traversal will speed up things a bit.
|
// prefetching some properties during traversal will speed up things a bit.
|
||||||
let prefetchedProperties = [
|
let prefetchedProperties = [
|
||||||
URLResourceKey.isRegularFileKey,
|
URLResourceKey.isRegularFileKey,
|
||||||
URLResourceKey.fileAllocatedSizeKey,
|
URLResourceKey.fileAllocatedSizeKey,
|
||||||
URLResourceKey.totalFileAllocatedSizeKey,
|
URLResourceKey.totalFileAllocatedSizeKey,
|
||||||
]
|
]
|
||||||
|
|
||||||
// The error handler simply signals errors to outside code.
|
// The error handler simply signals errors to outside code.
|
||||||
var errorDidOccur: Error?
|
var errorDidOccur: Error?
|
||||||
let errorHandler: (URL, Error) -> Bool = { _, error in
|
let errorHandler: (URL, Error) -> Bool = { _, error in
|
||||||
errorDidOccur = error
|
errorDidOccur = error
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// We have to enumerate all directory contents, including subdirectories.
|
// We have to enumerate all directory contents, including subdirectories.
|
||||||
let enumerator = self.enumerator(at: directoryURL,
|
let enumerator = self.enumerator(at: directoryURL,
|
||||||
includingPropertiesForKeys: prefetchedProperties,
|
includingPropertiesForKeys: prefetchedProperties,
|
||||||
options: FileManager.DirectoryEnumerationOptions(),
|
options: FileManager.DirectoryEnumerationOptions(),
|
||||||
errorHandler: errorHandler)
|
errorHandler: errorHandler)
|
||||||
precondition(enumerator != nil)
|
precondition(enumerator != nil)
|
||||||
|
|
||||||
// Start the traversal:
|
// Start the traversal:
|
||||||
for item in enumerator! {
|
for item in enumerator! {
|
||||||
let contentItemURL = item as! NSURL
|
let contentItemURL = item as! NSURL
|
||||||
|
|
||||||
// Bail out on errors from the errorHandler.
|
// Bail out on errors from the errorHandler.
|
||||||
if let error = errorDidOccur { throw error }
|
if let error = errorDidOccur { throw error }
|
||||||
|
|
||||||
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
|
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
|
||||||
var value: AnyObject?
|
var value: AnyObject?
|
||||||
try contentItemURL.getResourceValue(&value, forKey: key)
|
try contentItemURL.getResourceValue(&value, forKey: key)
|
||||||
return value as? NSNumber
|
return value as? NSNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the type of this item, making sure we only sum up sizes of regular files.
|
// Get the type of this item, making sure we only sum up sizes of regular files.
|
||||||
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
|
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
}
|
}
|
||||||
|
|
||||||
guard isRegularFile.boolValue else {
|
guard isRegularFile.boolValue else {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
|
// 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.
|
// This includes metadata, compression (on file system level) and block size.
|
||||||
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
|
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
|
||||||
|
|
||||||
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
|
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
|
||||||
// This value should always be available.
|
// This value should always be available.
|
||||||
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
|
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
|
||||||
|
|
||||||
guard let size = fileSize else {
|
guard let size = fileSize else {
|
||||||
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
|
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're good, add up the value.
|
// We're good, add up the value.
|
||||||
accumulatedSize += size.uint64Value
|
accumulatedSize += size.uint64Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail out on errors from the errorHandler.
|
// Bail out on errors from the errorHandler.
|
||||||
if let error = errorDidOccur { throw error }
|
if let error = errorDidOccur { throw error }
|
||||||
|
|
||||||
// We finally got it.
|
// We finally got it.
|
||||||
return accumulatedSize
|
return accumulatedSize
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public class Globals {
|
public class Globals {
|
||||||
|
|
||||||
// Legacy paths (not shared)
|
// Legacy paths (not shared)
|
||||||
public static let documentPathLegacy = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
|
public static let documentPathLegacy = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
|
||||||
public static let libraryPathLegacy = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0];
|
public static let libraryPathLegacy = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)[0];
|
||||||
|
|
@ -19,11 +19,11 @@ public class Globals {
|
||||||
public static let gitSSHPrivateKeyPathLegacy = "\(documentPathLegacy)/ssh_key"
|
public static let gitSSHPrivateKeyPathLegacy = "\(documentPathLegacy)/ssh_key"
|
||||||
public static let gitSSHPrivateKeyURLLegacy = URL(fileURLWithPath: gitSSHPrivateKeyPathLegacy)
|
public static let gitSSHPrivateKeyURLLegacy = URL(fileURLWithPath: gitSSHPrivateKeyPathLegacy)
|
||||||
public static let repositoryPathLegacy = "\(libraryPathLegacy)/password-store"
|
public static let repositoryPathLegacy = "\(libraryPathLegacy)/password-store"
|
||||||
|
|
||||||
public static let bundleIdentifier = "me.mssun.passforios"
|
public static let bundleIdentifier = "me.mssun.passforios"
|
||||||
public static let groupIdentifier = "group." + bundleIdentifier
|
public static let groupIdentifier = "group." + bundleIdentifier
|
||||||
public static let passKitBundleIdentifier = bundleIdentifier + ".passKit"
|
public static let passKitBundleIdentifier = bundleIdentifier + ".passKit"
|
||||||
|
|
||||||
public static let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)!
|
public static let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)!
|
||||||
public static let documentPath = sharedContainerURL.appendingPathComponent("Documents").path
|
public static let documentPath = sharedContainerURL.appendingPathComponent("Documents").path
|
||||||
public static let libraryPath = sharedContainerURL.appendingPathComponent("Library").path
|
public static let libraryPath = sharedContainerURL.appendingPathComponent("Library").path
|
||||||
|
|
@ -33,19 +33,19 @@ public class Globals {
|
||||||
public static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath)
|
public static let gitSSHPrivateKeyURL = URL(fileURLWithPath: gitSSHPrivateKeyPath)
|
||||||
public static let repositoryPath = libraryPath + "/password-store"
|
public static let repositoryPath = libraryPath + "/password-store"
|
||||||
public static let dbPath = documentPath + "/pass.sqlite"
|
public static let dbPath = documentPath + "/pass.sqlite"
|
||||||
|
|
||||||
public static let iTunesFileSharingPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
|
public static let iTunesFileSharingPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
|
||||||
public static let iTunesFileSharingPGPPublic = iTunesFileSharingPath + "/gpg_key.pub"
|
public static let iTunesFileSharingPGPPublic = iTunesFileSharingPath + "/gpg_key.pub"
|
||||||
public static let iTunesFileSharingPGPPrivate = iTunesFileSharingPath + "/gpg_key"
|
public static let iTunesFileSharingPGPPrivate = iTunesFileSharingPath + "/gpg_key"
|
||||||
public static let iTunesFileSharingSSHPrivate = iTunesFileSharingPath + "/ssh_key"
|
public static let iTunesFileSharingSSHPrivate = iTunesFileSharingPath + "/ssh_key"
|
||||||
|
|
||||||
public static let gitSignatureDefaultName = "Pass for iOS"
|
public static let gitSignatureDefaultName = "Pass for iOS"
|
||||||
public static let gitSignatureDefaultEmail = "user@passforios"
|
public static let gitSignatureDefaultEmail = "user@passforios"
|
||||||
|
|
||||||
public static let passwordDots = "••••••••••••"
|
public static let passwordDots = "••••••••••••"
|
||||||
public static let oneTimePasswordDots = "••••••"
|
public static let oneTimePasswordDots = "••••••"
|
||||||
public static let passwordFont = UIFont(name: "Courier-Bold", size: UIFont.labelFontSize - 1)
|
public static let passwordFont = UIFont(name: "Courier-Bold", size: UIFont.labelFontSize - 1)
|
||||||
|
|
||||||
// UI related
|
// UI related
|
||||||
public static let red = UIColor(red:1.00, green:0.23, blue:0.19, alpha:1.0)
|
public static let red = UIColor(red:1.00, green:0.23, blue:0.19, alpha:1.0)
|
||||||
public static let blue = UIColor(red:0.00, green:0.48, blue:1.00, alpha:1.0)
|
public static let blue = UIColor(red:0.00, green:0.48, blue:1.00, alpha:1.0)
|
||||||
|
|
@ -53,7 +53,7 @@ public class Globals {
|
||||||
public static let symbolColor = UIColor(red:200/255.0, green:40/255.0, blue:41/255.0, alpha:1.0)
|
public static let symbolColor = UIColor(red:200/255.0, green:40/255.0, blue:41/255.0, alpha:1.0)
|
||||||
public static let digitColor = UIColor(red:66/255.0, green:113/255.0, blue:174/255.0, alpha:1.0)
|
public static let digitColor = UIColor(red:66/255.0, green:113/255.0, blue:174/255.0, alpha:1.0)
|
||||||
public static let tableCellButtonSize = CGFloat(20.0)
|
public static let tableCellButtonSize = CGFloat(20.0)
|
||||||
|
|
||||||
private init() { }
|
private init() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ public extension Notification.Name {
|
||||||
static let passwordStoreErased = Notification.Name("passwordStoreErased")
|
static let passwordStoreErased = Notification.Name("passwordStoreErased")
|
||||||
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
|
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
|
||||||
static let passwordSearch = Notification.Name("passwordSearch")
|
static let passwordSearch = Notification.Name("passwordSearch")
|
||||||
|
|
||||||
static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
|
static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
|
||||||
static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
|
static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension UIView {
|
extension UIView {
|
||||||
|
|
||||||
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
|
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
|
||||||
var safeTopAnchor: NSLayoutYAxisAnchor {
|
var safeTopAnchor: NSLayoutYAxisAnchor {
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
|
|
@ -18,7 +18,7 @@ extension UIView {
|
||||||
return self.topAnchor
|
return self.topAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var safeLeftAnchor: NSLayoutXAxisAnchor {
|
var safeLeftAnchor: NSLayoutXAxisAnchor {
|
||||||
if #available(iOS 11.0, *){
|
if #available(iOS 11.0, *){
|
||||||
return self.safeAreaLayoutGuide.leftAnchor
|
return self.safeAreaLayoutGuide.leftAnchor
|
||||||
|
|
@ -26,7 +26,7 @@ extension UIView {
|
||||||
return self.leftAnchor
|
return self.leftAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var safeRightAnchor: NSLayoutXAxisAnchor {
|
var safeRightAnchor: NSLayoutXAxisAnchor {
|
||||||
if #available(iOS 11.0, *){
|
if #available(iOS 11.0, *){
|
||||||
return self.safeAreaLayoutGuide.rightAnchor
|
return self.safeAreaLayoutGuide.rightAnchor
|
||||||
|
|
@ -34,7 +34,7 @@ extension UIView {
|
||||||
return self.rightAnchor
|
return self.rightAnchor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var safeBottomAnchor: NSLayoutYAxisAnchor {
|
var safeBottomAnchor: NSLayoutYAxisAnchor {
|
||||||
if #available(iOS 11.0, *) {
|
if #available(iOS 11.0, *) {
|
||||||
return self.safeAreaLayoutGuide.bottomAnchor
|
return self.safeAreaLayoutGuide.bottomAnchor
|
||||||
|
|
|
||||||
|
|
@ -15,17 +15,17 @@ public class Utils {
|
||||||
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||||
return (try? keychain.getString(name)) ?? nil
|
return (try? keychain.getString(name)) ?? nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func addPasswordToKeychain(name: String, password: String?) {
|
public static func addPasswordToKeychain(name: String, password: String?) {
|
||||||
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||||
keychain[name] = password
|
keychain[name] = password
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func removeKeychain(name: String) {
|
public static func removeKeychain(name: String) {
|
||||||
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||||
try? keychain.remove(name)
|
try? keychain.remove(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func removeAllKeychain() {
|
public static func removeAllKeychain() {
|
||||||
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
|
||||||
try? keychain.removeAll()
|
try? keychain.removeAll()
|
||||||
|
|
@ -53,7 +53,7 @@ public class Utils {
|
||||||
}
|
}
|
||||||
return attributedPassword
|
return attributedPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {
|
public static func alert(title: String, message: String, controller: UIViewController, handler: ((UIAlertAction) -> Void)? = nil, completion: (() -> Void)? = nil) {
|
||||||
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
|
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
|
||||||
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))
|
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))
|
||||||
|
|
|
||||||
|
|
@ -14,21 +14,21 @@ import ObjectiveGit
|
||||||
public struct GitCredential {
|
public struct GitCredential {
|
||||||
private var credential: Credential
|
private var credential: Credential
|
||||||
private let passwordStore = PasswordStore.shared
|
private let passwordStore = PasswordStore.shared
|
||||||
|
|
||||||
public enum Credential {
|
public enum Credential {
|
||||||
case http(userName: String)
|
case http(userName: String)
|
||||||
case ssh(userName: String, privateKeyFile: URL)
|
case ssh(userName: String, privateKeyFile: URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(credential: Credential) {
|
public init(credential: Credential) {
|
||||||
self.credential = credential
|
self.credential = credential
|
||||||
}
|
}
|
||||||
|
|
||||||
public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
|
public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
|
||||||
var attempts = 0
|
var attempts = 0
|
||||||
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
|
||||||
var credential: GTCredential? = nil
|
var credential: GTCredential? = nil
|
||||||
|
|
||||||
switch self.credential {
|
switch self.credential {
|
||||||
case let .http(userName):
|
case let .http(userName):
|
||||||
var lastPassword = self.passwordStore.gitPassword
|
var lastPassword = self.passwordStore.gitPassword
|
||||||
|
|
@ -63,7 +63,7 @@ public struct GitCredential {
|
||||||
return credential
|
return credential
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func delete() {
|
public func delete() {
|
||||||
switch credential {
|
switch credential {
|
||||||
case .http:
|
case .http:
|
||||||
|
|
|
||||||
|
|
@ -11,24 +11,24 @@ import LocalAuthentication
|
||||||
|
|
||||||
open class PasscodeLock {
|
open class PasscodeLock {
|
||||||
public static let shared = PasscodeLock()
|
public static let shared = PasscodeLock()
|
||||||
|
|
||||||
fileprivate let passcodeKey = "passcode.lock.passcode"
|
fileprivate let passcodeKey = "passcode.lock.passcode"
|
||||||
fileprivate var passcode: String? {
|
fileprivate var passcode: String? {
|
||||||
return SharedDefaults[.passcodeKey]
|
return SharedDefaults[.passcodeKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
public var hasPasscode: Bool {
|
public var hasPasscode: Bool {
|
||||||
return passcode != nil
|
return passcode != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func save(passcode: String) {
|
public func save(passcode: String) {
|
||||||
SharedDefaults[.passcodeKey] = passcode
|
SharedDefaults[.passcodeKey] = passcode
|
||||||
}
|
}
|
||||||
|
|
||||||
public func check(passcode: String) -> Bool {
|
public func check(passcode: String) -> Bool {
|
||||||
return self.passcode == passcode
|
return self.passcode == passcode
|
||||||
}
|
}
|
||||||
|
|
||||||
public func delete() {
|
public func delete() {
|
||||||
SharedDefaults[.passcodeKey] = nil
|
SharedDefaults[.passcodeKey] = nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import OneTimePassword
|
||||||
import Base32
|
import Base32
|
||||||
|
|
||||||
public class Password {
|
public class Password {
|
||||||
|
|
||||||
public var name: String
|
public var name: String
|
||||||
public var url: URL
|
public var url: URL
|
||||||
public var plainText: String
|
public var plainText: String
|
||||||
|
|
@ -50,7 +50,7 @@ public class Password {
|
||||||
public var login: String? {
|
public var login: String? {
|
||||||
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var urlString: String? {
|
public var urlString: String? {
|
||||||
return getAdditionValue(withKey: Constants.URL_KEYWORD)
|
return getAdditionValue(withKey: Constants.URL_KEYWORD)
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +73,7 @@ public class Password {
|
||||||
self.plainText = plainText
|
self.plainText = plainText
|
||||||
initEverything()
|
initEverything()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public func updatePassword(name: String, url: URL, plainText: String) {
|
public func updatePassword(name: String, url: URL, plainText: String) {
|
||||||
guard self.plainText != plainText || self.url != url else {
|
guard self.plainText != plainText || self.url != url else {
|
||||||
|
|
@ -129,7 +129,7 @@ public class Password {
|
||||||
let toLowercase = { (string: String) -> String in caseSensitive ? string : string.lowercased() }
|
let toLowercase = { (string: String) -> String in caseSensitive ? string : string.lowercased() }
|
||||||
return additions.first(where: { toLowercase($0.title) == toLowercase(key) })?.content
|
return additions.first(where: { toLowercase($0.title) == toLowercase(key) })?.content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the OTP token if we are able to construct a valid one.
|
/// Set the OTP token if we are able to construct a valid one.
|
||||||
///
|
///
|
||||||
/// Example of TOTP otpauth:
|
/// Example of TOTP otpauth:
|
||||||
|
|
@ -164,7 +164,7 @@ public class Password {
|
||||||
.usingCounter(getAdditionValue(withKey: Constants.OTP_COUNTER))
|
.usingCounter(getAdditionValue(withKey: Constants.OTP_COUNTER))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the OTP description and the current password.
|
/// Get the OTP description and the current password.
|
||||||
public func getOtpStrings() -> (description: String, otp: String)? {
|
public func getOtpStrings() -> (description: String, otp: String)? {
|
||||||
guard otpToken != nil else {
|
guard otpToken != nil else {
|
||||||
|
|
@ -178,18 +178,18 @@ public class Password {
|
||||||
}
|
}
|
||||||
return (description, otpToken!.currentPassword ?? "error")
|
return (description, otpToken!.currentPassword ?? "error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the password strings
|
// return the password strings
|
||||||
// it is guaranteed that it is a HOTP password when we call this
|
// it is guaranteed that it is a HOTP password when we call this
|
||||||
public func getNextHotp() -> String? {
|
public func getNextHotp() -> String? {
|
||||||
// increase the counter
|
// increase the counter
|
||||||
otpToken = otpToken?.updatedToken()
|
otpToken = otpToken?.updatedToken()
|
||||||
|
|
||||||
// replace old HOTP settings with the new otpauth
|
// replace old HOTP settings with the new otpauth
|
||||||
var newOtpauth = try! otpToken?.toURL().absoluteString
|
var newOtpauth = try! otpToken?.toURL().absoluteString
|
||||||
newOtpauth?.append("&secret=")
|
newOtpauth?.append("&secret=")
|
||||||
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
|
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
|
||||||
|
|
||||||
var lines : [String] = []
|
var lines : [String] = []
|
||||||
self.plainText.enumerateLines() { line, _ in
|
self.plainText.enumerateLines() { line, _ in
|
||||||
let (key, _) = Parser.getKeyValuePair(from: line)
|
let (key, _) = Parser.getKeyValuePair(from: line)
|
||||||
|
|
@ -205,7 +205,7 @@ public class Password {
|
||||||
lines.append(newOtpauth!)
|
lines.append(newOtpauth!)
|
||||||
}
|
}
|
||||||
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
|
||||||
|
|
||||||
// get and return the password
|
// get and return the password
|
||||||
return self.otpToken?.currentPassword
|
return self.otpToken?.currentPassword
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ import Foundation
|
||||||
import SwiftyUserDefaults
|
import SwiftyUserDefaults
|
||||||
|
|
||||||
extension PasswordEntity {
|
extension PasswordEntity {
|
||||||
|
|
||||||
public var nameWithCategory: String {
|
public var nameWithCategory: String {
|
||||||
get {
|
get {
|
||||||
if let p = path, p.hasSuffix(".gpg") {
|
if let p = path, p.hasSuffix(".gpg") {
|
||||||
|
|
@ -20,11 +20,11 @@ extension PasswordEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getCategoryText() -> String {
|
public func getCategoryText() -> String {
|
||||||
return getCategoryArray().joined(separator: " > ")
|
return getCategoryArray().joined(separator: " > ")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getCategoryArray() -> [String] {
|
public func getCategoryArray() -> [String] {
|
||||||
var parentEntity = parent
|
var parentEntity = parent
|
||||||
var passwordCategoryArray: [String] = []
|
var passwordCategoryArray: [String] = []
|
||||||
|
|
@ -35,7 +35,7 @@ extension PasswordEntity {
|
||||||
passwordCategoryArray.reverse()
|
passwordCategoryArray.reverse()
|
||||||
return passwordCategoryArray
|
return passwordCategoryArray
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getURL() -> URL? {
|
public func getURL() -> URL? {
|
||||||
if let p = getPath().stringByAddingPercentEncodingForRFC3986() {
|
if let p = getPath().stringByAddingPercentEncodingForRFC3986() {
|
||||||
return URL(string: p)
|
return URL(string: p)
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ public class PasswordStore {
|
||||||
public static let shared = PasswordStore()
|
public static let shared = PasswordStore()
|
||||||
public let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
|
public let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
|
||||||
public let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
|
public let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
|
||||||
|
|
||||||
public var storeRepository: GTRepository?
|
public var storeRepository: GTRepository?
|
||||||
public var pgpKeyID: String?
|
public var pgpKeyID: String?
|
||||||
public var publicKey: Key? {
|
public var publicKey: Key? {
|
||||||
|
|
@ -31,7 +31,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public var privateKey: Key?
|
public var privateKey: Key?
|
||||||
|
|
||||||
public var gitSignatureForNow: GTSignature {
|
public var gitSignatureForNow: GTSignature {
|
||||||
get {
|
get {
|
||||||
let gitSignatureName = SharedDefaults[.gitSignatureName] ?? Globals.gitSignatureDefaultName
|
let gitSignatureName = SharedDefaults[.gitSignatureName] ?? Globals.gitSignatureDefaultName
|
||||||
|
|
@ -39,9 +39,9 @@ public class PasswordStore {
|
||||||
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())!
|
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public let keyring = ObjectivePGP.defaultKeyring
|
public let keyring = ObjectivePGP.defaultKeyring
|
||||||
|
|
||||||
public var pgpKeyPassphrase: String? {
|
public var pgpKeyPassphrase: String? {
|
||||||
set {
|
set {
|
||||||
Utils.addPasswordToKeychain(name: "pgpKeyPassphrase", password: newValue)
|
Utils.addPasswordToKeychain(name: "pgpKeyPassphrase", password: newValue)
|
||||||
|
|
@ -50,7 +50,7 @@ public class PasswordStore {
|
||||||
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
|
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var gitPassword: String? {
|
public var gitPassword: String? {
|
||||||
set {
|
set {
|
||||||
Utils.addPasswordToKeychain(name: "gitPassword", password: newValue)
|
Utils.addPasswordToKeychain(name: "gitPassword", password: newValue)
|
||||||
|
|
@ -59,7 +59,7 @@ public class PasswordStore {
|
||||||
return Utils.getPasswordFromKeychain(name: "gitPassword")
|
return Utils.getPasswordFromKeychain(name: "gitPassword")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public var gitSSHPrivateKeyPassphrase: String? {
|
public var gitSSHPrivateKeyPassphrase: String? {
|
||||||
set {
|
set {
|
||||||
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue)
|
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue)
|
||||||
|
|
@ -68,7 +68,7 @@ public class PasswordStore {
|
||||||
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase")
|
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let fm = FileManager.default
|
private let fm = FileManager.default
|
||||||
lazy private var context: NSManagedObjectContext = {
|
lazy private var context: NSManagedObjectContext = {
|
||||||
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
|
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
|
||||||
|
|
@ -82,7 +82,7 @@ public class PasswordStore {
|
||||||
if let error = error as NSError? {
|
if let error = error as NSError? {
|
||||||
// Replace this implementation with code to handle the error appropriately.
|
// 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.
|
// 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:
|
Typical reasons for an error here include:
|
||||||
* The parent directory does not exist, cannot be created, or disallows writing.
|
* The parent directory does not exist, cannot be created, or disallows writing.
|
||||||
|
|
@ -96,11 +96,11 @@ public class PasswordStore {
|
||||||
})
|
})
|
||||||
return container.viewContext
|
return container.viewContext
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public var numberOfPasswords : Int {
|
public var numberOfPasswords : Int {
|
||||||
return self.fetchPasswordEntityCoreData(withDir: false).count
|
return self.fetchPasswordEntityCoreData(withDir: false).count
|
||||||
}
|
}
|
||||||
|
|
||||||
public var sizeOfRepositoryByteCount : UInt64 {
|
public var sizeOfRepositoryByteCount : UInt64 {
|
||||||
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0
|
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0
|
||||||
}
|
}
|
||||||
|
|
@ -112,12 +112,12 @@ public class PasswordStore {
|
||||||
public var lastSyncedTime: Date? {
|
public var lastSyncedTime: Date? {
|
||||||
return SharedDefaults[.lastSyncedTime]
|
return SharedDefaults[.lastSyncedTime]
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
// File migration to group
|
// File migration to group
|
||||||
migrateIfNeeded()
|
migrateIfNeeded()
|
||||||
backwardCompatibility()
|
backwardCompatibility()
|
||||||
|
|
||||||
do {
|
do {
|
||||||
if fm.fileExists(atPath: storeURL.path) {
|
if fm.fileExists(atPath: storeURL.path) {
|
||||||
try storeRepository = GTRepository.init(url: storeURL)
|
try storeRepository = GTRepository.init(url: storeURL)
|
||||||
|
|
@ -127,14 +127,14 @@ public class PasswordStore {
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func migrateIfNeeded() {
|
private func migrateIfNeeded() {
|
||||||
// migrate happens only if the repository was cloned and pgp keys were set up using earlier versions
|
// migrate happens only if the repository was cloned and pgp keys were set up using earlier versions
|
||||||
let needMigration = !pgpKeyExists() && !gitSSHKeyExists() && !fm.fileExists(atPath: Globals.repositoryPath) && fm.fileExists(atPath: Globals.repositoryPathLegacy)
|
let needMigration = !pgpKeyExists() && !gitSSHKeyExists() && !fm.fileExists(atPath: Globals.repositoryPath) && fm.fileExists(atPath: Globals.repositoryPathLegacy)
|
||||||
guard needMigration == true else {
|
guard needMigration == true else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
// migrate Defaults
|
// migrate Defaults
|
||||||
let userDefaults = UserDefaults()
|
let userDefaults = UserDefaults()
|
||||||
|
|
@ -143,7 +143,7 @@ public class PasswordStore {
|
||||||
SharedDefaults.setValue(userDefaults.value(forKey: key), forKey: key)
|
SharedDefaults.setValue(userDefaults.value(forKey: key), forKey: key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrate files
|
// migrate files
|
||||||
try fm.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
|
try fm.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
|
||||||
try fm.createDirectory(atPath: Globals.libraryPath, withIntermediateDirectories: true, attributes: nil)
|
try fm.createDirectory(atPath: Globals.libraryPath, withIntermediateDirectories: true, attributes: nil)
|
||||||
|
|
@ -162,7 +162,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
updatePasswordEntityCoreData()
|
updatePasswordEntityCoreData()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func backwardCompatibility() {
|
private func backwardCompatibility() {
|
||||||
// For the newly-introduced isRememberGitCredentialPassphraseOn (20171008)
|
// For the newly-introduced isRememberGitCredentialPassphraseOn (20171008)
|
||||||
if (self.gitPassword != nil || self.gitSSHPrivateKeyPassphrase != nil) && SharedDefaults[.isRememberGitCredentialPassphraseOn] == false {
|
if (self.gitPassword != nil || self.gitSSHPrivateKeyPassphrase != nil) && SharedDefaults[.isRememberGitCredentialPassphraseOn] == false {
|
||||||
|
|
@ -173,21 +173,21 @@ public class PasswordStore {
|
||||||
SharedDefaults[.isRememberPGPPassphraseOn] = true
|
SharedDefaults[.isRememberPGPPassphraseOn] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SSHKeyType {
|
enum SSHKeyType {
|
||||||
case `public`, secret
|
case `public`, secret
|
||||||
}
|
}
|
||||||
|
|
||||||
public func initGitSSHKey(with armorKey: String) throws {
|
public func initGitSSHKey(with armorKey: String) throws {
|
||||||
let keyPath = Globals.gitSSHPrivateKeyPath
|
let keyPath = Globals.gitSSHPrivateKeyPath
|
||||||
try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii)
|
try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func initPGPKeys() throws {
|
public func initPGPKeys() throws {
|
||||||
try initPGPKey(.public)
|
try initPGPKey(.public)
|
||||||
try initPGPKey(.secret)
|
try initPGPKey(.secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func initPGPKey(_ keyType: PGPKeyType) throws {
|
public func initPGPKey(_ keyType: PGPKeyType) throws {
|
||||||
switch keyType {
|
switch keyType {
|
||||||
case .public:
|
case .public:
|
||||||
|
|
@ -206,7 +206,7 @@ public class PasswordStore {
|
||||||
throw AppError.UnknownError
|
throw AppError.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func initPGPKey(from url: URL, keyType: PGPKeyType) throws {
|
public func initPGPKey(from url: URL, keyType: PGPKeyType) throws {
|
||||||
var pgpKeyLocalPath = ""
|
var pgpKeyLocalPath = ""
|
||||||
if keyType == .public {
|
if keyType == .public {
|
||||||
|
|
@ -218,7 +218,7 @@ public class PasswordStore {
|
||||||
try pgpKeyData.write(to: URL(fileURLWithPath: pgpKeyLocalPath), options: .atomic)
|
try pgpKeyData.write(to: URL(fileURLWithPath: pgpKeyLocalPath), options: .atomic)
|
||||||
try initPGPKey(keyType)
|
try initPGPKey(keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func initPGPKey(with armorKey: String, keyType: PGPKeyType) throws {
|
public func initPGPKey(with armorKey: String, keyType: PGPKeyType) throws {
|
||||||
var pgpKeyLocalPath = ""
|
var pgpKeyLocalPath = ""
|
||||||
if keyType == .public {
|
if keyType == .public {
|
||||||
|
|
@ -229,8 +229,8 @@ public class PasswordStore {
|
||||||
try armorKey.write(toFile: pgpKeyLocalPath, atomically: true, encoding: .ascii)
|
try armorKey.write(toFile: pgpKeyLocalPath, atomically: true, encoding: .ascii)
|
||||||
try initPGPKey(keyType)
|
try initPGPKey(keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func importKey(from keyPath: String) -> Key? {
|
private func importKey(from keyPath: String) -> Key? {
|
||||||
if fm.fileExists(atPath: keyPath) {
|
if fm.fileExists(atPath: keyPath) {
|
||||||
let keys = try! ObjectivePGP.readKeys(fromPath: keyPath)
|
let keys = try! ObjectivePGP.readKeys(fromPath: keyPath)
|
||||||
|
|
@ -245,12 +245,12 @@ public class PasswordStore {
|
||||||
public func getPgpPrivateKey() -> Key {
|
public func getPgpPrivateKey() -> Key {
|
||||||
return keyring.keys.filter({$0.secretKey != nil})[0]
|
return keyring.keys.filter({$0.secretKey != nil})[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
public func repositoryExisted() -> Bool {
|
public func repositoryExisted() -> Bool {
|
||||||
let fm = FileManager()
|
let fm = FileManager()
|
||||||
return fm.fileExists(atPath: Globals.repositoryPath)
|
return fm.fileExists(atPath: Globals.repositoryPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func passwordExisted(password: Password) -> Bool {
|
public func passwordExisted(password: Password) -> Bool {
|
||||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -266,7 +266,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func passwordEntityExisted(path: String) -> Bool {
|
public func passwordEntityExisted(path: String) -> Bool {
|
||||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -282,7 +282,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
|
public func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
|
||||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -292,7 +292,7 @@ public class PasswordStore {
|
||||||
fatalError("Failed to fetch password entities: \(error)")
|
fatalError("Failed to fetch password entities: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func cloneRepository(remoteRepoURL: URL,
|
public func cloneRepository(remoteRepoURL: URL,
|
||||||
credential: GitCredential,
|
credential: GitCredential,
|
||||||
requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?,
|
||||||
|
|
@ -323,7 +323,7 @@ public class PasswordStore {
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pullRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
public func pullRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UnsafePointer<git_transfer_progress>, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -339,7 +339,7 @@ public class PasswordStore {
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updatePasswordEntityCoreData() {
|
private func updatePasswordEntityCoreData() {
|
||||||
deleteCoreData(entityName: "PasswordEntity")
|
deleteCoreData(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -393,7 +393,7 @@ public class PasswordStore {
|
||||||
print("Error with save: \(error)")
|
print("Error with save: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getRecentCommits(count: Int) throws -> [GTCommit] {
|
public func getRecentCommits(count: Int) throws -> [GTCommit] {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
return []
|
return []
|
||||||
|
|
@ -410,7 +410,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return commits
|
return commits
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
public func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
|
||||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -421,7 +421,7 @@ public class PasswordStore {
|
||||||
fatalError("Failed to fetch passwords: \(error)")
|
fatalError("Failed to fetch passwords: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
public func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
|
||||||
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
do {
|
do {
|
||||||
|
|
@ -434,8 +434,8 @@ public class PasswordStore {
|
||||||
fatalError("Failed to fetch passwords: \(error)")
|
fatalError("Failed to fetch passwords: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
public func fetchUnsyncedPasswords() -> [PasswordEntity] {
|
||||||
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
|
||||||
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
|
||||||
|
|
@ -446,7 +446,7 @@ public class PasswordStore {
|
||||||
fatalError("Failed to fetch passwords: \(error)")
|
fatalError("Failed to fetch passwords: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func setAllSynced() {
|
public func setAllSynced() {
|
||||||
let passwordEntities = fetchUnsyncedPasswords()
|
let passwordEntities = fetchUnsyncedPasswords()
|
||||||
for passwordEntity in passwordEntities {
|
for passwordEntity in passwordEntities {
|
||||||
|
|
@ -460,7 +460,7 @@ public class PasswordStore {
|
||||||
fatalError("Failed to save: \(error)")
|
fatalError("Failed to save: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func getLatestUpdateInfo(filename: String) -> String {
|
public func getLatestUpdateInfo(filename: String) -> String {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
return "Unknown"
|
return "Unknown"
|
||||||
|
|
@ -486,10 +486,10 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return autoFormattedDifference
|
return autoFormattedDifference
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateRemoteRepo() {
|
public func updateRemoteRepo() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func gitAdd(path: String) throws {
|
private func gitAdd(path: String) throws {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -497,7 +497,7 @@ public class PasswordStore {
|
||||||
try storeRepository.index().addFile(path)
|
try storeRepository.index().addFile(path)
|
||||||
try storeRepository.index().write()
|
try storeRepository.index().write()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func gitRm(path: String) throws {
|
private func gitRm(path: String) throws {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -509,7 +509,7 @@ public class PasswordStore {
|
||||||
try storeRepository.index().removeFile(path)
|
try storeRepository.index().removeFile(path)
|
||||||
try storeRepository.index().write()
|
try storeRepository.index().write()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deleteDirectoryTree(at url: URL) throws {
|
private func deleteDirectoryTree(at url: URL) throws {
|
||||||
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||||
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||||
|
|
@ -519,12 +519,12 @@ public class PasswordStore {
|
||||||
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func createDirectoryTree(at url: URL) throws {
|
private func createDirectoryTree(at url: URL) throws {
|
||||||
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
|
||||||
try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
|
try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func gitMv(from: String, to: String) throws {
|
private func gitMv(from: String, to: String) throws {
|
||||||
let fromURL = storeURL.appendingPathComponent(from)
|
let fromURL = storeURL.appendingPathComponent(from)
|
||||||
let toURL = storeURL.appendingPathComponent(to)
|
let toURL = storeURL.appendingPathComponent(to)
|
||||||
|
|
@ -532,7 +532,7 @@ public class PasswordStore {
|
||||||
try gitAdd(path: to)
|
try gitAdd(path: to)
|
||||||
try gitRm(path: from)
|
try gitRm(path: from)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func gitCommit(message: String) throws -> GTCommit? {
|
private func gitCommit(message: String) throws -> GTCommit? {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -546,7 +546,7 @@ public class PasswordStore {
|
||||||
let commit = try storeRepository.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
let commit = try storeRepository.createCommit(with: newTree, message: message, author: signature, committer: signature, parents: [parent], updatingReferenceNamed: headReference.name)
|
||||||
return commit
|
return commit
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
|
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -555,7 +555,7 @@ public class PasswordStore {
|
||||||
let branches = try storeRepository.branches(withPrefix: reference)
|
let branches = try storeRepository.branches(withPrefix: reference)
|
||||||
return branches.first
|
return branches.first
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pushRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
public func pushRepository(credential: GitCredential, requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?, transferProgressBlock: @escaping (UInt32, UInt32, Int, UnsafeMutablePointer<ObjCBool>) -> Void) throws {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -571,12 +571,12 @@ public class PasswordStore {
|
||||||
throw(error)
|
throw(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
|
||||||
guard !passwordExisted(password: password) else {
|
guard !passwordExisted(password: password) else {
|
||||||
throw AppError.PasswordDuplicatedError
|
throw AppError.PasswordDuplicatedError
|
||||||
}
|
}
|
||||||
|
|
||||||
var passwordURL = password.url
|
var passwordURL = password.url
|
||||||
var previousPathLength = Int.max
|
var previousPathLength = Int.max
|
||||||
var paths: [String] = []
|
var paths: [String] = []
|
||||||
|
|
@ -606,7 +606,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? {
|
private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? {
|
||||||
var ret: PasswordEntity? = nil
|
var ret: PasswordEntity? = nil
|
||||||
if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity {
|
if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity {
|
||||||
|
|
@ -624,7 +624,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
public func add(password: Password) throws -> PasswordEntity? {
|
public func add(password: Password) throws -> PasswordEntity? {
|
||||||
try createDirectoryTree(at: password.url)
|
try createDirectoryTree(at: password.url)
|
||||||
let newPasswordEntity = try addPasswordEntities(password: password)
|
let newPasswordEntity = try addPasswordEntities(password: password)
|
||||||
|
|
@ -635,7 +635,7 @@ public class PasswordStore {
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
return newPasswordEntity
|
return newPasswordEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
public func delete(passwordEntity: PasswordEntity) throws {
|
public func delete(passwordEntity: PasswordEntity) throws {
|
||||||
let deletedFileURL = passwordEntity.getURL()!
|
let deletedFileURL = passwordEntity.getURL()!
|
||||||
try gitRm(path: deletedFileURL.path)
|
try gitRm(path: deletedFileURL.path)
|
||||||
|
|
@ -644,7 +644,7 @@ public class PasswordStore {
|
||||||
let _ = try gitCommit(message: "Remove \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) from store using Pass for iOS.")
|
let _ = try gitCommit(message: "Remove \(deletedFileURL.deletingPathExtension().path.removingPercentEncoding!) from store using Pass for iOS.")
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
|
public func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
|
||||||
var newPasswordEntity: PasswordEntity? = passwordEntity
|
var newPasswordEntity: PasswordEntity? = passwordEntity
|
||||||
|
|
||||||
|
|
@ -656,16 +656,16 @@ public class PasswordStore {
|
||||||
newPasswordEntity = passwordEntity
|
newPasswordEntity = passwordEntity
|
||||||
newPasswordEntity?.synced = false
|
newPasswordEntity?.synced = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if password.changed&PasswordChange.path.rawValue != 0 {
|
if password.changed&PasswordChange.path.rawValue != 0 {
|
||||||
let deletedFileURL = passwordEntity.getURL()!
|
let deletedFileURL = passwordEntity.getURL()!
|
||||||
// add
|
// add
|
||||||
try createDirectoryTree(at: password.url)
|
try createDirectoryTree(at: password.url)
|
||||||
newPasswordEntity = try addPasswordEntities(password: password)
|
newPasswordEntity = try addPasswordEntities(password: password)
|
||||||
|
|
||||||
// mv
|
// mv
|
||||||
try gitMv(from: deletedFileURL.path, to: password.url.path)
|
try gitMv(from: deletedFileURL.path, to: password.url.path)
|
||||||
|
|
||||||
// delete
|
// delete
|
||||||
try deleteDirectoryTree(at: deletedFileURL)
|
try deleteDirectoryTree(at: deletedFileURL)
|
||||||
try deletePasswordEntities(passwordEntity: passwordEntity)
|
try deletePasswordEntities(passwordEntity: passwordEntity)
|
||||||
|
|
@ -675,7 +675,7 @@ public class PasswordStore {
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
return newPasswordEntity
|
return newPasswordEntity
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
|
||||||
var current: PasswordEntity? = passwordEntity
|
var current: PasswordEntity? = passwordEntity
|
||||||
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
|
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
|
||||||
|
|
@ -689,7 +689,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func saveUpdated(passwordEntity: PasswordEntity) {
|
public func saveUpdated(passwordEntity: PasswordEntity) {
|
||||||
do {
|
do {
|
||||||
try context.save()
|
try context.save()
|
||||||
|
|
@ -697,11 +697,11 @@ public class PasswordStore {
|
||||||
fatalError("Failed to save a PasswordEntity: \(error)")
|
fatalError("Failed to save a PasswordEntity: \(error)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func deleteCoreData(entityName: String) {
|
public func deleteCoreData(entityName: String) {
|
||||||
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
|
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
|
||||||
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try context.execute(deleteRequest)
|
try context.execute(deleteRequest)
|
||||||
try context.save()
|
try context.save()
|
||||||
|
|
@ -710,7 +710,7 @@ public class PasswordStore {
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateImage(passwordEntity: PasswordEntity, image: Data?) {
|
public func updateImage(passwordEntity: PasswordEntity, image: Data?) {
|
||||||
guard let image = image else {
|
guard let image = image else {
|
||||||
return
|
return
|
||||||
|
|
@ -733,7 +733,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func erase() {
|
public func erase() {
|
||||||
publicKey = nil
|
publicKey = nil
|
||||||
privateKey = nil
|
privateKey = nil
|
||||||
|
|
@ -743,19 +743,19 @@ public class PasswordStore {
|
||||||
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
|
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
|
||||||
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
|
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
|
||||||
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
||||||
|
|
||||||
Utils.removeAllKeychain()
|
Utils.removeAllKeychain()
|
||||||
|
|
||||||
deleteCoreData(entityName: "PasswordEntity")
|
deleteCoreData(entityName: "PasswordEntity")
|
||||||
|
|
||||||
SharedDefaults.removeAll()
|
SharedDefaults.removeAll()
|
||||||
storeRepository = nil
|
storeRepository = nil
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
NotificationCenter.default.post(name: .passwordStoreErased, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreErased, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// return the number of discarded commits
|
// return the number of discarded commits
|
||||||
public func reset() throws -> Int {
|
public func reset() throws -> Int {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -772,7 +772,7 @@ public class PasswordStore {
|
||||||
try storeRepository.reset(to: newHead, resetType: .hard)
|
try storeRepository.reset(to: newHead, resetType: .hard)
|
||||||
self.setAllSynced()
|
self.setAllSynced()
|
||||||
self.updatePasswordEntityCoreData()
|
self.updatePasswordEntityCoreData()
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
|
||||||
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
|
||||||
return localCommits.count
|
return localCommits.count
|
||||||
|
|
@ -780,8 +780,8 @@ public class PasswordStore {
|
||||||
return 0 // no new commit
|
return 0 // no new commit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private func getLocalCommits() throws -> [GTCommit]? {
|
private func getLocalCommits() throws -> [GTCommit]? {
|
||||||
guard let storeRepository = storeRepository else {
|
guard let storeRepository = storeRepository else {
|
||||||
throw AppError.RepositoryNotSetError
|
throw AppError.RepositoryNotSetError
|
||||||
|
|
@ -791,18 +791,18 @@ public class PasswordStore {
|
||||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
throw AppError.RepositoryRemoteMasterNotFoundError
|
||||||
}
|
}
|
||||||
let remoteMasterBranch = try storeRepository.remoteBranches()[index]
|
let remoteMasterBranch = try storeRepository.remoteBranches()[index]
|
||||||
|
|
||||||
// check oid before calling localCommitsRelative
|
// check oid before calling localCommitsRelative
|
||||||
guard remoteMasterBranch.oid != nil else {
|
guard remoteMasterBranch.oid != nil else {
|
||||||
throw AppError.RepositoryRemoteMasterNotFoundError
|
throw AppError.RepositoryRemoteMasterNotFoundError
|
||||||
}
|
}
|
||||||
|
|
||||||
// get a list of local commits
|
// get a list of local commits
|
||||||
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
|
||||||
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
|
||||||
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
let encryptedData = try Data(contentsOf: encryptedDataPath)
|
||||||
|
|
@ -817,7 +817,7 @@ public class PasswordStore {
|
||||||
}
|
}
|
||||||
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
|
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encrypt(password: Password) throws -> Data {
|
public func encrypt(password: Password) throws -> Data {
|
||||||
guard keyring.keys.count > 0 else {
|
guard keyring.keys.count > 0 else {
|
||||||
throw AppError.PGPPublicKeyNotExistError
|
throw AppError.PGPPublicKeyNotExistError
|
||||||
|
|
@ -830,7 +830,7 @@ public class PasswordStore {
|
||||||
return encryptedData
|
return encryptedData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removePGPKeys() {
|
public func removePGPKeys() {
|
||||||
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
|
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
|
||||||
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
|
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
|
||||||
|
|
@ -844,14 +844,14 @@ public class PasswordStore {
|
||||||
publicKey = nil
|
publicKey = nil
|
||||||
privateKey = nil
|
privateKey = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func removeGitSSHKeys() {
|
public func removeGitSSHKeys() {
|
||||||
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
|
||||||
Defaults.remove(.gitSSHPrivateKeyArmor)
|
Defaults.remove(.gitSSHPrivateKeyArmor)
|
||||||
Defaults.remove(.gitSSHPrivateKeyURL)
|
Defaults.remove(.gitSSHPrivateKeyURL)
|
||||||
self.gitSSHPrivateKeyPassphrase = nil
|
self.gitSSHPrivateKeyPassphrase = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool {
|
public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool {
|
||||||
if inFileSharing == false {
|
if inFileSharing == false {
|
||||||
return fm.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
|
return fm.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
|
||||||
|
|
@ -859,7 +859,7 @@ public class PasswordStore {
|
||||||
return fm.fileExists(atPath: Globals.iTunesFileSharingSSHPrivate)
|
return fm.fileExists(atPath: Globals.iTunesFileSharingSSHPrivate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func pgpKeyExists(inFileSharing: Bool = false) -> Bool {
|
public func pgpKeyExists(inFileSharing: Bool = false) -> Bool {
|
||||||
if inFileSharing == false {
|
if inFileSharing == false {
|
||||||
return fm.fileExists(atPath: Globals.pgpPublicKeyPath) && fm.fileExists(atPath: Globals.pgpPrivateKeyPath)
|
return fm.fileExists(atPath: Globals.pgpPublicKeyPath) && fm.fileExists(atPath: Globals.pgpPrivateKeyPath)
|
||||||
|
|
@ -867,7 +867,7 @@ public class PasswordStore {
|
||||||
return fm.fileExists(atPath: Globals.iTunesFileSharingPGPPublic) && fm.fileExists(atPath: Globals.iTunesFileSharingPGPPrivate)
|
return fm.fileExists(atPath: Globals.iTunesFileSharingPGPPublic) && fm.fileExists(atPath: Globals.iTunesFileSharingPGPPrivate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func gitSSHKeyImportFromFileSharing() throws {
|
public func gitSSHKeyImportFromFileSharing() throws {
|
||||||
try fm.moveItem(atPath: Globals.iTunesFileSharingSSHPrivate, toPath: Globals.gitSSHPrivateKeyPath)
|
try fm.moveItem(atPath: Globals.iTunesFileSharingSSHPrivate, toPath: Globals.gitSSHPrivateKeyPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ extension AdditionField {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AdditionField: Equatable {
|
extension AdditionField: Equatable {
|
||||||
|
|
||||||
public static func == (first: AdditionField, second: AdditionField) -> Bool {
|
public static func == (first: AdditionField, second: AdditionField) -> Bool {
|
||||||
return first.asTuple == second.asTuple
|
return first.asTuple == second.asTuple
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ public enum OtpType: String {
|
||||||
var description: String {
|
var description: String {
|
||||||
return rawValue
|
return rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
init(token: Token?) {
|
init(token: Token?) {
|
||||||
switch token?.generator.factor {
|
switch token?.generator.factor {
|
||||||
case .some(.counter):
|
case .some(.counter):
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ class PasswordTest: XCTestCase {
|
||||||
|
|
||||||
func testUrl() {
|
func testUrl() {
|
||||||
let password = getPasswordObjectWith(content: "")
|
let password = getPasswordObjectWith(content: "")
|
||||||
|
|
||||||
XCTAssertEqual(password.url, PASSWORD_URL)
|
XCTAssertEqual(password.url, PASSWORD_URL)
|
||||||
XCTAssertEqual(password.namePath, PASSWORD_PATH)
|
XCTAssertEqual(password.namePath, PASSWORD_PATH)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,27 +10,27 @@ import XCTest
|
||||||
@testable import passKit
|
@testable import passKit
|
||||||
|
|
||||||
class passKitTests: XCTestCase {
|
class passKitTests: XCTestCase {
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExample() {
|
func testExample() {
|
||||||
// This is an example of a functional test case.
|
// This is an example of a functional test case.
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPerformanceExample() {
|
func testPerformanceExample() {
|
||||||
// This is an example of a performance test case.
|
// This is an example of a performance test case.
|
||||||
self.measure {
|
self.measure {
|
||||||
// Put the code you want to measure the time of here.
|
// Put the code you want to measure the time of here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,27 +9,27 @@
|
||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
class passTests: XCTestCase {
|
class passTests: XCTestCase {
|
||||||
|
|
||||||
override func setUp() {
|
override func setUp() {
|
||||||
super.setUp()
|
super.setUp()
|
||||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
}
|
}
|
||||||
|
|
||||||
override func tearDown() {
|
override func tearDown() {
|
||||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
super.tearDown()
|
super.tearDown()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testExample() {
|
func testExample() {
|
||||||
// This is an example of a functional test case.
|
// This is an example of a functional test case.
|
||||||
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
// Use XCTAssert and related functions to verify your tests produce the correct results.
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPerformanceExample() {
|
func testPerformanceExample() {
|
||||||
// This is an example of a performance test case.
|
// This is an example of a performance test case.
|
||||||
self.measure {
|
self.measure {
|
||||||
// Put the code you want to measure the time of here.
|
// Put the code you want to measure the time of here.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue