From d25cc9eb9a35bfc424aad0fa87b9044c7c26f869 Mon Sep 17 00:00:00 2001 From: Bob Sun Date: Tue, 20 Jun 2017 13:00:43 -0700 Subject: [PATCH 01/18] Version bump --- pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme | 10 ++++++++++ pass/Info.plist | 2 +- passExtension/Info.plist | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme index 73fdf63..062dfaf 100644 --- a/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme +++ b/pass.xcodeproj/xcshareddata/xcschemes/pass.xcscheme @@ -38,6 +38,16 @@ ReferencedContainer = "container:pass.xcodeproj"> + + + + CFBundlePackageType APPL CFBundleShortVersionString - 0.2.7 + 0.2.8 CFBundleVersion 1 ITSAppUsesNonExemptEncryption diff --git a/passExtension/Info.plist b/passExtension/Info.plist index 971000a..077ef2c 100644 --- a/passExtension/Info.plist +++ b/passExtension/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 0.2.7 + 0.2.8 CFBundleVersion 1 NSExtension From 72424c7db2471047089ec342e20fe5dc5a279281 Mon Sep 17 00:00:00 2001 From: Bob Sun Date: Thu, 22 Jun 2017 00:09:12 -0700 Subject: [PATCH 02/18] Use split view for Settings --- pass.xcodeproj/project.pbxproj | 4 + pass/Base.lproj/Main.storyboard | 213 ++++++++++-------- .../AdvancedSettingsTableViewController.swift | 3 - ...GPKeyArmorSettingTableViewController.swift | 5 - .../PasswordEditorTableViewController.swift | 4 - .../SettingsSplitViewController.swift | 24 ++ .../SettingsTableViewController.swift | 6 - 7 files changed, 143 insertions(+), 116 deletions(-) create mode 100644 pass/Controllers/SettingsSplitViewController.swift diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index c780732..f1f55ba 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -80,6 +80,7 @@ DCC408C71E307DBB00F29B0E /* SVProgressHUD.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */; }; DCC441521E8F6C06008A90C4 /* RawPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */; }; DCC441541E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */; }; + DCD3C65E1EFB9BB400CBE842 /* SettingsSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */; }; DCDDEAB01E4639F300F68193 /* LabelTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */; }; DCDDEAB31E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */; }; DCFB779A1E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */; }; @@ -253,6 +254,7 @@ DCC408C61E307DBB00F29B0E /* SVProgressHUD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SVProgressHUD.framework; path = Carthage/Build/iOS/SVProgressHUD.framework; sourceTree = ""; }; DCC441511E8F6C06008A90C4 /* RawPasswordViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RawPasswordViewController.swift; sourceTree = ""; }; DCC441531E916382008A90C4 /* GitSSHKeyArmorSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GitSSHKeyArmorSettingTableViewController.swift; sourceTree = ""; }; + DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSplitViewController.swift; sourceTree = ""; }; DCDDEAAF1E4639F300F68193 /* LabelTableViewCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = LabelTableViewCell.xib; sourceTree = ""; }; DCDDEAB11E4896BF00F68193 /* PasswordDetailTitleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasswordDetailTitleTableViewCell.swift; sourceTree = ""; }; DCFB77981E4F3BCF008DE471 /* TitleTextFieldTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TitleTextFieldTableViewCell.swift; sourceTree = ""; }; @@ -415,6 +417,7 @@ DC19400C1E4B39400077E0A3 /* Controllers */ = { isa = PBXGroup; children = ( + DCD3C65D1EFB9BB400CBE842 /* SettingsSplitViewController.swift */, DC3E64E51E656F11009A83DE /* CommitLogsTableViewController.swift */, DC5F385A1E56AADB00C69ACA /* PGPKeyArmorSettingTableViewController.swift */, DC037CB11E4CAB1700609409 /* AboutRepositoryTableViewController.swift */, @@ -1042,6 +1045,7 @@ DC4914991E434600007FF592 /* PasswordDetailTableViewController.swift in Sources */, DC962CDF1E4B62C10033B5D8 /* AboutTableViewController.swift in Sources */, DC5734AE1E439AD400D09270 /* PasswordsViewController.swift in Sources */, + DCD3C65E1EFB9BB400CBE842 /* SettingsSplitViewController.swift in Sources */, DC3E64E61E656F11009A83DE /* CommitLogsTableViewController.swift in Sources */, DC037CAA1E4B8EAE00609409 /* SpecialThanksTableViewController.swift in Sources */, DC037CA61E4B883900609409 /* OpenSourceComponentsTableViewController.swift in Sources */, diff --git a/pass/Base.lproj/Main.storyboard b/pass/Base.lproj/Main.storyboard index 60b3202..0cb0b3b 100644 --- a/pass/Base.lproj/Main.storyboard +++ b/pass/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -102,7 +102,7 @@ - + @@ -129,7 +129,7 @@ - + @@ -222,7 +222,7 @@ - + @@ -250,7 +250,7 @@ - + @@ -267,8 +267,8 @@ - - + + @@ -288,7 +288,7 @@ - + - + @@ -1073,7 +1077,7 @@ Phone Support PIN #: 84719 - + @@ -1090,7 +1094,7 @@ Phone Support PIN #: 84719 - + @@ -1124,7 +1128,7 @@ Phone Support PIN #: 84719 - + @@ -1148,7 +1152,7 @@ Phone Support PIN #: 84719 - + @@ -1168,7 +1172,7 @@ Phone Support PIN #: 84719 - + @@ -1188,7 +1192,7 @@ Phone Support PIN #: 84719 - + @@ -1305,7 +1309,7 @@ Cgo - + @@ -1331,7 +1335,7 @@ Cgo - + @@ -1343,7 +1347,7 @@ Cgo - + @@ -1369,7 +1373,7 @@ Cgo - + @@ -1382,11 +1386,6 @@ Cgo - - - - - @@ -1503,7 +1502,7 @@ Cgo - + @@ -1526,7 +1525,7 @@ Cgo - + @@ -1570,7 +1569,7 @@ Cgo - + - - - - - @@ -1660,25 +1654,7 @@ Cgo - - - - - - - - - - - - - - - - - - - + @@ -1693,7 +1669,7 @@ Cgo @@ -1756,28 +1725,76 @@ Cgo - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pass/Controllers/AdvancedSettingsTableViewController.swift b/pass/Controllers/AdvancedSettingsTableViewController.swift index 5f9e0e5..ed85ce7 100644 --- a/pass/Controllers/AdvancedSettingsTableViewController.swift +++ b/pass/Controllers/AdvancedSettingsTableViewController.swift @@ -88,9 +88,6 @@ class AdvancedSettingsTableViewController: UITableViewController { SharedDefaults[.encryptInArmored] = encryptInASCIIArmoredSwitch.isOn } - @IBAction func cancelGitConfigSetting(segue: UIStoryboardSegue) { - } - @IBAction func saveGitConfigSetting(segue: UIStoryboardSegue) { if let controller = segue.source as? GitConfigSettingTableViewController { if let gitSignatureName = controller.nameTextField.text, diff --git a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift index 4aa883b..5e28b73 100644 --- a/pass/Controllers/PGPKeyArmorSettingTableViewController.swift +++ b/pass/Controllers/PGPKeyArmorSettingTableViewController.swift @@ -204,9 +204,4 @@ class PGPKeyArmorSettingTableViewController: UITableViewController, UITextViewDe } } } - - @IBAction private func cancelPGPScanner(segue: UIStoryboardSegue) { - - } - } diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index 28992a5..4083794 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -234,10 +234,6 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl } } - @IBAction private func cancelOTPScanner(segue: UIStoryboardSegue) { - - } - // update the data table after editing func textFieldDidEndEditing(_ textField: UITextField) { if textField == nameCell?.contentTextField { diff --git a/pass/Controllers/SettingsSplitViewController.swift b/pass/Controllers/SettingsSplitViewController.swift new file mode 100644 index 0000000..baf70ae --- /dev/null +++ b/pass/Controllers/SettingsSplitViewController.swift @@ -0,0 +1,24 @@ +// +// SettingsSplitViewController.swift +// pass +// +// Created by Mingshen Sun on 6/21/17. +// Copyright © 2017 Bob Sun. All rights reserved. +// + +import UIKit + +class SettingsSplitViewController: UISplitViewController, UISplitViewControllerDelegate { + override func viewDidLoad() { + self.delegate = self + self.preferredDisplayMode = .allVisible + } + + func splitViewController( + _ splitViewController: UISplitViewController, + collapseSecondary secondaryViewController: UIViewController, + onto primaryViewController: UIViewController) -> Bool { + // Return true to prevent UIKit from applying its default behavior + return true + } +} diff --git a/pass/Controllers/SettingsTableViewController.swift b/pass/Controllers/SettingsTableViewController.swift index e2b812d..86c308a 100644 --- a/pass/Controllers/SettingsTableViewController.swift +++ b/pass/Controllers/SettingsTableViewController.swift @@ -28,9 +28,6 @@ class SettingsTableViewController: UITableViewController { @IBOutlet weak var passwordRepositoryTableViewCell: UITableViewCell! let passwordStore = PasswordStore.shared var passcodeLockConfig = PasscodeLockConfiguration.shared - - @IBAction func cancelPGPKey(segue: UIStoryboardSegue) { - } @IBAction func savePGPKey(segue: UIStoryboardSegue) { if let controller = segue.source as? PGPKeySettingTableViewController { @@ -117,9 +114,6 @@ class SettingsTableViewController: UITableViewController { } } - @IBAction func cancelGitServerSetting(segue: UIStoryboardSegue) { - } - @IBAction func saveGitServerSetting(segue: UIStoryboardSegue) { self.passwordRepositoryTableViewCell.detailTextLabel?.text = SharedDefaults[.gitURL]?.host } From daacfbea83b67b9a06ce09b09ff5f913e9534ecc Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Thu, 22 Jun 2017 23:15:37 +0800 Subject: [PATCH 03/18] Rename a file --- .../{passextension.entitlements => passExtension.entitlements} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename passExtension/{passextension.entitlements => passExtension.entitlements} (100%) diff --git a/passExtension/passextension.entitlements b/passExtension/passExtension.entitlements similarity index 100% rename from passExtension/passextension.entitlements rename to passExtension/passExtension.entitlements From fb55f327974d634846eb155f8cc9f31115093541 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Fri, 23 Jun 2017 21:57:03 +0800 Subject: [PATCH 04/18] Support Chrome and app logins --- pass.xcodeproj/project.pbxproj | 30 +++++-- pass/Info.plist | 9 +++ passExtension/ExtensionViewController.swift | 79 +++++++++++++------ passExtension/Info.plist | 6 +- .../OnePasswordExtensionConstants.swift | 51 ++++++++++++ 5 files changed, 144 insertions(+), 31 deletions(-) create mode 100644 passExtension/OnePasswordExtensionConstants.swift diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index f1f55ba..899d17e 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2C58F31EECC494C7A7F00A98 /* libPods-passKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB6C63FA1652F925B5C9F0B5 /* libPods-passKitTests.a */; }; 398A8F69C2230A8117820BB7 /* libPods-passKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BAA15189E80AA544EAF7AD /* libPods-passKit.a */; }; 6930A9D26085DE7CA1A7AACC /* libPods-passExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B240CA444AC9172F3053651 /* libPods-passExtension.a */; }; + A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */; }; A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; }; A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; }; A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */; }; @@ -166,6 +167,7 @@ 7592A214C22CEBBEF4596CC1 /* libPods-pass.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-pass.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7E088A9255B6CB576EF757C1 /* Pods-passKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-passKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-passKit/Pods-passKit.debug.xcconfig"; sourceTree = ""; }; A02ACA4077630047EA669D05 /* Pods-pass.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-pass.debug.xcconfig"; path = "Pods/Target Support Files/Pods-pass/Pods-pass.debug.xcconfig"; sourceTree = ""; }; + A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnePasswordExtensionConstants.swift; sourceTree = ""; }; A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = ""; }; A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; A2227D4C1EEE5E25002A69A9 /* libObjectivePGP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libObjectivePGP.a; path = "Pods/../build/Debug-iphoneos/ObjectivePGP/libObjectivePGP.a"; sourceTree = ""; }; @@ -326,6 +328,24 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A2168A801EFD431A005EA873 /* Controllers */ = { + isa = PBXGroup; + children = ( + A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */, + A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */, + ); + name = Controllers; + sourceTree = ""; + }; + A2168A811EFD4322005EA873 /* Helpers */ = { + isa = PBXGroup; + children = ( + A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */, + A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */, + ); + name = Helpers; + sourceTree = ""; + }; A26075791EEC6F34005DB03E /* passKit */ = { isa = PBXGroup; children = ( @@ -350,14 +370,13 @@ A26700251EEC466A00176B8A /* passExtension */ = { isa = PBXGroup; children = ( + A26700331EEC46C900176B8A /* passExtension.entitlements */, + A267002B1EEC466A00176B8A /* Info.plist */, + A2168A801EFD431A005EA873 /* Controllers */, + A2168A811EFD4322005EA873 /* Helpers */, A2367B9F1EF0387000C8FE8B /* Assets.xcassets */, A26700351EEC475600176B8A /* passProcessor.js */, - A26700331EEC46C900176B8A /* passExtension.entitlements */, A26700281EEC466A00176B8A /* MainInterface.storyboard */, - A267002B1EEC466A00176B8A /* Info.plist */, - A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */, - A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */, - A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */, ); path = passExtension; sourceTree = ""; @@ -1010,6 +1029,7 @@ buildActionMask = 2147483647; files = ( A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */, + A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */, A28C66681EF10EC900A398A1 /* PasscodeExtensionDisplay.swift in Sources */, A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */, ); diff --git a/pass/Info.plist b/pass/Info.plist index 584eb13..01bea53 100644 --- a/pass/Info.plist +++ b/pass/Info.plist @@ -18,6 +18,15 @@ APPL CFBundleShortVersionString 0.2.8 + CFBundleURLTypes + + + CFBundleURLSchemes + + org-appextension-feature-password-management + + + CFBundleVersion 1 ITSAppUsesNonExemptEncryption diff --git a/passExtension/ExtensionViewController.swift b/passExtension/ExtensionViewController.swift index 17e55af..fa8871a 100644 --- a/passExtension/ExtensionViewController.swift +++ b/passExtension/ExtensionViewController.swift @@ -26,13 +26,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV private let passwordStore = PasswordStore.shared private var searchActive = false - - // the URL passed to the extension - private var extensionURL: String? - 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 @@ -64,24 +66,47 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV // initialize table entries initPasswordsTableEntries() - // search using the extensionContext inputs + // get the provider let item = extensionContext?.inputItems.first as! NSExtensionItem - let provider = item.attachments?.first as! NSItemProvider - let propertyList = String(kUTTypePropertyList) - if provider.hasItemConformingToTypeIdentifier(propertyList) { - provider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in + let provider: NSItemProvider + if item.attachments?.count == 1 { + // Safari + provider = item.attachments?.first as! NSItemProvider + } else { + // e.g., Chrome, apps + provider = item.attachments?[1] as! NSItemProvider + } + + // search using the extensionContext inputs + if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) { + provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, error) -> Void in let dictionary = item as! NSDictionary - let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary - let url = URL(string: (results["url"] as? String)!)?.host + var url: String? + if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String { + if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { + urlString = "http://" + urlString + } + url = URL(string: urlString)?.host + } DispatchQueue.main.async { [weak self] in + self?.extensionAction = .findLogin + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { + provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, error) -> Void in + let url = (item as? NSURL)!.host + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .fillBrowser // force search (set text, set active, force search) self?.searchBar.text = url self?.searchBar.becomeFirstResponder() self?.searchBarSearchButtonClicked((self?.searchBar)!) } }) - } else { - print("error") } } @@ -116,18 +141,28 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV do { decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) DispatchQueue.main.async { - Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) - let title = "Password Copied" - let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)" - let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in - // return a dictionary for JavaScript for best-effor fill in + switch self.extensionAction { + case .findLogin: + // prepare a dictionary to return let extensionItem = NSExtensionItem() - let returnDictionary = [ NSExtensionJavaScriptFinalizeArgumentKey : ["username": decryptedPassword?.getUsername() ?? "", "password": decryptedPassword?.password ?? ""]] + var returnDictionary = [OnePasswordExtensionKey.usernameKey: decryptedPassword?.getUsername() ?? "", + OnePasswordExtensionKey.passwordKey: decryptedPassword?.password ?? ""] + if let totpPassword = decryptedPassword?.getOtp() { + returnDictionary[OnePasswordExtensionKey.totpKey] = totpPassword + } extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))] self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil) - })) - self.present(alert, animated: true, completion: nil) + default: + // copy the password to the clipboard + Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) + let title = "Password Copied" + let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)" + let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) + alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in + self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) + })) + self.present(alert, animated: true, completion: nil) + } } } catch { print(error) diff --git a/passExtension/Info.plist b/passExtension/Info.plist index 077ef2c..db41ae3 100644 --- a/passExtension/Info.plist +++ b/passExtension/Info.plist @@ -28,11 +28,9 @@ NSExtensionActivationSupportsText - NSExtensionActivationSupportsWebPageWithMaxCount - 100 + NSExtensionActivationSupportsWebURLWithMaxCount + 1 - NSExtensionJavaScriptPreprocessingFile - passProcessor NSExtensionMainStoryboard MainInterface diff --git a/passExtension/OnePasswordExtensionConstants.swift b/passExtension/OnePasswordExtensionConstants.swift new file mode 100644 index 0000000..894b921 --- /dev/null +++ b/passExtension/OnePasswordExtensionConstants.swift @@ -0,0 +1,51 @@ +// +// OnePasswordExtensionConstants.swift +// pass +// +// Created by Yishi Lin on 2017/6/23. +// Copyright © 2017 Bob Sun. All rights reserved. +// +// This file contains constants from https://github.com/agilebits/onepassword-app-extension/ + +class OnePasswordExtensionActions { + static let findLogin = "org.appextension.find-login-action" + static let saveLogin = "org.appextension.save-login-action" + static let changePassword = "org.appextension.change-password-action" + static let fillWebView = "org.appextension.fill-webview-action" + static let fillBrowser = "org.appextension.fill-browser-action" +} + +class OnePasswordExtensionKey { + // Login Dictionary keys - Used to get or set the properties of a 1Password Login + static let URLStringKey = "url_string" + static let usernameKey = "username" + static let passwordKey = "password" + static let totpKey = "totp" + static let titleKey = "login_title" + static let notesKey = "notes" + static let sectionTitleKey = "section_title" + static let fieldsKey = "fields" + 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" + static let generatedPasswordRequireDigitsKey = "password_require_digits" + static let generatedPasswordRequireSymbolsKey = "password_require_symbols" + static let generatedPasswordForbiddenCharactersKey = "password_forbidden_characters" +} + +// Errors codes +class OnePasswordExtensionError { + static let errorDomain = "OnePasswordExtension" + static let errorCodeCancelledByUser = 0 + static let errorCodeAPINotAvailable = 1 + static let errorCodeFailedToContactExtension = 2 + static let errorCodeFailedToLoadItemProviderData = 3 + static let errorCodeCollectFieldsScriptFailed = 4 + static let errorCodeFillFieldsScriptFailed = 5 + static let errorCodeUnexpectedData = 6 + static let errorCodeFailedToObtainURLStringFromWebView = 7 +} From c7135596e8431fef223cddd28fcc411046233615 Mon Sep 17 00:00:00 2001 From: Jordan Phillips Date: Fri, 23 Jun 2017 07:10:11 -0700 Subject: [PATCH 05/18] Include directory in "All" scope searches --- pass/Controllers/PasswordsViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pass/Controllers/PasswordsViewController.swift b/pass/Controllers/PasswordsViewController.swift index 2bae84b..5fa05b4 100644 --- a/pass/Controllers/PasswordsViewController.swift +++ b/pass/Controllers/PasswordsViewController.swift @@ -456,7 +456,8 @@ class PasswordsViewController: UIViewController, UITableViewDataSource, UITableV switch scope { case "All": filteredPasswordsTableEntries = passwordsTableAllEntries.filter { entry in - return entry.title.lowercased().contains(searchText.lowercased()) + let name = entry.passwordEntity?.nameWithCategory ?? entry.title + return name.localizedCaseInsensitiveContains(searchText) } if searchController.isActive && searchController.searchBar.text != "" { reloadTableView(data: filteredPasswordsTableEntries) From 5d71f997119ce9e33ec454ab24368f990873b5f3 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 12:52:08 +0800 Subject: [PATCH 06/18] Support Safari/Chrome auto fill --- pass.xcodeproj/project.pbxproj | 4 +- passExtension/ExtensionViewController.swift | 118 ++++++++++++-------- passExtension/Info.plist | 4 + passExtension/passProcessor.js | 24 ++-- 4 files changed, 92 insertions(+), 58 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 899d17e..3a0a0e1 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1294,7 +1294,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.extension; + PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9feb619f-73bb-429c-a2a8-856ddcbb9e33"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.extension"; @@ -1326,7 +1326,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.extension; + PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9b4419c3-a959-4af8-8810-8af134008ec5"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.extension"; diff --git a/passExtension/ExtensionViewController.swift b/passExtension/ExtensionViewController.swift index fa8871a..ff733ee 100644 --- a/passExtension/ExtensionViewController.swift +++ b/passExtension/ExtensionViewController.swift @@ -67,46 +67,66 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV initPasswordsTableEntries() // get the provider - let item = extensionContext?.inputItems.first as! NSExtensionItem - let provider: NSItemProvider - if item.attachments?.count == 1 { - // Safari - provider = item.attachments?.first as! NSItemProvider - } else { - // e.g., Chrome, apps - provider = item.attachments?[1] as! NSItemProvider + guard let extensionItems = extensionContext?.inputItems as? [NSExtensionItem] else { + return } - // search using the extensionContext inputs - if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) { - provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, error) -> Void in - let dictionary = item as! NSDictionary - var url: String? - if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String { - if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { - urlString = "http://" + urlString + for extensionItem in extensionItems { + if let itemProviders = extensionItem.attachments as? [NSItemProvider] { + for provider in itemProviders { + // search using the extensionContext inputs + if provider.hasItemConformingToTypeIdentifier(OnePasswordExtensionActions.findLogin) { + provider.loadItem(forTypeIdentifier: OnePasswordExtensionActions.findLogin, options: nil, completionHandler: { (item, error) -> Void in + let dictionary = item as! NSDictionary + var url: String? + if var urlString = dictionary[OnePasswordExtensionKey.URLStringKey] as? String { + if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { + urlString = "http://" + urlString + } + url = URL(string: urlString)?.host + } + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .findLogin + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } + else if provider.hasItemConformingToTypeIdentifier(kUTTypePropertyList as String) { + provider.loadItem(forTypeIdentifier: kUTTypePropertyList as String, options: nil, completionHandler: { (item, error) -> Void in + var url: String? + if let dictionary = item as? NSDictionary, + let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary, + var urlString = results[OnePasswordExtensionKey.URLStringKey] as? String { + if !urlString.hasPrefix("http://") && !urlString.hasPrefix("https://") { + urlString = "http://" + urlString + } + url = URL(string: urlString)?.host + } + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .fillBrowser + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) + } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { + provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, error) -> Void in + let url = (item as? NSURL)!.host + DispatchQueue.main.async { [weak self] in + self?.extensionAction = .fillBrowser + // force search (set text, set active, force search) + self?.searchBar.text = url + self?.searchBar.becomeFirstResponder() + self?.searchBarSearchButtonClicked((self?.searchBar)!) + } + }) } - url = URL(string: urlString)?.host } - DispatchQueue.main.async { [weak self] in - self?.extensionAction = .findLogin - // force search (set text, set active, force search) - self?.searchBar.text = url - self?.searchBar.becomeFirstResponder() - self?.searchBarSearchButtonClicked((self?.searchBar)!) - } - }) - } else if provider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) { - provider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (item, error) -> Void in - let url = (item as? NSURL)!.host - DispatchQueue.main.async { [weak self] in - self?.extensionAction = .fillBrowser - // force search (set text, set active, force search) - self?.searchBar.text = url - self?.searchBar.becomeFirstResponder() - self?.searchBarSearchButtonClicked((self?.searchBar)!) - } - }) + } } } @@ -140,28 +160,28 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV var decryptedPassword: Password? do { decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) - DispatchQueue.main.async { + let username = decryptedPassword?.getUsername() ?? "" + let password = decryptedPassword?.password ?? "" + DispatchQueue.main.async {// prepare a dictionary to return switch self.extensionAction { case .findLogin: - // prepare a dictionary to return let extensionItem = NSExtensionItem() - var returnDictionary = [OnePasswordExtensionKey.usernameKey: decryptedPassword?.getUsername() ?? "", - OnePasswordExtensionKey.passwordKey: decryptedPassword?.password ?? ""] + var returnDictionary = [OnePasswordExtensionKey.usernameKey: username, + OnePasswordExtensionKey.passwordKey: password] if let totpPassword = decryptedPassword?.getOtp() { returnDictionary[OnePasswordExtensionKey.totpKey] = totpPassword } extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))] self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil) - default: - // copy the password to the clipboard + case .fillBrowser: Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) - let title = "Password Copied" - let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)" - let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) - alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in - self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) - })) - self.present(alert, animated: true, completion: nil) + // return a dictionary for JavaScript for best-effor fill in + let extensionItem = NSExtensionItem() + let returnDictionary = [NSExtensionJavaScriptFinalizeArgumentKey : ["username": username, "password": password]] + extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))] + self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil) + default: + self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil) } } } catch { diff --git a/passExtension/Info.plist b/passExtension/Info.plist index db41ae3..830754c 100644 --- a/passExtension/Info.plist +++ b/passExtension/Info.plist @@ -28,9 +28,13 @@ NSExtensionActivationSupportsText + NSExtensionActivationSupportsWebPageWithMaxCount + 1 NSExtensionActivationSupportsWebURLWithMaxCount 1 + NSExtensionJavaScriptPreprocessingFile + passProcessor NSExtensionMainStoryboard MainInterface diff --git a/passExtension/passProcessor.js b/passExtension/passProcessor.js index 6db3d04..2cb114e 100644 --- a/passExtension/passProcessor.js +++ b/passExtension/passProcessor.js @@ -3,22 +3,32 @@ var PassProcessor = function() {}; PassProcessor.prototype = { run: function(arguments) { var url - var html var error try { - url = document.URL; - html = document.body.innerHTML + url = document.URL } catch (e) { error = e } finally { - arguments.completionFunction({"url": url, "html": html, "error": error}); + arguments.completionFunction({"url_string": url, "error": error}); } }, finalize: function(arguments) { - var str = "username: " + arguments["username"] + "\r\npassword: " + arguments["password"]; - // alert(str) - // document.body.innerHTML = arguments["content"]; + if (arguments["password"]) { + var passwordElement = document.querySelector("input[type=password]") + if (passwordElement) { + passwordElement.setAttribute('value', arguments["password"]) + passwordElement.value = arguments["password"] + } + } + + if (arguments["username"]) { + var usernameElement = document.querySelector("input[type=email], input[type=text]") + if (usernameElement) { + usernameElement.setAttribute('value', arguments["username"]) + usernameElement.value = arguments["username"] + } + } } }; From 37bb92418148c2e035c63935eac57d43f42896b1 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 13:20:03 +0800 Subject: [PATCH 07/18] Change how the product bundle identifier is set - Making modifying the product bundle identifier easier: only need to change in Project/Build settings --- pass.xcodeproj/project.pbxproj | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 3a0a0e1..3fcb5de 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1173,7 +1173,7 @@ MODULEMAP_FILE = ""; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKit; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1218,7 +1218,7 @@ MODULEMAP_FILE = ""; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKit; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKit"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; @@ -1244,7 +1244,7 @@ INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKitTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1264,7 +1264,7 @@ INFOPLIST_FILE = passKitTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios.passKitTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passKitTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1294,7 +1294,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.find-login-action-extension"; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9feb619f-73bb-429c-a2a8-856ddcbb9e33"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.extension"; @@ -1326,7 +1326,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; - PRODUCT_BUNDLE_IDENTIFIER = "me.mssun.passforios.find-login-action-extension"; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9b4419c3-a959-4af8-8810-8af134008ec5"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.extension"; @@ -1344,7 +1344,7 @@ INFOPLIST_FILE = passTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1360,7 +1360,7 @@ INFOPLIST_FILE = passTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passTests; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).passTests"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/pass.app/pass"; @@ -1410,6 +1410,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -1452,6 +1453,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.2; MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; VALIDATE_PRODUCT = YES; @@ -1483,7 +1485,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "${inherited}"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "893c10b3-79b1-46f7-914a-e625bf10d665"; PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios"; @@ -1519,7 +1521,7 @@ LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; OTHER_LDFLAGS = "${inherited}"; - PRODUCT_BUNDLE_IDENTIFIER = me.mssun.passforios; + PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER)"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "aefeea85-1194-4db2-9ce4-fb9995e2fdff"; PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios"; From 6f0a8f5d148f9a4f6ff2ea2df31f089b0cec3faf Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 13:49:17 +0800 Subject: [PATCH 08/18] Fix project config --- pass.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index 3fcb5de..f975495 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1297,7 +1297,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9feb619f-73bb-429c-a2a8-856ddcbb9e33"; - PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.extension"; + PROVISIONING_PROFILE_SPECIFIER = "match Development me.mssun.passforios.find-login-action-extension"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; @@ -1329,7 +1329,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "$(PRODUCT_BUNDLE_IDENTIFIER).find-login-action-extension"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = "9b4419c3-a959-4af8-8810-8af134008ec5"; - PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.extension"; + PROVISIONING_PROFILE_SPECIFIER = "match AppStore me.mssun.passforios.find-login-action-extension"; SKIP_INSTALL = YES; SWIFT_VERSION = 3.0; }; From 99741f258dc4642891bce39f58b002a5b8040161 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 14:54:16 +0800 Subject: [PATCH 09/18] Fix fastlane --- fastlane/Fastfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f8f0654..607740b 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -55,7 +55,7 @@ platform :ios do ) match( type: "appstore", - app_identifier: "me.mssun.passforios.extension", + app_identifier: "me.mssun.passforios.find-login-action-extension", keychain_name: ENV["MATCH_KEYCHAIN_NAME"], keychain_password: ENV["MATCH_KEYCHAIN_PASSWORD"], readonly: true From 73edac6e1469c6b59714261c54e653eae007387d Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 15:19:29 +0800 Subject: [PATCH 10/18] Fix extension info plist --- passExtension/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/passExtension/Info.plist b/passExtension/Info.plist index 830754c..f0802bb 100644 --- a/passExtension/Info.plist +++ b/passExtension/Info.plist @@ -29,7 +29,7 @@ NSExtensionActivationSupportsText NSExtensionActivationSupportsWebPageWithMaxCount - 1 + 1 NSExtensionActivationSupportsWebURLWithMaxCount 1 From 62136b0cfda90d2a982443b5873f251e2a68ee1a Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 22:07:53 +0800 Subject: [PATCH 11/18] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 816c01c..d10c16b 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,12 @@ Testflight, drop an email to `developer@passforios.mssun.me`. Thank you. ## Features - Try to be compatible with the Password Store command line tool -- Support to view, copy, add, and edit password entries +- View, copy, add, and edit password entries - Encrypt and decrypt password entries by PGP keys - Synchronize with your password Git repository - User-friendly interface: search, long press to copy, copy and open link, etc. -- Support one-time password (OTP) tokens (QR code and otpauth URI) +- Support one-time password tokens (two-factor authentication codes) +- Autofill in Safari/Chrome and [supported apps](https://github.com/agilebits/onepassword-app-extension) - Written in Swift - No need to jailbreak your devices @@ -38,8 +39,8 @@ Testflight, drop an email to `developer@passforios.mssun.me`. Thank you. ## Usages - Setup your password-store ([official `Pass` introduction](https://www.passwordstore.org/)) -- Get Pass for iOS from the App Store or build one by yourself -- Start to use Pass for iOS on your iPhone/iPad ([quick-start guide](https://github.com/mssun/passforios/wiki#quick-start-guide-for-pass-for-ios)) +- Get Pass for iOS from the App Store or [build by yourself](https://github.com/mssun/passforios/wiki/Building-Pass-for-iOS) +- Setup Pass for iOS ([quick-start guide](https://github.com/mssun/passforios/wiki#quick-start-guide-for-pass-for-ios)) For more, please read the [wiki page](https://github.com/mssun/pass-ios/wiki). From 69b58e686d79ab04cd3c01aab7a29e5087eadc2f Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Wed, 28 Jun 2017 00:18:06 +0800 Subject: [PATCH 12/18] Polish the Password class - About "additions", remove "additionKeys" --- .../PasswordDetailTableViewController.swift | 13 ++-- passKit/Models/Password.swift | 60 ++++++++++--------- 2 files changed, 35 insertions(+), 38 deletions(-) diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index 1bbe900..a910b14 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -278,17 +278,12 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni } // show additional information - let filteredAdditionKeys = password.additionKeys.filter { - $0.lowercased() != "username" && - $0.lowercased() != "password" && - (!$0.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) && - (!Password.otpKeywords.contains($0) || !SharedDefaults[.isHideOTPOn]) } - + let filteredAdditionKeys = password.getFilteredAdditions() if filteredAdditionKeys.count > 0 { section = TableSection(type: .addition, header: "additions") - for additionKey in filteredAdditionKeys { - section.item.append(TableCell(title: additionKey, content: password.additions[additionKey]!)) - } + filteredAdditionKeys.forEach({ (key: String, value: String) in + section.item.append(TableCell(title: key, content: value)) + }) tableData.append(section) } diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index 78d4826..440bfe5 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -36,11 +36,10 @@ public class Password { } } public var password = "" - public var additions = [String: String]() - public var additionKeys = [String]() public var changed: Int = 0 public var plainText = "" + private var additions = [String: String]() private var firstLineIsOTPField = false private var otpToken: Token? @@ -82,16 +81,29 @@ public class Password { self.name = name self.url = url self.plainText = plainText - self.additions.removeAll() - self.additionKeys.removeAll() + additions.removeAll() - // get password and additional fields + // split the plain text let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) { $0 == "\n" || $0 == "\r\n" }.map(String.init) - self.password = plainTextSplit.first ?? "" + + // get password + password = plainTextSplit.first ?? "" + + // get additonal fields if plainTextSplit.count == 2 { - (self.additions, self.additionKeys) = Password.getAdditionFields(from: plainTextSplit[1]) + var unknownIndex = 0 + plainTextSplit[1].enumerateLines() { line, _ in + if !line.isEmpty { + var (key, value) = Password.getKeyValuePair(from: line) + if key == nil { + unknownIndex += 1 + key = "unknown \(unknownIndex)" + } + self.additions[key!] = value + } + } } // check whether the first line of the plainText looks like an otp entry @@ -99,7 +111,6 @@ public class Password { if Password.otpKeywords.contains(key ?? "") { firstLineIsOTPField = true self.additions[key!] = value - self.additionKeys.insert(key!, at: 0) } else { firstLineIsOTPField = false } @@ -108,6 +119,18 @@ public class Password { self.updateOtpToken() } + public func getFilteredAdditions() -> [String: String] { + var filteredAdditions = [String: String]() + additions.forEach { (key: String, value: String) in + if key.lowercased() != "username" && key.lowercased() != "password" && + (!key.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) && + (!Password.otpKeywords.contains(key) || !SharedDefaults[.isHideOTPOn]) { + filteredAdditions[key] = value + } + } + return filteredAdditions + } + public func getUsername() -> String? { return getAdditionValue(withKey: "Username") ?? getAdditionValue(withKey: "username") } @@ -138,27 +161,6 @@ public class Password { return (key, value) } - private static func getAdditionFields(from additionFieldsPlainText: String) -> ([String: String], [String]){ - var additions = [String: String]() - var additionKeys = [String]() - var unknownIndex = 0 - - additionFieldsPlainText.enumerateLines() { line, _ in - if line == "" { - return - } - var (key, value) = getKeyValuePair(from: line) - if key == nil { - unknownIndex += 1 - key = "unknown \(unknownIndex)" - } - additions[key!] = value - additionKeys.append(key!) - } - - return (additions, additionKeys) - } - public func getAdditionsPlainText() -> String { // lines starting from the second let plainTextSplit = plainText.characters.split(maxSplits: 1, omittingEmptySubsequences: false) { From 39820a410891ef97f7c952f1400da00041cb8a05 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Wed, 28 Jun 2017 00:32:50 +0800 Subject: [PATCH 13/18] Look for "login" as a username field in auto fill - Will look for "username" first --- .../PasswordDetailTableViewController.swift | 3 +++ passExtension/ExtensionViewController.swift | 2 +- passKit/Models/Password.swift | 24 +++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/pass/Controllers/PasswordDetailTableViewController.swift b/pass/Controllers/PasswordDetailTableViewController.swift index a910b14..e402159 100644 --- a/pass/Controllers/PasswordDetailTableViewController.swift +++ b/pass/Controllers/PasswordDetailTableViewController.swift @@ -261,6 +261,9 @@ class PasswordDetailTableViewController: UITableViewController, UIGestureRecogni if let username = password.getUsername() { section.item.append(TableCell(title: "username", content: username)) } + if let login = password.getLogin() { + section.item.append(TableCell(title: "login", content: login)) + } section.item.append(TableCell(title: "password", content: password.password)) tableData.append(section) diff --git a/passExtension/ExtensionViewController.swift b/passExtension/ExtensionViewController.swift index ff733ee..29f6f0b 100644 --- a/passExtension/ExtensionViewController.swift +++ b/passExtension/ExtensionViewController.swift @@ -160,7 +160,7 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV var decryptedPassword: Password? do { decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) - let username = decryptedPassword?.getUsername() ?? "" + let username = decryptedPassword?.getUsername() ?? decryptedPassword?.getLogin() ?? "" let password = decryptedPassword?.password ?? "" DispatchQueue.main.async {// prepare a dictionary to return switch self.extensionAction { diff --git a/passKit/Models/Password.swift b/passKit/Models/Password.swift index 440bfe5..52b6566 100644 --- a/passKit/Models/Password.swift +++ b/passKit/Models/Password.swift @@ -122,7 +122,7 @@ public class Password { public func getFilteredAdditions() -> [String: String] { var filteredAdditions = [String: String]() additions.forEach { (key: String, value: String) in - if key.lowercased() != "username" && key.lowercased() != "password" && + if key.lowercased() != "username" && key.lowercased() != "login" && key.lowercased() != "password" && (!key.hasPrefix("unknown") || !SharedDefaults[.isHideUnknownOn]) && (!Password.otpKeywords.contains(key) || !SharedDefaults[.isHideOTPOn]) { filteredAdditions[key] = value @@ -132,11 +132,15 @@ public class Password { } public func getUsername() -> String? { - return getAdditionValue(withKey: "Username") ?? getAdditionValue(withKey: "username") + return getAdditionValue(withKey: "username", caseSensitive: false) + } + + public func getLogin() -> String? { + return getAdditionValue(withKey: "login", caseSensitive: false) } public func getURLString() -> String? { - return getAdditionValue(withKey: "URL") ?? getAdditionValue(withKey: "url") ?? getAdditionValue(withKey: "Url") + return getAdditionValue(withKey: "url", caseSensitive: false) } // return a key-value pair from the line @@ -181,8 +185,18 @@ public class Password { return getPlainText().data(using: .utf8)! } - private func getAdditionValue(withKey key: String) -> String? { - return self.additions[key] + private func getAdditionValue(withKey key: String, caseSensitive: Bool = true) -> String? { + if caseSensitive { + return additions[key] + } else { + let searchKey = key.lowercased() + for k in additions.keys { + if searchKey == k.lowercased() { + return additions[k] + } + } + return nil + } } /* From 85ad59b17d95dab52bc2992014b4dee6723d10cb Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Wed, 28 Jun 2017 00:45:25 +0800 Subject: [PATCH 14/18] Use project deployment target --- pass.xcodeproj/project.pbxproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pass.xcodeproj/project.pbxproj b/pass.xcodeproj/project.pbxproj index f975495..162e70e 100644 --- a/pass.xcodeproj/project.pbxproj +++ b/pass.xcodeproj/project.pbxproj @@ -1167,7 +1167,6 @@ ); INFOPLIST_FILE = passKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MODULEMAP_FILE = ""; @@ -1212,7 +1211,6 @@ ); INFOPLIST_FILE = passKit/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 10.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited)"; LIBRARY_SEARCH_PATHS = "$(inherited)"; MODULEMAP_FILE = ""; @@ -1290,7 +1288,6 @@ "$(SRCROOT)/Carthage/Build/iOS/ObjectiveGit.framework/Headers/", ); INFOPLIST_FILE = passExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; @@ -1322,7 +1319,6 @@ "$(SRCROOT)/Carthage/Build/iOS/ObjectiveGit.framework/Headers/", ); INFOPLIST_FILE = passExtension/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 10.3; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_CFLAGS = "$(inherited)"; From f8f43a122587404ff5bc7e3f5077d5e99c41c807 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Wed, 28 Jun 2017 00:59:44 +0800 Subject: [PATCH 15/18] Fix a warning in pod install --- Podfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Podfile b/Podfile index e208186..fe7b6d7 100644 --- a/Podfile +++ b/Podfile @@ -1,3 +1,5 @@ +platform :ios, '10.2' + def generate_modulemap(name, path) f = File.new(File.join("#{path}/module.modulemap"), "w+") module_name = "#{name}" From 3593974ca421403809d61c84b7d41f29b92529f0 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Fri, 7 Jul 2017 11:32:03 +0800 Subject: [PATCH 16/18] Fix bugs in the password editor --- .../PasswordEditorTableViewController.swift | 21 ++++++++++++++----- passKit/Helpers/Utils.swift | 6 ++++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pass/Controllers/PasswordEditorTableViewController.swift b/pass/Controllers/PasswordEditorTableViewController.swift index 4083794..b320c14 100644 --- a/pass/Controllers/PasswordEditorTableViewController.swift +++ b/pass/Controllers/PasswordEditorTableViewController.swift @@ -88,6 +88,7 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl case .fillPasswordCell: fillPasswordCell = tableView.dequeueReusableCell(withIdentifier: "fillPasswordCell", for: indexPath) as? FillPasswordTableViewCell fillPasswordCell?.delegate = self + fillPasswordCell?.contentTextField.delegate = self fillPasswordCell?.setContent(content: cellData[PasswordEditorCellKey.content] as? String) if tableData[passwordSection].count == 1 { fillPasswordCell?.settingButton.isHidden = true @@ -97,10 +98,18 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl passwordLengthCell = tableView.dequeueReusableCell(withIdentifier: "passwordLengthCell", for: indexPath) as? SliderTableViewCell let lengthSetting = Globals.passwordDefaultLength[SharedDefaults[.passwordGeneratorFlavor]] ?? Globals.passwordDefaultLength["Random"] + let minimumLength = lengthSetting?.min ?? 0 + let maximumLength = lengthSetting?.max ?? 0 + var defaultLength = lengthSetting?.def ?? 0 + if let currentPasswordLength = (tableData[passwordSection][0][PasswordEditorCellKey.content] as? String)?.characters.count, + currentPasswordLength >= minimumLength, + currentPasswordLength <= maximumLength { + defaultLength = currentPasswordLength + } passwordLengthCell?.reset(title: "Length", - minimumValue: lengthSetting?.min ?? 0, - maximumValue: lengthSetting?.max ?? 0, - defaultValue: lengthSetting?.def ?? 0) + minimumValue: minimumLength, + maximumValue: maximumLength, + defaultValue: defaultLength) passwordLengthCell?.delegate = self return passwordLengthCell! case .additionsCell: @@ -234,14 +243,16 @@ class PasswordEditorTableViewController: UITableViewController, FillPasswordTabl } } - // update the data table after editing + // update tableData so to make sure reloadData() works correctly func textFieldDidEndEditing(_ textField: UITextField) { if textField == nameCell?.contentTextField { tableData[nameSection][0][PasswordEditorCellKey.content] = nameCell?.getContent() + } else if textField == fillPasswordCell?.contentTextField { + tableData[passwordSection][0][PasswordEditorCellKey.content] = fillPasswordCell?.getContent() } } - // update the data table after editing + // update tableData so to make sure reloadData() works correctly func textViewDidEndEditing(_ textView: UITextView) { if textView == additionsCell?.contentTextView { tableData[additionsSection][0][PasswordEditorCellKey.content] = additionsCell?.getContent() diff --git a/passKit/Helpers/Utils.swift b/passKit/Helpers/Utils.swift index 785f892..9ce518e 100644 --- a/passKit/Helpers/Utils.swift +++ b/passKit/Helpers/Utils.swift @@ -111,11 +111,13 @@ public class Utils { // draw all digits in the password into red // draw all punctuation characters in the password into blue for (index, element) in plainPassword.unicodeScalars.enumerated() { + var charColor = UIColor.darkText if NSCharacterSet.decimalDigits.contains(element) { - attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.red, range: NSRange(location: index, length: 1)) + charColor = Globals.red } else if !NSCharacterSet.letters.contains(element) { - attributedPassword.addAttribute(NSForegroundColorAttributeName, value: Globals.blue, range: NSRange(location: index, length: 1)) + charColor = Globals.blue } + attributedPassword.addAttribute(NSForegroundColorAttributeName, value: charColor, range: NSRange(location: index, length: 1)) } return attributedPassword } From 4b5ea10b404066e84594b9e3a96859b684add4cc Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Fri, 7 Jul 2017 17:36:04 +0800 Subject: [PATCH 17/18] Force the PasscodeLockVC to be portrait --- pass/AppDelegate.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pass/AppDelegate.swift b/pass/AppDelegate.swift index f50ac64..c08f282 100644 --- a/pass/AppDelegate.swift +++ b/pass/AppDelegate.swift @@ -38,6 +38,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + if let _ = window?.rootViewController as? PasscodeLockViewController { + // Force the PasscodeLockVC to be portrait because the rotation seems bugy in some cases + return .portrait + } else { + return .all + } + } + func postSearchNotification() { NotificationCenter.default.post(name: .passwordSearch, object: nil) } From fa78a7e0b7bad20add50daadc41a53d3873ad5a9 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 16 Jul 2017 23:15:24 +0800 Subject: [PATCH 18/18] Fix passcode lock view rotation problem --- pass/AppDelegate.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pass/AppDelegate.swift b/pass/AppDelegate.swift index c08f282..23da43a 100644 --- a/pass/AppDelegate.swift +++ b/pass/AppDelegate.swift @@ -40,11 +40,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { if let _ = window?.rootViewController as? PasscodeLockViewController { - // Force the PasscodeLockVC to be portrait because the rotation seems bugy in some cases - return .portrait - } else { - return .all + window?.frame = UIScreen.main.bounds } + return .all } func postSearchNotification() {