Skip to content

Commit

Permalink
PM-15435: Use TOTP manual entry in extension when camera isn't suppor…
Browse files Browse the repository at this point in the history
…ted (#1202)
  • Loading branch information
matt-livefront authored Dec 17, 2024
1 parent fcdc0ed commit 625bbd3
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,9 @@ final class AddEditItemProcessor: StateProcessor<// swiftlint:disable:this type_
/// Kicks off the TOTP setup flow.
///
private func setupTotp() async {
guard services.cameraService.deviceSupportsCamera() else {
guard services.cameraService.deviceSupportsCamera(),
appExtensionDelegate?.isInAppExtension != true // Extensions don't allow camera access.
else {
coordinator.navigate(to: .setupTotpManual, context: self)
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1393,6 +1393,16 @@ class AddEditItemProcessorTests: BitwardenTestCase {
XCTAssertEqual(coordinator.routes.last, .setupTotpManual)
}

/// `perform(_:)` with `.setupTotpPressed` when in the app extension navigates to the
/// `.setupTotpManual` route.
@MainActor
func test_perform_setupTotpPressed_extension() async {
appExtensionDelegate.isInAppExtension = true
await subject.perform(.setupTotpPressed)

XCTAssertEqual(coordinator.routes.last, .setupTotpManual)
}

/// `receive(_:)` with `authKeyVisibilityTapped` updates the value in the state.
@MainActor
func test_receive_authKeyVisibilityTapped() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ final class AuthenticatorKeyCaptureCoordinator: Coordinator, HasStackNavigator {

// MARK: Private Properties

/// A delegate used to communicate with the app extension.
private weak var appExtensionDelegate: AppExtensionDelegate?

/// A delegate that responds to events in this coordinator.
private weak var delegate: AuthenticatorKeyCaptureDelegate?

Expand All @@ -64,15 +67,18 @@ final class AuthenticatorKeyCaptureCoordinator: Coordinator, HasStackNavigator {
/// Creates a new `AuthenticatorKeyCaptureCoordinator`.
///
/// - Parameters:
/// - appExtensionDelegate: A delegate used to communicate with the app extension.
/// - delegate: An optional delegate that responds to events in this coordinator.
/// - services: The services used by this coordinator.
/// - stackNavigator: The stack navigator that is managed by this coordinator.
///
init(
appExtensionDelegate: AppExtensionDelegate?,
delegate: AuthenticatorKeyCaptureDelegate?,
services: Services,
stackNavigator: StackNavigator
) {
self.appExtensionDelegate = appExtensionDelegate
self.delegate = delegate
self.services = services
self.stackNavigator = stackNavigator
Expand Down Expand Up @@ -168,11 +174,13 @@ final class AuthenticatorKeyCaptureCoordinator: Coordinator, HasStackNavigator {
/// Shows the totp manual setup screen.
///
private func showManualTotp() {
let deviceSupportsCamera = services.cameraService.deviceSupportsCamera() &&
appExtensionDelegate?.isInAppExtension != true // Extensions don't allow camera access.
let processor = ManualEntryProcessor(
coordinator: asAnyCoordinator(),
services: services,
state: DefaultEntryState(
deviceSupportsCamera: services.cameraService.deviceSupportsCamera()
deviceSupportsCamera: deviceSupportsCamera
)
)
let view = ManualEntryView(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import XCTest
class AuthenticatorKeyCaptureCoordinatorTests: BitwardenTestCase {
// MARK: Properties

var appExtensionDelegate: MockAppExtensionDelegate!
var cameraService: MockCameraService!
var delegate: MockAuthenticatorKeyCaptureDelegate!
var errorReporter: MockErrorReporter!
Expand All @@ -18,12 +19,14 @@ class AuthenticatorKeyCaptureCoordinatorTests: BitwardenTestCase {
override func setUp() {
super.setUp()

appExtensionDelegate = MockAppExtensionDelegate()
cameraService = MockCameraService()
delegate = MockAuthenticatorKeyCaptureDelegate()
errorReporter = MockErrorReporter()
stackNavigator = MockStackNavigator()

subject = AuthenticatorKeyCaptureCoordinator(
appExtensionDelegate: appExtensionDelegate,
delegate: delegate,
services: ServiceContainer.withMocks(
cameraService: cameraService,
Expand All @@ -38,6 +41,7 @@ class AuthenticatorKeyCaptureCoordinatorTests: BitwardenTestCase {
override func tearDown() {
super.tearDown()

appExtensionDelegate = nil
cameraService = nil
errorReporter = nil
stackNavigator = nil
Expand Down Expand Up @@ -92,8 +96,21 @@ class AuthenticatorKeyCaptureCoordinatorTests: BitwardenTestCase {

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .replaced)
let view = action.view as? (any View)
XCTAssertNotNil(try? view?.inspect().find(ManualEntryView.self))
let view = try XCTUnwrap(action.view as? ManualEntryView)
XCTAssertTrue(view.store.state.deviceSupportsCamera)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view in the extension.
@MainActor
func test_navigateTo_setupTotpManual_extension() throws {
appExtensionDelegate.isInAppExtension = true
cameraService.deviceHasCamera = true
subject.navigate(to: .manualKeyEntry)

let action = try XCTUnwrap(stackNavigator.actions.last)
XCTAssertEqual(action.type, .replaced)
let view = try XCTUnwrap(action.view as? ManualEntryView)
XCTAssertFalse(view.store.state.deviceSupportsCamera)
}

/// `navigate(to:)` with `.setupTotpManual` presents the manual entry view.
Expand Down
2 changes: 2 additions & 0 deletions BitwardenShared/UI/Vault/VaultItem/VaultItemCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class VaultItemCoordinator: NSObject, Coordinator, HasStackNavigator { // swiftl
private func showCamera(delegate: AuthenticatorKeyCaptureDelegate) async {
let navigationController = UINavigationController()
let coordinator = AuthenticatorKeyCaptureCoordinator(
appExtensionDelegate: appExtensionDelegate,
delegate: delegate,
services: services,
stackNavigator: navigationController
Expand Down Expand Up @@ -339,6 +340,7 @@ class VaultItemCoordinator: NSObject, Coordinator, HasStackNavigator { // swiftl
private func showManualTotp(delegate: AuthenticatorKeyCaptureDelegate) {
let navigationController = UINavigationController()
let coordinator = AuthenticatorKeyCaptureCoordinator(
appExtensionDelegate: appExtensionDelegate,
delegate: delegate,
services: services,
stackNavigator: navigationController
Expand Down

0 comments on commit 625bbd3

Please sign in to comment.