Support Chrome and app logins

This commit is contained in:
Yishi Lin 2017-06-23 21:57:03 +08:00
parent daacfbea83
commit fb55f32797
5 changed files with 144 additions and 31 deletions

View file

@ -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)

View file

@ -28,11 +28,9 @@
<dict>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
<integer>100</integer>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
</dict>
<key>NSExtensionJavaScriptPreprocessingFile</key>
<string>passProcessor</string>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>

View file

@ -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
}