blob: 8bda4d0dae872a453b9ee8b942d441b17d48cc8a [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <utility>
#include "base/callback_helpers.h"
#include "base/test/bind.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/ui/webauthn/authenticator_request_dialog.h"
#include "chrome/browser/webauthn/authenticator_reference.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "components/cbor/values.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/fido_request_handler_base.h"
#include "device/fido/pin.h"
#include "device/fido/public_key_credential_user_entity.h"
class AuthenticatorDialogTest : public DialogBrowserTest {
public:
AuthenticatorDialogTest() = default;
AuthenticatorDialogTest(const AuthenticatorDialogTest&) = delete;
AuthenticatorDialogTest& operator=(const AuthenticatorDialogTest&) = delete;
// 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);
auto model = std::make_unique<AuthenticatorRequestDialogModel>(
/*relying_party_id=*/"example.com");
::device::FidoRequestHandlerBase::TransportAvailabilityInfo
transport_availability;
transport_availability.available_transports = {
AuthenticatorTransport::kUsbHumanInterfaceDevice,
AuthenticatorTransport::kInternal,
AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy};
if (name == "cable_server_link_activate") {
transport_availability.available_transports.insert(
AuthenticatorTransport::kAndroidAccessory);
}
transport_availability.has_recognized_platform_authenticator_credential =
false;
transport_availability.request_type =
device::FidoRequestType::kGetAssertion;
model->StartFlow(std::move(transport_availability),
/*use_location_bar_bubble=*/false);
// The dialog should immediately close as soon as it is displayed.
if (name == "mechanisms") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kMechanismSelection);
} else if (name == "activate_usb") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kUsbInsertAndActivate);
} else if (name == "timeout") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kTimedOut);
} else if (name == "no_available_transports") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kErrorNoAvailableTransports);
} else if (name == "key_not_registered") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kKeyNotRegistered);
} else if (name == "key_already_registered") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kKeyAlreadyRegistered);
} else if (name == "internal_unrecognized_error") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kErrorInternalUnrecognized);
} else if (name == "ble_power_on_manual") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kBlePowerOnManual);
} else if (name == "touchid_incognito") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kOffTheRecordInterstitial);
} else if (name == "cable_activate" ||
name == "cable_server_link_activate") {
model->set_cable_transport_info(
/*extension_is_v2=*/false,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableActivate);
} else if (name == "cable_v2_activate") {
model->set_cable_transport_info(
/*extension_is_v2=*/absl::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableActivate);
} else if (name == "cable_v2_pair") {
model->set_cable_transport_info(
/*extension_is_v2=*/absl::nullopt,
/*paired_phones=*/{},
/*contact_phone_callback=*/base::DoNothing(), "fido://qrcode");
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kCableV2QRCode);
} else if (name == "set_pin") {
model->CollectPIN(device::pin::PINEntryReason::kSet,
device::pin::PINEntryError::kNoError, 6, 0,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin") {
model->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kNoError, 6, 8,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_two_tries_remaining") {
model->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kWrongPIN, 6, 2,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_one_try_remaining") {
model->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kWrongPIN, 6, 1,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "get_pin_fallback") {
model->CollectPIN(device::pin::PINEntryReason::kChallenge,
device::pin::PINEntryError::kInternalUvLocked, 6, 8,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "inline_bio_enrollment") {
model->StartInlineBioEnrollment(base::DoNothing());
timer_.Start(
FROM_HERE, base::Seconds(2),
base::BindLambdaForTesting([&, weak_model = model->GetWeakPtr()] {
if (!weak_model || weak_model->current_step() !=
AuthenticatorRequestDialogModel::Step::
kInlineBioEnrollment) {
return;
}
weak_model->OnSampleCollected(--bio_samples_remaining_);
if (bio_samples_remaining_ <= 0)
timer_.Stop();
}));
} else if (name == "retry_uv") {
model->OnRetryUserVerification(5);
} else if (name == "retry_uv_two_tries_remaining") {
model->OnRetryUserVerification(2);
} else if (name == "retry_uv_one_try_remaining") {
model->OnRetryUserVerification(1);
} else if (name == "force_pin_change") {
model->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") {
model->CollectPIN(device::pin::PINEntryReason::kChange,
device::pin::PINEntryError::kSameAsCurrentPIN, 6, 0,
base::BindOnce([](std::u16string pin) {}));
} else if (name == "second_tap") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinTapAgain);
} else if (name == "soft_block") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinErrorSoftBlock);
} else if (name == "hard_block") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kClientPinErrorHardBlock);
} else if (name == "authenticator_removed") {
model->SetCurrentStepForTesting(AuthenticatorRequestDialogModel::Step::
kClientPinErrorAuthenticatorRemoved);
} else if (name == "missing_capability") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kMissingCapability);
} else if (name == "storage_full") {
model->SetCurrentStepForTesting(
AuthenticatorRequestDialogModel::Step::kStorageFull);
} else if (name == "account_select") {
// 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 = {
{"foo@example.com", "Test User 1"},
{"", "Test User 2"},
{"", ""},
{"bat@example.com", "Test User 4"},
{"encoded@example.com", lang_and_dir_encoded},
{"encoded2@example.com", lang_jp_encoded},
{"encoded3@example.com", 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] = {0};
static const uint8_t kSignatureCounter[4] = {0};
device::AuthenticatorData auth_data(kAppParam, 0 /* flags */,
kSignatureCounter, absl::nullopt);
device::AuthenticatorGetAssertionResponse response(
std::move(auth_data), {10, 11, 12, 13} /* signature */);
device::PublicKeyCredentialUserEntity user({1, 2, 3, 4});
user.name = info.first;
user.display_name = info.second;
response.user_entity = std::move(user);
responses.emplace_back(std::move(response));
}
model->SelectAccount(
std::move(responses),
base::BindOnce([](device::AuthenticatorGetAssertionResponse) {}));
} else if (name == "request_attestation_permission") {
model->RequestAttestationPermission(false, base::DoNothing());
} else if (name == "request_enterprise_attestation_permission") {
model->RequestAttestationPermission(true, base::DoNothing());
}
ShowAuthenticatorRequestDialog(
browser()->tab_strip_model()->GetActiveWebContents(), std::move(model));
}
private:
base::RepeatingTimer timer_;
int bio_samples_remaining_ = 5;
};
// Run with:
// --gtest_filter=BrowserUiTest.Invoke --test-launcher-interactive \
// --ui=AuthenticatorDialogTest.InvokeUi_default
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_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_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_server_link_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_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_account_select) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_request_attestation_permission) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest,
InvokeUi_request_enterprise_attestation_permission) {
ShowAndVerifyUi();
}