Skip to content
This repository has been archived by the owner on Dec 14, 2021. It is now read-only.

Commit

Permalink
autolock respected and reset properly in autofill contexts (#993)
Browse files Browse the repository at this point in the history
* autolock respected when accessing credentials

* locking behaving as expected

* add comment about credentialactions

* credentialproviderpresenterspec
  • Loading branch information
sashei authored May 20, 2019
1 parent 63bf7f8 commit f956711
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 146 deletions.
18 changes: 12 additions & 6 deletions CredentialProvider/Action/CredentialStatusAction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,38 @@

import Foundation
import MozillaAppServices
import AuthenticationServices

@available(iOS 12, *)
enum CredentialStatusAction: Action {
case extensionConfigured, userCanceled, loginSelected(login: LoginRecord, relock: Bool)
case extensionConfigured,
cancelled(error: ASExtensionError.Code),
loginSelected(login: LoginRecord)
}

@available(iOS 12, *)
extension CredentialStatusAction: Equatable {
static func ==(lhs: CredentialStatusAction, rhs: CredentialStatusAction) -> Bool {
switch (lhs, rhs) {
case (.extensionConfigured, .extensionConfigured):
return true
case (.userCanceled, .userCanceled):
return true
case (.loginSelected(let lhLogin, let lhRelock), .loginSelected(let rhLogin, let rhRelock)):
return lhLogin == rhLogin && lhRelock == rhRelock
case (.cancelled(let lhError), .cancelled(let rhError)):
return lhError == rhError
case (.loginSelected(let lhLogin), .loginSelected(let rhLogin)):
return lhLogin == rhLogin
default:
return false
}
}
}

@available(iOS 12, *)
extension CredentialStatusAction: TelemetryAction {
var eventMethod: TelemetryEventMethod {
switch self {
case .extensionConfigured:
return .settingChanged
case .userCanceled:
case .cancelled:
return .canceled
case .loginSelected:
return .login_selected
Expand Down
10 changes: 4 additions & 6 deletions CredentialProvider/Presenter/CredentialItemListPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,14 @@ class ItemListPresenter: BaseItemListPresenter {
return
}

if let view = target.view {
view.dismissKeyboard()
}
target.view?.dismissKeyboard()

target.dataStore.get(id)
.map { login -> CredentialStatusAction in
if let login = login {
return CredentialStatusAction.loginSelected(login: login, relock: false)
return CredentialStatusAction.loginSelected(login: login)
} else {
return CredentialStatusAction.userCanceled
return CredentialStatusAction.cancelled(error: .userCanceled)
}
}
.subscribe(onNext: { target.dispatcher.dispatch(action: $0) })
Expand All @@ -45,7 +43,7 @@ class ItemListPresenter: BaseItemListPresenter {

lazy var cancelButtonObserver: AnyObserver<Void> = {
return Binder(self) { target, _ in
target.dispatcher.dispatch(action: CredentialStatusAction.userCanceled)
target.dispatcher.dispatch(action: CredentialStatusAction.cancelled(error: .userCanceled))
}.asObserver()
}()
}
68 changes: 32 additions & 36 deletions CredentialProvider/Presenter/CredentialProviderPresenter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,12 @@ class CredentialProviderPresenter {
private let accountStore: AccountStore
private let telemetryStore: TelemetryStore
private let userDefaultStore: UserDefaultStore
private let dataStore: DataStore
fileprivate let dataStore: DataStore
private let telemetryActionHandler: TelemetryActionHandler
private let credentialProviderStore: CredentialProviderStore
private let autoLockSupport: AutoLockSupport
private var credentialProvisionBag = DisposeBag()
private let disposeBag = DisposeBag()

private var dismissObserver: AnyObserver<Void> {
return Binder(self) { target, _ in
target.cancelWith(.userCanceled)
}.asObserver()
}

init(view: CredentialProviderViewProtocol,
dispatcher: Dispatcher = .shared,
accountStore: AccountStore = .shared,
Expand All @@ -43,7 +37,6 @@ class CredentialProviderPresenter {
dataStore: DataStore = .shared,
telemetryActionHandler: TelemetryActionHandler = TelemetryActionHandler(accountStore: AccountStore.shared),
credentialProviderStore: CredentialProviderStore = .shared,
autoLockSupport: AutoLockSupport = .shared,
sizeClassStore: SizeClassStore = .shared) { // SizeClassStore needs to be initialized
self.view = view
self.dispatcher = dispatcher
Expand All @@ -53,7 +46,6 @@ class CredentialProviderPresenter {
self.dataStore = dataStore
self.telemetryActionHandler = telemetryActionHandler
self.credentialProviderStore = credentialProviderStore
self.autoLockSupport = autoLockSupport

self.accountStore.syncCredentials
.filterNil()
Expand All @@ -68,21 +60,16 @@ class CredentialProviderPresenter {
switch action {
case .extensionConfigured:
self?.view?.extensionContext.completeExtensionConfigurationRequest()
case .loginSelected(let login, let relock):
case .loginSelected(let login):
self?.view?.extensionContext.completeRequest(withSelectedCredential: login.passwordCredential) { _ in
self?.dispatcher.dispatch(action: DataStoreAction.touch(id: login.id))

if relock {
self?.dispatcher.dispatch(action: DataStoreAction.lock)
}
}
case .userCanceled:
self?.cancelWith(.userCanceled)
case .cancelled(let error):
self?.cancelWith(error)
}
})
.disposed(by: self.disposeBag)

self.dispatcher.dispatch(action: DataStoreAction.unlock)
self.startTelemetry()
}

Expand All @@ -105,12 +92,26 @@ class CredentialProviderPresenter {
.take(1)
.bind { [weak self] locked in
if locked {
self?.dispatcher.dispatch(action: DataStoreAction.unlock)
self?.dispatcher.dispatch(action: CredentialProviderAction.authenticationRequested)
self?.dispatcher.dispatch(action: CredentialStatusAction.cancelled(error: .userInteractionRequired))
} else {
self?.provideCredential(for: credentialIdentity)
}

self?.provideCredential(for: credentialIdentity, relock: locked)
}
.disposed(by: self.disposeBag)
.disposed(by: self.credentialProvisionBag)
}

func prepareAuthentication(for credentialIdentity: ASPasswordCredentialIdentity) {
self.dataStore.locked
.asDriver(onErrorJustReturn: true)
.drive(onNext: { [weak self] locked in
if locked {
self?.view?.displayWelcome()
} else {
self?.provideCredential(for: credentialIdentity)
}
})
.disposed(by: self.credentialProvisionBag)
}

func changeDisplay(traitCollection: UITraitCollection) {
Expand All @@ -126,39 +127,34 @@ class CredentialProviderPresenter {
self?.view?.displayWelcome()
} else {
self?.view?.displayItemList()
// guard let dismissObserver = self?.dismissObserver else { return }
// self?.view?.displayAlertController(buttons: [
// AlertActionButtonConfiguration(title: "OK", tapObserver: dismissObserver, style: .default)
// ],
// title: "Credential list not available yet",
// message: "Please check back later",
// style: .alert)
}
})
.disposed(by: self.disposeBag)
.disposed(by: self.credentialProvisionBag)
}
}

@available(iOS 12, *)
extension CredentialProviderPresenter {
private func provideCredential(for credentialIdentity: ASPasswordCredentialIdentity, relock: Bool) {
private func provideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
self.credentialProvisionBag = DisposeBag()

guard let id = credentialIdentity.recordIdentifier else {
self.cancelWith(.credentialIdentityNotFound)
self.dispatcher.dispatch(action: CredentialStatusAction.cancelled(error: .credentialIdentityNotFound))
return
}

self.dataStore.locked
.filter { !$0 }
.take(1)
.flatMap { _ in self.dataStore.get(id) }
.bind { [weak self] login in
.map { login -> Action in
guard let login = login else {
self?.cancelWith(.credentialIdentityNotFound)
return
return CredentialStatusAction.cancelled(error: .credentialIdentityNotFound)
}

self?.dispatcher.dispatch(action: CredentialStatusAction.loginSelected(login: login, relock: relock))
return CredentialStatusAction.loginSelected(login: login)
}
.subscribe(onNext: { self.dispatcher.dispatch(action: $0) })
.disposed(by: self.disposeBag)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class CredentialWelcomePresenter: BaseWelcomePresenter {
return
}

self?.dispatcher.dispatch(action: CredentialStatusAction.userCanceled)
self?.dispatcher.dispatch(action: CredentialStatusAction.cancelled(error: .userCanceled))
}
)
.disposed(by: self.authenticationBag)
Expand Down
28 changes: 27 additions & 1 deletion CredentialProvider/Store/CredentialDataStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,35 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import Foundation
import RxSwift

class DataStore: BaseDataStore {
private var dispatcher: Dispatcher

public static let shared = DataStore()

init(dispatcher: Dispatcher = .shared) {
self.dispatcher = dispatcher
super.init()
}

override func initialized() { }
override func initialized() {
self.dispatcher.register
.filterByType(class: CredentialStatusAction.self)
/* when we get credential status actions & are unlocked, store the next lock time
*
* why this works: credential status actions are sent at the conclusion of a user's interaction with the
* credential provider, and thus mirror the more generic "background" as relied on in the app lifecycle.
* from a user perception (and therefore functionality) standpoint, this begins the timer to the next
* lock time. */
.withLatestFrom(self.locked, resultSelector: { (_, locked) -> Void? in
// if we are already locked, do not store the next lock time
return locked ? nil : ()
})
.filterNil()
.subscribe(onNext: { [weak self] _ in
self?.autoLockSupport.storeNextAutolockTime()
})
.disposed(by: self.disposeBag)
}
}
6 changes: 5 additions & 1 deletion CredentialProvider/View/CredentialProviderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class CredentialProviderView: ASCredentialProviderViewController {
}

override var preferredStatusBarStyle: UIStatusBarStyle {
return self.currentViewController?.preferredStatusBarStyle ?? .default
return self.currentViewController?.preferredStatusBarStyle ?? .lightContent
}

required init?(coder aDecoder: NSCoder) {
Expand All @@ -48,6 +48,10 @@ class CredentialProviderView: ASCredentialProviderViewController {
self.presenter?.credentialList(for: serviceIdentifiers)
}

override func prepareInterfaceToProvideCredential(for credentialIdentity: ASPasswordCredentialIdentity) {
self.presenter?.prepareAuthentication(for: credentialIdentity)
}

override func provideCredentialWithoutUserInteraction(for credentialIdentity: ASPasswordCredentialIdentity) {
self.presenter?.credentialProvisionRequested(for: credentialIdentity)
}
Expand Down
2 changes: 1 addition & 1 deletion Shared/Store/BaseDataStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class BaseDataStore {

private let dispatcher: Dispatcher
private let keychainWrapper: KeychainWrapper
private let autoLockSupport: AutoLockSupport
internal let autoLockSupport: AutoLockSupport
private let dataStoreSupport: DataStoreSupport
private let networkStore: NetworkStore
private let lifecycleStore: LifecycleStore
Expand Down
Loading

0 comments on commit f956711

Please sign in to comment.