lint: delete trailing whitespaces

This commit is contained in:
Mingshen Sun 2018-12-09 16:59:07 -08:00
parent 2ba6917710
commit ed387069a4
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
59 changed files with 624 additions and 623 deletions

View file

@ -1,4 +1,4 @@
<img src="icon/icon_round.png" width="76"/>
<img src="icon/icon_round.png" width="76"/>
# Pass
[![GitHub release](https://img.shields.io/github/release/mssun/passforios.svg)](https://github.com/mssun/passforios/releases)

View file

@ -1 +1,2 @@
theme: jekyll-theme-minimal
theme: jekyll-theme-minimal

View file

@ -23,7 +23,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
let presenter = PasscodeLockPresenter(mainWindow: self.window)
return presenter
}()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
SVProgressHUD.setMinimumSize(CGSize(width: 150, height: 100))
@ -35,18 +35,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
return true
}
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if let _ = window?.rootViewController as? PasscodeLockViewController {
window?.frame = UIScreen.main.bounds
}
return .all
}
@objc func postSearchNotification() {
NotificationCenter.default.post(name: .passwordSearch, object: nil)
}
func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) {
if shortcutItem.type == Globals.bundleIdentifier + ".search" {
let tabBarController = self.window!.rootViewController as! UITabBarController
@ -60,7 +60,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
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.
// 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
let blurEffect = UIBlurEffect(style: UIBlurEffectStyle.light)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
@ -68,7 +68,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
blurEffectView.tag = ViewTag.blur.rawValue
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.window?.addSubview(blurEffectView)
// Display the Pass icon in the middle of the screen
let iconsDictionary = Bundle.main.infoDictionary?["CFBundleIcons"] as? NSDictionary
let primaryIconsDictionary = iconsDictionary?["CFBundlePrimaryIcon"] as? NSDictionary
@ -85,7 +85,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
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.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
@ -95,7 +95,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
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.
self.window?.viewWithTag(ViewTag.appicon.rawValue)?.removeFromSuperview()
self.window?.viewWithTag(ViewTag.blur.rawValue)?.removeFromSuperview()
}
@ -107,7 +107,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
@ -126,7 +126,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
@ -140,9 +140,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {

View file

@ -10,7 +10,7 @@ import UIKit
import passKit
class AboutRepositoryTableViewController: BasicStaticTableViewController {
private var needRefresh = false
private var indicator: UIActivityIndicatorView = {
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)
tableView.addSubview(indicator)
setTableData()
// all password store updates (including erase, discard) will trigger the refresh
NotificationCenter.default.addObserver(self, selector: #selector(setNeedRefresh), name: .passwordStoreUpdated, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if needRefresh {
@ -37,14 +37,14 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
needRefresh = false
}
}
private func setTableData() {
// clear current contents (if any)
self.tableData.removeAll(keepingCapacity: true)
self.tableView.reloadData()
indicator.startAnimating()
// reload the table
DispatchQueue.global(qos: .userInitiated).async {
let passwords = self.numberOfPasswordsString()
@ -52,7 +52,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
let localCommits = self.numberOfLocalCommitsString()
let lastSynced = self.lastSyncedTimeString()
let commits = self.numberOfPasswordsString()
DispatchQueue.main.async { [weak self] in
guard let strongSelf = self else {
return
@ -97,7 +97,7 @@ class AboutRepositoryTableViewController: BasicStaticTableViewController {
formatter.timeStyle = .short
return formatter.string(from: date)
}
@objc func setNeedRefresh() {
needRefresh = true
}

View file

@ -9,21 +9,21 @@
import UIKit
class AboutTableViewController: BasicStaticTableViewController {
override func viewDidLoad() {
tableData = [
// section 0
[[.title: "Website", .action: "link", .link: "https://github.com/mssun/pass-ios.git"],
[.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"],],
// section 1,
[[.title: "Open Source Components", .action: "segue", .link: "showOpenSourceComponentsSegue"],
[.title: "Special Thanks", .action: "segue", .link: "showSpecialThanksSegue"],],
]
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section == tableData.count - 1 {
let view = UIView()
@ -38,7 +38,7 @@ class AboutTableViewController: BasicStaticTableViewController {
}
return nil
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 1 {
return "Acknowledgements".uppercased()

View file

@ -27,7 +27,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
tableData[0][0][PasswordEditorCellKey.content] = defaultDirPrefix
super.viewDidLoad()
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveAddPasswordSegue" {
// check PGP key
@ -37,7 +37,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
return false
}
// check name
guard checkName() == true else {
return false
@ -45,7 +45,7 @@ class AddPasswordTableViewController: PasswordEditorTableViewController {
}
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "saveAddPasswordSegue" {

View file

@ -17,7 +17,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
@IBOutlet weak var eraseDataTableViewCell: UITableViewCell!
@IBOutlet weak var discardChangesTableViewCell: UITableViewCell!
let passwordStore = PasswordStore.shared
let encryptInASCIIArmoredSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
@ -33,7 +33,7 @@ class AdvancedSettingsTableViewController: UITableViewController {
encryptInASCIIArmoredTableViewCell.selectionStyle = .none
setGitSignatureText()
}
private func setGitSignatureText() {
let gitSignatureName = passwordStore.gitSignatureForNow.name!
let gitSignatureEmail = passwordStore.gitSignatureForNow.email!
@ -77,17 +77,17 @@ class AdvancedSettingsTableViewController: UITableViewController {
} catch {
Utils.alert(title: "Error", message: error.localizedDescription, controller: self, completion: nil)
}
}))
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.cancel, handler:nil))
self.present(alert, animated: true, completion: nil)
}
}
@objc func encryptInASCIIArmoredAction(_ sender: Any?) {
SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn
}
@IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) {
if let controller = segue.source as? GitConfigSettingTableViewController {
if let gitSignatureName = controller.nameTextField.text,

View file

@ -27,18 +27,18 @@ enum CellDataKey {
class BasicStaticTableViewController: UITableViewController, MFMailComposeViewControllerDelegate {
var tableData = [[Dictionary<CellDataKey, Any>]]()
var navigationItemTitle: String?
override func viewDidLoad() {
super.viewDidLoad()
if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return tableData.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableData[section].count
}
@ -47,13 +47,13 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = tableData[indexPath.section][indexPath.row]
let cellDataStyle = cellData[CellDataKey.style] as? CellDataStyle
var cell: UITableViewCell?
switch cellDataStyle ?? .defaultStyle {
case .value1:
cell = UITableViewCell(style: .value1, reuseIdentifier: "value1 cell")
@ -61,7 +61,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
default:
cell = UITableViewCell(style: .default, reuseIdentifier: "default cell")
}
if let detailText = cellData[CellDataKey.detailText] as? String {
cell?.detailTextLabel?.text = detailText
}
@ -71,17 +71,17 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
cell?.accessoryType = .disclosureIndicator
cell?.selectionStyle = .default
}
cell?.textLabel?.text = cellData[CellDataKey.title] as? String
return cell ?? UITableViewCell()
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let cellData = tableData[indexPath.section][indexPath.row]
let selector = cellData[CellDataKey.detailDisclosureAction] as? Selector
perform(selector, with: cellData[CellDataKey.detailDisclosureData])
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let cellData = tableData[indexPath.section][indexPath.row]
@ -116,7 +116,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
break
}
}
func sendEmail(toRecipients recipients: [String], subject: String) {
let mailVC = MFMailComposeViewController()
mailVC.mailComposeDelegate = self
@ -125,7 +125,7 @@ class BasicStaticTableViewController: UITableViewController, MFMailComposeViewCo
mailVC.setMessageBody("", isHTML: false)
self.present(mailVC, animated: true, completion: nil)
}
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
controller.dismiss(animated: true)
}

View file

@ -13,7 +13,7 @@ import passKit
class CommitLogsTableViewController: UITableViewController {
var commits: [GTCommit] = []
let passwordStore = PasswordStore.shared
override func viewDidLoad() {
super.viewDidLoad()
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 {
return commits.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commitLogCell", for: indexPath)
let formatter = DateFormatter()
formatter.dateStyle = DateFormatter.Style.medium
formatter.timeStyle = .medium
let dateString = formatter.string(from: commits[indexPath.row].commitDate)
let author = cell.contentView.viewWithTag(200) as? UILabel
let dateLabel = cell.contentView.viewWithTag(201) as? UILabel
let messageLabel = cell.contentView.viewWithTag(202) as? UILabel
@ -41,12 +41,12 @@ class CommitLogsTableViewController: UITableViewController {
messageLabel?.text = commits[indexPath.row].message?.trimmed
return cell
}
@objc func updateCommitLogs() {
commits = getCommitLogs()
tableView.reloadData()
}
private func getCommitLogs() -> [GTCommit] {
do {
return try passwordStore.getRecentCommits(count: 20)

View file

@ -24,7 +24,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
tableData[1].append([.type: PasswordEditorCellType.memorablePasswordGeneratorCell])
super.viewDidLoad()
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveEditPasswordSegue" {
// check name
@ -34,7 +34,7 @@ class EditPasswordTableViewController: PasswordEditorTableViewController {
}
return true
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "saveEditPasswordSegue" {

View file

@ -19,7 +19,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
uiSwitch.addTarget(self, action: #selector(hideUnknownSwitchAction(_:)), for: UIControlEvents.valueChanged)
return uiSwitch
}()
let hideOTPSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
@ -27,7 +27,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
uiSwitch.addTarget(self, action: #selector(hideOTPSwitchAction(_:)), for: UIControlEvents.valueChanged)
return uiSwitch
}()
let rememberPGPPassphraseSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
@ -36,7 +36,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
uiSwitch.isOn = SharedDefaults[.isRememberPGPPassphraseOn]
return uiSwitch
}()
let rememberGitCredentialPassphraseSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
@ -45,7 +45,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
uiSwitch.isOn = SharedDefaults[.isRememberGitCredentialPassphraseOn]
return uiSwitch
}()
let showFolderSwitch: UISwitch = {
let uiSwitch = UISwitch()
uiSwitch.onTintColor = Globals.blue
@ -59,12 +59,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
tableData = [
// section 0
[[.title: "About Repository", .action: "segue", .link: "showAboutRepositorySegue"],],
// section 1
[
[.title: "Password Generator Flavor", .action: "none", .style: CellDataStyle.value1],
],
// section 2
[
[.title: "Remember PGP Key Passphrase", .action: "none",],
@ -78,9 +78,9 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
]
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
switch cell.textLabel!.text! {
@ -127,7 +127,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
super.tableView(tableView, didSelectRowAt: indexPath)
let cell = tableView.cellForRow(at: indexPath)!
@ -136,7 +136,7 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
showPasswordGeneratorFlavorActionSheet(sourceCell: cell)
}
}
func showPasswordGeneratorFlavorActionSheet(sourceCell: UITableViewCell) {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
var randomFlavorActionTitle = ""
@ -152,12 +152,12 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
SharedDefaults[.passwordGeneratorFlavor] = "Random"
sourceCell.detailTextLabel?.text = "Random"
}
let appleFlavorAction = UIAlertAction(title: appleFlavorActionTitle, style: .default) { _ in
SharedDefaults[.passwordGeneratorFlavor] = "Apple"
sourceCell.detailTextLabel?.text = "Apple"
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
optionMenu.addAction(randomFlavorAction)
optionMenu.addAction(appleFlavorAction)
@ -166,37 +166,37 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
optionMenu.popoverPresentationController?.sourceRect = sourceCell.bounds
self.present(optionMenu, animated: true, completion: nil)
}
@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 alertTitle = "Hide Unknown Fields"
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
}
@objc func tapHideOTPSwitchDetailButton(_ sender: Any?) {
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 alertTitle = "Hide One Time Password Fields"
Utils.alert(title: alertTitle, message: alertMessage, controller: self, completion: nil)
}
@objc func hideUnknownSwitchAction(_ sender: Any?) {
SharedDefaults[.isHideUnknownOn] = hideUnknownSwitch.isOn
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
}
@objc func hideOTPSwitchAction(_ sender: Any?) {
SharedDefaults[.isHideOTPOn] = hideOTPSwitch.isOn
NotificationCenter.default.post(name: .passwordDetailDisplaySettingChanged, object: nil)
}
@objc func rememberPGPPassphraseSwitchAction(_ sender: Any?) {
SharedDefaults[.isRememberPGPPassphraseOn] = rememberPGPPassphraseSwitch.isOn
if rememberPGPPassphraseSwitch.isOn == false {
passwordStore.pgpKeyPassphrase = nil
}
}
@objc func rememberGitCredentialPassphraseSwitchAction(_ sender: Any?) {
SharedDefaults[.isRememberGitCredentialPassphraseOn] = rememberGitCredentialPassphraseSwitch.isOn
if rememberGitCredentialPassphraseSwitch.isOn == false {
@ -204,10 +204,10 @@ class GeneralSettingsTableViewController: BasicStaticTableViewController {
passwordStore.gitPassword = nil
}
}
@objc func showFolderSwitchAction(_ sender: Any?) {
SharedDefaults[.isShowFolderOn] = showFolderSwitch.isOn
NotificationCenter.default.post(name: .passwordDisplaySettingChanged, object: nil)
}
}

View file

@ -12,21 +12,21 @@ import passKit
class GitConfigSettingTableViewController: UITableViewController {
let passwordStore = PasswordStore.shared
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var emailTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
let signature = passwordStore.gitSignatureForNow
nameTextField.placeholder = signature.name
emailTextField.placeholder = signature.email
nameTextField.text = SharedDefaults[.gitSignatureName]
emailTextField.text = SharedDefaults[.gitSignatureEmail]
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "saveGitConfigSettingSegue" {
let name = nameTextField.text!.isEmpty ? Globals.gitSignatureDefaultName : nameTextField.text!

View file

@ -12,10 +12,10 @@ import passKit
class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextViewDelegate, QRScannerControllerDelegate {
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var gitSSHPrivateKeyPassphrase: String?
let passwordStore = PasswordStore.shared
class ScannedSSHKey {
static let maxNumberOfGif = 100
var numberOfSegments = 0
@ -24,7 +24,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
var message = ""
var hasStarted = false
var isDone = false
func reset() {
numberOfSegments = 0
previousSegment = ""
@ -33,14 +33,14 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
hasStarted = false
isDone = false
}
func addSegment(segment: String) {
// skip duplicated segments
guard segment != previousSegment else {
return
}
previousSegment = segment
// check whether we have found the first block
if hasStarted == false {
hasStarted = segment.contains("-----BEGIN")
@ -48,38 +48,38 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
guard hasStarted == true else {
return
}
// check the number of segments
numberOfSegments = numberOfSegments + 1
guard numberOfSegments <= ScannedSSHKey.maxNumberOfGif else {
key = "Too many QR codes"
return
}
// update full text and check whether we are done
key.append(segment)
if let index1 = key.range(of: "-----END")?.lowerBound,
let _ = key.suffix(from: index1).range(of: "KEY-----")?.lowerBound {
isDone = true
}
// update message
message = "\(numberOfSegments) scanned QR codes."
}
}
var scanned = ScannedSSHKey()
override func viewDidLoad() {
super.viewDidLoad()
armorPrivateKeyTextView.text = SharedDefaults[.gitSSHPrivateKeyArmor]
armorPrivateKeyTextView.delegate = self
scanPrivateKeyCell?.textLabel?.text = "Scan Private Key QR Codes"
scanPrivateKeyCell?.textLabel?.textColor = Globals.blue
scanPrivateKeyCell?.selectionStyle = .default
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
}
@IBAction func doneButtonTapped(_ sender: Any) {
SharedDefaults[.gitSSHPrivateKeyArmor] = armorPrivateKeyTextView.text
do {
@ -90,7 +90,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
SharedDefaults[.gitSSHKeySource] = "armor"
self.navigationController!.popViewController(animated: true)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes something, do the copy here again and clear the pasteboard in 45s
@ -98,7 +98,7 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
}
return true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == scanPrivateKeyCell {
@ -107,8 +107,8 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
}
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
scanned.addSegment(segment: line)
@ -118,12 +118,12 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
return (accept: false, message: scanned.message)
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
armorPrivateKeyTextView.text = scanned.key
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showSSHScannerSegue" {
if let navController = segue.destination as? UINavigationController {
@ -135,9 +135,9 @@ class GitSSHKeyArmorSettingTableViewController: UITableViewController, UITextVie
}
}
}
@IBAction private func cancelSSHScanner(segue: UIStoryboardSegue) {
}
}

View file

@ -37,7 +37,7 @@ class GitServerSettingTableViewController: UITableViewController {
sshKeyCheckView.isHidden = true
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Grey out ssh option if ssh_key is not present
@ -55,30 +55,30 @@ class GitServerSettingTableViewController: UITableViewController {
checkAuthenticationMethod(method: authenticationMethod)
authSSHKeyCell.accessoryType = .detailButton
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let cell = tableView.cellForRow(at: indexPath)
if cell == authSSHKeyCell {
showSSHKeyActionSheet()
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
view.endEditing(true)
}
private func cloneAndSegueIfSuccess() {
// try to clone
let gitRepostiroyURL = gitURLTextField.text!.trimmed
let username = usernameTextField.text!
let auth = authenticationMethod
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "Prepare Repository")
@ -160,15 +160,15 @@ class GitServerSettingTableViewController: UITableViewController {
checkAuthenticationMethod(method: authenticationMethod)
tableView.deselectRow(at: indexPath, animated: true)
}
@IBAction func save(_ sender: Any) {
// some sanity checks
guard let gitURL = URL(string: gitURLTextField.text!) else {
Utils.alert(title: "Cannot Save", message: "Please set the Git repository URL.", controller: self, completion: nil)
return
}
switch gitURL.scheme {
case let val where val == "https":
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)
return
}
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)
alert.addAction(UIAlertAction(title: "Overwrite", style: UIAlertActionStyle.destructive, handler: { _ in
@ -202,13 +202,13 @@ class GitServerSettingTableViewController: UITableViewController {
cloneAndSegueIfSuccess()
}
}
func showSSHKeyActionSheet() {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
var urlActionTitle = "Download from URL"
var armorActionTitle = "ASCII-Armor Encrypted Key"
var fileActionTitle = "iTunes File Sharing"
if SharedDefaults[.gitSSHKeySource] == "url" {
urlActionTitle = "\(urlActionTitle)"
} else if SharedDefaults[.gitSSHKeySource] == "armor" {
@ -225,7 +225,7 @@ class GitServerSettingTableViewController: UITableViewController {
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
optionMenu.addAction(urlAction)
optionMenu.addAction(armorAction)
if passwordStore.gitSSHKeyExists(inFileSharing: true) {
// might keys updated via iTunes, or downloaded/pasted inside the app
fileActionTitle.append(" (Import)")
@ -249,7 +249,7 @@ class GitServerSettingTableViewController: UITableViewController {
}
optionMenu.addAction(fileAction)
}
if SharedDefaults[.gitSSHKeySource] != nil {
let deleteAction = UIAlertAction(title: "Remove Git SSH Keys", style: .destructive) { _ in
self.passwordStore.removeGitSSHKeys()
@ -266,7 +266,7 @@ class GitServerSettingTableViewController: UITableViewController {
optionMenu.popoverPresentationController?.sourceRect = authSSHKeyCell.bounds
self.present(optionMenu, animated: true, completion: nil)
}
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
let sem = DispatchSemaphore(value: 0)
var password: String?
@ -277,7 +277,7 @@ class GitServerSettingTableViewController: UITableViewController {
case .ssh:
message = "Please fill in the passphrase of your SSH key."
}
DispatchQueue.main.async {
SVProgressHUD.dismiss()
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
@ -295,7 +295,7 @@ class GitServerSettingTableViewController: UITableViewController {
})
self.present(alert, animated: true, completion: nil)
}
let _ = sem.wait(timeout: .distantFuture)
return password
}

View file

@ -33,7 +33,7 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
"https://github.com/SVProgressHUD/SVProgressHUD",
"https://github.com/SVProgressHUD/SVProgressHUD/blob/master/LICENSE.txt"],
]
override func viewDidLoad() {
tableData.append([])
for item in openSourceComponents {
@ -43,7 +43,7 @@ class OpenSourceComponentsTableViewController: BasicStaticTableViewController {
}
super.viewDidLoad()
}
@objc func actOnDetailDisclosureButton(_ sender: Any?) {
if let link = sender as? String {
let svc = SFSafariViewController(url: URL(string: link)!, entersReaderIfAvailable: false)

View file

@ -14,10 +14,10 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
@IBOutlet weak var armorPrivateKeyTextView: UITextView!
@IBOutlet weak var scanPublicKeyCell: UITableViewCell!
@IBOutlet weak var scanPrivateKeyCell: UITableViewCell!
var pgpPassphrase: String?
let passwordStore = PasswordStore.shared
class ScannedPGPKey {
static let maxNumberOfGif = 100
enum KeyType {
@ -30,7 +30,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
var message = ""
var hasStarted = false
var isDone = false
func reset(keytype: KeyType) {
self.keyType = keytype
numberOfSegments = 0
@ -40,14 +40,14 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
hasStarted = false
isDone = false
}
func addSegment(segment: String) {
// skip duplicated segments
guard segment != previousSegment else {
return
}
previousSegment = segment
// check whether we have found the first block
if hasStarted == false {
let findPublic = segment.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
@ -68,32 +68,32 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
guard hasStarted == true else {
return
}
// check the number of segments
numberOfSegments = numberOfSegments + 1
guard numberOfSegments <= ScannedPGPKey.maxNumberOfGif else {
key = "Too many QR codes"
return
}
// update full text and check whether we are done
key.append(segment)
if key.contains("-----END PGP PUBLIC KEY BLOCK-----") || key.contains("-----END PGP PRIVATE KEY BLOCK-----") {
isDone = true
}
// update message
message = "\(numberOfSegments) scanned QR codes."
}
}
var scanned = ScannedPGPKey()
override func viewDidLoad() {
super.viewDidLoad()
armorPublicKeyTextView.text = SharedDefaults[.pgpPublicKeyArmor]
armorPrivateKeyTextView.text = SharedDefaults[.pgpPrivateKeyArmor]
pgpPassphrase = passwordStore.pgpKeyPassphrase
scanPublicKeyCell?.textLabel?.text = "Scan Public Key QR Codes"
scanPublicKeyCell?.textLabel?.textColor = Globals.blue
scanPublicKeyCell?.selectionStyle = .default
@ -104,7 +104,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
scanPrivateKeyCell?.selectionStyle = .default
scanPrivateKeyCell?.accessoryType = .disclosureIndicator
}
@IBAction func save(_ sender: Any) {
guard armorPublicKeyTextView.text.isEmpty == false else {
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)
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if text == UIPasteboard.general.string {
// user pastes something, do the copy here again and clear the pasteboard in 45s
@ -146,7 +146,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
}
return true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == scanPublicKeyCell {
@ -158,7 +158,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
}
tableView.deselectRow(at: indexPath, animated: true)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
scanned.addSegment(segment: line)
@ -168,7 +168,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
return (accept: false, message: scanned.message)
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
switch scanned.keyType {
@ -178,7 +178,7 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe
armorPrivateKeyTextView.text = scanned.key
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showPGPScannerSegue" {
if let navController = segue.destination as? UINavigationController {

View file

@ -15,7 +15,7 @@ class PGPKeySettingTableViewController: UITableViewController {
@IBOutlet weak var pgpPrivateKeyURLTextField: UITextField!
var pgpPassphrase: String?
let passwordStore = PasswordStore.shared
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
@ -23,7 +23,7 @@ class PGPKeySettingTableViewController: UITableViewController {
pgpPrivateKeyURLTextField.text = SharedDefaults[.pgpPrivateKeyURL]?.absoluteString
pgpPassphrase = passwordStore.pgpKeyPassphrase
}
private func validatePGPKeyURL(input: String?) -> Bool {
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)
@ -35,7 +35,7 @@ class PGPKeySettingTableViewController: UITableViewController {
}
return true
}
@IBAction func save(_ sender: Any) {
guard validatePGPKeyURL(input: pgpPublicKeyURLTextField.text) == true,
validatePGPKeyURL(input: pgpPrivateKeyURLTextField.text) == true else {
@ -65,6 +65,6 @@ class PGPKeySettingTableViewController: UITableViewController {
})
self.present(savePassphraseAlert, animated: true, completion: nil)
}
}

View file

@ -18,7 +18,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
private var oneTimePasswordIndexPath : IndexPath?
private var shouldPopCurrentView = false
private let passwordStore = PasswordStore.shared
private lazy var editUIBarButtonItem: UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(pressEdit(_:)))
return uiBarButtonItem
@ -31,18 +31,18 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
title = ""
content = ""
}
init(title: String) {
self.title = title
self.content = ""
}
init(title: String, content: String) {
self.title = title
self.content = content
}
}
private struct TableSection {
var type: PasswordDetailTableViewControllerSectionType
var header: String?
@ -52,56 +52,56 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
header = nil
item = [TableCell]()
}
init(type: PasswordDetailTableViewControllerSectionType, header: String) {
self.init(type: type)
self.header = header
}
}
private var tableData = Array<TableSection>()
private enum PasswordDetailTableViewControllerSectionType {
case name, main, addition, misc
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "LabelTableViewCell", bundle: nil), forCellReuseIdentifier: "labelCell")
tableView.register(UINib(nibName: "PasswordDetailTitleTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordDetailTitleTableViewCell")
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PasswordDetailTableViewController.tapMenu(recognizer:)))
tapGesture.cancelsTouchesInView = false
tableView.addGestureRecognizer(tapGesture)
tapGesture.delegate = self
tableView.contentInset = UIEdgeInsetsMake(-36, 0, 44, 0);
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 52
editUIBarButtonItem.isEnabled = false
navigationItem.rightBarButtonItem = editUIBarButtonItem
if #available(iOS 11.0, *) {
navigationItem.largeTitleDisplayMode = .never
}
if let imageData = passwordEntity?.getImage() {
let image = UIImage(data: imageData as Data)
passwordImage = image
}
self.decryptThenShowPassword()
self.setupOneTimePasswordAutoRefresh()
// pop the current view because this password might be "discarded"
NotificationCenter.default.addObserver(self, selector: #selector(setShouldPopCurrentView), name: .passwordStoreChangeDiscarded, object: nil)
// reset the data table if some password (maybe another one) has been updated
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordStoreUpdated, object: nil)
// reset the data table if the disaply settings have been changed
NotificationCenter.default.addObserver(self, selector: #selector(decryptThenShowPassword), name: .passwordDetailDisplaySettingChanged, object: nil)
}
override func viewDidAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if self.shouldPopCurrentView {
@ -112,7 +112,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
self.present(alert, animated: true, completion: nil)
}
}
private func requestPGPKeyPassphrase() -> String {
let sem = DispatchSemaphore(value: 0)
var passphrase = ""
@ -134,7 +134,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
return passphrase
}
@objc private func decryptThenShowPassword() {
guard let passwordEntity = passwordEntity else {
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()
}
}
private func showPassword() {
DispatchQueue.main.async { [weak self] in
self?.setTableData()
@ -179,7 +179,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
}
private func setupOneTimePasswordAutoRefresh() {
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) {
[weak self] timer in
@ -204,19 +204,19 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
}
@objc private func pressEdit(_ sender: Any?) {
performSegue(withIdentifier: "editPasswordSegue", sender: self)
}
@objc private func setShouldPopCurrentView() {
self.shouldPopCurrentView = true
}
@IBAction private func cancelEditPassword(segue: UIStoryboardSegue) {
}
@IBAction private func saveEditPassword(segue: UIStoryboardSegue) {
if self.password!.changed != 0 {
SVProgressHUD.show(withStatus: "Saving")
@ -231,7 +231,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
SVProgressHUD.dismiss(withDelay: 1)
}
}
@IBAction private func deletePassword(segue: UIStoryboardSegue) {
do {
try passwordStore.delete(passwordEntity: passwordEntity!)
@ -243,7 +243,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
private func setTableData() {
self.tableData = Array<TableSection>()
// name section
var section = TableSection(type: .name)
section.item.append(TableCell())
@ -261,9 +261,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
section.item.append(TableCell(title: "password", content: password.password))
tableData.append(section)
// addition section
// show one time password
if password.otpType != .none {
if let (title, otp) = self.password?.getOtpStrings() {
@ -273,7 +273,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
oneTimePasswordIndexPath = IndexPath(row: 0, section: tableData.count - 1)
}
}
// show additional information
let filteredAdditionKeys = password.getFilteredAdditions()
if filteredAdditionKeys.count > 0 {
@ -283,14 +283,14 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
})
tableData.append(section)
}
// misc section
section = TableSection(type: .misc)
section.item.append(TableCell(title: "Show Raw"))
tableData.append(section)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "editPasswordSegue" {
if let controller = segue.destination as? UINavigationController {
@ -306,7 +306,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
}
private func updatePasswordImage(urlString: String) {
var newUrlString = urlString
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
newUrlString = "https://\(urlString)"
}
try? FavIcon.downloadPreferred(newUrlString) { [weak self] result in
if case let .success(image) = result {
let indexPath = IndexPath(row: 0, section: 0)
@ -334,7 +334,7 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
}
@objc private func tapMenu(recognizer: UITapGestureRecognizer) {
if recognizer.state == UIGestureRecognizerState.ended {
let tapLocation = recognizer.location(in: self.tableView)
@ -353,17 +353,17 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
}
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if touch.view!.isKind(of: UIButton.classForCoder()) {
return false
}
return true
}
@IBAction func back(segue:UIStoryboardSegue) {
}
func getNextHOTP() {
guard password != nil, passwordEntity != nil, password?.otpType == .hotp else {
DispatchQueue.main.async {
@ -371,12 +371,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
return;
}
// copy HOTP to pasteboard (will update counter)
if let plainPassword = password!.getNextHotp() {
SecurePasteboard.shared.copy(textToCopy: plainPassword)
}
// commit the change of HOTP counter
if password!.changed != 0 {
do {
@ -465,11 +465,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
detailTextLabel.textColor = .gray
detailTextLabel.text = "\(numberOfHiddenFields) hidden field\(numberOfHiddenFields > 1 ? "s" : "")"
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return tableData[section].header
}
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if section == tableData.count - 1 {
let view = UIView()
@ -484,13 +484,13 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
}
return nil
}
override func tableView(_ tableView: UITableView, performAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) {
if action == #selector(copy(_:)) {
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 {
let section = tableData[indexPath.section]
switch(section.type) {
@ -500,11 +500,11 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni
return false
}
}
override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let section = tableData[indexPath.section]
if section.type == .misc {

View file

@ -20,21 +20,21 @@ enum PasswordEditorCellKey {
}
class PasswordEditorTableViewController: UITableViewController, FillPasswordTableViewCellDelegate, PasswordSettingSliderTableViewCellDelegate, QRScannerControllerDelegate, UITextFieldDelegate, UITextViewDelegate, SFSafariViewControllerDelegate {
var tableData = [
[Dictionary<PasswordEditorCellKey, Any>]
]()
var password: Password?
private var navigationItemTitle: String?
private var sectionHeaderTitles = ["name", "password", "additions",""].map {$0.uppercased()}
private var sectionFooterTitles = ["", "", "Use \"key: value\" format for additional fields.", ""]
private let nameSection = 0
private let passwordSection = 1
private let additionsSection = 2
private var hidePasswordSettings = true
var nameCell: TextFieldTableViewCell?
var fillPasswordCell: FillPasswordTableViewCell?
private var passwordLengthCell: SliderTableViewCell?
@ -42,39 +42,39 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
private var deletePasswordCell: UITableViewCell?
private var scanQRCodeCell: UITableViewCell?
private var memorablePasswordGeneratorCell: UITableViewCell?
override func loadView() {
super.loadView()
deletePasswordCell = UITableViewCell(style: .default, reuseIdentifier: "default")
deletePasswordCell!.textLabel?.text = "Delete Password"
deletePasswordCell!.textLabel?.textColor = Globals.red
deletePasswordCell?.selectionStyle = .default
scanQRCodeCell = UITableViewCell(style: .default, reuseIdentifier: "default")
scanQRCodeCell?.textLabel?.text = "Add One-Time Password"
scanQRCodeCell?.textLabel?.textColor = Globals.blue
scanQRCodeCell?.selectionStyle = .default
scanQRCodeCell?.accessoryType = .disclosureIndicator
memorablePasswordGeneratorCell = UITableViewCell(style: .default, reuseIdentifier: "default")
memorablePasswordGeneratorCell?.textLabel?.text = "Get a Memorable One: xkpasswd"
memorablePasswordGeneratorCell?.textLabel?.textColor = Globals.blue
memorablePasswordGeneratorCell?.selectionStyle = .default
memorablePasswordGeneratorCell?.accessoryType = .disclosureIndicator
}
override func viewDidLoad() {
super.viewDidLoad()
if navigationItemTitle != nil {
navigationItem.title = navigationItemTitle
}
tableView.register(UINib(nibName: "TextFieldTableViewCell", bundle: nil), forCellReuseIdentifier: "textFieldCell")
tableView.register(UINib(nibName: "TextViewTableViewCell", bundle: nil), forCellReuseIdentifier: "textViewCell")
tableView.register(UINib(nibName: "FillPasswordTableViewCell", bundle: nil), forCellReuseIdentifier: "fillPasswordCell")
tableView.register(UINib(nibName: "SliderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordLengthCell")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 48
self.tableView.sectionFooterHeight = UITableViewAutomaticDimension;
@ -83,10 +83,10 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
override func viewDidLayoutSubviews() {
additionsCell?.contentTextView.setContentOffset(.zero, animated: false)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellData = tableData[indexPath.section][indexPath.row]
switch cellData[PasswordEditorCellKey.type] as! PasswordEditorCellType {
case .nameCell:
nameCell = tableView.dequeueReusableCell(withIdentifier: "textFieldCell", for: indexPath) as? TextFieldTableViewCell
@ -132,7 +132,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
return scanQRCodeCell!
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
@ -153,11 +153,11 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sectionHeaderTitles[section]
}
override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return sectionFooterTitles[section]
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
if selectedCell == deletePasswordCell {
@ -175,12 +175,12 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
let vc = SFSafariViewController(url: url, entersReaderIfAvailable: false)
vc.delegate = self
present(vc, animated: true)
}
}
tableView.deselectRow(at: indexPath, animated: true)
}
// generate password, copy to pasteboard, and set the cell
// check whether the current password looks like an OTP field
func generateAndCopyPassword() {
@ -195,23 +195,23 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
self.generateAndCopyPasswordNoOtpCheck()
}
}
// generate the password, don't care whether the original line is otp
func generateAndCopyPasswordNoOtpCheck() {
// show password settings (e.g., the length slider)
showPasswordSettings()
let length = passwordLengthCell?.roundedValue ?? 0
let plainPassword = PasswordGeneratorFlavour.from(SharedDefaults[.passwordGeneratorFlavor]).generatePassword(length: length)
SecurePasteboard.shared.copy(textToCopy: plainPassword)
// update tableData so to make sure reloadData() works correctly
tableData[passwordSection][0][PasswordEditorCellKey.content] = plainPassword
// update cell manually, no need to call reloadData()
fillPasswordCell?.setContent(content: plainPassword)
}
// show password settings (e.g., the length slider)
func showPasswordSettings() {
if hidePasswordSettings == true {
@ -219,13 +219,13 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
tableView.reloadSections([passwordSection], with: .fade)
}
}
// show/hide password settings (e.g., the length slider)
func showHidePasswordSettings() {
hidePasswordSettings = !hidePasswordSettings
tableView.reloadSections([passwordSection], with: .fade)
}
func insertScannedOTPFields(_ otpauth: String) {
// update tableData
var additionsString = ""
@ -235,11 +235,11 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
additionsString = otpauth
}
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsString
// reload the additions cell
additionsCell?.setContent(content: additionsString)
}
// MARK: - QRScannerControllerDelegate Methods
func checkScannedOutput(line: String) -> (accept: Bool, message: String) {
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")
}
}
// MARK: - QRScannerControllerDelegate Methods
func handleScannedOutput(line: String) {
insertScannedOTPFields(line)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showQRScannerSegue" {
if let navController = segue.destination as? UINavigationController {
@ -265,7 +265,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
}
}
}
// update tableData so to make sure reloadData() works correctly
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == nameCell?.contentTextField {
@ -277,48 +277,48 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
}
}
}
// update tableData so to make sure reloadData() works correctly
func textViewDidEndEditing(_ textView: UITextView) {
if textView == additionsCell?.contentTextView {
tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent()
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == fillPasswordCell?.contentTextField {
// show password generation settings automatically
showPasswordSettings()
}
}
func getNameURL() -> (String, URL) {
let encodedName = (nameCell?.getContent()?.stringByAddingPercentEncodingForRFC3986())!
let name = URL(string: encodedName)!.lastPathComponent
let url = URL(string: encodedName)!.appendingPathExtension("gpg")
return (name, url)
}
func checkName() -> Bool {
// the name field should not be empty
guard let name = nameCell?.getContent(), name.isEmpty == false else {
Utils.alert(title: "Cannot Save", message: "Please fill in the name.", controller: self, completion: nil)
return false
}
// the name should not start with /
guard name.hasPrefix("/") == false else {
Utils.alert(title: "Cannot Save", message: "Please remove the prefix \"/\" from your password name.", controller: self, completion: nil)
return false
}
// the name field should be a valid url
guard let path = name.stringByAddingPercentEncodingForRFC3986(),
var passwordURL = URL(string: path) else {
Utils.alert(title: "Cannot Save", message: "Password name is invalid.", controller: self, completion: nil)
return false
}
// check whether we can parse the filename (be consistent with PasswordStore::addPasswordEntities)
var previousPathLength = Int.max
while passwordURL.path != "." {
@ -329,10 +329,10 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
}
previousPathLength = passwordURL.path.count
}
return true
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
let copiedLinesSplit = UIPasteboard.general.string?.components(separatedBy: CharacterSet.whitespacesAndNewlines).filter({ !$0.isEmpty })
if copiedLinesSplit?.count ?? 0 > 0 {
@ -353,5 +353,5 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl
self.present(alert, animated: true, completion: nil)
}
}
}

View file

@ -27,13 +27,13 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private var parentPasswordEntity: PasswordEntity? = nil
private let passwordStore = PasswordStore.shared
private var tapTabBarTime: TimeInterval = 0
private var sections = [(title: String, entries: [PasswordsTableEntry])]()
private var searchActive : Bool = false
private lazy var searchController: UISearchController = {
let uiSearchController = UISearchController(searchResultsController: nil)
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(_:)))
return backUIBarButtonItem
}()
private lazy var transitionFromRight: CATransition = {
let transition = CATransition()
transition.type = kCATransitionPush
@ -69,7 +69,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
transition.subtype = kCATransitionFromRight
return transition
}()
private lazy var transitionFromLeft: CATransition = {
let transition = CATransition()
transition.type = kCATransitionPush
@ -81,7 +81,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}()
@IBOutlet weak var tableView: UITableView!
private func initPasswordsTableEntries(parent: PasswordEntity?) {
passwordsTableEntries.removeAll()
passwordsTableAllEntries.removeAll()
@ -102,9 +102,9 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
parentPasswordEntity = parent
}
@IBAction func cancelAddPassword(segue: UIStoryboardSegue) {
}
@IBAction func saveAddPassword(segue: UIStoryboardSegue) {
if let controller = segue.source as? AddPasswordTableViewController {
@ -127,7 +127,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
private func syncPasswords() {
guard passwordStore.repositoryExisted() else {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(800)) {
@ -190,17 +190,17 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if SharedDefaults[.isShowFolderOn] {
searchController.searchBar.scopeButtonTitles = ["Current", "All"]
} else {
searchController.searchBar.scopeButtonTitles = nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchBar.delegate = self
@ -220,22 +220,22 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
tableView.refreshControl = syncControl
SVProgressHUD.setDefaultMaskType(.black)
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
// initialize the password table
reloadTableView(parent: nil)
// reset the data table if some password (maybe another one) has been updated
NotificationCenter.default.addObserver(self, selector: #selector(actOnReloadTableViewRelatedNotification), name: .passwordStoreUpdated, object: nil)
// 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(actOnSearchNotification), name: .passwordSearch, object: nil)
// listen to the swipe back guesture
let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture))
swipeRight.direction = UISwipeGestureRecognizerDirection.right
self.view.addGestureRecognizer(swipeRight)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
tabBarController!.delegate = self
@ -243,7 +243,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
tableView.deselectRow(at: path, animated: false)
}
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
guard #available(iOS 11, *) else {
@ -252,21 +252,21 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
return
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].entries.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
longPressGestureRecognizer.minimumPressDuration = 0.6
if SharedDefaults[.isShowFolderOn] && searchController.searchBar.selectedScopeButtonIndex == 0{
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
let entry = getPasswordEntry(by: indexPath)
if entry.passwordEntity!.synced {
cell.textLabel?.text = entry.title
@ -299,11 +299,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
return sections[indexPath.section].entries[indexPath.row]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath)
if !entry.isDir {
@ -318,7 +318,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
reloadTableView(parent: entry.passwordEntity, anim: transitionFromRight)
}
}
@objc func respondToSwipeGesture(gesture: UIGestureRecognizer) {
if let swipeGesture = gesture as? UISwipeGestureRecognizer {
// swipe right -> swipe back
@ -327,7 +327,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
@objc func backAction(_ sender: Any?) {
guard SharedDefaults[.isShowFolderOn] else { return }
var anim: CATransition? = transitionFromLeft
@ -336,7 +336,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
reloadTableView(parent: parentPasswordEntity?.parent, anim: anim)
}
@objc func longPressAction(_ gesture: UILongPressGestureRecognizer) {
if gesture.state == UIGestureRecognizerState.began {
let touchPoint = gesture.location(in: tableView)
@ -345,7 +345,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].title
}
@ -357,11 +357,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return index
}
func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
decryptThenCopyPassword(from: indexPath)
}
private func requestPGPKeyPassphrase() -> String {
let sem = DispatchSemaphore(value: 0)
var passphrase = ""
@ -389,7 +389,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
return passphrase
}
private func decryptThenCopyPassword(from indexPath: IndexPath) {
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)
@ -418,39 +418,39 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
private func generateSections(item: [PasswordsTableEntry]) {
let collation = UILocalizedIndexedCollation.current()
let sectionTitles = collation.sectionIndexTitles
var newSections = [(title: String, entries: [PasswordsTableEntry])]()
// initialize all sections
for i in 0..<sectionTitles.count {
newSections.append((title: sectionTitles[i], entries: [PasswordsTableEntry]()))
}
// put entries into sections
for entry in item {
let sectionNumber = collation.section(for: entry, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[sectionNumber].entries.append(entry)
}
// sort each list and set sectionTitles
for i in 0..<sectionTitles.count {
let entriesToSort = newSections[i].entries
let sortedEntries = collation.sortedArray(from: entriesToSort, collationStringSelector: #selector(getter: PasswordsTableEntry.title))
newSections[i].entries = sortedEntries as! [PasswordsTableEntry]
}
// only keep non-empty sections
sections = newSections.filter {$0.entries.count > 0}
}
@objc func actOnSearchNotification() {
searchController.searchBar.becomeFirstResponder()
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "showPasswordDetail" {
guard self.passwordStore.privateKey != nil else {
@ -487,7 +487,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
func filterContentForSearchText(searchText: String, scope: String = "All") {
switch scope {
case "All":
@ -512,10 +512,10 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
default:
break
}
}
private func reloadTableView(data: [PasswordsTableEntry], anim: CAAnimation? = nil) {
// set navigation item
let numberOfLocalCommits = self.passwordStore.numberOfLocalCommits
@ -529,7 +529,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
} else {
navigationItem.leftBarButtonItem = nil
}
// set the password table
generateSections(item: data)
if anim != nil {
@ -537,7 +537,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
tableView.reloadData()
self.tableView.layer.removeAnimation(forKey: "UITableViewReloadDataAnimationKey")
// set the sync control title
let atribbutedTitle = "Last Synced: \(lastSyncedTimeString())"
syncControl.attributedTitle = NSAttributedString(string: atribbutedTitle)
@ -552,12 +552,12 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
formatter.timeStyle = .short
return formatter.string(from: date)
}
private func reloadTableView(parent: PasswordEntity?, anim: CAAnimation? = nil) {
initPasswordsTableEntries(parent: parent)
reloadTableView(data: passwordsTableEntries, anim: anim)
}
@objc func actOnReloadTableViewRelatedNotification() {
DispatchQueue.main.async { [weak weakSelf = self] in
guard let strongSelf = weakSelf else { return }
@ -565,11 +565,11 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
strongSelf.reloadTableView(data: strongSelf.passwordsTableEntries)
}
}
@objc func handleRefresh(_ syncControl: UIRefreshControl) {
syncPasswords()
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
if viewController == self.navigationController {
let currentTime = Date().timeIntervalSince1970
@ -586,14 +586,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
backAction(self)
}
}
func searchBar(_ searchBar: UISearchBar, selectedScopeButtonIndexDidChange selectedScope: Int) {
// update the default search scope
SharedDefaults[.isSearchDefaultAll] = searchController.searchBar.scopeButtonTitles![selectedScope] == "All"
updateSearchResults(for: searchController)
}
func searchBarShouldBeginEditing(_ searchBar: UISearchBar) -> Bool {
// set the default search scope to "all"
if SharedDefaults[.isShowFolderOn] && SharedDefaults[.isSearchDefaultAll] {
@ -603,14 +603,14 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
}
return true
}
func searchBarShouldEndEditing(_ searchBar: UISearchBar) -> Bool {
// set the default search scope to "current"
searchController.searchBar.selectedScopeButtonIndex = 0
updateSearchResults(for: searchController)
return true
}
private func requestGitPassword(credential: GitCredential.Credential, lastPassword: String?) -> String? {
let sem = DispatchSemaphore(value: 0)
var password: String?
@ -621,7 +621,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
case .ssh:
message = "Please fill in the password of your SSH key."
}
DispatchQueue.main.async {
SVProgressHUD.dismiss()
let alert = UIAlertController(title: "Password", message: message, preferredStyle: UIAlertControllerStyle.alert)
@ -639,7 +639,7 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV
})
self.present(alert, animated: true, completion: nil)
}
let _ = sem.wait(timeout: .distantFuture)
return password
}

View file

@ -17,90 +17,90 @@ protocol QRScannerControllerDelegate {
}
class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
@IBOutlet weak var scannerOutput: UILabel!
var captureSession: AVCaptureSession?
var videoPreviewLayer: AVCaptureVideoPreviewLayer?
var qrCodeFrameView: UIView?
let supportedCodeTypes = [AVMetadataObject.ObjectType.qr]
var delegate: QRScannerControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
if AVCaptureDevice.authorizationStatus(for: .video) == .denied {
presentCameraSettings()
}
// 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)
do {
// Get an instance of the AVCaptureDeviceInput class using the previous device object.
let input = try AVCaptureDeviceInput(device: captureDevice!)
// Initialize the captureSession object.
captureSession = AVCaptureSession()
// Set the input device on the capture session.
captureSession?.addInput(input)
// Initialize a AVCaptureMetadataOutput object and set it as the output device to the capture session.
let captureMetadataOutput = AVCaptureMetadataOutput()
captureSession?.addOutput(captureMetadataOutput)
// Set delegate and use the default dispatch queue to execute the call back
captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
// Initialize the video preview layer and add it as a sublayer to the viewPreview view's layer.
videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession!)
videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
videoPreviewLayer?.frame = view.layer.bounds
view.layer.addSublayer(videoPreviewLayer!)
// Start video capture.
captureSession?.startRunning()
// Move the message label to the front
scannerOutput.layer.cornerRadius = 10
scannerOutput.text = "No QR code detected"
view.bringSubview(toFront: scannerOutput)
// Initialize QR Code Frame to highlight the QR code
qrCodeFrameView = UIView()
if let qrCodeFrameView = qrCodeFrameView {
qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
qrCodeFrameView.layer.borderWidth = 2
view.addSubview(qrCodeFrameView)
view.bringSubview(toFront: qrCodeFrameView)
}
} catch {
scannerOutput.text = error.localizedDescription
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - AVCaptureMetadataOutputObjectsDelegate Methods
func metadataOutput(_ captureOutput: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
if let metadataObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject,
supportedCodeTypes.contains(metadataObj.type),
let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj) {
// draw a bounds on the found QR code
qrCodeFrameView?.frame = barCodeObject.bounds
// check whether it is a valid result
if let scanned = metadataObj.stringValue {
if let (accept, message) = delegate?.checkScannedOutput(line: scanned) {
@ -121,13 +121,13 @@ class QRScannerController: UIViewController, AVCaptureMetadataOutputObjectsDeleg
} else {
scannerOutput.text = "No string value"
}
} else {
qrCodeFrameView?.frame = CGRect.zero
scannerOutput.text = "No QR code detected"
}
}
func presentCameraSettings() {
let alertController = UIAlertController(title: "Error",
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)
}
}

View file

@ -13,7 +13,7 @@ class RawPasswordViewController: UIViewController {
@IBOutlet weak var rawPasswordTextView: UITextView!
var password: Password?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = password?.name

View file

@ -14,21 +14,21 @@ class SSHKeySettingTableViewController: UITableViewController {
@IBOutlet weak var privateKeyURLTextField: UITextField!
let passwordStore = PasswordStore.shared
override func viewDidLoad() {
super.viewDidLoad()
privateKeyURLTextField.text = SharedDefaults[.gitSSHPrivateKeyURL]?.absoluteString
}
@IBAction func doneButtonTapped(_ sender: UIButton) {
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)
return
}
SharedDefaults[.gitSSHPrivateKeyURL] = privateKeyURL
do {
try Data(contentsOf: privateKeyURL).write(to: URL(fileURLWithPath: Globals.gitSSHPrivateKeyPath), options: .atomic)
} catch {

View file

@ -13,7 +13,7 @@ class SettingsSplitViewController: UISplitViewController, UISplitViewControllerD
self.delegate = self
self.preferredDisplayMode = .allVisible
}
func splitViewController(
_ splitViewController: UISplitViewController,
collapseSecondary secondaryViewController: UIViewController,

View file

@ -16,14 +16,14 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
@IBOutlet weak var passcodeTableViewCell: UITableViewCell!
@IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell!
var setPasscodeLockAlert: UIAlertController?
let passwordStore = PasswordStore.shared
var passcodeLock = PasscodeLock.shared
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
navigationController?.popViewController(animated: true)
}
@IBAction func savePGPKey(segue: UIStoryboardSegue) {
if let controller = segue.source as? PGPKeySettingTableViewController {
SharedDefaults[.pgpPrivateKeyURL] = URL(string: controller.pgpPrivateKeyURLTextField.text!.trimmed)
@ -32,7 +32,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
self.passwordStore.pgpKeyPassphrase = controller.pgpPassphrase
}
SharedDefaults[.pgpKeySource] = "url"
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "Fetching PGP Key")
@ -53,7 +53,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
}
}
} else if let controller = segue.source as? PGPKeyArmorSettingTableViewController {
SharedDefaults[.pgpKeySource] = "armor"
if SharedDefaults[.isRememberPGPPassphraseOn] {
@ -62,7 +62,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
SharedDefaults[.pgpPublicKeyArmor] = controller.armorPublicKeyTextView.text!
SharedDefaults[.pgpPrivateKeyArmor] = controller.armorPrivateKeyTextView.text!
SVProgressHUD.setDefaultMaskType(.black)
SVProgressHUD.setDefaultStyle(.light)
SVProgressHUD.show(withStatus: "Fetching PGP Key")
@ -84,7 +84,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
}
}
private func saveImportedPGPKey() {
// load keys
SharedDefaults[.pgpKeySource] = "file"
@ -108,11 +108,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
}
}
@IBAction func saveGitServerSetting(segue: UIStoryboardSegue) {
self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return super.tableView(tableView, numberOfRowsInSection: section)
}
@ -125,12 +125,12 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
setPasswordRepositoryTableViewCellDetailText()
setPasscodeLockCell()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
tabBarController!.delegate = self
}
private func setPasscodeLockCell() {
if passcodeLock.hasPasscode {
self.passcodeTableViewCell.detailTextLabel?.text = "On"
@ -138,7 +138,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
self.passcodeTableViewCell.detailTextLabel?.text = "Off"
}
}
private func setPGPKeyTableViewCellDetailText() {
if let pgpKeyID = self.passwordStore.pgpKeyID {
pgpKeyTableViewCell.detailTextLabel?.text = pgpKeyID
@ -146,7 +146,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
pgpKeyTableViewCell.detailTextLabel?.text = "Not Set"
}
}
private func setPasswordRepositoryTableViewCellDetailText() {
if SharedDefaults[.gitURL] == nil {
passwordRepositoryTableViewCell.detailTextLabel?.text = "Not Set"
@ -154,13 +154,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]!.host
}
}
@objc func actOnPasswordStoreErasedNotification() {
setPGPKeyTableViewCellDetailText()
setPasswordRepositoryTableViewCellDetailText()
setPasscodeLockCell()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView.cellForRow(at: indexPath) == passcodeTableViewCell {
if SharedDefaults[.passcodeKey] != nil{
@ -173,13 +173,13 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
tableView.deselectRow(at: indexPath, animated: true)
}
func showPGPKeyActionSheet() {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
var urlActionTitle = "Download from URL"
var armorActionTitle = "ASCII-Armor Encrypted Key"
var fileActionTitle = "iTunes File Sharing"
if SharedDefaults[.pgpKeySource] == "url" {
urlActionTitle = "\(urlActionTitle)"
} else if SharedDefaults[.pgpKeySource] == "armor" {
@ -235,8 +235,8 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
optionMenu.addAction(fileAction)
}
if SharedDefaults[.pgpKeySource] != nil {
let deleteAction = UIAlertAction(title: "Remove PGP Keys", style: .destructive) { _ in
self.passwordStore.removePGPKeys()
@ -249,12 +249,12 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
optionMenu.popoverPresentationController?.sourceRect = pgpKeyTableViewCell.bounds
self.present(optionMenu, animated: true, completion: nil)
}
func showPasscodeActionSheet() {
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let passcodeRemoveViewController = PasscodeLockViewController()
let removePasscodeAction = UIAlertAction(title: "Remove Passcode", style: .destructive) { [weak self] _ in
passcodeRemoveViewController.successCallback = {
self?.passcodeLock.delete()
@ -262,11 +262,11 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
self?.present(passcodeRemoveViewController, animated: true, completion: nil)
}
let changePasscodeAction = UIAlertAction(title: "Change Passcode", style: .default) { [weak self] _ in
self?.setPasscodeLock()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
optionMenu.addAction(removePasscodeAction)
optionMenu.addAction(changePasscodeAction)
@ -289,7 +289,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
}
}
}
func setPasscodeLock() {
// 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)
@ -303,7 +303,7 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
textField.isSecureTextEntry = true
textField.addTarget(self, action: #selector(self.alertTextFieldDidChange(_:)), for: UIControlEvents.editingChanged)
})
// save action
let saveAction = UIAlertAction(title: "Save", style: .default) { (action:UIAlertAction) -> Void in
let passcode: String = self.setPasscodeLockAlert!.textFields![0].text!
@ -312,10 +312,10 @@ class SettingsTableViewController: UITableViewController, UITabBarControllerDele
self.setPasscodeLockCell()
}
saveAction.isEnabled = false // disable the Save button by default
// cancel action
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
// present
setPasscodeLockAlert?.addAction(saveAction)
setPasscodeLockAlert?.addAction(cancelAction)

View file

@ -19,7 +19,7 @@ class SpecialThanksTableViewController: BasicStaticTableViewController {
["FlatIcon",
"https://www.flaticon.com"],
]
override func viewDidLoad() {
tableData.append([])
for item in openSourceComponents {

View file

@ -12,33 +12,33 @@ import UIKit
class SecurePasteboard {
public static let shared = SecurePasteboard()
private var backgroundTaskID = UIBackgroundTaskInvalid
func copy(textToCopy: String?, expirationTime: Double = 45) {
// copy to the pasteboard
UIPasteboard.general.string = textToCopy ?? ""
// clean the pasteboard after expirationTime
guard expirationTime > 0 else {
return
}
// exit the existing background task, if any
if backgroundTaskID != UIBackgroundTaskInvalid {
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
backgroundTaskID = UIApplication.shared.beginBackgroundTask(expirationHandler: { [weak self] in
UIPasteboard.general.string = ""
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
self?.backgroundTaskID = UIBackgroundTaskInvalid
})
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + expirationTime) { [weak self] in
UIPasteboard.general.string = ""
UIApplication.shared.endBackgroundTask(UIBackgroundTaskInvalid)
self?.backgroundTaskID = UIBackgroundTaskInvalid
}
}
}

View file

@ -18,11 +18,11 @@ class ContentTableViewCell: UITableViewCell {
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
func getContent() -> String? {
return nil
}
func setContent(content: String?) { }
}

View file

@ -18,15 +18,15 @@ class FillPasswordTableViewCell: ContentTableViewCell {
@IBOutlet weak var contentTextField: UITextField!
var delegate: FillPasswordTableViewCellDelegate?
@IBOutlet weak var settingButton: UIButton!
@IBOutlet weak var generateButton: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
contentTextField.font = Globals.passwordFont
// Force aspect ratio of button images
settingButton.imageView?.contentMode = .scaleAspectFit
generateButton.imageView?.contentMode = .scaleAspectFit
@ -37,24 +37,24 @@ class FillPasswordTableViewCell: ContentTableViewCell {
// Configure the view for the selected state
}
@IBAction func generatePassword(_ sender: UIButton) {
self.delegate?.generateAndCopyPassword()
}
@IBAction func showHidePasswordSettings() {
self.delegate?.showHidePasswordSettings()
}
// re-color
@IBAction func textFieldDidChange(_ sender: UITextField) {
contentTextField.attributedText = Utils.attributedPassword(plainPassword: sender.text ?? "")
}
override func getContent() -> String? {
return contentTextField.attributedText?.string
}
override func setContent(content: String?) {
contentTextField.attributedText = Utils.attributedPassword(plainPassword: content ?? "")
}

View file

@ -19,19 +19,19 @@ class LabelTableViewCell: UITableViewCell {
@IBOutlet weak var contentLabel: UILabel!
@IBOutlet weak var titleLabel: UILabel!
private enum CellType {
case password, URL, HOTP, other
}
private var type = CellType.other
private var isReveal = false
weak var delegatePasswordTableView : PasswordDetailTableViewController?
private var passwordDisplayButton: UIButton?
private var buttons: UIView?
var cellData: LabelTableViewCellData? {
didSet {
guard let title = cellData?.title, let content = cellData?.content else {
@ -72,7 +72,7 @@ class LabelTableViewCell: UITableViewCell {
updateButtons()
}
}
override var canBecomeFirstResponder: Bool {
get {
return true
@ -103,7 +103,7 @@ class LabelTableViewCell: UITableViewCell {
override func copy(_ sender: Any?) {
SecurePasteboard.shared.copy(textToCopy: cellData?.content)
}
@objc func revealPassword(_ sender: Any?) {
let plainPassword = cellData?.content ?? ""
if type == .password {
@ -114,7 +114,7 @@ class LabelTableViewCell: UITableViewCell {
isReveal = true
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Invisible"), for: .normal)
}
@objc func concealPassword(_ sender: Any?) {
if type == .password {
if cellData?.content.isEmpty == false {
@ -128,7 +128,7 @@ class LabelTableViewCell: UITableViewCell {
isReveal = false
passwordDisplayButton?.setImage(#imageLiteral(resourceName: "Visible"), for: .normal)
}
@objc func reversePasswordDisplay(_ sender: Any?) {
if isReveal {
// conceal
@ -143,21 +143,21 @@ class LabelTableViewCell: UITableViewCell {
// if isURLCell, passwordTableView should not be nil
delegatePasswordTableView!.openLink(to: cellData?.content)
}
@objc func getNextHOTP(_ sender: Any?) {
// if isHOTPCell, passwordTableView should not be nil
delegatePasswordTableView!.getNextHOTP()
}
private func updateButtons() {
// total width and height of a button
let height = min(self.bounds.height, 36.0)
let width = max(height * 0.8, Globals.tableCellButtonSize)
// margins (between button boundary and icon)
let marginY = max((height - Globals.tableCellButtonSize) / 2, 0.0)
let marginX = max((width - Globals.tableCellButtonSize) / 2, 0.0)
switch type {
case .password:
if let content = cellData?.content, content != "" {
@ -178,7 +178,7 @@ class LabelTableViewCell: UITableViewCell {
nextButton.imageView?.contentMode = .scaleAspectFit
nextButton.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
nextButton.addTarget(self, action: #selector(getNextHOTP), for: UIControlEvents.touchUpInside)
// password button
passwordDisplayButton = UIButton(type: .system)
passwordDisplayButton!.frame = CGRect(x: width, y: 0, width: width, height: height)
@ -187,7 +187,7 @@ class LabelTableViewCell: UITableViewCell {
passwordDisplayButton!.imageView?.contentMode = .scaleAspectFit
passwordDisplayButton!.contentEdgeInsets = UIEdgeInsetsMake(marginY, marginX, marginY, marginX)
passwordDisplayButton!.addTarget(self, action: #selector(reversePasswordDisplay), for: UIControlEvents.touchUpInside)
buttons = UIView()
buttons!.frame = CGRect(x: 0, y: 0, width: width * 2, height: height)
buttons!.addSubview(nextButton)

View file

@ -22,5 +22,5 @@ class PasswordDetailTitleTableViewCell: UITableViewCell {
// Configure the view for the selected state
}
}

View file

@ -18,15 +18,15 @@ class SliderTableViewCell: ContentTableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var valueLabel: UILabel!
@IBOutlet weak var slider: UISlider!
var delegate: UITableViewController?
var roundedValue: Int {
get {
return Int(valueLabel.text!)!
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
@ -37,7 +37,7 @@ class SliderTableViewCell: ContentTableViewCell {
// Configure the view for the selected state
}
@IBAction func handleSliderValueChange(_ sender: UISlider) {
let oldRoundedValue = self.roundedValue
let newRoundedValue = Int(sender.value)
@ -51,14 +51,14 @@ class SliderTableViewCell: ContentTableViewCell {
delegate.generateAndCopyPassword()
}
}
func reset(title: String, minimumValue: Int, maximumValue: Int, defaultValue: Int) {
titleLabel.text = title
slider.minimumValue = Float(minimumValue)
slider.maximumValue = Float(maximumValue)
slider.value = Float(defaultValue)
valueLabel.text = String(defaultValue)
// "not editable"
if minimumValue == maximumValue {
titleLabel.textColor = UIColor.gray
@ -66,5 +66,5 @@ class SliderTableViewCell: ContentTableViewCell {
slider.isUserInteractionEnabled = false
}
}
}

View file

@ -11,15 +11,15 @@ import UIKit
class TextFieldTableViewCell: ContentTableViewCell {
@IBOutlet weak var contentTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func getContent() -> String? {
return contentTextField.text
}

View file

@ -19,11 +19,11 @@ class TextViewTableViewCell: ContentTableViewCell {
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func getContent() -> String? {
return contentTextView.text
}
override func setContent(content: String?) {
contentTextView.text = content
}

View file

@ -12,7 +12,7 @@ class TitleTextFieldTableViewCell: UITableViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var contentTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
@ -20,7 +20,7 @@ class TitleTextFieldTableViewCell: UITableViewCell {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tap(_:)))
titleLabel.addGestureRecognizer(tapGestureRecognizer)
}
@objc func tap(_ sender: Any?) {
contentTextField.becomeFirstResponder()
}
@ -28,5 +28,5 @@ class TitleTextFieldTableViewCell: UITableViewCell {
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}

View file

@ -25,18 +25,18 @@ fileprivate class PasswordsTableEntry : NSObject {
class CredentialProviderViewController: ASCredentialProviderViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate {
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
private let passwordStore = PasswordStore.shared
private var searchActive = false
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
private lazy var passcodelock: PasscodeExtensionDisplay = {
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
return passcodelock
}()
/*
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
@ -50,14 +50,14 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
searchBarSearchButtonClicked(searchBar)
return
}
// get the domain
var identifier = serviceIdentifiers[0].identifier
if !identifier.hasPrefix("http://") && !identifier.hasPrefix("https://") {
identifier = "http://" + identifier
}
let url = URL(string: identifier)?.host ?? ""
// "click" search
searchBar.text = url
searchBar.becomeFirstResponder()
@ -102,20 +102,20 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
super.viewWillAppear(animated)
passcodelock.presentPasscodeLockIfNeeded(self)
}
override func viewDidLoad() {
super.viewDidLoad()
// prepare
searchBar.delegate = self
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
// initialize table entries
initPasswordsTableEntries()
}
private func initPasswordsTableEntries() {
passwordsTableEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
@ -125,7 +125,7 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
PasswordsTableEntry($0)
}
}
// define cell contents, and set long press action
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
@ -140,16 +140,16 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
cell.detailTextLabel?.text = entry.categoryText
return cell
}
// select row -> extension returns (with username and password)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath)
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)
return
}
let passwordEntity = entry.passwordEntity!
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
DispatchQueue.global(qos: .userInteractive).async {
@ -171,18 +171,18 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive {
return filteredPasswordsTableEntries.count
}
return passwordsTableEntries.count;
}
private func requestPGPKeyPassphrase() -> String {
let sem = DispatchSemaphore(value: 0)
var passphrase = ""
@ -204,13 +204,13 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
}
return passphrase
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
searchActive = false
self.tableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, searchText.isEmpty == false {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
@ -229,11 +229,11 @@ class CredentialProviderViewController: ASCredentialProviderViewController, UITa
}
self.tableView.reloadData()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBarSearchButtonClicked(searchBar)
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
if searchActive {
return filteredPasswordsTableEntries[indexPath.row]

View file

@ -31,7 +31,7 @@ class PasscodeExtensionDisplay {
private var isPasscodePresented = false
private let passcodeLockVC: PasscodeLockViewControllerForExtension
private let extensionContext: ASCredentialProviderExtensionContext?
public init(extensionContext: ASCredentialProviderExtensionContext?) {
self.extensionContext = extensionContext
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
@ -40,7 +40,7 @@ class PasscodeExtensionDisplay {
}
passcodeLockVC.setCancellable(true)
}
// present the passcode lock view if passcode is set and the view controller is not presented
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
@ -49,7 +49,7 @@ class PasscodeExtensionDisplay {
isPasscodePresented = true
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
}
public func dismiss(animated: Bool = true) {
isPasscodePresented = false
}

View file

@ -26,24 +26,24 @@ fileprivate class PasswordsTableEntry : NSObject {
class ExtensionViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchBarDelegate, UINavigationBarDelegate {
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
private let passwordStore = PasswordStore.shared
private var searchActive = false
private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
enum Action {
case findLogin, fillBrowser, unknown
}
private var extensionAction = Action.unknown
private lazy var passcodelock: PasscodeExtensionDisplay = {
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
return passcodelock
}()
private func initPasswordsTableEntries() {
passwordsTableEntries.removeAll()
filteredPasswordsTableEntries.removeAll()
@ -53,12 +53,12 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
PasswordsTableEntry($0)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
passcodelock.presentPasscodeLockIfNeeded(self)
}
override func viewDidLoad() {
super.viewDidLoad()
// prepare
@ -66,15 +66,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
tableView.delegate = self
tableView.dataSource = self
tableView.register(UINib(nibName: "PasswordWithFolderTableViewCell", bundle: nil), forCellReuseIdentifier: "passwordWithFolderTableViewCell")
// initialize table entries
initPasswordsTableEntries()
// get the provider
guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else {
return
}
for extensionItem in extensionItems {
if let itemProviders = extensionItem.attachments as? [NSItemProvider] {
for provider in itemProviders {
@ -133,7 +133,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
// define cell contents, and set long press action
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "passwordTableViewCell", for: indexPath)
@ -148,16 +148,16 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
cell.detailTextLabel?.text = entry.categoryText
return cell
}
// select row -> extension returns (with username and password)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let entry = getPasswordEntry(by: indexPath)
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)
return
}
let passwordEntity = entry.passwordEntity!
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
DispatchQueue.global(qos: .userInteractive).async {
@ -197,19 +197,19 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searchActive{
return filteredPasswordsTableEntries.count
}
return passwordsTableEntries.count;
}
private func requestPGPKeyPassphrase() -> String {
let sem = DispatchSemaphore(value: 0)
var passphrase = ""
@ -231,17 +231,17 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
return passphrase
}
@IBAction func cancelExtension(_ sender: Any) {
extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.text = ""
searchActive = false
self.tableView.reloadData()
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let searchText = searchBar.text, searchText.isEmpty == false {
filteredPasswordsTableEntries = passwordsTableEntries.filter { entry in
@ -260,11 +260,11 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
}
self.tableView.reloadData()
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchBarSearchButtonClicked(searchBar)
}
private func getPasswordEntry(by indexPath: IndexPath) -> PasswordsTableEntry {
if searchActive {
return filteredPasswordsTableEntries[indexPath.row]

View file

@ -28,7 +28,7 @@ class OnePasswordExtensionKey {
static let returnedFieldsKey = "returned_fields"
static let oldPasswordKey = "old_password"
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
static let generatedPasswordMinLengthKey = "password_min_length"
static let generatedPasswordMaxLengthKey = "password_max_length"

View file

@ -30,7 +30,7 @@ class PasscodeExtensionDisplay {
private var isPasscodePresented = false
private let passcodeLockVC: PasscodeLockViewControllerForExtension
private let extensionContext: NSExtensionContext?
public init(extensionContext: NSExtensionContext?) {
self.extensionContext = extensionContext
passcodeLockVC = PasscodeLockViewControllerForExtension(extensionContext: extensionContext)
@ -39,7 +39,7 @@ class PasscodeExtensionDisplay {
}
passcodeLockVC.setCancellable(true)
}
// present the passcode lock view if passcode is set and the view controller is not presented
public func presentPasscodeLockIfNeeded(_ extensionVC: UIViewController) {
guard PasscodeLock.shared.hasPasscode && !isPasscodePresented == true else {
@ -48,7 +48,7 @@ class PasscodeExtensionDisplay {
isPasscodePresented = true
extensionVC.present(passcodeLockVC, animated: true, completion: nil)
}
public func dismiss(animated: Bool = true) {
isPasscodePresented = false
}

View file

@ -12,7 +12,7 @@ run: function(arguments) {
arguments.completionFunction({"url_string": url, "error": error});
}
},
finalize: function(arguments) {
if (arguments["password"]) {
var passwordElement = document.querySelector("input[type=password]")
@ -21,7 +21,7 @@ finalize: function(arguments) {
passwordElement.value = arguments["password"]
}
}
if (arguments["username"]) {
var usernameElement = document.querySelector("input[type=email], input[type=text]")
if (usernameElement) {

View file

@ -11,7 +11,7 @@
import UIKit
open class PasscodeLockPresenter {
fileprivate var mainWindow: UIWindow?
fileprivate var passcodeLockWindow: UIWindow?
@ -21,16 +21,16 @@ open class PasscodeLockPresenter {
open func present(windowLevel: CGFloat?) {
guard PasscodeLock.shared.hasPasscode else { return }
// dismiss the original window
dismiss()
// new window
mainWindow?.endEditing(true)
passcodeLockWindow = UIWindow(frame: self.mainWindow!.frame)
moveWindowsToFront(windowLevel: windowLevel)
passcodeLockWindow?.isHidden = false
// new vc
let passcodeLockVC = PasscodeLockViewController()
let userDismissCompletionCallback = passcodeLockVC.dismissCompletionCallback

View file

@ -12,23 +12,23 @@ import UIKit
import LocalAuthentication
open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
open var dismissCompletionCallback: (()->Void)?
open var successCallback: (()->Void)?
open var cancelCallback: (()->Void)?
weak var passcodeLabel: UILabel?
weak var passcodeWrongAttemptsLabel: UILabel?
weak var passcodeTextField: UITextField?
weak var biometryAuthButton: UIButton?
open weak var cancelButton: UIButton?
var passcodeFailedAttempts = 0
var isCancellable: Bool = false
open override func loadView() {
super.loadView()
let passcodeLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeLabel.text = "Enter passcode for Pass"
passcodeLabel.font = UIFont.boldSystemFont(ofSize: 18)
@ -37,7 +37,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeLabel)
self.passcodeLabel = passcodeLabel
let passcodeWrongAttemptsLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeWrongAttemptsLabel.text = ""
passcodeWrongAttemptsLabel.textColor = UIColor.red
@ -45,7 +45,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeWrongAttemptsLabel.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeWrongAttemptsLabel)
self.passcodeWrongAttemptsLabel = passcodeWrongAttemptsLabel
let passcodeTextField = UITextField(frame: CGRect(x: 0, y: 0, width: 300, height: 40))
passcodeTextField.borderStyle = UITextBorderStyle.roundedRect
passcodeTextField.placeholder = "passcode"
@ -57,7 +57,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeTextField.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(passcodeTextField)
self.passcodeTextField = passcodeTextField
let biometryAuthButton = UIButton(type: .custom)
biometryAuthButton.setTitle("", for: .normal)
biometryAuthButton.setTitleColor(Globals.blue, for: .normal)
@ -66,7 +66,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
biometryAuthButton.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(biometryAuthButton)
self.biometryAuthButton = biometryAuthButton
let myContext = LAContext()
var authError: NSError?
if #available(iOS 8.0, macOS 10.12.1, *) {
@ -81,7 +81,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
biometryAuthButton.isHidden = false
}
}
let cancelButton = UIButton(type: .custom)
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.setTitleColor(Globals.blue, for: .normal)
@ -91,7 +91,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
cancelButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left
self.view.addSubview(cancelButton)
self.cancelButton = cancelButton
NSLayoutConstraint.activate([
passcodeTextField.widthAnchor.constraint(equalToConstant: 300),
passcodeTextField.heightAnchor.constraint(equalToConstant: 40),
@ -118,13 +118,13 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
cancelButton.topAnchor.constraint(equalTo: self.view.safeTopAnchor),
cancelButton.leftAnchor.constraint(equalTo: self.view.safeLeftAnchor, constant: 20)
])
}
open override func viewDidLoad() {
super.viewDidLoad()
}
open override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let biometryAuthButton = biometryAuthButton {
@ -137,7 +137,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
DispatchQueue.main.async {
self.passcodeTextField?.text = ""
}
// pop
if presentingViewController?.presentedViewController == self {
// if presented as modal
@ -160,16 +160,16 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
passcodeWrongAttemptsLabel?.text = ""
dismissPasscodeLock(completionHandler: successCallback)
}
@objc func passcodeLockDidCancel() {
dismissPasscodeLock(completionHandler: cancelCallback)
}
@objc func bioButtonPressedAction(_ uiButton: UIButton) {
let myContext = LAContext()
let myLocalizedReasonString = "Authentication is needed to access Pass."
var authError: NSError?
if #available(iOS 8.0, *) {
if myContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &authError) {
myContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: myLocalizedReasonString) { success, evaluateError in
@ -183,7 +183,7 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
}
}
}
public override func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == passcodeTextField {
if !PasscodeLock.shared.check(passcode: textField.text ?? "") {
@ -198,13 +198,13 @@ open class PasscodeLockViewController: UIViewController, UITextFieldDelegate {
textField.resignFirstResponder()
return true
}
@objc func passcodeTextFieldDidChange(_ textField: UITextField) {
if PasscodeLock.shared.check(passcode: textField.text ?? "") {
self.passcodeLockDidSucceed()
}
}
public func setCancellable(_ isCancellable: Bool) {
self.isCancellable = isCancellable
cancelButton?.isHidden = !isCancellable

View file

@ -15,10 +15,10 @@ public extension DefaultsKeys {
static let pgpKeySource = DefaultsKey<String?>("pgpKeySource")
static let pgpPublicKeyURL = DefaultsKey<URL?>("pgpPublicKeyURL")
static let pgpPrivateKeyURL = DefaultsKey<URL?>("pgpPrivateKeyURL")
static let pgpPublicKeyArmor = DefaultsKey<String?>("pgpPublicKeyArmor")
static let pgpPrivateKeyArmor = DefaultsKey<String?>("pgpPrivateKeyArmor")
static let gitURL = DefaultsKey<URL?>("gitURL")
static let gitAuthenticationMethod = DefaultsKey<String?>("gitAuthenticationMethod")
static let gitUsername = DefaultsKey<String?>("gitUsername")
@ -29,10 +29,10 @@ public extension DefaultsKeys {
static let gitSignatureEmail = DefaultsKey<String?>("gitSignatureEmail")
static let lastSyncedTime = DefaultsKey<Date?>("lastSyncedTime")
static let isTouchIDOn = DefaultsKey<Bool>("isTouchIDOn")
static let passcodeKey = DefaultsKey<String?>("passcodeKey")
static let isHideUnknownOn = DefaultsKey<Bool>("isHideUnknownOn")
static let isHideOTPOn = DefaultsKey<Bool>("isHideOTPOn")
static let isRememberPGPPassphraseOn = DefaultsKey<Bool>("isRememberPGPPassphraseOn")
@ -40,6 +40,6 @@ public extension DefaultsKeys {
static let isShowFolderOn = DefaultsKey<Bool>("isShowFolderOn")
static let isSearchDefaultAll = DefaultsKey<Bool>("isSearchDefaultAll")
static let passwordGeneratorFlavor = DefaultsKey<String>("passwordGeneratorFlavor")
static let encryptInArmored = DefaultsKey<Bool>("encryptInArmored")
}

View file

@ -10,7 +10,7 @@ import Foundation
// https://gist.github.com/NikolaiRuhe/eeb135d20c84a7097516
public extension FileManager {
/// This method calculates the accumulated size of a directory on the volume in bytes.
///
/// As there's no simple way to get this information from the file system it has to crawl the entire hierarchy,
@ -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
/// directories, hard links, ...).
func allocatedSizeOfDirectoryAtURL(directoryURL : URL) throws -> UInt64 {
// We'll sum up content size here:
var accumulatedSize = UInt64(0)
// prefetching some properties during traversal will speed up things a bit.
let prefetchedProperties = [
URLResourceKey.isRegularFileKey,
URLResourceKey.fileAllocatedSizeKey,
URLResourceKey.totalFileAllocatedSizeKey,
]
// The error handler simply signals errors to outside code.
var errorDidOccur: Error?
let errorHandler: (URL, Error) -> Bool = { _, error in
errorDidOccur = error
return false
}
// We have to enumerate all directory contents, including subdirectories.
let enumerator = self.enumerator(at: directoryURL,
includingPropertiesForKeys: prefetchedProperties,
options: FileManager.DirectoryEnumerationOptions(),
errorHandler: errorHandler)
precondition(enumerator != nil)
// Start the traversal:
for item in enumerator! {
let contentItemURL = item as! NSURL
// Bail out on errors from the errorHandler.
if let error = errorDidOccur { throw error }
let resourceValueForKey: (URLResourceKey) throws -> NSNumber? = { key in
var value: AnyObject?
try contentItemURL.getResourceValue(&value, forKey: key)
return value as? NSNumber
}
// Get the type of this item, making sure we only sum up sizes of regular files.
guard let isRegularFile = try resourceValueForKey(URLResourceKey.isRegularFileKey) else {
preconditionFailure()
}
guard isRegularFile.boolValue else {
continue
}
// To get the file's size we first try the most comprehensive value in terms of what the file may use on disk.
// This includes metadata, compression (on file system level) and block size.
var fileSize = try resourceValueForKey(URLResourceKey.totalFileAllocatedSizeKey)
// In case the value is unavailable we use the fallback value (excluding meta data and compression)
// This value should always be available.
fileSize = try fileSize ?? resourceValueForKey(URLResourceKey.fileAllocatedSizeKey)
guard let size = fileSize else {
preconditionFailure("huh? NSURLFileAllocatedSizeKey should always return a value")
}
// We're good, add up the value.
accumulatedSize += size.uint64Value
}
// Bail out on errors from the errorHandler.
if let error = errorDidOccur { throw error }
// We finally got it.
return accumulatedSize
}

View file

@ -10,7 +10,7 @@ import Foundation
import UIKit
public class Globals {
// Legacy paths (not shared)
public static let documentPathLegacy = NSSearchPathForDirectoriesInDomains(.documentDirectory, .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 gitSSHPrivateKeyURLLegacy = URL(fileURLWithPath: gitSSHPrivateKeyPathLegacy)
public static let repositoryPathLegacy = "\(libraryPathLegacy)/password-store"
public static let bundleIdentifier = "me.mssun.passforios"
public static let groupIdentifier = "group." + bundleIdentifier
public static let passKitBundleIdentifier = bundleIdentifier + ".passKit"
public static let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: groupIdentifier)!
public static let documentPath = sharedContainerURL.appendingPathComponent("Documents").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 repositoryPath = libraryPath + "/password-store"
public static let dbPath = documentPath + "/pass.sqlite"
public static let iTunesFileSharingPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
public static let iTunesFileSharingPGPPublic = iTunesFileSharingPath + "/gpg_key.pub"
public static let iTunesFileSharingPGPPrivate = iTunesFileSharingPath + "/gpg_key"
public static let iTunesFileSharingSSHPrivate = iTunesFileSharingPath + "/ssh_key"
public static let gitSignatureDefaultName = "Pass for iOS"
public static let gitSignatureDefaultEmail = "user@passforios"
public static let passwordDots = "••••••••••••"
public static let oneTimePasswordDots = "••••••"
public static let passwordFont = UIFont(name: "Courier-Bold", size: UIFont.labelFontSize - 1)
// UI related
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)
@ -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 digitColor = UIColor(red:66/255.0, green:113/255.0, blue:174/255.0, alpha:1.0)
public static let tableCellButtonSize = CGFloat(20.0)
private init() { }
}

View file

@ -13,7 +13,7 @@ public extension Notification.Name {
static let passwordStoreErased = Notification.Name("passwordStoreErased")
static let passwordStoreChangeDiscarded = Notification.Name("passwordStoreChangeDiscarded")
static let passwordSearch = Notification.Name("passwordSearch")
static let passwordDisplaySettingChanged = Notification.Name("passwordDisplaySettingChanged")
static let passwordDetailDisplaySettingChanged = Notification.Name("passwordDetailDisplaySettingChanged")
}

View file

@ -9,7 +9,7 @@
import Foundation
extension UIView {
// Save anchors: https://stackoverflow.com/questions/46317061/use-safe-area-layout-programmatically
var safeTopAnchor: NSLayoutYAxisAnchor {
if #available(iOS 11.0, *) {
@ -18,7 +18,7 @@ extension UIView {
return self.topAnchor
}
}
var safeLeftAnchor: NSLayoutXAxisAnchor {
if #available(iOS 11.0, *){
return self.safeAreaLayoutGuide.leftAnchor
@ -26,7 +26,7 @@ extension UIView {
return self.leftAnchor
}
}
var safeRightAnchor: NSLayoutXAxisAnchor {
if #available(iOS 11.0, *){
return self.safeAreaLayoutGuide.rightAnchor
@ -34,7 +34,7 @@ extension UIView {
return self.rightAnchor
}
}
var safeBottomAnchor: NSLayoutYAxisAnchor {
if #available(iOS 11.0, *) {
return self.safeAreaLayoutGuide.bottomAnchor

View file

@ -15,17 +15,17 @@ public class Utils {
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
return (try? keychain.getString(name)) ?? nil
}
public static func addPasswordToKeychain(name: String, password: String?) {
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
keychain[name] = password
}
public static func removeKeychain(name: String) {
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
try? keychain.remove(name)
}
public static func removeAllKeychain() {
let keychain = Keychain(service: Globals.bundleIdentifier, accessGroup: Globals.groupIdentifier)
try? keychain.removeAll()
@ -53,7 +53,7 @@ public class Utils {
}
return attributedPassword
}
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)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: handler))

View file

@ -14,21 +14,21 @@ import ObjectiveGit
public struct GitCredential {
private var credential: Credential
private let passwordStore = PasswordStore.shared
public enum Credential {
case http(userName: String)
case ssh(userName: String, privateKeyFile: URL)
}
public init(credential: Credential) {
self.credential = credential
}
public func credentialProvider(requestGitPassword: @escaping (Credential, String?) -> String?) throws -> GTCredentialProvider {
var attempts = 0
return GTCredentialProvider { (_, _, _) -> (GTCredential?) in
var credential: GTCredential? = nil
switch self.credential {
case let .http(userName):
var lastPassword = self.passwordStore.gitPassword
@ -63,7 +63,7 @@ public struct GitCredential {
return credential
}
}
public func delete() {
switch credential {
case .http:

View file

@ -11,24 +11,24 @@ import LocalAuthentication
open class PasscodeLock {
public static let shared = PasscodeLock()
fileprivate let passcodeKey = "passcode.lock.passcode"
fileprivate var passcode: String? {
return SharedDefaults[.passcodeKey]
}
public var hasPasscode: Bool {
return passcode != nil
}
public func save(passcode: String) {
SharedDefaults[.passcodeKey] = passcode
}
public func check(passcode: String) -> Bool {
return self.passcode == passcode
}
public func delete() {
SharedDefaults[.passcodeKey] = nil
}

View file

@ -10,7 +10,7 @@ import OneTimePassword
import Base32
public class Password {
public var name: String
public var url: URL
public var plainText: String
@ -50,7 +50,7 @@ public class Password {
public var login: String? {
return getAdditionValue(withKey: Constants.LOGIN_KEYWORD)
}
public var urlString: String? {
return getAdditionValue(withKey: Constants.URL_KEYWORD)
}
@ -73,7 +73,7 @@ public class Password {
self.plainText = plainText
initEverything()
}
public func updatePassword(name: String, url: URL, plainText: String) {
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() }
return additions.first(where: { toLowercase($0.title) == toLowercase(key) })?.content
}
/// Set the OTP token if we are able to construct a valid one.
///
/// Example of TOTP otpauth:
@ -164,7 +164,7 @@ public class Password {
.usingCounter(getAdditionValue(withKey: Constants.OTP_COUNTER))
.build()
}
/// Get the OTP description and the current password.
public func getOtpStrings() -> (description: String, otp: String)? {
guard otpToken != nil else {
@ -178,18 +178,18 @@ public class Password {
}
return (description, otpToken!.currentPassword ?? "error")
}
// return the password strings
// it is guaranteed that it is a HOTP password when we call this
public func getNextHotp() -> String? {
// increase the counter
otpToken = otpToken?.updatedToken()
// replace old HOTP settings with the new otpauth
var newOtpauth = try! otpToken?.toURL().absoluteString
newOtpauth?.append("&secret=")
newOtpauth?.append(MF_Base32Codec.base32String(from: otpToken?.generator.secret))
var lines : [String] = []
self.plainText.enumerateLines() { line, _ in
let (key, _) = Parser.getKeyValuePair(from: line)
@ -205,7 +205,7 @@ public class Password {
lines.append(newOtpauth!)
}
self.updatePassword(name: self.name, url: self.url, plainText: lines.joined(separator: "\n"))
// get and return the password
return self.otpToken?.currentPassword
}

View file

@ -10,7 +10,7 @@ import Foundation
import SwiftyUserDefaults
extension PasswordEntity {
public var nameWithCategory: String {
get {
if let p = path, p.hasSuffix(".gpg") {
@ -20,11 +20,11 @@ extension PasswordEntity {
}
}
}
public func getCategoryText() -> String {
return getCategoryArray().joined(separator: " > ")
}
public func getCategoryArray() -> [String] {
var parentEntity = parent
var passwordCategoryArray: [String] = []
@ -35,7 +35,7 @@ extension PasswordEntity {
passwordCategoryArray.reverse()
return passwordCategoryArray
}
public func getURL() -> URL? {
if let p = getPath().stringByAddingPercentEncodingForRFC3986() {
return URL(string: p)

View file

@ -18,7 +18,7 @@ public class PasswordStore {
public static let shared = PasswordStore()
public let storeURL = URL(fileURLWithPath: "\(Globals.repositoryPath)")
public let tempStoreURL = URL(fileURLWithPath: "\(Globals.repositoryPath)-temp")
public var storeRepository: GTRepository?
public var pgpKeyID: String?
public var publicKey: Key? {
@ -31,7 +31,7 @@ public class PasswordStore {
}
}
public var privateKey: Key?
public var gitSignatureForNow: GTSignature {
get {
let gitSignatureName = SharedDefaults[.gitSignatureName] ?? Globals.gitSignatureDefaultName
@ -39,9 +39,9 @@ public class PasswordStore {
return GTSignature(name: gitSignatureName, email: gitSignatureEmail, time: Date())!
}
}
public let keyring = ObjectivePGP.defaultKeyring
public var pgpKeyPassphrase: String? {
set {
Utils.addPasswordToKeychain(name: "pgpKeyPassphrase", password: newValue)
@ -50,7 +50,7 @@ public class PasswordStore {
return Utils.getPasswordFromKeychain(name: "pgpKeyPassphrase")
}
}
public var gitPassword: String? {
set {
Utils.addPasswordToKeychain(name: "gitPassword", password: newValue)
@ -59,7 +59,7 @@ public class PasswordStore {
return Utils.getPasswordFromKeychain(name: "gitPassword")
}
}
public var gitSSHPrivateKeyPassphrase: String? {
set {
Utils.addPasswordToKeychain(name: "gitSSHPrivateKeyPassphrase", password: newValue)
@ -68,7 +68,7 @@ public class PasswordStore {
return Utils.getPasswordFromKeychain(name: "gitSSHPrivateKeyPassphrase")
}
}
private let fm = FileManager.default
lazy private var context: NSManagedObjectContext = {
let modelURL = Bundle(identifier: Globals.passKitBundleIdentifier)!.url(forResource: "pass", withExtension: "momd")!
@ -82,7 +82,7 @@ public class PasswordStore {
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
@ -96,11 +96,11 @@ public class PasswordStore {
})
return container.viewContext
}()
public var numberOfPasswords : Int {
return self.fetchPasswordEntityCoreData(withDir: false).count
return self.fetchPasswordEntityCoreData(withDir: false).count
}
public var sizeOfRepositoryByteCount : UInt64 {
return (try? fm.allocatedSizeOfDirectoryAtURL(directoryURL: self.storeURL)) ?? 0
}
@ -112,12 +112,12 @@ public class PasswordStore {
public var lastSyncedTime: Date? {
return SharedDefaults[.lastSyncedTime]
}
private init() {
// File migration to group
migrateIfNeeded()
backwardCompatibility()
do {
if fm.fileExists(atPath: storeURL.path) {
try storeRepository = GTRepository.init(url: storeURL)
@ -127,14 +127,14 @@ public class PasswordStore {
print(error)
}
}
private func migrateIfNeeded() {
// 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)
guard needMigration == true else {
return
}
do {
// migrate Defaults
let userDefaults = UserDefaults()
@ -143,7 +143,7 @@ public class PasswordStore {
SharedDefaults.setValue(userDefaults.value(forKey: key), forKey: key)
}
}
// migrate files
try fm.createDirectory(atPath: Globals.documentPath, withIntermediateDirectories: true, attributes: nil)
try fm.createDirectory(atPath: Globals.libraryPath, withIntermediateDirectories: true, attributes: nil)
@ -162,7 +162,7 @@ public class PasswordStore {
}
updatePasswordEntityCoreData()
}
private func backwardCompatibility() {
// For the newly-introduced isRememberGitCredentialPassphraseOn (20171008)
if (self.gitPassword != nil || self.gitSSHPrivateKeyPassphrase != nil) && SharedDefaults[.isRememberGitCredentialPassphraseOn] == false {
@ -173,21 +173,21 @@ public class PasswordStore {
SharedDefaults[.isRememberPGPPassphraseOn] = true
}
}
enum SSHKeyType {
case `public`, secret
}
public func initGitSSHKey(with armorKey: String) throws {
let keyPath = Globals.gitSSHPrivateKeyPath
try armorKey.write(toFile: keyPath, atomically: true, encoding: .ascii)
}
public func initPGPKeys() throws {
try initPGPKey(.public)
try initPGPKey(.secret)
}
public func initPGPKey(_ keyType: PGPKeyType) throws {
switch keyType {
case .public:
@ -206,7 +206,7 @@ public class PasswordStore {
throw AppError.UnknownError
}
}
public func initPGPKey(from url: URL, keyType: PGPKeyType) throws {
var pgpKeyLocalPath = ""
if keyType == .public {
@ -218,7 +218,7 @@ public class PasswordStore {
try pgpKeyData.write(to: URL(fileURLWithPath: pgpKeyLocalPath), options: .atomic)
try initPGPKey(keyType)
}
public func initPGPKey(with armorKey: String, keyType: PGPKeyType) throws {
var pgpKeyLocalPath = ""
if keyType == .public {
@ -229,8 +229,8 @@ public class PasswordStore {
try armorKey.write(toFile: pgpKeyLocalPath, atomically: true, encoding: .ascii)
try initPGPKey(keyType)
}
private func importKey(from keyPath: String) -> Key? {
if fm.fileExists(atPath: keyPath) {
let keys = try! ObjectivePGP.readKeys(fromPath: keyPath)
@ -245,12 +245,12 @@ public class PasswordStore {
public func getPgpPrivateKey() -> Key {
return keyring.keys.filter({$0.secretKey != nil})[0]
}
public func repositoryExisted() -> Bool {
let fm = FileManager()
return fm.fileExists(atPath: Globals.repositoryPath)
}
public func passwordExisted(password: Password) -> Bool {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do {
@ -266,7 +266,7 @@ public class PasswordStore {
}
return true
}
public func passwordEntityExisted(path: String) -> Bool {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do {
@ -282,7 +282,7 @@ public class PasswordStore {
}
return true
}
public func getPasswordEntity(by path: String, isDir: Bool) -> PasswordEntity? {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do {
@ -292,7 +292,7 @@ public class PasswordStore {
fatalError("Failed to fetch password entities: \(error)")
}
}
public func cloneRepository(remoteRepoURL: URL,
credential: GitCredential,
requestGitPassword: @escaping (GitCredential.Credential, String?) -> String?,
@ -323,7 +323,7 @@ public class PasswordStore {
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 {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -339,7 +339,7 @@ public class PasswordStore {
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
}
}
private func updatePasswordEntityCoreData() {
deleteCoreData(entityName: "PasswordEntity")
do {
@ -393,7 +393,7 @@ public class PasswordStore {
print("Error with save: \(error)")
}
}
public func getRecentCommits(count: Int) throws -> [GTCommit] {
guard let storeRepository = storeRepository else {
return []
@ -410,7 +410,7 @@ public class PasswordStore {
}
return commits
}
public func fetchPasswordEntityCoreData(parent: PasswordEntity?) -> [PasswordEntity] {
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do {
@ -421,7 +421,7 @@ public class PasswordStore {
fatalError("Failed to fetch passwords: \(error)")
}
}
public func fetchPasswordEntityCoreData(withDir: Bool) -> [PasswordEntity] {
let passwordEntityFetch = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
do {
@ -434,8 +434,8 @@ public class PasswordStore {
fatalError("Failed to fetch passwords: \(error)")
}
}
public func fetchUnsyncedPasswords() -> [PasswordEntity] {
let passwordEntityFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "PasswordEntity")
passwordEntityFetchRequest.predicate = NSPredicate(format: "synced = %i", 0)
@ -446,7 +446,7 @@ public class PasswordStore {
fatalError("Failed to fetch passwords: \(error)")
}
}
public func setAllSynced() {
let passwordEntities = fetchUnsyncedPasswords()
for passwordEntity in passwordEntities {
@ -460,7 +460,7 @@ public class PasswordStore {
fatalError("Failed to save: \(error)")
}
}
public func getLatestUpdateInfo(filename: String) -> String {
guard let storeRepository = storeRepository else {
return "Unknown"
@ -486,10 +486,10 @@ public class PasswordStore {
}
return autoFormattedDifference
}
public func updateRemoteRepo() {
}
private func gitAdd(path: String) throws {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -497,7 +497,7 @@ public class PasswordStore {
try storeRepository.index().addFile(path)
try storeRepository.index().write()
}
private func gitRm(path: String) throws {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -509,7 +509,7 @@ public class PasswordStore {
try storeRepository.index().removeFile(path)
try storeRepository.index().write()
}
private func deleteDirectoryTree(at url: URL) throws {
var tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
var count = try fm.contentsOfDirectory(atPath: tempURL.path).count
@ -519,12 +519,12 @@ public class PasswordStore {
count = try fm.contentsOfDirectory(atPath: tempURL.path).count
}
}
private func createDirectoryTree(at url: URL) throws {
let tempURL = storeURL.appendingPathComponent(url.deletingLastPathComponent().path)
try fm.createDirectory(at: tempURL, withIntermediateDirectories: true, attributes: nil)
}
private func gitMv(from: String, to: String) throws {
let fromURL = storeURL.appendingPathComponent(from)
let toURL = storeURL.appendingPathComponent(to)
@ -532,7 +532,7 @@ public class PasswordStore {
try gitAdd(path: to)
try gitRm(path: from)
}
private func gitCommit(message: String) throws -> GTCommit? {
guard let storeRepository = storeRepository else {
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)
return commit
}
private func getLocalBranch(withName branchName: String) throws -> GTBranch? {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -555,7 +555,7 @@ public class PasswordStore {
let branches = try storeRepository.branches(withPrefix: reference)
return branches.first
}
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 {
throw AppError.RepositoryNotSetError
@ -571,12 +571,12 @@ public class PasswordStore {
throw(error)
}
}
private func addPasswordEntities(password: Password) throws -> PasswordEntity? {
guard !passwordExisted(password: password) else {
throw AppError.PasswordDuplicatedError
}
var passwordURL = password.url
var previousPathLength = Int.max
var paths: [String] = []
@ -606,7 +606,7 @@ public class PasswordStore {
}
return nil
}
private func insertPasswordEntity(name: String, path: String, parent: PasswordEntity?, synced: Bool = false, isDir: Bool = false) -> PasswordEntity? {
var ret: PasswordEntity? = nil
if let passwordEntity = NSEntityDescription.insertNewObject(forEntityName: "PasswordEntity", into: self.context) as? PasswordEntity {
@ -624,7 +624,7 @@ public class PasswordStore {
}
return ret
}
public func add(password: Password) throws -> PasswordEntity? {
try createDirectoryTree(at: password.url)
let newPasswordEntity = try addPasswordEntities(password: password)
@ -635,7 +635,7 @@ public class PasswordStore {
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
return newPasswordEntity
}
public func delete(passwordEntity: PasswordEntity) throws {
let deletedFileURL = passwordEntity.getURL()!
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.")
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
}
public func edit(passwordEntity: PasswordEntity, password: Password) throws -> PasswordEntity? {
var newPasswordEntity: PasswordEntity? = passwordEntity
@ -656,16 +656,16 @@ public class PasswordStore {
newPasswordEntity = passwordEntity
newPasswordEntity?.synced = false
}
if password.changed&PasswordChange.path.rawValue != 0 {
let deletedFileURL = passwordEntity.getURL()!
// add
try createDirectoryTree(at: password.url)
newPasswordEntity = try addPasswordEntities(password: password)
// mv
try gitMv(from: deletedFileURL.path, to: password.url.path)
// delete
try deleteDirectoryTree(at: deletedFileURL)
try deletePasswordEntities(passwordEntity: passwordEntity)
@ -675,7 +675,7 @@ public class PasswordStore {
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
return newPasswordEntity
}
private func deletePasswordEntities(passwordEntity: PasswordEntity) throws {
var current: PasswordEntity? = passwordEntity
while current != nil && (current!.children!.count == 0 || !current!.isDir) {
@ -689,7 +689,7 @@ public class PasswordStore {
}
}
}
public func saveUpdated(passwordEntity: PasswordEntity) {
do {
try context.save()
@ -697,11 +697,11 @@ public class PasswordStore {
fatalError("Failed to save a PasswordEntity: \(error)")
}
}
public func deleteCoreData(entityName: String) {
let deleteFetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: entityName)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetchRequest)
do {
try context.execute(deleteRequest)
try context.save()
@ -710,7 +710,7 @@ public class PasswordStore {
print(error)
}
}
public func updateImage(passwordEntity: PasswordEntity, image: Data?) {
guard let image = image else {
return
@ -733,7 +733,7 @@ public class PasswordStore {
}
}
}
public func erase() {
publicKey = nil
privateKey = nil
@ -743,19 +743,19 @@ public class PasswordStore {
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
Utils.removeAllKeychain()
deleteCoreData(entityName: "PasswordEntity")
SharedDefaults.removeAll()
storeRepository = nil
NotificationCenter.default.post(name: .passwordStoreUpdated, 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 {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -772,7 +772,7 @@ public class PasswordStore {
try storeRepository.reset(to: newHead, resetType: .hard)
self.setAllSynced()
self.updatePasswordEntityCoreData()
NotificationCenter.default.post(name: .passwordStoreUpdated, object: nil)
NotificationCenter.default.post(name: .passwordStoreChangeDiscarded, object: nil)
return localCommits.count
@ -780,8 +780,8 @@ public class PasswordStore {
return 0 // no new commit
}
}
private func getLocalCommits() throws -> [GTCommit]? {
guard let storeRepository = storeRepository else {
throw AppError.RepositoryNotSetError
@ -791,18 +791,18 @@ public class PasswordStore {
throw AppError.RepositoryRemoteMasterNotFoundError
}
let remoteMasterBranch = try storeRepository.remoteBranches()[index]
// check oid before calling localCommitsRelative
guard remoteMasterBranch.oid != nil else {
throw AppError.RepositoryRemoteMasterNotFoundError
}
// get a list of local commits
return try storeRepository.localCommitsRelative(toRemoteBranch: remoteMasterBranch)
}
public func decrypt(passwordEntity: PasswordEntity, requestPGPKeyPassphrase: () -> String) throws -> Password? {
let encryptedDataPath = storeURL.appendingPathComponent(passwordEntity.getPath())
let encryptedData = try Data(contentsOf: encryptedDataPath)
@ -817,7 +817,7 @@ public class PasswordStore {
}
return Password(name: passwordEntity.getName(), url: url, plainText: plainText)
}
public func encrypt(password: Password) throws -> Data {
guard keyring.keys.count > 0 else {
throw AppError.PGPPublicKeyNotExistError
@ -830,7 +830,7 @@ public class PasswordStore {
return encryptedData
}
}
public func removePGPKeys() {
try? fm.removeItem(atPath: Globals.pgpPublicKeyPath)
try? fm.removeItem(atPath: Globals.pgpPrivateKeyPath)
@ -844,14 +844,14 @@ public class PasswordStore {
publicKey = nil
privateKey = nil
}
public func removeGitSSHKeys() {
try? fm.removeItem(atPath: Globals.gitSSHPrivateKeyPath)
Defaults.remove(.gitSSHPrivateKeyArmor)
Defaults.remove(.gitSSHPrivateKeyURL)
self.gitSSHPrivateKeyPassphrase = nil
}
public func gitSSHKeyExists(inFileSharing: Bool = false) -> Bool {
if inFileSharing == false {
return fm.fileExists(atPath: Globals.gitSSHPrivateKeyPath)
@ -859,7 +859,7 @@ public class PasswordStore {
return fm.fileExists(atPath: Globals.iTunesFileSharingSSHPrivate)
}
}
public func pgpKeyExists(inFileSharing: Bool = false) -> Bool {
if inFileSharing == false {
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)
}
}
public func gitSSHKeyImportFromFileSharing() throws {
try fm.moveItem(atPath: Globals.iTunesFileSharingSSHPrivate, toPath: Globals.gitSSHPrivateKeyPath)
}

View file

@ -35,7 +35,7 @@ extension AdditionField {
}
extension AdditionField: Equatable {
public static func == (first: AdditionField, second: AdditionField) -> Bool {
return first.asTuple == second.asTuple
}

View file

@ -16,7 +16,7 @@ public enum OtpType: String {
var description: String {
return rawValue
}
init(token: Token?) {
switch token?.generator.factor {
case .some(.counter):

View file

@ -14,7 +14,7 @@ class PasswordTest: XCTestCase {
func testUrl() {
let password = getPasswordObjectWith(content: "")
XCTAssertEqual(password.url, PASSWORD_URL)
XCTAssertEqual(password.namePath, PASSWORD_PATH)
}

View file

@ -10,27 +10,27 @@ import XCTest
@testable import passKit
class passKitTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}

View file

@ -9,27 +9,27 @@
import XCTest
class passTests: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}