chromium/chrome/browser/webauthn/chrome_webauthn_autofill_mac_interactive_uitest.mm

// 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 <memory>
#include <vector>

#include "base/test/bind.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/password_manager/chrome_webauthn_credentials_delegate.h"
#include "chrome/browser/ssl/cert_verifier_browser_test.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h"
#include "chrome/browser/ui/autofill/autofill_popup_controller_impl_test_api.h"
#include "chrome/browser/ui/autofill/autofill_suggestion_controller.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/browser/ui/suggestion_type.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/password_manager/core/browser/password_ui_utils.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "device/fido/mac/credential_store.h"
#include "device/fido/mac/scoped_touch_id_test_environment.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "ui/base/l10n/l10n_util.h"

namespace {

static constexpr char kConditionalUIRequest[] = R"((() => {
navigator.credentials.get({
  mediation: 'conditional',
  publicKey: {
    challenge: new Uint8Array([1,2,3,4]),
    timeout: 10000,
    allowCredentials: [],
  }}).then(c => window.domAutomationController.send('webauthn: OK'),
           e => window.domAutomationController.send('error ' + e));
})())";

class WebAuthnMacAutofillIntegrationTest : public CertVerifierBrowserTest {
 protected:
  void SetUpCommandLine(base::CommandLine* command_line) override {
    CertVerifierBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
  }

  void SetUp() override {
    ASSERT_TRUE(https_server_.InitializeAndListen());
    CertVerifierBrowserTest::SetUp();
  }

  void SetUpOnMainThread() override {
    CertVerifierBrowserTest::SetUpOnMainThread();

    https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
    https_server_.StartAcceptingConnections();
    host_resolver()->AddRule("*", "127.0.0.1");

    // Allowlist all certs for the HTTPS server.
    auto cert = https_server_.GetCertificate();
    net::CertVerifyResult verify_result;
    verify_result.cert_status = 0;
    verify_result.verified_cert = cert;
    mock_cert_verifier()->AddResultForCert(cert.get(), verify_result, net::OK);
    ASSERT_TRUE(ui_test_utils::NavigateToURL(
        browser(),
        https_server_.GetURL("www.example.com",
                             "/webauthn_conditional_mediation.html")));

    // Set up the fake keychain.
    config_ =
        ChromeWebAuthenticationDelegate::TouchIdAuthenticatorConfigForProfile(
            browser()->profile());
    touch_id_test_environment_ =
        std::make_unique<device::fido::mac::ScopedTouchIdTestEnvironment>(
            config_);
    store_ =
        std::make_unique<device::fido::mac::TouchIdCredentialStore>(config_);
    device::PublicKeyCredentialUserEntity user({1, 2, 3, 4}, "flandre",
                                               "Flandre Scarlet");
    store_->CreateCredential(
        "www.example.com", std::move(user),
        device::fido::mac::TouchIdCredentialStore::kDiscoverable);
    touch_id_test_environment_->SimulateTouchIdPromptSuccess();
  }

  net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
  device::fido::mac::AuthenticatorConfig config_;
  std::unique_ptr<device::fido::mac::ScopedTouchIdTestEnvironment>
      touch_id_test_environment_;
  std::unique_ptr<device::fido::mac::TouchIdCredentialStore> store_;
};

// Integration test between the mac keychain platform authenticator and autofill
// UI.
IN_PROC_BROWSER_TEST_F(WebAuthnMacAutofillIntegrationTest, SelectAccount) {
  // Make sure input events cannot close the autofill popup.
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();
  autofill::ChromeAutofillClient* autofill_client =
      autofill::ChromeAutofillClient::FromWebContentsForTesting(web_contents);
  autofill_client->SetKeepPopupOpenForTesting(true);

  // Execute the Conditional UI request.
  content::DOMMessageQueue message_queue(web_contents);
  content::ExecuteScriptAsync(web_contents, kConditionalUIRequest);

  // Interact with the username field until the popup shows up. This has the
  // effect of waiting for the browser to send the renderer the password
  // information, and waiting for the UI to render.
  base::WeakPtr<autofill::AutofillSuggestionController> controller;
  while (!controller) {
    content::SimulateMouseClickOrTapElementWithId(web_contents, "username");
    controller = autofill_client->suggestion_controller_for_testing();
  }

  auto suggestions = controller->GetSuggestions();
  size_t suggestion_index;
  autofill::Suggestion webauthn_entry;
  for (suggestion_index = 0; suggestion_index < suggestions.size();
       ++suggestion_index) {
    if (suggestions[suggestion_index].type ==
        autofill::SuggestionType::kWebauthnCredential) {
      webauthn_entry = suggestions[suggestion_index];
      break;
    }
  }
  ASSERT_LT(suggestion_index, suggestions.size()) << "WebAuthn entry not found";
  EXPECT_EQ(webauthn_entry.main_text.value, u"flandre");
  EXPECT_EQ(webauthn_entry.labels.at(0).at(0).value,
            l10n_util::GetStringUTF16(
                IDS_PASSWORD_MANAGER_PASSKEY_FROM_CHROME_PROFILE));
  EXPECT_EQ(webauthn_entry.icon, autofill::Suggestion::Icon::kGlobe);

  // Click the credential.
  test_api(static_cast<autofill::AutofillPopupControllerImpl&>(*controller))
      .DisableThreshold(true);
  controller->AcceptSuggestion(suggestion_index);
  std::string result;
  ASSERT_TRUE(message_queue.WaitForMessage(&result));
  EXPECT_EQ(result, "\"webauthn: OK\"");
}

}  // namespace