// 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