blob: c2846917ae8360523013a7455d09fa910c913f9d [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/passwords/manage_passwords_ui_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_controller.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/authenticator_transport.h"
#include "chrome/browser/webauthn/enclave_manager.h"
#include "chrome/browser/webauthn/enclave_manager_factory.h"
#include "chrome/browser/webauthn/webauthn_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/identity_manager/identity_test_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/test/browser_test.h"
#include "device/fido/authenticator_data.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/fido_constants.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "device/fido/pin.h"
#include "device/fido/public_key_credential_descriptor.h"
#include "device/fido/public_key_credential_user_entity.h"
#include "google_apis/gaia/gaia_switches.h"
#include "net/dns/mock_host_resolver.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
constexpr char kPhoneName[] = "Elisa's Pixel 6 Pro";
using BleStatus = device::FidoRequestHandlerBase::BleStatus;
void UpdateModelBeforeStartFlow(
AuthenticatorRequestDialogModel* model,
device::FidoRequestHandlerBase::TransportAvailabilityInfo tai) {
model->request_type = tai.request_type;
model->resident_key_requirement = tai.resident_key_requirement;
model->attestation_conveyance_preference =
tai.attestation_conveyance_preference;
model->ble_adapter_is_powered =
tai.ble_status == device::FidoRequestHandlerBase::BleStatus::kOn;
model->show_security_key_on_qr_sheet =
base::Contains(tai.available_transports,
device::FidoTransportProtocol::kUsbHumanInterfaceDevice);
model->is_off_the_record = tai.is_off_the_record_context;
model->platform_has_biometrics = tai.platform_has_biometrics;
}
} // namespace
// Run with:
//
// --gtest_filter=BrowserUiTest.Invoke --test-launcher-interactive \
// --ui=AuthenticatorDialogTest.InvokeUi_${test_name}
//
// where test_name is the second arg to IN_PROC_BROWSER_TEST_F().
class AuthenticatorDialogTest : public DialogBrowserTest {
public:
AuthenticatorDialogTest() = default;
AuthenticatorDialogTest(const AuthenticatorDialogTest&) = delete;
AuthenticatorDialogTest& operator=(const AuthenticatorDialogTest&) = delete;
void SetUpOnMainThread() override {
DialogBrowserTest::SetUpOnMainThread();
signin::MakePrimaryAccountAvailable(
IdentityManagerFactory::GetForProfile(browser()->profile()),
"[email protected]", signin::ConsentLevel::kSync);
}
void TearDownOnMainThread() override {
if (controller_) {
// Close the dialog before the entire browser is torn down.
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClosed);
}
DialogBrowserTest::TearDownOnMainThread();
}
// DialogBrowserTest:
void ShowUi(const std::string& name) override {
// Web modal dialogs' bounds may exceed the display's work area.
// https://crbug.com/893292.
set_should_verify_dialog_bounds(false);
content::RenderFrameHost* rfh = browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame();
model_ = base::MakeRefCounted<AuthenticatorRequestDialogModel>(rfh);
model_->relying_party_id = "example.com";
// Since this code tests UI, it is possible to do everything by configuring
// just the Model. However, it's easier to do that via a Controller.
controller_ = std::make_unique<AuthenticatorRequestDialogController>(
model_.get(), rfh);
device::FidoRequestHandlerBase::TransportAvailabilityInfo&
transport_availability =
controller_->transport_availability_for_testing();
transport_availability.available_transports = {
AuthenticatorTransport::kUsbHumanInterfaceDevice,
AuthenticatorTransport::kInternal,
AuthenticatorTransport::kHybrid,
};
std::vector<std::unique_ptr<device::cablev2::Pairing>> phones;
auto phone = std::make_unique<device::cablev2::Pairing>();
phone->from_sync_deviceinfo = false;
phone->name = kPhoneName;
phones.emplace_back(std::move(phone));
transport_availability.has_platform_authenticator_credential = device::
FidoRequestHandlerBase::RecognizedCredential::kNoRecognizedCredential;
transport_availability.request_type =
device::FidoRequestType::kGetAssertion;
// The dialog should immediately close as soon as it is displayed.
if (name == "mechanisms" || name == "mechanisms_disabled") {
// A phone is configured so that the "Manage devices" button is shown.
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt, std::move(phones),
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kMechanismSelection);
} else if (name == "mechanisms_create" ||
name == "mechanisms_create_disabled") {
transport_availability.make_credential_attachment =
device::AuthenticatorAttachment::kAny;
transport_availability.request_type =
device::FidoRequestType::kMakeCredential;
transport_availability.attestation_conveyance_preference =
device::AttestationConveyancePreference::kNone;
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kMechanismSelection);
} else if (name == "activate_usb") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kUsbInsertAndActivate);
} else if (name == "timeout") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kTimedOut);
} else if (name == "no_available_transports") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kErrorNoAvailableTransports);
} else if (name == "key_not_registered") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kKeyNotRegistered);
} else if (name == "key_already_registered") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kKeyAlreadyRegistered);
} else if (name == "windows_hello_not_enabled") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kErrorWindowsHelloNotEnabled);
} else if (name == "internal_unrecognized_error") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kErrorInternalUnrecognized);
} else if (name == "ble_power_on_manual") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kBlePowerOnManual);
} else if (name == "touchid_incognito") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kOffTheRecordInterstitial);
} else if (name == "cable_activate") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/false, std::move(phones),
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->ContactPhoneForTesting(kPhoneName);
} else if (name == "cable_v2_activate") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt, std::move(phones),
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->ContactPhoneForTesting(kPhoneName);
} else if (name == "cable_v2_pair") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableV2QRCode);
} else if (name == "cable_v2_connecting") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableV2Connecting);
} else if (name == "cable_v2_connected") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableV2Connected);
} else if (name == "cable_v2_error") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableV2Error);
} else if (name == "set_pin") {
controller_->CollectPIN(device::pin::PINEntryReason::kSet,
device::pin::PINEntryError::kNoError, 6, 0,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin") {
controller_->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kNoError, 6, 8,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_two_tries_remaining") {
controller_->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kWrongPIN, 6, 2,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_one_try_remaining") {
controller_->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kWrongPIN, 6, 1,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_fallback") {
controller_->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kInternalUvLocked, 6,
8, base::BindOnce([](std::u16string pin) {}));
} else if (name == "inline_bio_enrollment") {
controller_->StartInlineBioEnrollment(base::DoNothing());
timer_.Start(
FROM_HERE, base::Seconds(2),
base::BindLambdaForTesting(
[&, weak_controller = controller_->GetWeakPtr()] {
if (!weak_controller || weak_controller->model()->step() !=
AuthenticatorRequestDialogModel::
Step::kInlineBioEnrollment) {
return;
}
weak_controller->OnSampleCollected(--bio_samples_remaining_);
if (bio_samples_remaining_ <= 0) {
timer_.Stop();
}
}));
} else if (name == "retry_uv") {
controller_->OnRetryUserVerification(5);
} else if (name == "retry_uv_two_tries_remaining") {
controller_->OnRetryUserVerification(2);
} else if (name == "retry_uv_one_try_remaining") {
controller_->OnRetryUserVerification(1);
} else if (name == "force_pin_change") {
controller_->CollectPIN(device::pin::PINEntryReason::kChange,
device::pin::PINEntryError::kNoError, 6, 0,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "force_pin_change_same_as_current") {
controller_->CollectPIN(device::pin::PINEntryReason::kChange,
device::pin::PINEntryError::kSameAsCurrentPIN, 6,
0, base::BindOnce([](std::u16string pin) {}));
} else if (name == "second_tap") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinTapAgain);
} else if (name == "soft_block") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinErrorSoftBlock);
} else if (name == "hard_block") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinErrorHardBlock);
} else if (name == "authenticator_removed") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::
kClientPinErrorAuthenticatorRemoved);
} else if (name == "missing_capability") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kMissingCapability);
} else if (name == "storage_full") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kStorageFull);
} else if (name == "single_account_select") {
const std::vector<std::pair<std::string, std::string>> infos = {
{"verylong@"
"reallylongreallylongreallylongreallylongreallylongreallylong.com",
"Very Long String Very Long String Very Long String Very Long "
"String Very Long String Very Long String "},
};
std::vector<device::AuthenticatorGetAssertionResponse> responses;
for (const auto& info : infos) {
static const uint8_t kAppParam[32] = {};
static const uint8_t kSignatureCounter[4] = {};
device::AuthenticatorData auth_data(kAppParam, 0 /* flags */,
kSignatureCounter, std::nullopt);
device::AuthenticatorGetAssertionResponse response(
std::move(auth_data), {10, 11, 12, 13} /* signature */,
/*transport_used=*/std::nullopt);
device::PublicKeyCredentialUserEntity user({1, 2, 3, 4});
user.name = info.first;
user.display_name = info.second;
response.credential = device::PublicKeyCredentialDescriptor(
device::CredentialType::kPublicKey, {1, 2, 3, 4});
response.user_entity = std::move(user);
responses.emplace_back(std::move(response));
}
controller_->SelectAccount(
std::move(responses),
base::BindOnce([](device::AuthenticatorGetAssertionResponse) {}));
} else if (name == "account_select" || name == "account_select_disabled") {
// These strings attempt to exercise the encoding of direction and
// language from https://github.com/w3c/webauthn/pull/1530.
// lang_and_dir_encoded contains a string with right-to-left and ar-SA
// tags. It's the UTF-8 encoding of the code points {0xE0001, 0xE0061,
// 0xE0072, 0xE002D, 0xE0053, 0xE0041, 0x200F, 0xFEA2, 0xFE92, 0xFBFF,
// 0xFE91, 0x20, 0xFE8E, 0xFEDF, 0xFEAE, 0xFEA4, 0xFEE3, 0xFE8E, 0xFEE7}.
const std::string lang_and_dir_encoded =
"\xf3\xa0\x80\x81\xf3\xa0\x81\xa1\xf3\xa0\x81\xb2\xf3\xa0\x80\xad\xf3"
"\xa0\x81\x93\xf3\xa0\x81\x81\xe2\x80\x8f\xef\xba\xa2\xef\xba\x92\xef"
"\xaf\xbf\xef\xba\x91\x20\xef\xba\x8e\xef\xbb\x9f\xef\xba\xae\xef\xba"
"\xa4\xef\xbb\xa3\xef\xba\x8e\xef\xbb\xa7";
// lang_jp_encoded specifies a kanji with language jp. This is the middle
// glyph from the example given in
// https://www.w3.org/TR/string-meta/#capturing-the-text-processing-language.
// It's the UTF-8 encoding of the code points {0xE0001, 0xE006a, 0xE0070,
// 0x76f4}.
const std::string lang_jp_encoded =
"\xf3\xa0\x80\x81\xf3\xa0\x81\xaa\xf3\xa0\x81\xb0\xe7\x9b\xb4";
// lang_zh_hant_encoded specifies the same code point as
// |lang_jp_encoded|, but with the language set to zh-Hant. According to
// the W3C document referenced above, this should display differently.
// It's the UTF-8 encoding of the code points {0xE0001, 0xe007a, 0xe0068,
// 0xe002d, 0xe0048, 0xe0061, 0xe006e, 0xe0074}.
const std::string lang_zh_hant_encoded =
"\xf3\xa0\x80\x81\xf3\xa0\x81\xba\xf3\xa0\x81\xa8\xf3\xa0\x80\xad\xf3"
"\xa0\x81\x88\xf3\xa0\x81\xa1\xf3\xa0\x81\xae\xf3\xa0\x81\xb4";
const std::vector<std::pair<std::string, std::string>> infos = {
{"[email protected]", "Test User 1"},
{"", "Test User 2"},
{"", ""},
{"[email protected]", "Test User 4"},
{"user name with\na line break", "display name\nwith a line break"},
{"[email protected]", lang_and_dir_encoded},
{"[email protected]", lang_jp_encoded},
{"[email protected]", lang_zh_hant_encoded},
{"verylong@"
"reallylongreallylongreallylongreallylongreallylongreallylong.com",
"Very Long String Very Long String Very Long String Very Long "
"String Very Long String Very Long String "},
};
std::vector<device::AuthenticatorGetAssertionResponse> responses;
for (const auto& info : infos) {
static const uint8_t kAppParam[32] = {};
static const uint8_t kSignatureCounter[4] = {};
device::AuthenticatorData auth_data(kAppParam, 0 /* flags */,
kSignatureCounter, std::nullopt);
device::AuthenticatorGetAssertionResponse response(
std::move(auth_data), {10, 11, 12, 13} /* signature */,
/*transport_used=*/std::nullopt);
device::PublicKeyCredentialUserEntity user({1, 2, 3, 4});
user.name = info.first;
user.display_name = info.second;
response.credential = device::PublicKeyCredentialDescriptor(
device::CredentialType::kPublicKey, {1, 2, 3, 4});
response.user_entity = std::move(user);
responses.emplace_back(std::move(response));
}
controller_->SelectAccount(
std::move(responses),
base::BindOnce([](device::AuthenticatorGetAssertionResponse) {}));
} else if (name == "server_link_title_UNLOCK_YOUR_PHONE") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/true, /*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableActivate);
} else if (name == "create_passkey") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCreatePasskey);
} else if (name == "phone_confirmation") {
// The phone must be from Sync.
phones[0]->from_sync_deviceinfo = true;
controller_->set_cable_transport_info(
/*extension_is_v2=*/true, std::move(phones),
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kPhoneConfirmationSheet);
}
#if BUILDFLAG(IS_MAC)
else if (name == "ble_permission_mac") { // NOLINT
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kBlePermissionMac);
}
#endif
UpdateModelBeforeStartFlow(model_.get(), transport_availability);
controller_->StartFlow(std::move(transport_availability), {});
if (name.ends_with("_disabled")) {
model_->ui_disabled_ = true;
model_->OnSheetModelChanged();
}
}
private:
scoped_refptr<AuthenticatorRequestDialogModel> model_;
std::unique_ptr<AuthenticatorRequestDialogController> controller_;
base::RepeatingTimer timer_;
int bio_samples_remaining_ = 5;
};
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_default) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_force_pin_change) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_force_pin_change_same_as_current) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_mechanisms) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_mechanisms_create) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_mechanisms_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_mechanisms_create_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_activate_usb) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_timeout) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_no_available_transports) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_key_not_registered) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_key_already_registered) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_windows_hello_not_enabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_internal_unrecognized_error) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_ble_power_on_manual) {
ShowAndVerifyUi();
}
#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_touchid) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_touchid_incognito) {
ShowAndVerifyUi();
}
#endif // BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_activate) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_v2_activate) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_v2_pair) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_v2_connecting) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_v2_connected) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_cable_v2_error) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_set_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_get_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_get_pin_two_tries_remaining) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_get_pin_one_try_remaining) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_get_pin_fallback) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_inline_bio_enrollment) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_retry_uv) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_retry_uv_two_tries_remaining) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_retry_uv_one_try_remaining) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_second_tap) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_soft_block) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_hard_block) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_authenticator_removed) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_missing_capability) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_storage_full) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_resident_credential_confirm) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_single_account_select) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_account_select) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_account_select_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_server_link_title_UNLOCK_YOUR_PHONE) {
ShowAndVerifyUi();
}
#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_ble_permission_mac) {
ShowAndVerifyUi();
}
#endif
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_create_passkey) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_phone_confirmation) {
ShowAndVerifyUi();
}
// Run with:
//
// --gtest_filter=BrowserUiTest.Invoke --test-launcher-interactive \
// --ui=GPMPasskeysAuthenticatorDialogTest.InvokeUi_${test_name}
//
// where test_name is the second arg to IN_PROC_BROWSER_TEST_F().
class GPMPasskeysAuthenticatorDialogTest : public DialogBrowserTest {
public:
void SetUpOnMainThread() override {
signin::MakePrimaryAccountAvailable(
IdentityManagerFactory::GetForProfile(browser()->profile()),
"[email protected]", signin::ConsentLevel::kSync);
}
void TearDownOnMainThread() override {
if (controller_) {
// Close the dialog before the entire browser is torn down.
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClosed);
}
DialogBrowserTest::TearDownOnMainThread();
}
// AuthenticatorDialogTest:
void ShowUi(const std::string& name) override {
// Web modal dialogs' bounds may exceed the display's work area.
// https://crbug.com/893292.
set_should_verify_dialog_bounds(false);
content::RenderFrameHost* rfh = browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame();
model_ = base::MakeRefCounted<AuthenticatorRequestDialogModel>(rfh);
model_->relying_party_id = "example.com";
controller_ = std::make_unique<AuthenticatorRequestDialogController>(
model_.get(), rfh);
controller_->SetAccountPreselectedCallback(base::DoNothing());
device::FidoRequestHandlerBase::TransportAvailabilityInfo&
transport_availability =
controller_->transport_availability_for_testing();
transport_availability.request_type =
device::FidoRequestType::kGetAssertion;
transport_availability.available_transports = {
AuthenticatorTransport::kUsbHumanInterfaceDevice,
AuthenticatorTransport::kInternal,
AuthenticatorTransport::kHybrid,
};
device::DiscoverableCredentialMetadata gpm_cred(
device::AuthenticatorType::kEnclave, "example.com", {1},
device::PublicKeyCredentialUserEntity({1}, "[email protected]",
"Elisa Beckett"),
std::nullopt);
device::DiscoverableCredentialMetadata local_cred1(
device::AuthenticatorType::kTouchID, "example.com", {1},
device::PublicKeyCredentialUserEntity({1}, "[email protected]",
"Elisa Beckett"),
std::nullopt);
device::DiscoverableCredentialMetadata local_cred2(
device::AuthenticatorType::kTouchID, "example.com", {2},
device::PublicKeyCredentialUserEntity({2}, "[email protected]",
"Elisa Beckett"),
std::nullopt);
device::DiscoverableCredentialMetadata phone_cred1(
device::AuthenticatorType::kPhone, "example.com", {3},
device::PublicKeyCredentialUserEntity({1}, "[email protected]",
"Elisa Beckett"),
std::nullopt);
device::DiscoverableCredentialMetadata phone_cred2(
device::AuthenticatorType::kPhone, "example.com", {4},
device::PublicKeyCredentialUserEntity({2}, "[email protected]",
"Elisa Beckett"),
std::nullopt);
device::DiscoverableCredentialMetadata ick_cred1(
device::AuthenticatorType::kICloudKeychain, "example.com", {5},
device::PublicKeyCredentialUserEntity({1}, "[email protected]",
"Elisa Beckett"),
"Example Passkey Provider");
device::DiscoverableCredentialMetadata ick_cred2(
device::AuthenticatorType::kICloudKeychain, "example.com", {6},
device::PublicKeyCredentialUserEntity({2}, "[email protected]",
"Elisa Beckett"),
"Another Example Passkey Provider");
model_->user_entity = local_cred1.user;
// Configure a phone from sync.
std::vector<std::unique_ptr<device::cablev2::Pairing>> phones;
auto phone = std::make_unique<device::cablev2::Pairing>();
phone->from_sync_deviceinfo = true;
phone->name = kPhoneName;
phones.emplace_back(std::move(phone));
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt, std::move(phones),
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
if (name == "no_passkeys_discovered") {
transport_availability.recognized_credentials = {};
} else if (name == "local_and_phone") {
transport_availability.recognized_credentials = {
std::move(local_cred1),
std::move(local_cred2),
std::move(phone_cred1),
std::move(phone_cred2),
};
} else if (name == "local_only" || name == "local_only_disabled") {
transport_availability.recognized_credentials = {
std::move(local_cred1),
std::move(local_cred2),
};
} else if (name == "local_no_other_devices") {
transport_availability.recognized_credentials = {
std::move(local_cred1),
std::move(local_cred2),
};
transport_availability.available_transports = {
device::FidoTransportProtocol::kInternal};
} else if (name == "phone_only") {
transport_availability.recognized_credentials = {
std::move(phone_cred1),
std::move(phone_cred2),
};
} else if (name == "priority_mech" || name == "priority_mech_disabled") {
transport_availability.has_empty_allow_list = true;
transport_availability.recognized_credentials = {
std::move(gpm_cred),
};
} else if (name == "one_phone_cred") {
transport_availability.recognized_credentials = {
std::move(phone_cred1),
};
} else if (name == "get_assertion_qr_with_usb") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
transport_availability.ble_status = BleStatus::kOn;
transport_availability.available_transports = {
AuthenticatorTransport::kHybrid,
AuthenticatorTransport::kUsbHumanInterfaceDevice,
};
} else if (name == "get_assertion_qr_without_usb") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
transport_availability.ble_status = BleStatus::kOn;
transport_availability.available_transports = {
AuthenticatorTransport::kHybrid,
};
} else if (name == "make_credential_qr_with_usb") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
transport_availability.request_type =
device::FidoRequestType::kMakeCredential;
transport_availability.attestation_conveyance_preference =
device::AttestationConveyancePreference::kDirect;
transport_availability.ble_status = BleStatus::kOn;
transport_availability.available_transports = {
AuthenticatorTransport::kHybrid,
AuthenticatorTransport::kUsbHumanInterfaceDevice,
};
} else if (name == "make_credential_qr_without_usb") {
controller_->set_cable_transport_info(
/*extension_is_v2=*/std::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
transport_availability.request_type =
device::FidoRequestType::kMakeCredential;
transport_availability.attestation_conveyance_preference =
device::AttestationConveyancePreference::kDirect;
transport_availability.ble_status = BleStatus::kOn;
transport_availability.available_transports = {
AuthenticatorTransport::kHybrid,
};
} else if (name == "trust_this_computer_assertion") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kTrustThisComputerAssertion);
} else if (name == "trust_this_computer_creation") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kTrustThisComputerCreation);
} else if (name == "gpm_create_passkey") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMCreatePasskey);
} else if (name == "touchid") {
transport_availability.request_type =
device::FidoRequestType::kMakeCredential;
transport_availability.attestation_conveyance_preference =
device::AttestationConveyancePreference::kNone;
transport_availability.make_credential_attachment =
device::AuthenticatorAttachment::kAny;
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMTouchID);
} else if (name == "gpm_change_pin" || name == "gpm_change_pin_disabled") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMChangePin);
} else if (name == "gpm_create_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMCreatePin);
} else if (name == "gpm_enter_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMEnterPin);
} else if (name == "gpm_change_arbitrary_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMChangeArbitraryPin);
} else if (name == "gpm_create_arbitrary_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMCreateArbitraryPin);
} else if (name == "gpm_enter_arbitrary_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMEnterArbitraryPin);
} else if (name == "gpm_error") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMError);
} else if (name == "gpm_connecting") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMConnecting);
} else if (name == "gpm_confirm_incognito_create") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMConfirmOffTheRecordCreate);
} else if (name == "gpm_locked_pin") {
controller_->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kGPMLockedPin);
} else if (name == "icloud_keychain_cred") {
transport_availability.has_empty_allow_list = true;
controller_->set_allow_icloud_keychain(true);
transport_availability.recognized_credentials = {
std::move(ick_cred1),
std::move(ick_cred2),
};
} else {
NOTREACHED();
}
UpdateModelBeforeStartFlow(model_.get(), transport_availability);
controller_->StartFlow(std::move(transport_availability), {});
if (name.ends_with("_disabled")) {
model_->ui_disabled_ = true;
model_->OnSheetModelChanged();
}
}
private:
scoped_refptr<AuthenticatorRequestDialogModel> model_;
std::unique_ptr<AuthenticatorRequestDialogController> controller_;
};
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_no_passkeys_discovered) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_local_and_phone) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_local_only) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_local_only_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_local_no_other_devices) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_phone_only) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_priority_mech) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_priority_mech_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_one_phone_cred) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_get_assertion_qr_with_usb) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_get_assertion_qr_without_usb) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_make_credential_qr_with_usb) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_make_credential_qr_without_usb) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_trust_this_computer_assertion) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_trust_this_computer_creation) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_create_passkey) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_change_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_change_pin_disabled) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_create_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_enter_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_change_arbitrary_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_create_arbitrary_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_enter_arbitrary_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest, InvokeUi_gpm_error) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_connecting) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_confirm_incognito_create) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_gpm_locked_pin) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest,
InvokeUi_icloud_keychain_cred) {
ShowAndVerifyUi();
}
#if BUILDFLAG(IS_MAC)
IN_PROC_BROWSER_TEST_F(GPMPasskeysAuthenticatorDialogTest, InvokeUi_touchid) {
if (__builtin_available(macos 12, *)) {
ShowAndVerifyUi();
}
}
#endif // BUILDFLAG(IS_MAC)
// Tests the UI steps that show a pop-up window.
class AuthenticatorWindowTest : public InProcessBrowserTest {
public:
void SetUp() override {
https_server_.RegisterRequestHandler(
base::BindRepeating(&AuthenticatorWindowTest::HandleNetworkRequest,
base::Unretained(this)));
ASSERT_TRUE(https_server_.InitializeAndListen());
InProcessBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
command_line->AppendSwitchASCII(switches::kGaiaUrl,
https_server_.base_url().spec());
command_line->AppendSwitchASCII(
webauthn::switches::kGpmPinResetReauthUrlSwitch,
https_server_.GetURL("/encryption/pin/reset").spec());
}
void SetUpOnMainThread() override {
https_server_.ServeFilesFromSourceDirectory(GetChromeTestDataDir());
https_server_.StartAcceptingConnections();
host_resolver()->AddRule("*", "127.0.0.1");
model_ = base::MakeRefCounted<AuthenticatorRequestDialogModel>(
browser()
->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame());
}
protected:
scoped_refptr<AuthenticatorRequestDialogModel> model_;
net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS};
private:
std::unique_ptr<net::test_server::HttpResponse> HandleNetworkRequest(
const net::test_server::HttpRequest& request) {
const GURL url = request.GetURL();
const std::string_view path = url.path_piece();
auto response = std::make_unique<net::test_server::BasicHttpResponse>();
if (path == "/encryption/unlock/desktop") {
response->set_code(net::HTTP_OK);
response->set_content(R"(<html><head><title>Test MagicArch</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
chrome.setClientEncryptionKeys(
function() {},
"1234",
new Map([["hw_protected", [{epoch: 1, key: new ArrayBuffer(32)}]]]));
});
</script></head><body><p>Test MagicArch</p></body></html>)");
} else if (path == "/encryption/pin/reset") {
response->set_code(net::HTTP_OK);
response->set_content(R"(<html><head><title>Test Reauth</title>
<script>
document.addEventListener('DOMContentLoaded', function() {
const url = new URL(window.location.href);
if (url.searchParams.get("rapt") === null) {
url.searchParams.set("rapt", "RAPT");
window.location.href = url.href;
}
});
</script></head><body><p>Test Reauth</p></body></html>)");
} else {
LOG(ERROR) << "Unknown network request: " << url.spec();
response->set_code(net::HTTP_NOT_FOUND);
}
return response;
}
};
#if !BUILDFLAG(IS_CHROMEOS)
// This test doesn't work on Chrome OS because
// `trusted_vault_encryption_key_tab_helper.cc` will not send the keys to the
// EnclaveManager, since Chrome OS doesn't use the enclave.
// Quits the browser (and thus finishes the test) when keys are received by the
// EnclaveManager.
class QuitBrowserWhenKeysStored : public EnclaveManager::Observer {
public:
explicit QuitBrowserWhenKeysStored(Browser* browser) : browser_(browser) {
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser_->profile())
->AddObserver(this);
}
// EnclaveManager::Observer
void OnKeysStored() override {
LOG(INFO) << "QuitBrowserWhenKeysStored::OnKeysStored";
EnclaveManagerFactory::GetAsEnclaveManagerForProfile(browser_->profile())
->RemoveObserver(this);
browser_ = nullptr;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&chrome::AttemptExit));
}
private:
raw_ptr<Browser> browser_;
};
IN_PROC_BROWSER_TEST_F(AuthenticatorWindowTest, RecoverSecurityDomain) {
QuitBrowserWhenKeysStored observer(browser());
// This should open a pop-up to MagicArch. The fake MagicArch, configured
// by this test class, will immediately return keys, which will cause the
// browser to exit.
model_->SetStep(
AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain);
RunUntilBrowserProcessQuits();
}
#endif
class QuitBrowserWhenReauthTokenReceived
: public AuthenticatorRequestDialogModel::Observer {
public:
explicit QuitBrowserWhenReauthTokenReceived(
AuthenticatorRequestDialogModel* model)
: model_(model) {
model_->observers.AddObserver(this);
}
// AuthenticatorRequestDialogModel::Observer
void OnReauthComplete(std::string token) override {
LOG(INFO) << "QuitBrowserWhenKeysStored::OnReauthComplete";
CHECK_EQ(token, "RAPT");
model_->observers.RemoveObserver(this);
model_ = nullptr;
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&chrome::AttemptExit));
}
private:
raw_ptr<AuthenticatorRequestDialogModel> model_;
};
IN_PROC_BROWSER_TEST_F(AuthenticatorWindowTest, ReauthForPinReset) {
QuitBrowserWhenReauthTokenReceived observer(model_.get());
// This should open a pop-up to a GAIA reauth page. That page will be faked
// by this test class and the fake will immediately complete with a token
// with the value "RAPT". That will cause `QuitBrowserWhenReauthTokenReceived`
// to close the browser and complete the test.
model_->SetStep(AuthenticatorRequestDialogModel::Step::kGPMReauthForPinReset);
RunUntilBrowserProcessQuits();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorWindowTest, UINavigatesAway) {
// Test that closing the window (e.g. due to a timeout) doesn't cause any
// issues.
model_->SetStep(
AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain);
model_->SetStep(AuthenticatorRequestDialogModel::Step::kNotStarted);
}
// Run with:
//
// browser_tests
// --gtest_filter=BrowserUiTest.Invoke --test-launcher-interactive \
// --ui=PasskeyUpgradeConfirmationBubbleTest.InvokeUi_${test_name}
//
// where test_name is the second arg to IN_PROC_BROWSER_TEST_F().
class PasskeyUpgradeConfirmationBubbleTest : public DialogBrowserTest {
public:
PasskeyUpgradeConfirmationBubbleTest() = default;
PasskeyUpgradeConfirmationBubbleTest(
const PasskeyUpgradeConfirmationBubbleTest&) = delete;
PasskeyUpgradeConfirmationBubbleTest& operator=(
const PasskeyUpgradeConfirmationBubbleTest&) = delete;
void SetUpOnMainThread() override {
DialogBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
signin::MakePrimaryAccountAvailable(
IdentityManagerFactory::GetForProfile(browser()->profile()),
"[email protected]", signin::ConsentLevel::kSync);
}
// DialogBrowserTest:
void ShowUi(const std::string& name) override {
// Bubble can only show on webby URLs
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("a.test", "/empty.html")));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
auto* controller =
ManagePasswordsUIController::FromWebContents(web_contents);
controller->OnPasskeyUpgrade("example.com");
}
};
IN_PROC_BROWSER_TEST_F(PasskeyUpgradeConfirmationBubbleTest, InvokeUi_default) {
ShowAndVerifyUi();
}