| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/webauthn/authenticator_request_dialog_model.h" |
| |
| #include <iterator> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/containers/contains.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "device/fido/features.h" |
| #include "device/fido/fido_authenticator.h" |
| #include "device/fido/pin.h" |
| #include "device/fido/public_key_credential_user_entity.h" |
| |
| namespace { |
| |
| // Attempts to auto-select the most likely transport that will be used to |
| // service this request, or returns base::nullopt if unsure. |
| base::Optional<device::FidoTransportProtocol> SelectMostLikelyTransport( |
| const device::FidoRequestHandlerBase::TransportAvailabilityInfo& |
| transport_availability, |
| base::Optional<device::FidoTransportProtocol> last_used_transport, |
| bool cable_extension_provided, |
| bool have_paired_phones) { |
| const base::flat_set<AuthenticatorTransport>& candidate_transports( |
| transport_availability.available_transports); |
| |
| // If there is only one transport available, select that instead of showing a |
| // transport selection screen with only a single item. |
| if (candidate_transports.size() == 1) { |
| return *candidate_transports.begin(); |
| } |
| |
| // The remaining decisions apply to GetAssertion requests only. For |
| // MakeCredential, the user needs to choose from transport selection. |
| if (transport_availability.request_type != |
| device::FidoRequestHandlerBase::RequestType::kGetAssertion) { |
| return base::nullopt; |
| } |
| |
| // Auto advance to the platform authenticator if it has a matching credential |
| // for the (possibly empty) allow list. |
| if (base::Contains(candidate_transports, |
| device::FidoTransportProtocol::kInternal) && |
| *transport_availability |
| .has_recognized_platform_authenticator_credential) { |
| return device::FidoTransportProtocol::kInternal; |
| } |
| |
| // If the RP supplied the caBLE extension then respect that and always select |
| // caBLE for GetAssertion operations. |
| if (cable_extension_provided && |
| base::Contains( |
| candidate_transports, |
| AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy)) { |
| return AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy; |
| } |
| |
| // The remaining decisions are based on the most recently used successful |
| // transport. |
| if (!last_used_transport || |
| !base::Contains(candidate_transports, *last_used_transport)) { |
| return base::nullopt; |
| } |
| |
| // Auto-advancing to platform authenticator based on credential availability |
| // has been handled above. Hence, at this point it does not have a matching |
| // credential and should not be advanced to, because it would fail |
| // immediately. |
| if (*last_used_transport == device::FidoTransportProtocol::kInternal) { |
| return base::nullopt; |
| } |
| |
| // Auto-advancing to caBLE based on a caBLEv1 request extension has been |
| // handled above. For caBLEv2, only auto-advance if the user has previously |
| // paired a caBLEv2 authenticator. |
| if (*last_used_transport == |
| device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy && |
| !have_paired_phones) { |
| return base::nullopt; |
| } |
| |
| return *last_used_transport; |
| } |
| |
| } // namespace |
| |
| AuthenticatorRequestDialogModel::EphemeralState::EphemeralState() = default; |
| AuthenticatorRequestDialogModel::EphemeralState::~EphemeralState() = default; |
| |
| void AuthenticatorRequestDialogModel::EphemeralState::Reset() { |
| selected_authenticator_id_ = base::nullopt; |
| saved_authenticators_.RemoveAllAuthenticators(); |
| users_.clear(); |
| } |
| |
| AuthenticatorRequestDialogModel::AuthenticatorRequestDialogModel( |
| const std::string& relying_party_id) |
| : relying_party_id_(relying_party_id) {} |
| |
| AuthenticatorRequestDialogModel::~AuthenticatorRequestDialogModel() { |
| for (auto& observer : observers_) |
| observer.OnModelDestroyed(this); |
| } |
| |
| void AuthenticatorRequestDialogModel::SetCurrentStep(Step step) { |
| current_step_ = step; |
| for (auto& observer : observers_) |
| observer.OnStepTransition(); |
| } |
| |
| void AuthenticatorRequestDialogModel::HideDialog() { |
| SetCurrentStep(Step::kNotStarted); |
| } |
| |
| void AuthenticatorRequestDialogModel::StartFlow( |
| TransportAvailabilityInfo transport_availability, |
| base::Optional<device::FidoTransportProtocol> last_used_transport, |
| bool use_location_bar_bubble) { |
| use_location_bar_bubble_ = use_location_bar_bubble; |
| DCHECK_EQ(current_step(), Step::kNotStarted); |
| |
| transport_availability_ = std::move(transport_availability); |
| last_used_transport_ = last_used_transport; |
| |
| if (use_location_bar_bubble_) { |
| // This is a conditional request so show a lightweight, non-modal dialog |
| // instead. |
| StartLocationBarBubbleRequest(); |
| return; |
| } |
| StartGuidedFlowForMostLikelyTransportOrShowTransportSelection(); |
| } |
| |
| void AuthenticatorRequestDialogModel::StartOver() { |
| ephemeral_state_.Reset(); |
| |
| for (auto& observer : observers_) |
| observer.OnStartOver(); |
| |
| if (use_location_bar_bubble_) { |
| StartLocationBarBubbleRequest(); |
| return; |
| } |
| SetCurrentStep(Step::kTransportSelection); |
| } |
| |
| void AuthenticatorRequestDialogModel:: |
| StartGuidedFlowForMostLikelyTransportOrShowTransportSelection() { |
| DCHECK(current_step() == Step::kNotStarted); |
| |
| // If no authenticator other than the one for the native Windows API is |
| // available, or if the sole authenticator is caBLE, but there's no caBLE |
| // extension nor paired phone, then don't show Chrome UI but proceed straight |
| // to the native Windows UI. |
| if (transport_availability_.has_win_native_api_authenticator && |
| !win_native_api_already_tried_) { |
| const auto& transports = transport_availability_.available_transports; |
| if (transports.empty() || |
| (transports.size() == 1 && |
| base::Contains( |
| transports, |
| AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy) && |
| !cable_extension_provided_ && !have_paired_phones_)) { |
| StartWinNativeApi(); |
| return; |
| } |
| } |
| |
| auto most_likely_transport = |
| SelectMostLikelyTransport(transport_availability_, last_used_transport_, |
| cable_extension_provided_, have_paired_phones_); |
| if (most_likely_transport) { |
| StartGuidedFlowForTransport(*most_likely_transport); |
| } else if (!transport_availability_.available_transports.empty()) { |
| SetCurrentStep(Step::kTransportSelection); |
| } else { |
| SetCurrentStep(Step::kErrorNoAvailableTransports); |
| } |
| } |
| |
| void AuthenticatorRequestDialogModel::StartGuidedFlowForTransport( |
| AuthenticatorTransport transport) { |
| DCHECK(current_step() == Step::kTransportSelection || |
| current_step() == Step::kUsbInsertAndActivate || |
| current_step() == Step::kCableActivate || |
| current_step() == Step::kAndroidAccessory || |
| current_step() == Step::kNotStarted); |
| switch (transport) { |
| case AuthenticatorTransport::kUsbHumanInterfaceDevice: |
| SetCurrentStep(Step::kUsbInsertAndActivate); |
| break; |
| case AuthenticatorTransport::kNearFieldCommunication: |
| SetCurrentStep(Step::kTransportSelection); |
| break; |
| case AuthenticatorTransport::kInternal: |
| StartPlatformAuthenticatorFlow(); |
| break; |
| case AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy: |
| EnsureBleAdapterIsPoweredAndContinueWithCable(); |
| break; |
| case AuthenticatorTransport::kAndroidAccessory: |
| SetCurrentStep(Step::kAndroidAccessory); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void AuthenticatorRequestDialogModel:: |
| HideDialogAndDispatchToNativeWindowsApi() { |
| if (!transport_availability()->has_win_native_api_authenticator || |
| transport_availability()->win_native_api_authenticator_id.empty()) { |
| NOTREACHED(); |
| SetCurrentStep(Step::kClosed); |
| return; |
| } |
| |
| // The Windows-native UI already handles retrying so we do not offer a second |
| // level of retry in that case. |
| offer_try_again_in_ui_ = false; |
| |
| // There is no AuthenticatorReference for the Windows authenticator, hence |
| // directly call DispatchRequestAsyncInternal here. |
| DispatchRequestAsyncInternal( |
| transport_availability()->win_native_api_authenticator_id); |
| |
| HideDialog(); |
| } |
| |
| void AuthenticatorRequestDialogModel::StartWinNativeApi() { |
| DCHECK(transport_availability_.has_win_native_api_authenticator); |
| |
| if (resident_key_requirement() != |
| device::ResidentKeyRequirement::kDiscouraged && |
| !transport_availability_.win_native_ui_shows_resident_credential_notice) { |
| SetCurrentStep(Step::kResidentCredentialConfirmation); |
| } else { |
| HideDialogAndDispatchToNativeWindowsApi(); |
| } |
| } |
| |
| void AuthenticatorRequestDialogModel::StartPhonePairing() { |
| DCHECK(cable_qr_string_); |
| SetCurrentStep(Step::kCableV2QRCode); |
| } |
| |
| void AuthenticatorRequestDialogModel:: |
| EnsureBleAdapterIsPoweredAndContinueWithCable() { |
| DCHECK(current_step() == Step::kTransportSelection || |
| current_step() == Step::kUsbInsertAndActivate || |
| current_step() == Step::kCableActivate || |
| current_step() == Step::kAndroidAccessory || |
| current_step() == Step::kNotStarted); |
| Step cable_step; |
| if (cable_extension_provided_) { |
| // caBLEv1. |
| cable_step = Step::kCableActivate; |
| } else { |
| // caBLEv2. Display QR code if the user never paired a phone before, or |
| // show instructions how to use the previously paired phone otherwise. The |
| // user can still decide to pair a new phone on that screen. |
| cable_step = |
| have_paired_phones_ ? Step::kCableV2Activate : Step::kCableV2QRCode; |
| } |
| if (ble_adapter_is_powered()) { |
| SetCurrentStep(cable_step); |
| return; |
| } |
| |
| next_step_once_ble_powered_ = cable_step; |
| SetCurrentStep(transport_availability()->can_power_on_ble_adapter |
| ? Step::kBlePowerOnAutomatic |
| : Step::kBlePowerOnManual); |
| } |
| |
| void AuthenticatorRequestDialogModel::ContinueWithFlowAfterBleAdapterPowered() { |
| DCHECK(current_step() == Step::kBlePowerOnManual || |
| current_step() == Step::kBlePowerOnAutomatic); |
| DCHECK(ble_adapter_is_powered()); |
| DCHECK(next_step_once_ble_powered_.has_value()); |
| |
| SetCurrentStep(*next_step_once_ble_powered_); |
| } |
| |
| void AuthenticatorRequestDialogModel::PowerOnBleAdapter() { |
| DCHECK_EQ(current_step(), Step::kBlePowerOnAutomatic); |
| if (!bluetooth_adapter_power_on_callback_) |
| return; |
| |
| bluetooth_adapter_power_on_callback_.Run(); |
| } |
| |
| void AuthenticatorRequestDialogModel::TryUsbDevice() { |
| DCHECK_EQ(current_step(), Step::kUsbInsertAndActivate); |
| } |
| |
| void AuthenticatorRequestDialogModel::StartPlatformAuthenticatorFlow() { |
| // Never try the platform authenticator if the request is known in advance to |
| // fail. Proceed to a special error screen instead. |
| if (transport_availability_.request_type == |
| device::FidoRequestHandlerBase::RequestType::kGetAssertion) { |
| DCHECK(transport_availability_ |
| .has_recognized_platform_authenticator_credential); |
| if (!*transport_availability_ |
| .has_recognized_platform_authenticator_credential) { |
| SetCurrentStep(Step::kErrorInternalUnrecognized); |
| return; |
| } |
| } |
| |
| if (transport_availability_.request_type == |
| device::FidoRequestHandlerBase::RequestType::kMakeCredential && |
| transport_availability_.is_off_the_record_context) { |
| SetCurrentStep(Step::kPlatformAuthenticatorOffTheRecordInterstitial); |
| return; |
| } |
| |
| HideDialogAndDispatchToPlatformAuthenticator(); |
| } |
| |
| void AuthenticatorRequestDialogModel:: |
| HideDialogAndDispatchToPlatformAuthenticator() { |
| HideDialog(); |
| |
| auto& authenticators = |
| ephemeral_state_.saved_authenticators_.authenticator_list(); |
| auto platform_authenticator_it = |
| std::find_if(authenticators.begin(), authenticators.end(), |
| [](const auto& authenticator) { |
| return authenticator.transport == |
| device::FidoTransportProtocol::kInternal; |
| }); |
| |
| if (platform_authenticator_it == authenticators.end()) { |
| return; |
| } |
| |
| DispatchRequestAsync(&*platform_authenticator_it); |
| } |
| |
| void AuthenticatorRequestDialogModel::ShowCableUsbFallback() { |
| DCHECK_EQ(current_step(), Step::kCableActivate); |
| SetCurrentStep(Step::kAndroidAccessory); |
| } |
| |
| void AuthenticatorRequestDialogModel::ShowCable() { |
| DCHECK_EQ(current_step(), Step::kAndroidAccessory); |
| SetCurrentStep(Step::kCableActivate); |
| } |
| |
| void AuthenticatorRequestDialogModel::Cancel() { |
| if (is_request_complete()) { |
| SetCurrentStep(Step::kClosed); |
| } |
| |
| for (auto& observer : observers_) |
| observer.OnCancelRequest(); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnSheetModelDidChange() { |
| for (auto& observer : observers_) |
| observer.OnSheetModelChanged(); |
| } |
| |
| void AuthenticatorRequestDialogModel::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void AuthenticatorRequestDialogModel::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnRequestComplete() { |
| SetCurrentStep(Step::kClosed); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnRequestTimeout() { |
| if (current_step_ == Step::kLocationBarBubble) { |
| Cancel(); |
| return; |
| } |
| // The request may time out while the UI shows a different error. |
| if (!is_request_complete()) { |
| SetCurrentStep(Step::kTimedOut); |
| } |
| } |
| |
| void AuthenticatorRequestDialogModel::OnActivatedKeyNotRegistered() { |
| DCHECK(!is_request_complete()); |
| SetCurrentStep(Step::kKeyNotRegistered); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnActivatedKeyAlreadyRegistered() { |
| DCHECK(!is_request_complete()); |
| SetCurrentStep(Step::kKeyAlreadyRegistered); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnSoftPINBlock() { |
| SetCurrentStep(Step::kClientPinErrorSoftBlock); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnHardPINBlock() { |
| SetCurrentStep(Step::kClientPinErrorHardBlock); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAuthenticatorRemovedDuringPINEntry() { |
| SetCurrentStep(Step::kClientPinErrorAuthenticatorRemoved); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAuthenticatorMissingResidentKeys() { |
| SetCurrentStep(Step::kMissingCapability); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAuthenticatorMissingUserVerification() { |
| SetCurrentStep(Step::kMissingCapability); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAuthenticatorMissingLargeBlob() { |
| SetCurrentStep(Step::kMissingCapability); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnNoCommonAlgorithms() { |
| SetCurrentStep(Step::kMissingCapability); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAuthenticatorStorageFull() { |
| SetCurrentStep(Step::kStorageFull); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnUserConsentDenied() { |
| if (use_location_bar_bubble_) { |
| // Do not show a page-modal retry error sheet if the user cancelled out of |
| // their platform authenticator while displaying the location bar bubble UI. |
| Cancel(); |
| return; |
| } |
| SetCurrentStep(Step::kErrorInternalUnrecognized); |
| } |
| |
| bool AuthenticatorRequestDialogModel::OnWinUserCancelled() { |
| // If caBLE v2 isn't enabled then this event isn't handled and will cause the |
| // request to fail with a NotAllowedError. |
| if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) { |
| return false; |
| } |
| |
| // Otherwise, if the user cancels out of the Windows-native UI, we show the |
| // transport selection dialog which allows them to pair a phone. |
| win_native_api_already_tried_ = true; |
| |
| StartOver(); |
| return true; |
| } |
| |
| void AuthenticatorRequestDialogModel::OnBluetoothPoweredStateChanged( |
| bool powered) { |
| transport_availability_.is_ble_powered = powered; |
| |
| for (auto& observer : observers_) |
| observer.OnBluetoothPoweredStateChanged(); |
| |
| // For the manual flow, the user has to click the "next" button explicitly. |
| if (current_step() == Step::kBlePowerOnAutomatic) |
| ContinueWithFlowAfterBleAdapterPowered(); |
| } |
| |
| void AuthenticatorRequestDialogModel::SetRequestCallback( |
| RequestCallback request_callback) { |
| request_callback_ = request_callback; |
| } |
| |
| void AuthenticatorRequestDialogModel::SetBluetoothAdapterPowerOnCallback( |
| base::RepeatingClosure bluetooth_adapter_power_on_callback) { |
| bluetooth_adapter_power_on_callback_ = bluetooth_adapter_power_on_callback; |
| } |
| |
| void AuthenticatorRequestDialogModel::OnHavePIN(std::u16string pin) { |
| if (!pin_callback_) { |
| // Protect against the view submitting a PIN more than once without |
| // receiving a matching response first. |CollectPIN| is called again if |
| // the user needs to be prompted for a retry. |
| return; |
| } |
| std::move(pin_callback_).Run(pin); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnRetryUserVerification(int attempts) { |
| uv_attempts_ = attempts; |
| SetCurrentStep(Step::kRetryInternalUserVerification); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnResidentCredentialConfirmed() { |
| DCHECK_EQ(current_step(), Step::kResidentCredentialConfirmation); |
| HideDialogAndDispatchToNativeWindowsApi(); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAttestationPermissionResponse( |
| bool attestation_permission_granted) { |
| if (!attestation_callback_) { |
| return; |
| } |
| std::move(attestation_callback_).Run(attestation_permission_granted); |
| } |
| |
| void AuthenticatorRequestDialogModel::AddAuthenticator( |
| const device::FidoAuthenticator& authenticator) { |
| if (!authenticator.AuthenticatorTransport()) { |
| #if defined(OS_WIN) |
| DCHECK(authenticator.IsWinNativeApiAuthenticator()); |
| #endif // defined(OS_WIN) |
| return; |
| } |
| |
| AuthenticatorReference authenticator_reference( |
| authenticator.GetId(), *authenticator.AuthenticatorTransport()); |
| |
| ephemeral_state_.saved_authenticators_.AddAuthenticator( |
| std::move(authenticator_reference)); |
| } |
| |
| void AuthenticatorRequestDialogModel::RemoveAuthenticator( |
| base::StringPiece authenticator_id) { |
| ephemeral_state_.saved_authenticators_.RemoveAuthenticator(authenticator_id); |
| } |
| |
| void AuthenticatorRequestDialogModel::StartLocationBarBubbleRequest() { |
| ephemeral_state_.users_ = {}; |
| for (const auto& user : |
| transport_availability_.recognized_platform_authenticator_credentials) { |
| ephemeral_state_.users_.push_back(user); |
| } |
| SetCurrentStep(Step::kLocationBarBubble); |
| } |
| |
| void AuthenticatorRequestDialogModel::DispatchRequestAsync( |
| AuthenticatorReference* authenticator) { |
| // Dispatching to the same authenticator twice may result in unexpected |
| // behavior. |
| if (authenticator->dispatched) { |
| return; |
| } |
| |
| DispatchRequestAsyncInternal(authenticator->authenticator_id); |
| authenticator->dispatched = true; |
| } |
| |
| void AuthenticatorRequestDialogModel::DispatchRequestAsyncInternal( |
| const std::string& authenticator_id) { |
| if (!request_callback_) |
| return; |
| |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(request_callback_, authenticator_id)); |
| } |
| |
| // SelectAccount is called to trigger an account selection dialog. |
| void AuthenticatorRequestDialogModel::SelectAccount( |
| std::vector<device::AuthenticatorGetAssertionResponse> responses, |
| base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)> |
| callback) { |
| if (preselected_account_) { |
| for (auto& response : responses) { |
| if (response.user_entity == preselected_account_) { |
| std::move(callback).Run(std::move(response)); |
| return; |
| } |
| } |
| // The user selected an account that was not part of the responses. This |
| // shouldn't really happen, cancel the request. |
| Cancel(); |
| return; |
| } |
| ephemeral_state_.responses_ = std::move(responses); |
| ephemeral_state_.users_ = {}; |
| for (const auto& response : ephemeral_state_.responses_) { |
| ephemeral_state_.users_.push_back(*response.user_entity); |
| } |
| selection_callback_ = std::move(callback); |
| SetCurrentStep(Step::kSelectAccount); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnAccountSelected(size_t index) { |
| if (ephemeral_state_.responses_.empty()) { |
| // An account has been pre-selected from the conditional UI prompt. |
| preselected_account_ = std::move(ephemeral_state_.users_.at(index)); |
| ephemeral_state_.users_.clear(); |
| HideDialogAndDispatchToPlatformAuthenticator(); |
| return; |
| } |
| |
| if (!selection_callback_) { |
| // It's possible that the user could activate the dialog more than once |
| // before the Webauthn request is completed and its torn down. |
| return; |
| } |
| |
| device::AuthenticatorGetAssertionResponse response = |
| std::move(ephemeral_state_.responses_.at(index)); |
| ephemeral_state_.users_.clear(); |
| ephemeral_state_.responses_.clear(); |
| std::move(selection_callback_).Run(std::move(response)); |
| } |
| |
| void AuthenticatorRequestDialogModel::SetSelectedAuthenticatorForTesting( |
| AuthenticatorReference test_authenticator) { |
| ephemeral_state_.selected_authenticator_id_ = |
| test_authenticator.authenticator_id; |
| ephemeral_state_.saved_authenticators_.AddAuthenticator( |
| std::move(test_authenticator)); |
| } |
| |
| bool AuthenticatorRequestDialogModel::cable_should_suggest_usb() const { |
| return base::Contains(transport_availability_.available_transports, |
| AuthenticatorTransport::kAndroidAccessory); |
| } |
| |
| void AuthenticatorRequestDialogModel::CollectPIN( |
| device::pin::PINEntryReason reason, |
| device::pin::PINEntryError error, |
| uint32_t min_pin_length, |
| int attempts, |
| base::OnceCallback<void(std::u16string)> provide_pin_cb) { |
| pin_callback_ = std::move(provide_pin_cb); |
| min_pin_length_ = min_pin_length; |
| pin_error_ = error; |
| switch (reason) { |
| case device::pin::PINEntryReason::kChallenge: |
| pin_attempts_ = attempts; |
| SetCurrentStep(Step::kClientPinEntry); |
| return; |
| case device::pin::PINEntryReason::kChange: |
| SetCurrentStep(Step::kClientPinChange); |
| return; |
| case device::pin::PINEntryReason::kSet: |
| SetCurrentStep(Step::kClientPinSetup); |
| return; |
| } |
| } |
| |
| void AuthenticatorRequestDialogModel::StartInlineBioEnrollment( |
| base::OnceClosure next_callback) { |
| max_bio_samples_ = base::nullopt; |
| bio_samples_remaining_ = base::nullopt; |
| bio_enrollment_callback_ = std::move(next_callback); |
| SetCurrentStep(Step::kInlineBioEnrollment); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnSampleCollected( |
| int bio_samples_remaining) { |
| DCHECK(current_step_ == Step::kInlineBioEnrollment); |
| |
| bio_samples_remaining_ = bio_samples_remaining; |
| if (!max_bio_samples_) { |
| max_bio_samples_ = bio_samples_remaining + 1; |
| } |
| OnSheetModelDidChange(); |
| } |
| |
| void AuthenticatorRequestDialogModel::OnBioEnrollmentDone() { |
| std::move(bio_enrollment_callback_).Run(); |
| } |
| |
| void AuthenticatorRequestDialogModel::RequestAttestationPermission( |
| bool is_enterprise_attestation, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(current_step_ != Step::kClosed); |
| attestation_callback_ = std::move(callback); |
| SetCurrentStep(is_enterprise_attestation |
| ? Step::kEnterpriseAttestationPermissionRequest |
| : Step::kAttestationPermissionRequest); |
| } |
| |
| void AuthenticatorRequestDialogModel::set_cable_transport_info( |
| bool cable_extension_provided, |
| bool have_paired_phones, |
| const base::Optional<std::string>& cable_qr_string) { |
| cable_extension_provided_ = cable_extension_provided; |
| have_paired_phones_ = have_paired_phones; |
| cable_qr_string_ = cable_qr_string; |
| } |
| |
| base::WeakPtr<AuthenticatorRequestDialogModel> |
| AuthenticatorRequestDialogModel::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |