blob: 9334b3c625c705b01b1f745acc2df66a14452a5d [file] [log] [blame]
Adam Langley573d3ac2018-04-28 00:32:131// Copyright (c) 2018 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Sebastien Marchandf1349f52019-01-25 03:16:415#include "base/bind.h"
Adam Langley573d3ac2018-04-28 00:32:136#include "build/build_config.h"
7#include "chrome/browser/devtools/devtools_window_testing.h"
8#include "chrome/browser/permissions/permission_request_manager.h"
9#include "chrome/browser/ui/browser.h"
10#include "chrome/browser/ui/browser_commands.h"
11#include "chrome/test/base/in_process_browser_test.h"
12#include "chrome/test/base/interactive_test_utils.h"
13#include "chrome/test/base/ui_test_utils.h"
14#include "components/network_session_configurator/common/network_switches.h"
15#include "content/public/test/browser_test_utils.h"
16#include "content/public/test/test_service_manager_context.h"
17#include "device/fido/scoped_virtual_fido_device.h"
18#include "net/dns/mock_host_resolver.h"
19#include "net/test/embedded_test_server/embedded_test_server.h"
20#include "testing/gmock/include/gmock/gmock.h"
21
22namespace {
23
24class WebAuthFocusTest : public InProcessBrowserTest,
25 public PermissionRequestManager::Observer {
26 protected:
27 WebAuthFocusTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
28
29 void SetUpOnMainThread() override {
30 host_resolver()->AddRule("*", "127.0.0.1");
31 https_server_.ServeFilesFromSourceDirectory("content/test/data");
32 ASSERT_TRUE(https_server_.Start());
33 }
34
35 GURL GetHttpsURL(const std::string& hostname,
36 const std::string& relative_url) {
37 return https_server_.GetURL(hostname, relative_url);
38 }
39
40 // PermissionRequestManager::Observer implementation
41 void OnBubbleAdded() override {
42 // If this object is registered as a PermissionRequestManager observer then
43 // it'll attempt to complete all permissions bubbles by sending keystrokes.
44 // Note, however, that macOS rejects the permission bubble while other
45 // platforms accept it, because there's no key sequence for accepting a
46 // bubble on macOS.
47 base::ThreadTaskRunnerHandle::Get()->PostTask(
48 FROM_HERE,
49 base::BindOnce(
50 [](Browser* browser) {
51 for (const auto& key : std::vector<ui::KeyboardCode> {
52#if defined(OS_WIN) || defined(OS_CHROMEOS)
53 // Press tab (to select the "Allow" button of the
54 // permissions prompt) and then enter to activate it.
55 ui::KeyboardCode::VKEY_TAB, ui::KeyboardCode::VKEY_RETURN,
56#elif defined(OS_MACOSX)
57 // There is no way to allow the bubble, we have to
58 // press escape to reject it.
59 ui::KeyboardCode::VKEY_ESCAPE,
60#else
61 // Press tab twice (to select the "Allow" button of the
62 // permissions prompt) and then enter to activate it.
63 ui::KeyboardCode::VKEY_TAB,
64 ui::KeyboardCode::VKEY_TAB,
65 ui::KeyboardCode::VKEY_RETURN,
66#endif
67 }) {
68 ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
69 browser, key,
70 /*control=*/false, /*shift=*/false, /*alt=*/false,
71 /*command=*/false));
72 }
73 },
74 browser()));
75 }
76
77 private:
78 void SetUpCommandLine(base::CommandLine* command_line) override {
79 command_line->AppendSwitch(switches::kIgnoreCertificateErrors);
80 }
81
82 net::EmbeddedTestServer https_server_;
83
84 DISALLOW_COPY_AND_ASSIGN(WebAuthFocusTest);
85};
86
87IN_PROC_BROWSER_TEST_F(WebAuthFocusTest, Focus) {
88 // Web Authentication requests will often trigger machine-wide indications,
89 // such as a Security Key flashing for a touch. If background tabs were able
90 // to trigger this, there would be a risk of user confusion since the user
91 // would not know which tab they would be interacting with if they touched a
92 // Security Key. Because of that, some Web Authentication APIs require that
93 // the frame be in the foreground in a focused window.
94
95 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
96 ui_test_utils::NavigateToURL(browser(),
97 GetHttpsURL("www.example.com", "/title1.html"));
98
99 device::test::ScopedVirtualFidoDevice virtual_device;
100
101 constexpr char kRegisterTemplate[] =
102 "navigator.credentials.create({publicKey: {"
103 " rp: {name: 't'},"
104 " user: {id: new Uint8Array([1]), name: 't', displayName: 't'},"
105 " challenge: new Uint8Array([1,2,3,4]),"
106 " timeout: 10000,"
107 " attestation: '$1',"
108 " pubKeyCredParams: [{type: 'public-key', alg: -7}]"
109 "}}).then(c => window.domAutomationController.send('OK'),"
110 " e => window.domAutomationController.send(e.toString()));";
111 const std::string register_script = base::ReplaceStringPlaceholders(
112 kRegisterTemplate, std::vector<std::string>{"none"}, nullptr);
113
114 content::WebContents* const initial_web_contents =
115 browser()->tab_strip_model()->GetActiveWebContents();
116
117 std::string result;
118 // When operating in the foreground, the operation should succeed.
119 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
120 register_script, &result));
121 EXPECT_EQ(result, "OK");
122
123 // Open a new tab to put the previous page in the background.
124 chrome::NewTab(browser());
125
126 // When in the background, the same request should result in a focus error.
127 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
128 register_script, &result));
129 constexpr char kFocusErrorSubstring[] = "the page does not have focus";
130 EXPECT_THAT(result, ::testing::HasSubstr(kFocusErrorSubstring));
131
132 // Close the tab and the action should succeed again.
133 chrome::CloseTab(browser());
134 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
135 register_script, &result));
136 EXPECT_EQ(result, "OK");
137
138 // Start the request in the foreground and open a new tab between starting and
139 // finishing the request. This should fail because we don't want foreground
140 // pages to be able to start a request, open a trusted site in a new
141 // tab/window, and have the user believe that they are interacting with that
142 // trusted site.
143 virtual_device.mutable_state()->simulate_press_callback = base::BindRepeating(
144 [](Browser* browser) { chrome::NewTab(browser); }, browser());
145 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
146 register_script, &result));
147 EXPECT_THAT(result, ::testing::HasSubstr(kFocusErrorSubstring));
148
149 // Close the tab and the action should succeed again.
150 chrome::CloseTab(browser());
151 virtual_device.mutable_state()->simulate_press_callback.Reset();
152 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
153 register_script, &result));
154 EXPECT_EQ(result, "OK");
155
156 // Open dev tools and check that operations still succeed.
157 DevToolsWindow* dev_tools_window =
158 DevToolsWindowTesting::OpenDevToolsWindowSync(
159 initial_web_contents, true /* docked, not a separate window */);
160 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
161 register_script, &result));
162 EXPECT_EQ(result, "OK");
163 DevToolsWindowTesting::CloseDevToolsWindowSync(dev_tools_window);
164
165 // Open a second browser window.
166 ui_test_utils::BrowserAddedObserver browser_added_observer;
167 chrome::NewWindow(browser());
168 Browser* new_window = browser_added_observer.WaitForSingleNewBrowser();
169 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(new_window));
170
Balazs Engedy9311dc32018-06-14 13:56:06171 // Operations in the (now unfocused) window should still succeed, as the
172 // calling tab is still the active tab in that window.
Adam Langley573d3ac2018-04-28 00:32:13173 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
174 register_script, &result));
Balazs Engedy9311dc32018-06-14 13:56:06175 EXPECT_THAT(result, "OK");
Adam Langley573d3ac2018-04-28 00:32:13176
177 // Check that closing the window brings things back to a focused state.
178 chrome::CloseWindow(new_window);
179 ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
180 ASSERT_TRUE(content::ExecuteScriptAndExtractString(initial_web_contents,
181 register_script, &result));
182 EXPECT_EQ(result, "OK");
183
184 // Requesting "direct" attestation will trigger a permissions prompt.
185 const std::string get_assertion_with_attestation_script =
186 base::ReplaceStringPlaceholders(
187 kRegisterTemplate, std::vector<std::string>{"direct"}, nullptr);
188
189 PermissionRequestManager* const permission_request_manager =
190 PermissionRequestManager::FromWebContents(initial_web_contents);
191 // The observer callback will trigger the permissions prompt.
192 permission_request_manager->AddObserver(this);
193 ASSERT_TRUE(content::ExecuteScriptAndExtractString(
194 initial_web_contents, get_assertion_with_attestation_script, &result));
195#if defined(OS_MACOSX)
196 // The permissions bubble has to be rejected on macOS because there's no key
197 // sequence to accept it. Therefore a NotAllowedError is expected. This is not
198 // ideal as a timeout causes the same result, but it is distinct from a focus
199 // error.
200 EXPECT_THAT(result, ::testing::HasSubstr("NotAllowedError: "));
201#else
202 EXPECT_EQ(result, "OK");
203#endif
204 permission_request_manager->RemoveObserver(this);
205}
206
207} // anonymous namespace