| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/webauthn/android/webauthn_request_delegate_android.h" |
| |
| #include <iterator> |
| #include <memory> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/functional/callback.h" |
| #include "base/ranges/algorithm.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h" |
| #include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate_factory.h" |
| #include "chrome/browser/touch_to_fill/touch_to_fill_controller.h" |
| #include "chrome/browser/touch_to_fill/touch_to_fill_controller_webauthn_delegate.h" |
| #include "chrome/browser/webauthn/webauthn_metrics_util.h" |
| #include "components/password_manager/core/browser/origin_credential_store.h" |
| #include "components/password_manager/core/browser/passkey_credential.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "content/public/browser/web_contents.h" |
| #include "device/fido/discoverable_credential_metadata.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| using password_manager::PasskeyCredential; |
| |
| // static |
| WebAuthnRequestDelegateAndroid* |
| WebAuthnRequestDelegateAndroid::GetRequestDelegate( |
| content::WebContents* web_contents) { |
| static constexpr char kWebAuthnRequestDelegateKey[] = |
| "ConditionalUiDelegateKey"; |
| auto* delegate = static_cast<WebAuthnRequestDelegateAndroid*>( |
| web_contents->GetUserData(kWebAuthnRequestDelegateKey)); |
| if (!delegate) { |
| auto new_user_data = |
| std::make_unique<WebAuthnRequestDelegateAndroid>(web_contents); |
| delegate = new_user_data.get(); |
| web_contents->SetUserData(kWebAuthnRequestDelegateKey, |
| std::move(new_user_data)); |
| } |
| |
| return delegate; |
| } |
| |
| WebAuthnRequestDelegateAndroid::WebAuthnRequestDelegateAndroid( |
| content::WebContents* web_contents) |
| : web_contents_(web_contents) {} |
| |
| WebAuthnRequestDelegateAndroid::~WebAuthnRequestDelegateAndroid() = default; |
| |
| void WebAuthnRequestDelegateAndroid::OnWebAuthnRequestPending( |
| content::RenderFrameHost* frame_host, |
| const std::vector<device::DiscoverableCredentialMetadata>& credentials, |
| bool is_conditional_request, |
| base::RepeatingCallback<void(const std::vector<uint8_t>& id)> |
| get_assertion_callback, |
| base::RepeatingClosure hybrid_callback) { |
| get_assertion_callback_ = std::move(get_assertion_callback); |
| hybrid_callback_ = std::move(hybrid_callback); |
| |
| std::vector<PasskeyCredential> display_credentials; |
| base::ranges::transform( |
| credentials, std::back_inserter(display_credentials), |
| [](const auto& credential) { |
| return PasskeyCredential( |
| PasskeyCredential::Source::kAndroidPhone, |
| PasskeyCredential::RpId(credential.rp_id), |
| PasskeyCredential::CredentialId(credential.cred_id), |
| PasskeyCredential::UserId(credential.user.id), |
| PasskeyCredential::Username(credential.user.name.value_or("")), |
| PasskeyCredential::DisplayName( |
| credential.user.display_name.value_or(""))); |
| }); |
| |
| if (is_conditional_request) { |
| conditional_request_in_progress_ = true; |
| ReportConditionalUiPasskeyCount(credentials.size()); |
| ChromeWebAuthnCredentialsDelegate* credentials_delegate = |
| ChromeWebAuthnCredentialsDelegateFactory::GetFactory( |
| content::WebContents::FromRenderFrameHost(frame_host)) |
| ->GetDelegateForFrame(frame_host); |
| credentials_delegate->SetAndroidHybridAvailable( |
| ChromeWebAuthnCredentialsDelegate::AndroidHybridAvailable( |
| !hybrid_callback_.is_null())); |
| credentials_delegate->OnCredentialsReceived(std::move(display_credentials)); |
| return; |
| } |
| |
| if (!touch_to_fill_controller_) { |
| touch_to_fill_controller_ = std::make_unique<TouchToFillController>(); |
| } |
| touch_to_fill_controller_->Show( |
| std::vector<password_manager::UiCredential>(), display_credentials, |
| std::make_unique<TouchToFillControllerWebAuthnDelegate>( |
| this, !hybrid_callback_.is_null())); |
| } |
| |
| void WebAuthnRequestDelegateAndroid::CleanupWebAuthnRequest( |
| content::RenderFrameHost* frame_host) { |
| if (conditional_request_in_progress_) { |
| // Prevent autofill from offering WebAuthn credentials in the popup. |
| ChromeWebAuthnCredentialsDelegate* credentials_delegate = |
| ChromeWebAuthnCredentialsDelegateFactory::GetFactory( |
| content::WebContents::FromRenderFrameHost(frame_host)) |
| ->GetDelegateForFrame(frame_host); |
| credentials_delegate->NotifyWebAuthnRequestAborted(); |
| credentials_delegate->SetAndroidHybridAvailable( |
| ChromeWebAuthnCredentialsDelegate::AndroidHybridAvailable(false)); |
| } else { |
| touch_to_fill_controller_->Close(); |
| } |
| |
| conditional_request_in_progress_ = false; |
| get_assertion_callback_.Reset(); |
| } |
| |
| void WebAuthnRequestDelegateAndroid::OnWebAuthnAccountSelected( |
| const std::vector<uint8_t>& user_id) { |
| if (get_assertion_callback_) { |
| get_assertion_callback_.Run(user_id); |
| } |
| } |
| |
| void WebAuthnRequestDelegateAndroid::ShowHybridSignIn() { |
| if (hybrid_callback_) { |
| hybrid_callback_.Run(); |
| } |
| } |
| |
| content::WebContents* WebAuthnRequestDelegateAndroid::web_contents() { |
| return web_contents_; |
| } |