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

@ -12,6 +12,7 @@
2C58F31EECC494C7A7F00A98 /* libPods-passKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FB6C63FA1652F925B5C9F0B5 /* libPods-passKitTests.a */; }; 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 */; }; 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 */; }; 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 */; }; A217ACE21E9AB17C00A1A6CF /* OTPScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */; };
A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; }; A217ACE41E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */; };
A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.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; }; 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 = "<group>"; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnePasswordExtensionConstants.swift; sourceTree = "<group>"; };
A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = "<group>"; }; A217ACE11E9AB17C00A1A6CF /* OTPScannerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTPScannerController.swift; sourceTree = "<group>"; };
A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; A217ACE31E9BBBBD00A1A6CF /* GitConfigSettingTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GitConfigSettingTableViewController.swift; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
A2227D4C1EEE5E25002A69A9 /* libObjectivePGP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libObjectivePGP.a; path = "Pods/../build/Debug-iphoneos/ObjectivePGP/libObjectivePGP.a"; sourceTree = "<group>"; }; A2227D4C1EEE5E25002A69A9 /* libObjectivePGP.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libObjectivePGP.a; path = "Pods/../build/Debug-iphoneos/ObjectivePGP/libObjectivePGP.a"; sourceTree = "<group>"; };
@ -326,6 +328,24 @@
/* End PBXFrameworksBuildPhase section */ /* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */ /* Begin PBXGroup section */
A2168A801EFD431A005EA873 /* Controllers */ = {
isa = PBXGroup;
children = (
A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */,
A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */,
);
name = Controllers;
sourceTree = "<group>";
};
A2168A811EFD4322005EA873 /* Helpers */ = {
isa = PBXGroup;
children = (
A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */,
A2168A7E1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift */,
);
name = Helpers;
sourceTree = "<group>";
};
A26075791EEC6F34005DB03E /* passKit */ = { A26075791EEC6F34005DB03E /* passKit */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -350,14 +370,13 @@
A26700251EEC466A00176B8A /* passExtension */ = { A26700251EEC466A00176B8A /* passExtension */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
A26700331EEC46C900176B8A /* passExtension.entitlements */,
A267002B1EEC466A00176B8A /* Info.plist */,
A2168A801EFD431A005EA873 /* Controllers */,
A2168A811EFD4322005EA873 /* Helpers */,
A2367B9F1EF0387000C8FE8B /* Assets.xcassets */, A2367B9F1EF0387000C8FE8B /* Assets.xcassets */,
A26700351EEC475600176B8A /* passProcessor.js */, A26700351EEC475600176B8A /* passProcessor.js */,
A26700331EEC46C900176B8A /* passExtension.entitlements */,
A26700281EEC466A00176B8A /* MainInterface.storyboard */, A26700281EEC466A00176B8A /* MainInterface.storyboard */,
A267002B1EEC466A00176B8A /* Info.plist */,
A2A61C2B1EEFDF3300CFE063 /* ExtensionViewController.swift */,
A2367B9A1EEFE1B300C8FE8B /* UtilsExtension.swift */,
A28C66671EF10EC900A398A1 /* PasscodeExtensionDisplay.swift */,
); );
path = passExtension; path = passExtension;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1010,6 +1029,7 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */, A2A61C2C1EEFDF3300CFE063 /* ExtensionViewController.swift in Sources */,
A2168A7F1EFD40D5005EA873 /* OnePasswordExtensionConstants.swift in Sources */,
A28C66681EF10EC900A398A1 /* PasscodeExtensionDisplay.swift in Sources */, A28C66681EF10EC900A398A1 /* PasscodeExtensionDisplay.swift in Sources */,
A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */, A2367B9B1EEFE1B300C8FE8B /* UtilsExtension.swift in Sources */,
); );

View file

@ -18,6 +18,15 @@
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2.8</string> <string>0.2.8</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>org-appextension-feature-password-management</string>
</array>
</dict>
</array>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1</string> <string>1</string>
<key>ITSAppUsesNonExemptEncryption</key> <key>ITSAppUsesNonExemptEncryption</key>

View file

@ -26,13 +26,15 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
private let passwordStore = PasswordStore.shared private let passwordStore = PasswordStore.shared
private var searchActive = false private var searchActive = false
// the URL passed to the extension
private var extensionURL: String?
private var passwordsTableEntries: [PasswordsTableEntry] = [] private var passwordsTableEntries: [PasswordsTableEntry] = []
private var filteredPasswordsTableEntries: [PasswordsTableEntry] = [] private var filteredPasswordsTableEntries: [PasswordsTableEntry] = []
enum Action {
case findLogin, fillBrowser, unknown
}
private var extensionAction = Action.unknown
private lazy var passcodelock: PasscodeExtensionDisplay = { private lazy var passcodelock: PasscodeExtensionDisplay = {
let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext) let passcodelock = PasscodeExtensionDisplay(extensionContext: self.extensionContext)
return passcodelock return passcodelock
@ -64,24 +66,47 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
// initialize table entries // initialize table entries
initPasswordsTableEntries() initPasswordsTableEntries()
// search using the extensionContext inputs // get the provider
let item = extensionContext?.inputItems.first as! NSExtensionItem let item = extensionContext?.inputItems.first as! NSExtensionItem
let provider = item.attachments?.first as! NSItemProvider let provider: NSItemProvider
let propertyList = String(kUTTypePropertyList) if item.attachments?.count == 1 {
if provider.hasItemConformingToTypeIdentifier(propertyList) { // Safari
provider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in 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 dictionary = item as! NSDictionary
let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! NSDictionary var url: String?
let url = URL(string: (results["url"] as? String)!)?.host 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 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) // force search (set text, set active, force search)
self?.searchBar.text = url self?.searchBar.text = url
self?.searchBar.becomeFirstResponder() self?.searchBar.becomeFirstResponder()
self?.searchBarSearchButtonClicked((self?.searchBar)!) self?.searchBarSearchButtonClicked((self?.searchBar)!)
} }
}) })
} else {
print("error")
} }
} }
@ -116,19 +141,29 @@ class ExtensionViewController: UIViewController, UITableViewDataSource, UITableV
do { do {
decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase) decryptedPassword = try self.passwordStore.decrypt(passwordEntity: passwordEntity, requestPGPKeyPassphrase: self.requestPGPKeyPassphrase)
DispatchQueue.main.async { DispatchQueue.main.async {
switch self.extensionAction {
case .findLogin:
// prepare a dictionary to return
let extensionItem = NSExtensionItem()
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)
default:
// copy the password to the clipboard
Utils.copyToPasteboard(textToCopy: decryptedPassword?.password) Utils.copyToPasteboard(textToCopy: decryptedPassword?.password)
let title = "Password Copied" let title = "Password Copied"
let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)" let message = "Usename: " + (decryptedPassword?.getUsername() ?? "Unknown") + "\r\n(Remember to clear the clipboard.)"
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert) let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {_ in
// return a dictionary for JavaScript for best-effor fill in self.extensionContext!.completeRequest(returningItems: nil, completionHandler: nil)
let extensionItem = NSExtensionItem()
let returnDictionary = [ NSExtensionJavaScriptFinalizeArgumentKey : ["username": decryptedPassword?.getUsername() ?? "", "password": decryptedPassword?.password ?? ""]]
extensionItem.attachments = [NSItemProvider(item: returnDictionary as NSSecureCoding, typeIdentifier: String(kUTTypePropertyList))]
self.extensionContext!.completeRequest(returningItems: [extensionItem], completionHandler: nil)
})) }))
self.present(alert, animated: true, completion: nil) self.present(alert, animated: true, completion: nil)
} }
}
} catch { } catch {
print(error) print(error)
DispatchQueue.main.async { DispatchQueue.main.async {

View file

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