1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
6 
7 #include <iterator>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/metrics/histogram_macros.h"
12 #include "base/stl_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "build/build_config.h"
16 #include "device/fido/features.h"
17 #include "device/fido/fido_authenticator.h"
18 
19 namespace {
20 
21 // Attempts to auto-select the most likely transport that will be used to
22 // service this request, or returns base::nullopt if unsure.
SelectMostLikelyTransport(const device::FidoRequestHandlerBase::TransportAvailabilityInfo & transport_availability,base::Optional<device::FidoTransportProtocol> last_used_transport,bool cable_extension_provided,bool have_paired_phones)23 base::Optional<device::FidoTransportProtocol> SelectMostLikelyTransport(
24     const device::FidoRequestHandlerBase::TransportAvailabilityInfo&
25         transport_availability,
26     base::Optional<device::FidoTransportProtocol> last_used_transport,
27     bool cable_extension_provided,
28     bool have_paired_phones) {
29   const base::flat_set<AuthenticatorTransport>& candidate_transports(
30       transport_availability.available_transports);
31 
32   // If there is only one transport available, select that instead of showing a
33   // transport selection screen with only a single item.
34   if (candidate_transports.size() == 1) {
35     return *candidate_transports.begin();
36   }
37 
38   // The remaining decisions apply to GetAssertion requests only. For
39   // MakeCredential, the user needs to choose from transport selection.
40   if (transport_availability.request_type !=
41       device::FidoRequestHandlerBase::RequestType::kGetAssertion) {
42     return base::nullopt;
43   }
44 
45   // Auto advance to Touch ID if the authenticator has a matching credential
46   // for the (possibly empty) allow list.
47   if (base::Contains(candidate_transports,
48                      device::FidoTransportProtocol::kInternal) &&
49       transport_availability.has_recognized_mac_touch_id_credential) {
50     return device::FidoTransportProtocol::kInternal;
51   }
52 
53   // If the RP supplied the caBLE extension then respect that and always select
54   // caBLE for GetAssertion operations.
55   if (cable_extension_provided &&
56       base::Contains(
57           candidate_transports,
58           AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy)) {
59     return AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy;
60   }
61 
62   // The remaining decisions are based on the most recently used successful
63   // transport.
64   if (!last_used_transport ||
65       !base::Contains(candidate_transports, *last_used_transport)) {
66     return base::nullopt;
67   }
68 
69   // Auto-advancing to Touch ID based on credential availability has been
70   // handled above. Hence, at this point it does not have a matching credential
71   // and should not be advanced to, because it would fail immediately.
72   if (*last_used_transport == device::FidoTransportProtocol::kInternal) {
73     return base::nullopt;
74   }
75 
76   // Auto-advancing to caBLE based on a caBLEv1 request extension has been
77   // handled above. For caBLEv2, only auto-advance if the user has previously
78   // paired a caBLEv2 authenticator.
79   if (*last_used_transport ==
80           device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy &&
81       !have_paired_phones) {
82     return base::nullopt;
83   }
84 
85   return *last_used_transport;
86 }
87 
88 }  // namespace
89 
90 AuthenticatorRequestDialogModel::EphemeralState::EphemeralState() = default;
91 AuthenticatorRequestDialogModel::EphemeralState::~EphemeralState() = default;
92 
Reset()93 void AuthenticatorRequestDialogModel::EphemeralState::Reset() {
94   selected_authenticator_id_ = base::nullopt;
95   saved_authenticators_.RemoveAllAuthenticators();
96   has_attempted_pin_entry_ = false;
97   responses_.clear();
98 }
99 
AuthenticatorRequestDialogModel(const std::string & relying_party_id)100 AuthenticatorRequestDialogModel::AuthenticatorRequestDialogModel(
101     const std::string& relying_party_id)
102     : relying_party_id_(relying_party_id) {}
103 
~AuthenticatorRequestDialogModel()104 AuthenticatorRequestDialogModel::~AuthenticatorRequestDialogModel() {
105   for (auto& observer : observers_)
106     observer.OnModelDestroyed();
107 }
108 
SetCurrentStep(Step step)109 void AuthenticatorRequestDialogModel::SetCurrentStep(Step step) {
110   current_step_ = step;
111   for (auto& observer : observers_)
112     observer.OnStepTransition();
113 }
114 
HideDialog()115 void AuthenticatorRequestDialogModel::HideDialog() {
116   SetCurrentStep(Step::kNotStarted);
117 }
118 
StartFlow(TransportAvailabilityInfo transport_availability,base::Optional<device::FidoTransportProtocol> last_used_transport)119 void AuthenticatorRequestDialogModel::StartFlow(
120     TransportAvailabilityInfo transport_availability,
121     base::Optional<device::FidoTransportProtocol> last_used_transport) {
122   DCHECK_EQ(current_step(), Step::kNotStarted);
123 
124   transport_availability_ = std::move(transport_availability);
125   last_used_transport_ = last_used_transport;
126 
127   StartGuidedFlowForMostLikelyTransportOrShowTransportSelection();
128 }
129 
StartOver()130 void AuthenticatorRequestDialogModel::StartOver() {
131   ephemeral_state_.Reset();
132 
133   for (auto& observer : observers_)
134     observer.OnStartOver();
135   SetCurrentStep(Step::kTransportSelection);
136 }
137 
138 void AuthenticatorRequestDialogModel::
StartGuidedFlowForMostLikelyTransportOrShowTransportSelection()139     StartGuidedFlowForMostLikelyTransportOrShowTransportSelection() {
140   DCHECK(current_step() == Step::kNotStarted);
141 
142   // If no authenticator other than the one for the native Windows API is
143   // available, or if the sole authenticator is caBLE, but there's no caBLE
144   // extension nor paired phone, then don't show Chrome UI but proceed straight
145   // to the native Windows UI.
146   if (transport_availability_.has_win_native_api_authenticator &&
147       !win_native_api_already_tried_) {
148     const auto& transports = transport_availability_.available_transports;
149     if (transports.empty() ||
150         (transports.size() == 1 &&
151          base::Contains(
152              transports,
153              AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy) &&
154          !cable_extension_provided_ && !have_paired_phones_)) {
155       StartWinNativeApi();
156       return;
157     }
158   }
159 
160   auto most_likely_transport =
161       SelectMostLikelyTransport(transport_availability_, last_used_transport_,
162                                 cable_extension_provided_, have_paired_phones_);
163   if (most_likely_transport) {
164     StartGuidedFlowForTransport(*most_likely_transport);
165   } else if (!transport_availability_.available_transports.empty()) {
166     SetCurrentStep(Step::kTransportSelection);
167   } else {
168     SetCurrentStep(Step::kErrorNoAvailableTransports);
169   }
170 }
171 
StartGuidedFlowForTransport(AuthenticatorTransport transport)172 void AuthenticatorRequestDialogModel::StartGuidedFlowForTransport(
173     AuthenticatorTransport transport) {
174   DCHECK(current_step() == Step::kTransportSelection ||
175          current_step() == Step::kUsbInsertAndActivate ||
176          current_step() == Step::kCableActivate ||
177          current_step() == Step::kNotStarted);
178   switch (transport) {
179     case AuthenticatorTransport::kUsbHumanInterfaceDevice:
180       SetCurrentStep(Step::kUsbInsertAndActivate);
181       break;
182     case AuthenticatorTransport::kNearFieldCommunication:
183       SetCurrentStep(Step::kTransportSelection);
184       break;
185     case AuthenticatorTransport::kInternal:
186       StartTouchIdFlow();
187       break;
188     case AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy:
189       EnsureBleAdapterIsPoweredAndContinueWithCable();
190       break;
191     default:
192       break;
193   }
194 }
195 
196 void AuthenticatorRequestDialogModel::
HideDialogAndDispatchToNativeWindowsApi()197     HideDialogAndDispatchToNativeWindowsApi() {
198   if (!transport_availability()->has_win_native_api_authenticator ||
199       transport_availability()->win_native_api_authenticator_id.empty()) {
200     NOTREACHED();
201     SetCurrentStep(Step::kClosed);
202     return;
203   }
204 
205   // The Windows-native UI already handles retrying so we do not offer a second
206   // level of retry in that case.
207   offer_try_again_in_ui_ = false;
208 
209   // There is no AuthenticatorReference for the Windows authenticator, hence
210   // directly call DispatchRequestAsyncInternal here.
211   DispatchRequestAsyncInternal(
212       transport_availability()->win_native_api_authenticator_id);
213 
214   HideDialog();
215 }
216 
StartWinNativeApi()217 void AuthenticatorRequestDialogModel::StartWinNativeApi() {
218   DCHECK(transport_availability_.has_win_native_api_authenticator);
219 
220   if (might_create_resident_credential_ &&
221       !transport_availability_.win_native_ui_shows_resident_credential_notice) {
222     SetCurrentStep(Step::kResidentCredentialConfirmation);
223   } else {
224     HideDialogAndDispatchToNativeWindowsApi();
225   }
226 }
227 
StartPhonePairing()228 void AuthenticatorRequestDialogModel::StartPhonePairing() {
229   DCHECK(cable_qr_string_);
230   SetCurrentStep(Step::kCableV2QRCode);
231 }
232 
233 void AuthenticatorRequestDialogModel::
EnsureBleAdapterIsPoweredAndContinueWithCable()234     EnsureBleAdapterIsPoweredAndContinueWithCable() {
235   DCHECK(current_step() == Step::kTransportSelection ||
236          current_step() == Step::kUsbInsertAndActivate ||
237          current_step() == Step::kCableActivate ||
238          current_step() == Step::kNotStarted);
239   Step cable_step;
240   if (cable_extension_provided_) {
241     // caBLEv1.
242     cable_step = Step::kCableActivate;
243   } else {
244     // caBLEv2. Display QR code if the user never paired a phone before, or
245     // show instructions how to use the previously paired phone otherwise. The
246     // user can still decide to pair a new phone on that screen.
247     cable_step =
248         have_paired_phones_ ? Step::kCableV2Activate : Step::kCableV2QRCode;
249   }
250   if (ble_adapter_is_powered()) {
251     SetCurrentStep(cable_step);
252     return;
253   }
254 
255   next_step_once_ble_powered_ = cable_step;
256   SetCurrentStep(transport_availability()->can_power_on_ble_adapter
257                      ? Step::kBlePowerOnAutomatic
258                      : Step::kBlePowerOnManual);
259 }
260 
ContinueWithFlowAfterBleAdapterPowered()261 void AuthenticatorRequestDialogModel::ContinueWithFlowAfterBleAdapterPowered() {
262   DCHECK(current_step() == Step::kBlePowerOnManual ||
263          current_step() == Step::kBlePowerOnAutomatic);
264   DCHECK(ble_adapter_is_powered());
265   DCHECK(next_step_once_ble_powered_.has_value());
266 
267   SetCurrentStep(*next_step_once_ble_powered_);
268 }
269 
PowerOnBleAdapter()270 void AuthenticatorRequestDialogModel::PowerOnBleAdapter() {
271   DCHECK_EQ(current_step(), Step::kBlePowerOnAutomatic);
272   if (!bluetooth_adapter_power_on_callback_)
273     return;
274 
275   bluetooth_adapter_power_on_callback_.Run();
276 }
277 
TryUsbDevice()278 void AuthenticatorRequestDialogModel::TryUsbDevice() {
279   DCHECK_EQ(current_step(), Step::kUsbInsertAndActivate);
280 }
281 
StartTouchIdFlow()282 void AuthenticatorRequestDialogModel::StartTouchIdFlow() {
283   // Never try Touch ID if the request is known in advance to fail. Proceed to
284   // a special error screen instead.
285   if (transport_availability_.request_type ==
286           device::FidoRequestHandlerBase::RequestType::kGetAssertion &&
287       !transport_availability_.has_recognized_mac_touch_id_credential) {
288     SetCurrentStep(Step::kErrorInternalUnrecognized);
289     return;
290   }
291 
292   if (transport_availability_.request_type ==
293           device::FidoRequestHandlerBase::RequestType::kMakeCredential &&
294       incognito_mode_) {
295     SetCurrentStep(Step::kTouchIdIncognitoSpeedBump);
296     return;
297   }
298 
299   HideDialogAndTryTouchId();
300 }
301 
HideDialogAndTryTouchId()302 void AuthenticatorRequestDialogModel::HideDialogAndTryTouchId() {
303   HideDialog();
304 
305   auto& authenticators =
306       ephemeral_state_.saved_authenticators_.authenticator_list();
307   auto touch_id_authenticator_it =
308       std::find_if(authenticators.begin(), authenticators.end(),
309                    [](const auto& authenticator) {
310                      return authenticator.transport ==
311                             device::FidoTransportProtocol::kInternal;
312                    });
313 
314   if (touch_id_authenticator_it == authenticators.end()) {
315     return;
316   }
317 
318   DispatchRequestAsync(&*touch_id_authenticator_it);
319 }
320 
Cancel()321 void AuthenticatorRequestDialogModel::Cancel() {
322   if (is_request_complete()) {
323     SetCurrentStep(Step::kClosed);
324   }
325 
326   for (auto& observer : observers_)
327     observer.OnCancelRequest();
328 }
329 
OnSheetModelDidChange()330 void AuthenticatorRequestDialogModel::OnSheetModelDidChange() {
331   for (auto& observer : observers_)
332     observer.OnSheetModelChanged();
333 }
334 
AddObserver(Observer * observer)335 void AuthenticatorRequestDialogModel::AddObserver(Observer* observer) {
336   observers_.AddObserver(observer);
337 }
338 
RemoveObserver(Observer * observer)339 void AuthenticatorRequestDialogModel::RemoveObserver(Observer* observer) {
340   observers_.RemoveObserver(observer);
341 }
342 
OnRequestComplete()343 void AuthenticatorRequestDialogModel::OnRequestComplete() {
344   SetCurrentStep(Step::kClosed);
345 }
346 
OnRequestTimeout()347 void AuthenticatorRequestDialogModel::OnRequestTimeout() {
348   // The request may time out while the UI shows a different error.
349   if (!is_request_complete())
350     SetCurrentStep(Step::kTimedOut);
351 }
352 
OnActivatedKeyNotRegistered()353 void AuthenticatorRequestDialogModel::OnActivatedKeyNotRegistered() {
354   DCHECK(!is_request_complete());
355   SetCurrentStep(Step::kKeyNotRegistered);
356 }
357 
OnActivatedKeyAlreadyRegistered()358 void AuthenticatorRequestDialogModel::OnActivatedKeyAlreadyRegistered() {
359   DCHECK(!is_request_complete());
360   SetCurrentStep(Step::kKeyAlreadyRegistered);
361 }
362 
OnSoftPINBlock()363 void AuthenticatorRequestDialogModel::OnSoftPINBlock() {
364   SetCurrentStep(Step::kClientPinErrorSoftBlock);
365 }
366 
OnHardPINBlock()367 void AuthenticatorRequestDialogModel::OnHardPINBlock() {
368   SetCurrentStep(Step::kClientPinErrorHardBlock);
369 }
370 
OnAuthenticatorRemovedDuringPINEntry()371 void AuthenticatorRequestDialogModel::OnAuthenticatorRemovedDuringPINEntry() {
372   SetCurrentStep(Step::kClientPinErrorAuthenticatorRemoved);
373 }
374 
OnAuthenticatorMissingResidentKeys()375 void AuthenticatorRequestDialogModel::OnAuthenticatorMissingResidentKeys() {
376   SetCurrentStep(Step::kMissingCapability);
377 }
378 
OnAuthenticatorMissingUserVerification()379 void AuthenticatorRequestDialogModel::OnAuthenticatorMissingUserVerification() {
380   SetCurrentStep(Step::kMissingCapability);
381 }
382 
OnAuthenticatorMissingLargeBlob()383 void AuthenticatorRequestDialogModel::OnAuthenticatorMissingLargeBlob() {
384   SetCurrentStep(Step::kMissingCapability);
385 }
386 
OnNoCommonAlgorithms()387 void AuthenticatorRequestDialogModel::OnNoCommonAlgorithms() {
388   SetCurrentStep(Step::kMissingCapability);
389 }
390 
OnAuthenticatorStorageFull()391 void AuthenticatorRequestDialogModel::OnAuthenticatorStorageFull() {
392   SetCurrentStep(Step::kStorageFull);
393 }
394 
OnUserConsentDenied()395 void AuthenticatorRequestDialogModel::OnUserConsentDenied() {
396   SetCurrentStep(Step::kErrorInternalUnrecognized);
397 }
398 
OnWinUserCancelled()399 bool AuthenticatorRequestDialogModel::OnWinUserCancelled() {
400   // If caBLE v2 isn't enabled then this event isn't handled and will cause the
401   // request to fail with a NotAllowedError.
402   if (!base::FeatureList::IsEnabled(device::kWebAuthPhoneSupport)) {
403     return false;
404   }
405 
406   // Otherwise, if the user cancels out of the Windows-native UI, we show the
407   // transport selection dialog which allows them to pair a phone.
408   win_native_api_already_tried_ = true;
409 
410   StartOver();
411   return true;
412 }
413 
OnBluetoothPoweredStateChanged(bool powered)414 void AuthenticatorRequestDialogModel::OnBluetoothPoweredStateChanged(
415     bool powered) {
416   transport_availability_.is_ble_powered = powered;
417 
418   for (auto& observer : observers_)
419     observer.OnBluetoothPoweredStateChanged();
420 
421   // For the manual flow, the user has to click the "next" button explicitly.
422   if (current_step() == Step::kBlePowerOnAutomatic)
423     ContinueWithFlowAfterBleAdapterPowered();
424 }
425 
SetRequestCallback(RequestCallback request_callback)426 void AuthenticatorRequestDialogModel::SetRequestCallback(
427     RequestCallback request_callback) {
428   request_callback_ = request_callback;
429 }
430 
SetBluetoothAdapterPowerOnCallback(base::RepeatingClosure bluetooth_adapter_power_on_callback)431 void AuthenticatorRequestDialogModel::SetBluetoothAdapterPowerOnCallback(
432     base::RepeatingClosure bluetooth_adapter_power_on_callback) {
433   bluetooth_adapter_power_on_callback_ = bluetooth_adapter_power_on_callback;
434 }
435 
SetPINCallback(base::OnceCallback<void (std::string)> pin_callback)436 void AuthenticatorRequestDialogModel::SetPINCallback(
437     base::OnceCallback<void(std::string)> pin_callback) {
438   pin_callback_ = std::move(pin_callback);
439 }
440 
OnHavePIN(const std::string & pin)441 void AuthenticatorRequestDialogModel::OnHavePIN(const std::string& pin) {
442   if (!pin_callback_) {
443     // Protect against the view submitting a PIN more than once without
444     // receiving a matching response first. |SetPINCallback| is called again if
445     // the user needs to be prompted for a retry.
446     return;
447   }
448   std::move(pin_callback_).Run(pin);
449   ephemeral_state_.has_attempted_pin_entry_ = true;
450 }
451 
OnRetryUserVerification(int attempts)452 void AuthenticatorRequestDialogModel::OnRetryUserVerification(int attempts) {
453   uv_attempts_ = attempts;
454   SetCurrentStep(Step::kRetryInternalUserVerification);
455 }
456 
OnResidentCredentialConfirmed()457 void AuthenticatorRequestDialogModel::OnResidentCredentialConfirmed() {
458   DCHECK_EQ(current_step(), Step::kResidentCredentialConfirmation);
459   HideDialogAndDispatchToNativeWindowsApi();
460 }
461 
OnAttestationPermissionResponse(bool attestation_permission_granted)462 void AuthenticatorRequestDialogModel::OnAttestationPermissionResponse(
463     bool attestation_permission_granted) {
464   if (!attestation_callback_) {
465     return;
466   }
467   std::move(attestation_callback_).Run(attestation_permission_granted);
468 }
469 
AddAuthenticator(const device::FidoAuthenticator & authenticator)470 void AuthenticatorRequestDialogModel::AddAuthenticator(
471     const device::FidoAuthenticator& authenticator) {
472   if (!authenticator.AuthenticatorTransport()) {
473 #if defined(OS_WIN)
474     DCHECK(authenticator.IsWinNativeApiAuthenticator());
475 #endif  // defined(OS_WIN)
476     return;
477   }
478 
479   AuthenticatorReference authenticator_reference(
480       authenticator.GetId(), authenticator.GetDisplayName(),
481       *authenticator.AuthenticatorTransport());
482 
483   ephemeral_state_.saved_authenticators_.AddAuthenticator(
484       std::move(authenticator_reference));
485 }
486 
RemoveAuthenticator(base::StringPiece authenticator_id)487 void AuthenticatorRequestDialogModel::RemoveAuthenticator(
488     base::StringPiece authenticator_id) {
489   ephemeral_state_.saved_authenticators_.RemoveAuthenticator(authenticator_id);
490 }
491 
DispatchRequestAsync(AuthenticatorReference * authenticator)492 void AuthenticatorRequestDialogModel::DispatchRequestAsync(
493     AuthenticatorReference* authenticator) {
494   // Dispatching to the same authenticator twice may result in unexpected
495   // behavior.
496   if (authenticator->dispatched) {
497     return;
498   }
499 
500   DispatchRequestAsyncInternal(authenticator->authenticator_id);
501   authenticator->dispatched = true;
502 }
503 
DispatchRequestAsyncInternal(const std::string & authenticator_id)504 void AuthenticatorRequestDialogModel::DispatchRequestAsyncInternal(
505     const std::string& authenticator_id) {
506   if (!request_callback_)
507     return;
508 
509   base::SequencedTaskRunnerHandle::Get()->PostTask(
510       FROM_HERE, base::BindOnce(request_callback_, authenticator_id));
511 }
512 
513 // SelectAccount is called to trigger an account selection dialog.
SelectAccount(std::vector<device::AuthenticatorGetAssertionResponse> responses,base::OnceCallback<void (device::AuthenticatorGetAssertionResponse)> callback)514 void AuthenticatorRequestDialogModel::SelectAccount(
515     std::vector<device::AuthenticatorGetAssertionResponse> responses,
516     base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
517         callback) {
518   ephemeral_state_.responses_ = std::move(responses);
519   selection_callback_ = std::move(callback);
520   SetCurrentStep(Step::kSelectAccount);
521 }
522 
OnAccountSelected(size_t index)523 void AuthenticatorRequestDialogModel::OnAccountSelected(size_t index) {
524   if (!selection_callback_) {
525     // It's possible that the user could activate the dialog more than once
526     // before the Webauthn request is completed and its torn down.
527     return;
528   }
529 
530   auto selected = std::move(ephemeral_state_.responses_[index]);
531   ephemeral_state_.responses_.clear();
532   std::move(selection_callback_).Run(std::move(selected));
533 }
534 
SetSelectedAuthenticatorForTesting(AuthenticatorReference test_authenticator)535 void AuthenticatorRequestDialogModel::SetSelectedAuthenticatorForTesting(
536     AuthenticatorReference test_authenticator) {
537   ephemeral_state_.selected_authenticator_id_ =
538       test_authenticator.authenticator_id;
539   ephemeral_state_.saved_authenticators_.AddAuthenticator(
540       std::move(test_authenticator));
541 }
542 
CollectPIN(base::Optional<int> attempts,base::OnceCallback<void (std::string)> provide_pin_cb)543 void AuthenticatorRequestDialogModel::CollectPIN(
544     base::Optional<int> attempts,
545     base::OnceCallback<void(std::string)> provide_pin_cb) {
546   pin_callback_ = std::move(provide_pin_cb);
547   if (attempts) {
548     pin_attempts_ = attempts;
549     SetCurrentStep(Step::kClientPinEntry);
550   } else {
551     SetCurrentStep(Step::kClientPinSetup);
552   }
553 }
554 
StartInlineBioEnrollment(base::OnceClosure next_callback)555 void AuthenticatorRequestDialogModel::StartInlineBioEnrollment(
556     base::OnceClosure next_callback) {
557   max_bio_samples_ = base::nullopt;
558   bio_samples_remaining_ = base::nullopt;
559   bio_enrollment_callback_ = std::move(next_callback);
560   SetCurrentStep(Step::kInlineBioEnrollment);
561 }
562 
OnSampleCollected(int bio_samples_remaining)563 void AuthenticatorRequestDialogModel::OnSampleCollected(
564     int bio_samples_remaining) {
565   DCHECK(current_step_ == Step::kInlineBioEnrollment);
566 
567   bio_samples_remaining_ = bio_samples_remaining;
568   if (!max_bio_samples_) {
569     max_bio_samples_ = bio_samples_remaining + 1;
570   }
571   OnSheetModelDidChange();
572 }
573 
OnBioEnrollmentDone()574 void AuthenticatorRequestDialogModel::OnBioEnrollmentDone() {
575   std::move(bio_enrollment_callback_).Run();
576 }
577 
RequestAttestationPermission(base::OnceCallback<void (bool)> callback)578 void AuthenticatorRequestDialogModel::RequestAttestationPermission(
579     base::OnceCallback<void(bool)> callback) {
580   DCHECK(current_step_ != Step::kClosed);
581   attestation_callback_ = std::move(callback);
582   SetCurrentStep(Step::kAttestationPermissionRequest);
583 }
584 
set_cable_transport_info(bool cable_extension_provided,bool have_paired_phones,const base::Optional<std::string> & cable_qr_string)585 void AuthenticatorRequestDialogModel::set_cable_transport_info(
586     bool cable_extension_provided,
587     bool have_paired_phones,
588     const base::Optional<std::string>& cable_qr_string) {
589   cable_extension_provided_ = cable_extension_provided;
590   have_paired_phones_ = have_paired_phones;
591   cable_qr_string_ = cable_qr_string;
592 }
593 
594 base::WeakPtr<AuthenticatorRequestDialogModel>
GetWeakPtr()595 AuthenticatorRequestDialogModel::GetWeakPtr() {
596   return weak_factory_.GetWeakPtr();
597 }
598