1 // Copyright 2014 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 5 #include <map> 6 7 #include "base/values.h" 8 #include "chrome/browser/extensions/active_script_controller.h" 9 #include "chrome/browser/extensions/active_tab_permission_granter.h" 10 #include "chrome/browser/extensions/extension_util.h" 11 #include "chrome/browser/extensions/permissions_updater.h" 12 #include "chrome/browser/extensions/tab_helper.h" 13 #include "chrome/test/base/chrome_render_view_host_test_harness.h" 14 #include "chrome/test/base/testing_profile.h" 15 #include "components/crx_file/id_util.h" 16 #include "content/public/browser/navigation_controller.h" 17 #include "content/public/browser/navigation_entry.h" 18 #include "content/public/browser/web_contents.h" 19 #include "extensions/browser/extension_registry.h" 20 #include "extensions/common/extension.h" 21 #include "extensions/common/extension_builder.h" 22 #include "extensions/common/feature_switch.h" 23 #include "extensions/common/manifest.h" 24 #include "extensions/common/user_script.h" 25 #include "extensions/common/value_builder.h" 26 27 namespace extensions { 28 29 namespace { 30 31 const char kAllHostsPermission[] = "*://*/*"; 32 33 } // namespace 34 35 // Unittests for the ActiveScriptController mostly test the internal logic 36 // of the controller itself (when to allow/deny extension script injection). 37 // Testing real injection is allowed/denied as expected (i.e., that the 38 // ActiveScriptController correctly interfaces in the system) is done in the 39 // ActiveScriptControllerBrowserTests. 40 class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { 41 protected: 42 ActiveScriptControllerUnitTest(); 43 virtual ~ActiveScriptControllerUnitTest(); 44 45 // Creates an extension with all hosts permission and adds it to the registry. 46 const Extension* AddExtension(); 47 48 // Reloads |extension_| by removing it from the registry and recreating it. 49 const Extension* ReloadExtension(); 50 51 // Returns true if the |extension| requires user consent before injecting 52 // a script. 53 bool RequiresUserConsent(const Extension* extension) const; 54 55 // Request an injection for the given |extension|. 56 void RequestInjection(const Extension* extension); 57 58 // Returns the number of times a given extension has had a script execute. 59 size_t GetExecutionCountForExtension(const std::string& extension_id) const; 60 61 ActiveScriptController* controller() const { 62 return active_script_controller_; 63 } 64 65 private: 66 // Returns a closure to use as a script execution for a given extension. 67 base::Closure GetExecutionCallbackForExtension( 68 const std::string& extension_id); 69 70 // Increment the number of executions for the given |extension_id|. 71 void IncrementExecutionCount(const std::string& extension_id); 72 73 virtual void SetUp() OVERRIDE; 74 75 // Since ActiveScriptController's behavior is behind a flag, override the 76 // feature switch. 77 FeatureSwitch::ScopedOverride feature_override_; 78 79 // The associated ActiveScriptController. 80 ActiveScriptController* active_script_controller_; 81 82 // The map of observed executions, keyed by extension id. 83 std::map<std::string, int> extension_executions_; 84 85 scoped_refptr<const Extension> extension_; 86 }; 87 88 ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest() 89 : feature_override_(FeatureSwitch::scripts_require_action(), 90 FeatureSwitch::OVERRIDE_ENABLED), 91 active_script_controller_(NULL) { 92 } 93 94 ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() { 95 } 96 97 const Extension* ActiveScriptControllerUnitTest::AddExtension() { 98 const std::string kId = crx_file::id_util::GenerateId("all_hosts_extension"); 99 extension_ = ExtensionBuilder() 100 .SetManifest( 101 DictionaryBuilder() 102 .Set("name", "all_hosts_extension") 103 .Set("description", "an extension") 104 .Set("manifest_version", 2) 105 .Set("version", "1.0.0") 106 .Set("permissions", 107 ListBuilder().Append(kAllHostsPermission))) 108 .SetLocation(Manifest::INTERNAL) 109 .SetID(kId) 110 .Build(); 111 112 ExtensionRegistry::Get(profile())->AddEnabled(extension_); 113 PermissionsUpdater(profile()).InitializePermissions(extension_.get()); 114 return extension_.get(); 115 } 116 117 const Extension* ActiveScriptControllerUnitTest::ReloadExtension() { 118 ExtensionRegistry::Get(profile())->RemoveEnabled(extension_->id()); 119 return AddExtension(); 120 } 121 122 bool ActiveScriptControllerUnitTest::RequiresUserConsent( 123 const Extension* extension) const { 124 PermissionsData::AccessType access_type = 125 controller()->RequiresUserConsentForScriptInjectionForTesting( 126 extension, UserScript::PROGRAMMATIC_SCRIPT); 127 // We should never downright refuse access in these tests. 128 DCHECK_NE(PermissionsData::ACCESS_DENIED, access_type); 129 return access_type == PermissionsData::ACCESS_WITHHELD; 130 } 131 132 void ActiveScriptControllerUnitTest::RequestInjection( 133 const Extension* extension) { 134 controller()->RequestScriptInjectionForTesting( 135 extension, 136 GetExecutionCallbackForExtension(extension->id())); 137 } 138 139 size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension( 140 const std::string& extension_id) const { 141 std::map<std::string, int>::const_iterator iter = 142 extension_executions_.find(extension_id); 143 if (iter != extension_executions_.end()) 144 return iter->second; 145 return 0u; 146 } 147 148 base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension( 149 const std::string& extension_id) { 150 // We use base unretained here, but if this ever gets executed outside of 151 // this test's lifetime, we have a major problem anyway. 152 return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount, 153 base::Unretained(this), 154 extension_id); 155 } 156 157 void ActiveScriptControllerUnitTest::IncrementExecutionCount( 158 const std::string& extension_id) { 159 ++extension_executions_[extension_id]; 160 } 161 162 void ActiveScriptControllerUnitTest::SetUp() { 163 ChromeRenderViewHostTestHarness::SetUp(); 164 165 TabHelper::CreateForWebContents(web_contents()); 166 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); 167 // These should never be NULL. 168 DCHECK(tab_helper); 169 active_script_controller_ = tab_helper->active_script_controller(); 170 DCHECK(active_script_controller_); 171 } 172 173 // Test that extensions with all_hosts require permission to execute, and, once 174 // that permission is granted, do execute. 175 TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) { 176 const Extension* extension = AddExtension(); 177 ASSERT_TRUE(extension); 178 179 NavigateAndCommit(GURL("https://www.google.com")); 180 181 // Ensure that there aren't any executions pending. 182 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 183 ASSERT_FALSE(controller()->WantsToRun(extension)); 184 185 // Since the extension requests all_hosts, we should require user consent. 186 EXPECT_TRUE(RequiresUserConsent(extension)); 187 188 // Request an injection. The extension should want to run, but should not have 189 // executed. 190 RequestInjection(extension); 191 EXPECT_TRUE(controller()->WantsToRun(extension)); 192 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 193 194 // Click to accept the extension executing. 195 controller()->OnClicked(extension); 196 197 // The extension should execute, and the extension shouldn't want to run. 198 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 199 EXPECT_FALSE(controller()->WantsToRun(extension)); 200 201 // Since we already executed on the given page, we shouldn't need permission 202 // for a second time. 203 EXPECT_FALSE(RequiresUserConsent(extension)); 204 205 // Reloading and same-origin navigations shouldn't clear those permissions, 206 // and we shouldn't require user constent again. 207 Reload(); 208 EXPECT_FALSE(RequiresUserConsent(extension)); 209 NavigateAndCommit(GURL("https://www.google.com/foo")); 210 EXPECT_FALSE(RequiresUserConsent(extension)); 211 NavigateAndCommit(GURL("https://www.google.com/bar")); 212 EXPECT_FALSE(RequiresUserConsent(extension)); 213 214 // Cross-origin navigations should clear permissions. 215 NavigateAndCommit(GURL("https://otherdomain.google.com")); 216 EXPECT_TRUE(RequiresUserConsent(extension)); 217 218 // Grant access. 219 RequestInjection(extension); 220 controller()->OnClicked(extension); 221 EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id())); 222 EXPECT_FALSE(controller()->WantsToRun(extension)); 223 224 // Navigating to another site should also clear the permissions. 225 NavigateAndCommit(GURL("https://www.foo.com")); 226 EXPECT_TRUE(RequiresUserConsent(extension)); 227 } 228 229 // Test that injections that are not executed by the time the user navigates are 230 // ignored and never execute. 231 TEST_F(ActiveScriptControllerUnitTest, PendingInjectionsRemovedAtNavigation) { 232 const Extension* extension = AddExtension(); 233 ASSERT_TRUE(extension); 234 235 NavigateAndCommit(GURL("https://www.google.com")); 236 237 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 238 239 // Request an injection. The extension should want to run, but not execute. 240 RequestInjection(extension); 241 EXPECT_TRUE(controller()->WantsToRun(extension)); 242 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 243 244 // Reload. This should remove the pending injection, and we should not 245 // execute anything. 246 Reload(); 247 EXPECT_FALSE(controller()->WantsToRun(extension)); 248 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 249 250 // Request and accept a new injection. 251 RequestInjection(extension); 252 controller()->OnClicked(extension); 253 254 // The extension should only have executed once, even though a grand total 255 // of two executions were requested. 256 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 257 EXPECT_FALSE(controller()->WantsToRun(extension)); 258 } 259 260 // Test that queueing multiple pending injections, and then accepting, triggers 261 // them all. 262 TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) { 263 const Extension* extension = AddExtension(); 264 ASSERT_TRUE(extension); 265 NavigateAndCommit(GURL("https://www.google.com")); 266 267 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 268 269 const size_t kNumInjections = 3u; 270 // Queue multiple pending injections. 271 for (size_t i = 0u; i < kNumInjections; ++i) 272 RequestInjection(extension); 273 274 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 275 276 controller()->OnClicked(extension); 277 278 // All pending injections should have executed. 279 EXPECT_EQ(kNumInjections, GetExecutionCountForExtension(extension->id())); 280 EXPECT_FALSE(controller()->WantsToRun(extension)); 281 } 282 283 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsUseActiveTabPermissions) { 284 const Extension* extension = AddExtension(); 285 NavigateAndCommit(GURL("https://www.google.com")); 286 287 ActiveTabPermissionGranter* active_tab_permission_granter = 288 TabHelper::FromWebContents(web_contents()) 289 ->active_tab_permission_granter(); 290 ASSERT_TRUE(active_tab_permission_granter); 291 // Grant the extension active tab permissions. This normally happens, e.g., 292 // if the user clicks on a browser action. 293 active_tab_permission_granter->GrantIfRequested(extension); 294 295 // Since we have active tab permissions, we shouldn't need user consent 296 // anymore. 297 EXPECT_FALSE(RequiresUserConsent(extension)); 298 299 // Reloading and other same-origin navigations maintain the permission to 300 // execute. 301 Reload(); 302 EXPECT_FALSE(RequiresUserConsent(extension)); 303 NavigateAndCommit(GURL("https://www.google.com/foo")); 304 EXPECT_FALSE(RequiresUserConsent(extension)); 305 NavigateAndCommit(GURL("https://www.google.com/bar")); 306 EXPECT_FALSE(RequiresUserConsent(extension)); 307 308 // Navigating to a different origin will require user consent again. 309 NavigateAndCommit(GURL("https://yahoo.com")); 310 EXPECT_TRUE(RequiresUserConsent(extension)); 311 312 // Back to the original origin should also re-require constent. 313 NavigateAndCommit(GURL("https://www.google.com")); 314 EXPECT_TRUE(RequiresUserConsent(extension)); 315 316 RequestInjection(extension); 317 EXPECT_TRUE(controller()->WantsToRun(extension)); 318 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 319 320 // Grant active tab. 321 active_tab_permission_granter->GrantIfRequested(extension); 322 323 // The pending injections should have run since active tab permission was 324 // granted. 325 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 326 EXPECT_FALSE(controller()->WantsToRun(extension)); 327 } 328 329 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsCanHaveAllUrlsPref) { 330 const Extension* extension = AddExtension(); 331 ASSERT_TRUE(extension); 332 333 NavigateAndCommit(GURL("https://www.google.com")); 334 EXPECT_TRUE(RequiresUserConsent(extension)); 335 336 // Enable the extension on all urls. 337 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), true); 338 339 EXPECT_FALSE(RequiresUserConsent(extension)); 340 // This should carry across navigations, and websites. 341 NavigateAndCommit(GURL("http://www.foo.com")); 342 EXPECT_FALSE(RequiresUserConsent(extension)); 343 344 // Turning off the preference should have instant effect. 345 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), false); 346 EXPECT_TRUE(RequiresUserConsent(extension)); 347 348 // And should also persist across navigations and websites. 349 NavigateAndCommit(GURL("http://www.bar.com")); 350 EXPECT_TRUE(RequiresUserConsent(extension)); 351 } 352 353 TEST_F(ActiveScriptControllerUnitTest, TestAlwaysRun) { 354 const Extension* extension = AddExtension(); 355 ASSERT_TRUE(extension); 356 357 NavigateAndCommit(GURL("https://www.google.com/?gws_rd=ssl")); 358 359 // Ensure that there aren't any executions pending. 360 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 361 ASSERT_FALSE(controller()->WantsToRun(extension)); 362 363 // Since the extension requests all_hosts, we should require user consent. 364 EXPECT_TRUE(RequiresUserConsent(extension)); 365 366 // Request an injection. The extension should want to run, but not execute. 367 RequestInjection(extension); 368 EXPECT_TRUE(controller()->WantsToRun(extension)); 369 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 370 371 // Allow the extension to always run on this origin. 372 controller()->AlwaysRunOnVisibleOrigin(extension); 373 374 // The extension should execute, and the extension shouldn't want to run. 375 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 376 EXPECT_FALSE(controller()->WantsToRun(extension)); 377 378 // Since we already executed on the given page, we shouldn't need permission 379 // for a second time. 380 EXPECT_FALSE(RequiresUserConsent(extension)); 381 382 // Navigating to another site that hasn't been granted a persisted permission 383 // should necessitate user consent. 384 NavigateAndCommit(GURL("https://www.foo.com/bar")); 385 EXPECT_TRUE(RequiresUserConsent(extension)); 386 387 // We shouldn't need user permission upon returning to the original origin. 388 NavigateAndCommit(GURL("https://www.google.com/foo/bar")); 389 EXPECT_FALSE(RequiresUserConsent(extension)); 390 391 // Reloading the extension should not clear any granted host permissions. 392 extension = ReloadExtension(); 393 Reload(); 394 EXPECT_FALSE(RequiresUserConsent(extension)); 395 396 // Different host... 397 NavigateAndCommit(GURL("https://www.foo.com/bar")); 398 EXPECT_TRUE(RequiresUserConsent(extension)); 399 // Different scheme... 400 NavigateAndCommit(GURL("http://www.google.com/foo/bar")); 401 EXPECT_TRUE(RequiresUserConsent(extension)); 402 // Different subdomain... 403 NavigateAndCommit(GURL("https://en.google.com/foo/bar")); 404 EXPECT_TRUE(RequiresUserConsent(extension)); 405 // Only the "always run" origin should be allowed to run without user consent. 406 NavigateAndCommit(GURL("https://www.google.com/foo/bar")); 407 EXPECT_FALSE(RequiresUserConsent(extension)); 408 } 409 410 } // namespace extensions 411