1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
|
// Copyright 2020 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/files/file_path.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/devtools/devtools_window_testing.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/scoped_test_mv2_enabler.h"
#include "chrome/browser/policy/policy_test_utils.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/url_constants.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 "components/policy/core/common/policy_map.h"
#include "components/policy/policy_constants.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/no_renderer_crashes_assertion.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/api/messaging/messaging_delegate.h"
#include "extensions/common/extension.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "third_party/blink/public/common/chrome_debug_urls.h"
using extensions::mojom::ManifestLocation;
namespace policy {
namespace {
// Utility for waiting until the dev-mode controls are visible/hidden
// Uses a MutationObserver on the attributes of the DOM element.
void WaitForExtensionsDevModeControlsVisibility(
content::WebContents* contents,
const char* dev_controls_accessor_js,
const char* dev_controls_visibility_check_js,
bool expected_visible) {
ASSERT_TRUE(content::ExecJs(
contents,
base::StringPrintf(
"var screenElement = %s;"
"new Promise(resolve => {"
" function SendReplyIfAsExpected() {"
" var is_visible = %s;"
" if (is_visible != %s)"
" return false;"
" observer.disconnect();"
" resolve(true);"
" return true;"
" }"
" var observer = new MutationObserver(SendReplyIfAsExpected);"
" if (!SendReplyIfAsExpected()) {"
" var options = { 'attributes': true };"
" observer.observe(screenElement, options);"
" }"
"});",
dev_controls_accessor_js, dev_controls_visibility_check_js,
base::ToString(expected_visible))));
}
// Utility to get a PolicyMap for setting the DeveloperToolsAvailability policy
// to a given value.
PolicyMap MakeDeveloperToolsAvailabilityMap(int value) {
PolicyMap policies;
policies.Set(key::kDeveloperToolsAvailability, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(value),
nullptr);
return policies;
}
// Navigates the current tab of the browser to the given URL without any
// waiting after the navigation is triggered. Note: the
// ui_test_utils::BROWSER_TEST_NO_WAIT flag passed in results this returning
// right after the Browser::OpenURL() call without waiting for any load
// events.
void NavigateToURLNoWait(Browser* browser, const GURL& url) {
ui_test_utils::NavigateToURLWithDisposition(
browser, url, WindowOpenDisposition::CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NO_WAIT);
}
// Utility to navigate the current tab of the browser to the specified page and
// then kill it using chrome://kill, verifying that the page ends up crashed.
void VerifyPageAllowsKill(Browser* browser, const GURL& url) {
SCOPED_TRACE(base::StringPrintf("Verifying url allows kill: '%s'",
url.spec().c_str()));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
{
content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes;
content::RenderProcessHostWatcher exit_observer(
browser->tab_strip_model()
->GetActiveWebContents()
->GetPrimaryMainFrame()
->GetProcess(),
content::RenderProcessHostWatcher::WATCH_FOR_PROCESS_EXIT);
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser, GURL(blink::kChromeUIKillURL)));
exit_observer.Wait();
// The kill url will have left a hanging pending entry on the
// NavigationController and the contents will be marked as having crashed.
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(web_contents->GetController().GetPendingEntry());
EXPECT_TRUE(web_contents->IsCrashed());
EXPECT_FALSE(exit_observer.did_exit_normally());
}
}
// Utility to navigate the current tab of the browser to the specified page and
// then attempt to kill it using chrome://kill, verifying that the kill is
// blocked before any navigation is started.
void VerifyPageBlocksKill(Browser* browser, const GURL& url) {
SCOPED_TRACE(base::StringPrintf("Verifying url blocks kill: '%s'",
url.spec().c_str()));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
// We expect the debug URL to be blocked synchronously, so we don't have to
// wait for a load stop here. Afterwards we verify nothing has happened by
// checking there is no pending entry on the NavigationController (indicating
// no navigation was started) and that the contents has not crashed.
NavigateToURLNoWait(browser, GURL(blink::kChromeUIKillURL));
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
EXPECT_FALSE(web_contents->GetController().GetPendingEntry());
EXPECT_FALSE(web_contents->IsCrashed());
}
// Utility to navigate the current tab of the browser to the specified page and
// return true if a javascript URL can be run on it, false otherwise.
bool PageAllowsJavascriptURL(Browser* browser, const GURL& url) {
SCOPED_TRACE(base::StringPrintf("Checking url allows javascript URLs: '%s'",
url.spec().c_str()));
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser, url));
content::WebContents* web_contents =
browser->tab_strip_model()->GetActiveWebContents();
const std::u16string original_title = web_contents->GetTitle();
const GURL javascript_url("javascript:void(document.title='Modified Title')");
NavigateToURLNoWait(browser, javascript_url);
// Run another script we can wait on to ensure the javascript URL will have
// processed if it was going to.
EXPECT_EQ(true, content::EvalJs(web_contents, "true"));
// Check if the title was changed and return true if so.
if (web_contents->GetTitle() != original_title) {
EXPECT_EQ(u"Modified Title", web_contents->GetTitle());
return true;
}
EXPECT_NE(u"Modified Title", web_contents->GetTitle());
return false;
}
} // namespace
IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabledByLegacyPolicy) {
// Verifies that access to the developer tools can be disabled by setting the
// legacy DeveloperToolsDisabled policy.
// Open devtools.
EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
DevToolsWindow* devtools_window =
DevToolsWindow::GetInstanceForInspectedWebContents(contents);
EXPECT_TRUE(devtools_window);
// Disable devtools via policy.
PolicyMap policies;
policies.Set(key::kDeveloperToolsDisabled, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
nullptr);
content::WebContentsDestroyedWatcher close_observer(
DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
UpdateProviderPolicy(policies);
// wait for devtools close
close_observer.Wait();
// The existing devtools window should have closed.
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
// And it's not possible to open it again.
EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}
IN_PROC_BROWSER_TEST_F(PolicyTest,
DeveloperToolsDisabledByDeveloperToolsAvailability) {
// Verifies that access to the developer tools can be disabled by setting the
// DeveloperToolsAvailability policy.
// Open devtools.
EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
DevToolsWindow* devtools_window =
DevToolsWindow::GetInstanceForInspectedWebContents(contents);
EXPECT_TRUE(devtools_window);
content::WebContentsDestroyedWatcher close_observer(
DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
// Disable devtools via policy.
UpdateProviderPolicy(
MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
// wait for devtools close
close_observer.Wait();
// The existing devtools window should have closed.
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
// And it's not possible to open it again.
EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}
// Test for https://b/263040629
IN_PROC_BROWSER_TEST_F(PolicyTest, AvailabilityWins) {
// DeveloperToolsDisabled is true, but DeveloperToolsAvailability wins.
PolicyMap policies;
policies.Set(key::kDeveloperToolsAvailability, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD,
base::Value(1 /* DeveloperToolsAllowed */), nullptr);
policies.Set(key::kDeveloperToolsDisabled, POLICY_LEVEL_MANDATORY,
POLICY_SCOPE_USER, POLICY_SOURCE_CLOUD, base::Value(true),
nullptr);
UpdateProviderPolicy(policies);
EXPECT_TRUE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
DevToolsWindow* devtools_window =
DevToolsWindow::GetInstanceForInspectedWebContents(contents);
EXPECT_TRUE(devtools_window);
// Clearing DeveloperToolsAvailability leaves behind
// DeveloperToolsDisabled, so the DevTools window gets closed.
content::WebContentsDestroyedWatcher close_observer(
DevToolsWindowTesting::Get(devtools_window)->main_web_contents());
policies.Erase(key::kDeveloperToolsAvailability);
UpdateProviderPolicy(policies);
// wait for devtools close
close_observer.Wait();
// The existing devtools window should have closed.
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
// And it's not possible to open it again.
EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_DEV_TOOLS));
EXPECT_FALSE(DevToolsWindow::GetInstanceForInspectedWebContents(contents));
}
IN_PROC_BROWSER_TEST_F(PolicyTest,
ViewSourceDisabledByDeveloperToolsAvailability) {
// Verifies that entry points to ViewSource can be disabled by setting the
// DeveloperToolsAvailability policy.
// Disable devtools via policy.
UpdateProviderPolicy(
MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
// Verify that it's not possible to ViewSource.
EXPECT_FALSE(chrome::ExecuteCommand(browser(), IDC_VIEW_SOURCE));
}
IN_PROC_BROWSER_TEST_F(PolicyTest, DeveloperToolsDisabledExtensionsDevMode) {
// Verifies that when DeveloperToolsDisabled policy is set, the "dev mode"
// in chrome://extensions is actively turned off and the checkbox
// is disabled.
// Note: We don't test the indicator as it is tested in the policy pref test
// for kDeveloperToolsDisabled and kDeveloperToolsAvailability.
// This test depends on the following helper methods to locate the DOM
// elements to be tested.
const char define_helpers_js[] =
R"(function getToolbar() {
const manager = document.querySelector('extensions-manager');
return manager.shadowRoot.querySelector('extensions-toolbar');
}
function getToggle() {
return getToolbar().$.devMode;
}
function getControls() {
return getToolbar().$.devDrawer;
}
)";
const char toggle_dev_mode_accessor_js[] = "getToggle()";
const char dev_controls_accessor_js[] = "getControls()";
const char dev_controls_visibility_check_js[] =
"getControls().hasAttribute('expanded')";
// Navigate to the extensions frame and enabled "Developer mode"
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), GURL(chrome::kChromeUIExtensionsURL)));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(content::ExecJs(contents, std::string(define_helpers_js)));
EXPECT_TRUE(content::ExecJs(
contents, base::StringPrintf("domAutomationController.send(%s.click());",
toggle_dev_mode_accessor_js)));
WaitForExtensionsDevModeControlsVisibility(contents, dev_controls_accessor_js,
dev_controls_visibility_check_js,
true);
// Disable devtools via policy.
UpdateProviderPolicy(
MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
// Expect devcontrols to be hidden now...
WaitForExtensionsDevModeControlsVisibility(contents, dev_controls_accessor_js,
dev_controls_visibility_check_js,
false);
// ... and checkbox is disabled
EXPECT_EQ(true, content::EvalJs(contents, base::StringPrintf(
"%s.hasAttribute('disabled')",
toggle_dev_mode_accessor_js)));
}
// Verifies debug URLs, specifically chrome://kill and javascript URLs, are
// blocked or allowed for different pages depending on the
// DeveloperToolsAvailability policy setting. Note: javascript URLs are always
// blocked on extension schemes, regardless of the policy setting.
// TODO(crbug.com/40064953): The loading of a force installed extension in this
// test runs into an issue on branded Windows builders.
#if BUILDFLAG(GOOGLE_CHROME_BRANDING) && BUILDFLAG(IS_WIN)
#define MAYBE_DebugURLsDisabledByDeveloperToolsAvailability \
DISABLED_DebugURLsDisabledByDeveloperToolsAvailability
#else
#define MAYBE_DebugURLsDisabledByDeveloperToolsAvailability \
DebugURLsDisabledByDeveloperToolsAvailability
#endif
IN_PROC_BROWSER_TEST_F(PolicyTest,
MAYBE_DebugURLsDisabledByDeveloperToolsAvailability) {
// TODO(https://crbug.com/40804030): Remove this when updated to use MV3.
extensions::ScopedTestMV2Enabler mv2_enabler;
// Get a url for a standard web page.
ASSERT_TRUE(embedded_test_server()->Start());
GURL tab_url(embedded_test_server()->GetURL("/empty.html"));
// Get a url for a force installed extension.
base::FilePath crx_path(ui_test_utils::GetTestFilePath(
base::FilePath().AppendASCII("devtools").AppendASCII("extensions"),
base::FilePath().AppendASCII("options.crx")));
extensions::ChromeTestExtensionLoader loader(browser()->profile());
// TODO(crbug.com/40269105): We shouldn't need to ignore manifest warnings
// here, but there's an issue related to the _metadata folder added for
// content verification when force-installing an off-store crx in a branded
// build, which produces an install warning.
loader.set_ignore_manifest_warnings(true);
loader.set_location(ManifestLocation::kExternalPolicyDownload);
scoped_refptr<const extensions::Extension> extension =
loader.LoadExtension(crx_path);
ASSERT_TRUE(extension);
GURL extension_url("chrome-extension://" + extension->id() + "/options.html");
// The default for DeveloperToolsAvailability is to disallow for force
// installed extensions. Even though that is already the value, we set it here
// to be explicit about what the value currently is.
// With this setting force installed extension should block the debug and
// javascript URLs and but normal pages should allow them.
UpdateProviderPolicy(MakeDeveloperToolsAvailabilityMap(
0 /* DeveloperToolsDisallowedForForceInstalledExtensions */));
{
SCOPED_TRACE(
"Testing DeveloperToolsDisallowedForForceInstalledExtensions policy "
"setting");
VerifyPageAllowsKill(browser(), tab_url);
EXPECT_TRUE(PageAllowsJavascriptURL(browser(), tab_url));
VerifyPageBlocksKill(browser(), extension_url);
EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
}
// When the policy is set to always allow Devtools all the pages should allow
// debug URLs to be used, but javascript URLs will still be blocked on any
// extension schemes.
UpdateProviderPolicy(
MakeDeveloperToolsAvailabilityMap(1 /* DeveloperToolsAllowed */));
{
SCOPED_TRACE("Testing DeveloperToolsAllowed policy setting");
VerifyPageAllowsKill(browser(), tab_url);
EXPECT_TRUE(PageAllowsJavascriptURL(browser(), tab_url));
VerifyPageAllowsKill(browser(), extension_url);
EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
}
// When the policy is set to always disallow Devtools all the pages should
// block debug and javascript URLs.
UpdateProviderPolicy(
MakeDeveloperToolsAvailabilityMap(2 /* DeveloperToolsDisallowed */));
{
SCOPED_TRACE("Testing DeveloperToolsDisallowed policy setting");
VerifyPageBlocksKill(browser(), tab_url);
EXPECT_FALSE(PageAllowsJavascriptURL(browser(), tab_url));
VerifyPageBlocksKill(browser(), extension_url);
EXPECT_FALSE(PageAllowsJavascriptURL(browser(), extension_url));
}
}
} // namespace policy
|