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