From 5d71f997119ce9e33ec454ab24368f990873b5f3 Mon Sep 17 00:00:00 2001 From: Yishi Lin Date: Sun, 25 Jun 2017 12:52:08 +0800 Subject: [PATCH] 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"] + } + } } };