blob: 9098ef7cc737082972a68fbe8e3cfd6f635331c7 [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 "base/memory/raw_ptr.h"
#include "base/strings/string_util.h"
#include "base/test/bind.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/webauthn/authenticator_request_scheduler.h"
#include "chrome/browser/webauthn/chrome_authenticator_request_delegate.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "content/public/browser/scoped_authenticator_environment_for_testing.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "device/fido/virtual_fido_device_factory.h"
#include "net/dns/mock_host_resolver.h"
namespace {
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTabId);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTabId);
constexpr char kRegisterTemplate[] =
"() => {"
" return navigator.credentials.create({publicKey: {"
" rp: {name: 't'},"
" user: {id: new Uint8Array([1]), name: 't', displayName: 't'},"
" challenge: new Uint8Array([1,2,3,4]),"
" timeout: 10000,"
" attestation: '$1',"
" pubKeyCredParams: [{type: 'public-key', alg: -7}]"
" }}).then(c => {return 'OK';},"
" e => {return e.toString();})"
"}";
constexpr char kOkText[] = "OK";
constexpr char kNotAllowedText[] =
"NotAllowedError: The operation is not allowed at this time because the "
"page does not have focus.";
class WebAuthnFocusTest : public InteractiveBrowserTest,
public AuthenticatorRequestDialogModel::Observer {
public:
WebAuthnFocusTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
WebAuthnFocusTest(const WebAuthnFocusTest&) = delete;
WebAuthnFocusTest& operator=(const WebAuthnFocusTest&) = delete;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_.ServeFilesFromSourceDirectory("content/test/data");
ASSERT_TRUE(https_server_.Start());
InteractiveBrowserTest::SetUpOnMainThread();
ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
auto virtual_device_factory =
std::make_unique<device::test::VirtualFidoDeviceFactory>();
virtual_device_factory_ = virtual_device_factory.get();
auth_env_ =
std::make_unique<content::ScopedAuthenticatorEnvironmentForTesting>(
std::move(virtual_device_factory));
}
GURL GetHttpsURL(const std::string& hostname,
const std::string& relative_url) {
return https_server_.GetURL(hostname, relative_url);
}
device::test::VirtualFidoDeviceFactory* virtual_device_factory() {
return virtual_device_factory_;
}
private:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
}
void OnModelDestroyed(AuthenticatorRequestDialogModel* model) override {}
std::unique_ptr<content::ScopedAuthenticatorEnvironmentForTesting> auth_env_;
raw_ptr<device::test::VirtualFidoDeviceFactory> virtual_device_factory_;
net::EmbeddedTestServer https_server_;
};
IN_PROC_BROWSER_TEST_F(WebAuthnFocusTest, SucceedWhenTabIsFocused) {
RunTestSequence(
InstrumentTab(kFirstTabId),
NavigateWebContents(kFirstTabId,
GetHttpsURL("www.example.com", "/title1.html")),
CheckJsResult(
kFirstTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kOkText));
}
IN_PROC_BROWSER_TEST_F(WebAuthnFocusTest, FailWhenTabIsNotFocused) {
// Web Authentication requests will often trigger machine-wide indications,
// such as a Security Key flashing for a touch. If background tabs were able
// to trigger this, there would be a risk of user confusion since the user
// would not know which tab they would be interacting with if they touched a
// Security Key. Because of that, some Web Authentication APIs require that
// the frame be in the foreground in a focused window.
RunTestSequence(
InstrumentTab(kFirstTabId),
NavigateWebContents(kFirstTabId,
GetHttpsURL("www.example.com", "/title1.html")),
AddInstrumentedTab(kSecondTabId,
GetHttpsURL("www.example.com", "/title1.html")),
CheckJsResult(
kFirstTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kNotAllowedText),
CheckJsResult(
kSecondTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kOkText));
}
IN_PROC_BROWSER_TEST_F(WebAuthnFocusTest, FailWhenTabFocusChangesWhileRunning) {
// Start the request in the foreground and open a new tab between starting and
// finishing the request. This should fail because we don't want foreground
// pages to be able to start a request, open a trusted site in a new
// tab/window, and have the user believe that they are interacting with that
// trusted site.
virtual_device_factory()->mutable_state()->simulate_press_callback =
base::BindRepeating(
[](Browser* browser, device::VirtualFidoDevice* device) {
chrome::NewTab(browser);
return true;
},
browser());
RunTestSequence(
InstrumentTab(kFirstTabId),
NavigateWebContents(kFirstTabId,
GetHttpsURL("www.example.com", "/title1.html")),
CheckJsResult(
kFirstTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kNotAllowedText));
}
IN_PROC_BROWSER_TEST_F(WebAuthnFocusTest, SucceedWithDevtoolsOpen) {
RunTestSequence(
InstrumentTab(kFirstTabId),
NavigateWebContents(kFirstTabId,
GetHttpsURL("www.example.com", "/title1.html")),
FocusWebContents(kFirstTabId),
CheckJsResult(kFirstTabId, "() => document.hasFocus()", true),
Do([this]() {
DevToolsWindowTesting::OpenDevToolsWindowSync(
browser()->tab_strip_model()->GetActiveWebContents(),
true /* docked, not a separate window */);
}),
CheckJsResult(kFirstTabId, "() => document.hasFocus()", false),
CheckJsResult(
kFirstTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kOkText));
}
IN_PROC_BROWSER_TEST_F(WebAuthnFocusTest, SucceedWithNewWindowOpen) {
virtual_device_factory()->mutable_state()->simulate_press_callback =
base::BindRepeating(
[](Browser* browser, device::VirtualFidoDevice* device) {
chrome::NewWindow(browser);
return ui_test_utils::BringBrowserWindowToFront(
GetLastActiveBrowserWindowInterfaceWithAnyProfile());
},
browser());
RunTestSequence(
InstrumentTab(kFirstTabId),
NavigateWebContents(kFirstTabId,
GetHttpsURL("www.example.com", "/title1.html")),
CheckJsResult(
kFirstTabId,
base::ReplaceStringPlaceholders(
kRegisterTemplate, std::vector<std::string>{"none"}, nullptr),
kOkText));
}
} // anonymous namespace