Support selects a credential identity from the QuickType bar

This commit is contained in:
Mingshen Sun 2021-01-03 15:08:15 -08:00
parent d4669bbfcb
commit 29d74c48e5
No known key found for this signature in database
GPG key ID: 1F86BA2052FED3B4
6 changed files with 125 additions and 30 deletions

View file

@ -22,6 +22,8 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
embeddedNavigationController.viewControllers.first as! PasswordsViewController
}
lazy var credentialProvider = CredentialProvider(viewController: self, extensionContext: self.extensionContext)
override func viewDidLoad() {
super.viewDidLoad()
passcodelock.presentPasscodeLockIfNeeded(self)
@ -33,23 +35,23 @@ class CredentialProviderViewController: ASCredentialProviderViewController {
}
override func prepareCredentialList(for serviceIdentifiers: [ASCredentialServiceIdentifier]) {
credentialProvider.identifier = serviceIdentifiers.first
let url = serviceIdentifiers.first.flatMap { URL(string: $0.identifier) }
passwordsViewController.navigationItem.prompt = url?.host
let keywords = url?.host?.sanitizedDomain?.components(separatedBy: ".") ?? []
passwordsViewController.showPasswordsWithSuggstion(keywords)
}
override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
credentialProvider.credentials(for: credentialIdentity)
}
}
extension CredentialProviderViewController: PasswordSelectionDelegate {
func selected(password: PasswordTableEntry) {
let passwordEntity = password.passwordEntity
decryptPassword(in: self, with: passwordEntity) { password in
let username = password.getUsernameForCompletion()
let password = password.password
let passwordCredential = ASPasswordCredential(user: username, password: password)
self.extensionContext.completeRequest(withSelectedCredential: passwordCredential)
}
credentialProvider.persistAndProvideCredentials(with: passwordEntity.getPath())
}
}

View file

@ -74,7 +74,16 @@ extension PasswordsViewController: UISearchBarDelegate {
extension PasswordsViewController: UITableViewDelegate {
func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let entry = dataSource.filteredPasswordsTableEntries[indexPath.row]
var entry: PasswordTableEntry!
if dataSource.showSuggestion {
if indexPath.section == 0 {
entry = dataSource.suggestedPasswordsTableEntries[indexPath.row]
} else {
entry = dataSource.filteredPasswordsTableEntries[indexPath.row]
}
} else {
entry = dataSource.filteredPasswordsTableEntries[indexPath.row]
}
UIImpactFeedbackGenerator(style: .medium).impactOccurred()
self.selectionDelegate?.selected(password: entry)

View file

@ -0,0 +1,70 @@
//
// CredentialProvider.swift
// passAutoFillExtension
//
// Created by Sun, Mingshen on 1/2/21.
// Copyright © 2021 Bob Sun. All rights reserved.
//
import AuthenticationServices
import passKit
class CredentialProvider {
var identifier: ASCredentialServiceIdentifier?
weak var extensionContext: ASCredentialProviderExtensionContext?
weak var viewController: UIViewController?
init(viewController: UIViewController, extensionContext: ASCredentialProviderExtensionContext) {
self.viewController = viewController
self.extensionContext = extensionContext
}
func credentials(for identity: ASPasswordCredentialIdentity) {
guard let recordIdentifier = identity.recordIdentifier else {
return
}
guard let pwCredentials = provideCredentials(in: viewController, with: recordIdentifier) else {
return
}
extensionContext?.completeRequest(withSelectedCredential: pwCredentials)
}
func persistAndProvideCredentials(with passwordPath: String) {
guard let pwCredentials = provideCredentials(in: viewController, with: passwordPath) else {
return
}
guard let credentialIdentity = provideCredentialIdentity(for: identifier, user: pwCredentials.user, recordIdentifier: passwordPath) else {
return
}
let store = ASCredentialIdentityStore.shared
store.getState { state in
if state.isEnabled {
ASCredentialIdentityStore.shared.saveCredentialIdentities([credentialIdentity])
}
}
extensionContext?.completeRequest(withSelectedCredential: pwCredentials)
}
}
private func provideCredentialIdentity(for identifier: ASCredentialServiceIdentifier?, user: String, recordIdentifier: String?) -> ASPasswordCredentialIdentity? {
guard let serviceIdentifier = identifier else {
return nil
}
return ASPasswordCredentialIdentity(serviceIdentifier: serviceIdentifier, user: user, recordIdentifier: recordIdentifier)
}
private func provideCredentials(in viewController: UIViewController?, with path: String) -> ASPasswordCredential? {
print(path)
guard let viewController = viewController else {
return nil
}
var credential: ASPasswordCredential?
decryptPassword(in: viewController, with: path) { password in
let username = password.getUsernameForCompletion()
let password = password.password
credential = ASPasswordCredential(user: username, password: password)
}
return credential
}

View file

@ -9,30 +9,22 @@
import UIKit
import passKit
func decryptPassword(in controller: UIViewController, with passwordEntity: PasswordEntity, using keyID: String? = nil, completion: @escaping ((Password) -> Void)) {
DispatchQueue.global(qos: .userInteractive).async {
do {
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: controller)
let decryptedPassword = try PasswordStore.shared.decrypt(passwordEntity: passwordEntity, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
func decryptPassword(in controller: UIViewController, with passwordPath: String, using keyID: String? = nil, completion: @escaping ((Password) -> Void)) {
do {
let requestPGPKeyPassphrase = Utils.createRequestPGPKeyPassphraseHandler(controller: controller)
let decryptedPassword = try PasswordStore.shared.decrypt(path: passwordPath, keyID: keyID, requestPGPKeyPassphrase: requestPGPKeyPassphrase)
DispatchQueue.main.async {
completion(decryptedPassword)
}
} catch let AppError.pgpPrivateKeyNotFound(keyID: key) {
DispatchQueue.main.async {
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.pgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
let selectKey = UIAlertAction.selectKey(controller: controller) { action in
decryptPassword(in: controller, with: passwordEntity, using: action.title, completion: completion)
}
alert.addAction(selectKey)
controller.present(alert, animated: true)
}
} catch {
DispatchQueue.main.async {
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: controller)
}
completion(decryptedPassword)
} catch let AppError.pgpPrivateKeyNotFound(keyID: key) {
let alert = UIAlertController(title: "CannotShowPassword".localize(), message: AppError.pgpPrivateKeyNotFound(keyID: key).localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction.cancelAndPopView(controller: controller))
let selectKey = UIAlertAction.selectKey(controller: controller) { action in
decryptPassword(in: controller, with: passwordPath, using: action.title, completion: completion)
}
alert.addAction(selectKey)
controller.present(alert, animated: true)
} catch {
Utils.alert(title: "CannotCopyPassword".localize(), message: error.localizedDescription, controller: controller)
}
}