2017-06-14 00:25:38 +08:00
//
// P a s s w o r d s V i e w C o n t r o l l e r . s w i f t
// p a s s
//
// C r e a t e d b y Y i s h i L i n o n 1 3 / 6 / 1 7 .
// C o p y r i g h t © 2 0 1 7 B o b S u n . A l l r i g h t s r e s e r v e d .
//
import Foundation
import MobileCoreServices
import passKit
fileprivate class PasswordsTableEntry : NSObject {
var title : String
var passwordEntity : PasswordEntity ?
init ( title : String , passwordEntity : PasswordEntity ? ) {
self . title = title
self . passwordEntity = passwordEntity
}
}
class ExtensionViewController : UIViewController , UITableViewDataSource , UITableViewDelegate , UISearchBarDelegate , UINavigationBarDelegate {
@IBOutlet weak var searchBar : UISearchBar !
@IBOutlet weak var tableView : UITableView !
private let passwordStore = PasswordStore . shared
private var searchActive = false
private var passwordsTableEntries : [ PasswordsTableEntry ] = [ ]
private var filteredPasswordsTableEntries : [ PasswordsTableEntry ] = [ ]
2017-06-23 21:57:03 +08:00
enum Action {
case findLogin , fillBrowser , unknown
}
private var extensionAction = Action . unknown
2017-06-14 19:14:56 +08:00
private lazy var passcodelock : PasscodeExtensionDisplay = {
let passcodelock = PasscodeExtensionDisplay ( extensionContext : self . extensionContext )
return passcodelock
} ( )
2017-06-14 00:25:38 +08:00
private func initPasswordsTableEntries ( ) {
passwordsTableEntries . removeAll ( )
filteredPasswordsTableEntries . removeAll ( )
var passwordEntities = [ PasswordEntity ] ( )
passwordEntities = self . passwordStore . fetchPasswordEntityCoreData ( withDir : false )
passwordsTableEntries = passwordEntities . map {
PasswordsTableEntry ( title : $0 . name ! , passwordEntity : $0 )
}
}
2017-06-14 19:14:56 +08:00
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
passcodelock . presentPasscodeLockIfNeeded ( self )
}
2017-06-14 00:25:38 +08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
// p r e p a r e
searchBar . delegate = self
tableView . delegate = self
tableView . dataSource = self
tableView . register ( UINib ( nibName : " PasswordWithFolderTableViewCell " , bundle : nil ) , forCellReuseIdentifier : " passwordWithFolderTableViewCell " )
// i n i t i a l i z e t a b l e e n t r i e s
initPasswordsTableEntries ( )
2017-06-23 21:57:03 +08:00
// g e t t h e p r o v i d e r
2017-06-25 12:52:08 +08:00
guard let extensionItems = extensionContext ? . inputItems as ? [ NSExtensionItem ] else {
return
2017-06-23 21:57:03 +08:00
}
2017-06-25 12:52:08 +08:00
for extensionItem in extensionItems {
if let itemProviders = extensionItem . attachments as ? [ NSItemProvider ] {
for provider in itemProviders {
// s e a r c h u s i n g t h e e x t e n s i o n C o n t e x t i n p u t s
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
// f o r c e s e a r c h ( s e t t e x t , s e t a c t i v e , f o r c e s e a r c h )
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
// f o r c e s e a r c h ( s e t t e x t , s e t a c t i v e , f o r c e s e a r c h )
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
// f o r c e s e a r c h ( s e t t e x t , s e t a c t i v e , f o r c e s e a r c h )
self ? . searchBar . text = url
self ? . searchBar . becomeFirstResponder ( )
self ? . searchBarSearchButtonClicked ( ( self ? . searchBar ) ! )
}
} )
2017-06-23 21:57:03 +08:00
}
2017-06-14 00:25:38 +08:00
}
2017-06-25 12:52:08 +08:00
}
2017-06-14 00:25:38 +08:00
}
}
// d e f i n e c e l l c o n t e n t s , a n d s e t l o n g p r e s s a c t i o n
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : " passwordTableViewCell " , for : indexPath )
let entry = getPasswordEntry ( by : indexPath )
if entry . passwordEntity ! . synced {
cell . textLabel ? . text = entry . title
} else {
cell . textLabel ? . text = " ↻ \( entry . title ) "
}
cell . accessoryType = . none
cell . detailTextLabel ? . font = UIFont . preferredFont ( forTextStyle : . footnote )
cell . detailTextLabel ? . text = entry . passwordEntity ? . getCategoryText ( )
return cell
}
// s e l e c t r o w - > e x t e n s i o n r e t u r n s ( w i t h u s e r n a m e a n d p a s s w o r d )
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
let entry = getPasswordEntry ( by : indexPath )
guard self . passwordStore . privateKey != nil else {
Utils . alert ( title : " Cannot Copy Password " , message : " PGP Key is not set. Please set your PGP Key first. " , controller : self , completion : nil )
return
}
let passwordEntity = entry . passwordEntity !
UIImpactFeedbackGenerator ( style : . medium ) . impactOccurred ( )
DispatchQueue . global ( qos : . userInteractive ) . async {
var decryptedPassword : Password ?
do {
decryptedPassword = try self . passwordStore . decrypt ( passwordEntity : passwordEntity , requestPGPKeyPassphrase : self . requestPGPKeyPassphrase )
2017-06-28 00:32:50 +08:00
let username = decryptedPassword ? . getUsername ( ) ? ? decryptedPassword ? . getLogin ( ) ? ? " "
2017-06-25 12:52:08 +08:00
let password = decryptedPassword ? . password ? ? " "
DispatchQueue . main . async { // p r e p a r e a d i c t i o n a r y t o r e t u r n
2017-06-23 21:57:03 +08:00
switch self . extensionAction {
case . findLogin :
2017-06-14 00:25:38 +08:00
let extensionItem = NSExtensionItem ( )
2017-06-25 12:52:08 +08:00
var returnDictionary = [ OnePasswordExtensionKey . usernameKey : username ,
OnePasswordExtensionKey . passwordKey : password ]
2017-06-23 21:57:03 +08:00
if let totpPassword = decryptedPassword ? . getOtp ( ) {
returnDictionary [ OnePasswordExtensionKey . totpKey ] = totpPassword
}
2017-06-14 00:25:38 +08:00
extensionItem . attachments = [ NSItemProvider ( item : returnDictionary as NSSecureCoding , typeIdentifier : String ( kUTTypePropertyList ) ) ]
self . extensionContext ! . completeRequest ( returningItems : [ extensionItem ] , completionHandler : nil )
2017-06-25 12:52:08 +08:00
case . fillBrowser :
2017-06-23 21:57:03 +08:00
Utils . copyToPasteboard ( textToCopy : decryptedPassword ? . password )
2017-06-25 12:52:08 +08:00
// r e t u r n a d i c t i o n a r y f o r J a v a S c r i p t f o r b e s t - e f f o r f i l l i n
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 )
2017-06-23 21:57:03 +08:00
}
2017-06-14 00:25:38 +08:00
}
} catch {
print ( error )
DispatchQueue . main . async {
// r e m o v e t h e w r o n g p a s s p h r a s e s o t h a t u s e r s c o u l d e n t e r i t n e x t t i m e
self . passwordStore . pgpKeyPassphrase = nil
Utils . alert ( title : " Cannot Copy Password " , message : error . localizedDescription , controller : self , completion : nil )
}
}
}
}
func numberOfSectionsInTableView ( tableView : UITableView ) -> Int {
return 1
}
func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
if searchActive {
return filteredPasswordsTableEntries . count
}
return passwordsTableEntries . count ;
}
private func requestPGPKeyPassphrase ( ) -> String {
let sem = DispatchSemaphore ( value : 0 )
var passphrase = " "
DispatchQueue . main . async {
let alert = UIAlertController ( title : " Passphrase " , message : " Please fill in the passphrase of your PGP secret key. " , preferredStyle : UIAlertControllerStyle . alert )
alert . addAction ( UIAlertAction ( title : " OK " , style : UIAlertActionStyle . default , handler : { _ in
passphrase = alert . textFields ! . first ! . text !
sem . signal ( )
} ) )
alert . addTextField ( configurationHandler : { ( textField : UITextField ! ) in
textField . text = " "
textField . isSecureTextEntry = true
} )
self . present ( alert , animated : true , completion : nil )
}
let _ = sem . wait ( timeout : DispatchTime . distantFuture )
2017-10-08 21:37:58 +08:00
if SharedDefaults [ . isRememberPGPPassphraseOn ] {
2017-06-14 00:25:38 +08:00
self . passwordStore . pgpKeyPassphrase = passphrase
}
return passphrase
}
@IBAction func cancelExtension ( _ sender : Any ) {
extensionContext ! . completeRequest ( returningItems : [ ] , completionHandler : nil )
}
func searchBarCancelButtonClicked ( _ searchBar : UISearchBar ) {
searchBar . text = " "
searchActive = false
self . tableView . reloadData ( )
}
func searchBarSearchButtonClicked ( _ searchBar : UISearchBar ) {
if let searchText = searchBar . text , searchText . isEmpty = = false {
let searchTextLowerCased = searchText . lowercased ( )
filteredPasswordsTableEntries = passwordsTableEntries . filter { entry in
let entryTitle = entry . title . lowercased ( )
return entryTitle . contains ( searchTextLowerCased ) || searchTextLowerCased . contains ( entryTitle )
}
searchActive = true
} else {
searchActive = false
}
self . tableView . reloadData ( )
}
func searchBar ( _ searchBar : UISearchBar , textDidChange searchText : String ) {
searchBarSearchButtonClicked ( searchBar )
}
private func getPasswordEntry ( by indexPath : IndexPath ) -> PasswordsTableEntry {
if searchActive {
return filteredPasswordsTableEntries [ indexPath . row ]
} else {
return passwordsTableEntries [ indexPath . row ]
}
}
}