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
// t h e U R L p a s s e d t o t h e e x t e n s i o n
private var extensionURL : String ?
private var passwordsTableEntries : [ PasswordsTableEntry ] = [ ]
private var filteredPasswordsTableEntries : [ PasswordsTableEntry ] = [ ]
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 ( )
// 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
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 dictionary = item as ! NSDictionary
let results = dictionary [ NSExtensionJavaScriptPreprocessingResultsKey ] as ! NSDictionary
let url = URL ( string : ( results [ " url " ] as ? String ) ! ) ? . host
DispatchQueue . main . async { [ weak self ] in
// 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 {
print ( " error " )
}
}
// 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 )
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
// 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 " : 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 )
}
} 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 )
if SharedDefaults [ . isRememberPassphraseOn ] {
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 ]
}
}
}