chromium/chrome/browser/webauthn/android/webauthn_request_delegate_android.cc

// 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/functional/callback.h"
#include "base/memory/weak_ptr.h"
#include "base/ranges/algorithm.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/profiles/profile.h"
#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller.h"
#include "chrome/browser/touch_to_fill/password_manager/touch_to_fill_controller_webauthn_delegate.h"
#include "chrome/browser/webauthn/webauthn_metrics_util.h"
#include "components/password_manager/content/browser/content_password_manager_driver.h"
#include "components/password_manager/content/browser/keyboard_replacing_surface_visibility_controller_impl.h"
#include "components/password_manager/core/browser/origin_credential_store.h"
#include "components/password_manager/core/browser/passkey_credential.h"
#include "components/webauthn/android/webauthn_cred_man_delegate.h"
#include "components/webauthn/android/webauthn_cred_man_delegate_factory.h"
#include "content/public/browser/web_contents.h"
#include "device/fido/discoverable_credential_metadata.h"

using password_manager::ContentPasswordManagerDriver;
using password_manager::PasskeyCredential;
using webauthn::WebAuthnCredManDelegate;
using webauthn::WebAuthnCredManDelegateFactory;

// 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);
    if (!credentials_delegate) {
      return;
    }
    credentials_delegate->SetAndroidHybridAvailable(
        ChromeWebAuthnCredentialsDelegate::AndroidHybridAvailable(
            !hybrid_callback_.is_null()));
    credentials_delegate->OnCredentialsReceived(
        std::move(display_credentials),
        /*offer_passkey_from_another_device=*/true);
    return;
  }

  if (!visibility_controller_) {
    visibility_controller_ = std::make_unique<
        password_manager::KeyboardReplacingSurfaceVisibilityControllerImpl>();
  }
  if (!touch_to_fill_controller_) {
    touch_to_fill_controller_ = std::make_unique<TouchToFillController>(
        Profile::FromBrowserContext(frame_host->GetBrowserContext()),
        visibility_controller_->AsWeakPtr());
  }
  touch_to_fill_controller_->Show(
      std::vector<password_manager::UiCredential>(), display_credentials,
      std::make_unique<TouchToFillControllerWebAuthnDelegate>(
          this, !hybrid_callback_.is_null()),
      WebAuthnCredManDelegateFactory::GetFactory(web_contents())
          ->GetRequestDelegate(frame_host),
      ContentPasswordManagerDriver::GetForRenderFrameHost(frame_host)
          ->AsWeakPtrImpl());
}

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);

    if (credentials_delegate) {
      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_;
}