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/tab_helper.h" 12 #include "chrome/test/base/chrome_render_view_host_test_harness.h" 13 #include "chrome/test/base/testing_profile.h" 14 #include "content/public/browser/navigation_controller.h" 15 #include "content/public/browser/navigation_entry.h" 16 #include "content/public/browser/web_contents.h" 17 #include "extensions/browser/extension_registry.h" 18 #include "extensions/common/extension.h" 19 #include "extensions/common/extension_builder.h" 20 #include "extensions/common/feature_switch.h" 21 #include "extensions/common/id_util.h" 22 #include "extensions/common/manifest.h" 23 #include "extensions/common/value_builder.h" 24 25 namespace extensions { 26 27 namespace { 28 29 const char kAllHostsPermission[] = "*://*/*"; 30 31 } // namespace 32 33 // Unittests for the ActiveScriptController mostly test the internal logic 34 // of the controller itself (when to allow/deny extension script injection). 35 // Testing real injection is allowed/denied as expected (i.e., that the 36 // ActiveScriptController correctly interfaces in the system) is done in the 37 // ActiveScriptControllerBrowserTests. 38 class ActiveScriptControllerUnitTest : public ChromeRenderViewHostTestHarness { 39 protected: 40 ActiveScriptControllerUnitTest(); 41 virtual ~ActiveScriptControllerUnitTest(); 42 43 // Creates an extension with all hosts permission and adds it to the registry. 44 const Extension* AddExtension(); 45 46 // Returns the current page id. 47 int GetPageId(); 48 49 // Returns a closure to use as a script execution for a given extension. 50 base::Closure GetExecutionCallbackForExtension( 51 const std::string& extension_id); 52 53 // Returns the number of times a given extension has had a script execute. 54 size_t GetExecutionCountForExtension(const std::string& extension_id) const; 55 56 ActiveScriptController* controller() { return active_script_controller_; } 57 58 private: 59 // Increment the number of executions for the given |extension_id|. 60 void IncrementExecutionCount(const std::string& extension_id); 61 62 virtual void SetUp() OVERRIDE; 63 64 // Since ActiveScriptController's behavior is behind a flag, override the 65 // feature switch. 66 FeatureSwitch::ScopedOverride feature_override_; 67 68 // The associated ActiveScriptController. 69 ActiveScriptController* active_script_controller_; 70 71 // The map of observed executions, keyed by extension id. 72 std::map<std::string, int> extension_executions_; 73 }; 74 75 ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest() 76 : feature_override_(FeatureSwitch::scripts_require_action(), 77 FeatureSwitch::OVERRIDE_ENABLED), 78 active_script_controller_(NULL) { 79 } 80 81 ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() { 82 } 83 84 const Extension* ActiveScriptControllerUnitTest::AddExtension() { 85 const std::string kId = id_util::GenerateId("all_hosts_extension"); 86 scoped_refptr<const Extension> extension = 87 ExtensionBuilder() 88 .SetManifest( 89 DictionaryBuilder() 90 .Set("name", "all_hosts_extension") 91 .Set("description", "an extension") 92 .Set("manifest_version", 2) 93 .Set("version", "1.0.0") 94 .Set("permissions", 95 ListBuilder().Append(kAllHostsPermission))) 96 .SetLocation(Manifest::INTERNAL) 97 .SetID(kId) 98 .Build(); 99 100 ExtensionRegistry::Get(profile())->AddEnabled(extension); 101 return extension; 102 } 103 104 int ActiveScriptControllerUnitTest::GetPageId() { 105 content::NavigationEntry* navigation_entry = 106 web_contents()->GetController().GetVisibleEntry(); 107 DCHECK(navigation_entry); // This should never be NULL. 108 return navigation_entry->GetPageID(); 109 } 110 111 base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension( 112 const std::string& extension_id) { 113 // We use base unretained here, but if this ever gets executed outside of 114 // this test's lifetime, we have a major problem anyway. 115 return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount, 116 base::Unretained(this), 117 extension_id); 118 } 119 120 size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension( 121 const std::string& extension_id) const { 122 std::map<std::string, int>::const_iterator iter = 123 extension_executions_.find(extension_id); 124 if (iter != extension_executions_.end()) 125 return iter->second; 126 return 0u; 127 } 128 129 void ActiveScriptControllerUnitTest::IncrementExecutionCount( 130 const std::string& extension_id) { 131 ++extension_executions_[extension_id]; 132 } 133 134 void ActiveScriptControllerUnitTest::SetUp() { 135 ChromeRenderViewHostTestHarness::SetUp(); 136 137 TabHelper::CreateForWebContents(web_contents()); 138 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents()); 139 // None of these should ever be NULL. 140 DCHECK(tab_helper); 141 DCHECK(tab_helper->location_bar_controller()); 142 active_script_controller_ = 143 tab_helper->location_bar_controller()->active_script_controller(); 144 DCHECK(active_script_controller_); 145 } 146 147 // Test that extensions with all_hosts require permission to execute, and, once 148 // that permission is granted, do execute. 149 TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) { 150 const Extension* extension = AddExtension(); 151 ASSERT_TRUE(extension); 152 153 NavigateAndCommit(GURL("https://www.google.com")); 154 155 // Ensure that there aren't any executions pending. 156 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 157 ASSERT_FALSE(controller()->GetActionForExtension(extension)); 158 159 // Since the extension requests all_hosts, we should require user consent. 160 EXPECT_TRUE( 161 controller()->RequiresUserConsentForScriptInjection(extension)); 162 163 // Request an injection. There should be an action visible, but no executions. 164 controller()->RequestScriptInjection( 165 extension, 166 GetPageId(), 167 GetExecutionCallbackForExtension(extension->id())); 168 EXPECT_TRUE(controller()->GetActionForExtension(extension)); 169 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 170 171 // Click to accept the extension executing. 172 controller()->OnClicked(extension); 173 174 // The extension should execute, and the action should go away. 175 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 176 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 177 178 // Since we already executed on the given page, we shouldn't need permission 179 // for a second time. 180 EXPECT_FALSE( 181 controller()->RequiresUserConsentForScriptInjection(extension)); 182 183 // Reloading should clear those permissions, and we should again require user 184 // consent. 185 Reload(); 186 EXPECT_TRUE( 187 controller()->RequiresUserConsentForScriptInjection(extension)); 188 189 // Grant access. 190 controller()->RequestScriptInjection( 191 extension, 192 GetPageId(), 193 GetExecutionCallbackForExtension(extension->id())); 194 controller()->OnClicked(extension); 195 EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id())); 196 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 197 198 // Navigating to another site should also clear the permissions. 199 NavigateAndCommit(GURL("https://www.foo.com")); 200 EXPECT_TRUE( 201 controller()->RequiresUserConsentForScriptInjection(extension)); 202 } 203 204 // Test that injections that are not executed by the time the user navigates are 205 // ignored and never execute. 206 TEST_F(ActiveScriptControllerUnitTest, PendingInjectionsRemovedAtNavigation) { 207 const Extension* extension = AddExtension(); 208 ASSERT_TRUE(extension); 209 210 NavigateAndCommit(GURL("https://www.google.com")); 211 212 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 213 214 // Request an injection. There should be an action visible, but no executions. 215 controller()->RequestScriptInjection( 216 extension, 217 GetPageId(), 218 GetExecutionCallbackForExtension(extension->id())); 219 EXPECT_TRUE(controller()->GetActionForExtension(extension)); 220 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 221 222 // Reload. This should remove the pending injection, and we should not 223 // execute anything. 224 Reload(); 225 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 226 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 227 228 // Request and accept a new injection. 229 controller()->RequestScriptInjection( 230 extension, 231 GetPageId(), 232 GetExecutionCallbackForExtension(extension->id())); 233 controller()->OnClicked(extension); 234 235 // The extension should only have executed once, even though a grand total 236 // of two executions were requested. 237 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 238 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 239 } 240 241 // Test that queueing multiple pending injections, and then accepting, triggers 242 // them all. 243 TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) { 244 const Extension* extension = AddExtension(); 245 ASSERT_TRUE(extension); 246 NavigateAndCommit(GURL("https://www.google.com")); 247 248 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id())); 249 250 const size_t kNumInjections = 3u; 251 // Queue multiple pending injections. 252 for (size_t i = 0u; i < kNumInjections; ++i) { 253 controller()->RequestScriptInjection( 254 extension, 255 GetPageId(), 256 GetExecutionCallbackForExtension(extension->id())); 257 } 258 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 259 260 controller()->OnClicked(extension); 261 262 // All pending injections should have executed. 263 EXPECT_EQ(kNumInjections, GetExecutionCountForExtension(extension->id())); 264 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 265 } 266 267 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsUseActiveTabPermissions) { 268 const Extension* extension = AddExtension(); 269 NavigateAndCommit(GURL("https://www.google.com")); 270 271 ActiveTabPermissionGranter* active_tab_permission_granter = 272 TabHelper::FromWebContents(web_contents()) 273 ->active_tab_permission_granter(); 274 ASSERT_TRUE(active_tab_permission_granter); 275 // Grant the extension active tab permissions. This normally happens, e.g., 276 // if the user clicks on a browser action. 277 active_tab_permission_granter->GrantIfRequested(extension); 278 279 // Since we have active tab permissions, we shouldn't need user consent 280 // anymore. 281 EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension)); 282 283 // Also test that granting active tab runs any pending tasks. 284 Reload(); 285 // Navigating should mean we need permission again. 286 EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension)); 287 288 controller()->RequestScriptInjection( 289 extension, 290 GetPageId(), 291 GetExecutionCallbackForExtension(extension->id())); 292 EXPECT_TRUE(controller()->GetActionForExtension(extension)); 293 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id())); 294 295 // Grant active tab. 296 active_tab_permission_granter->GrantIfRequested(extension); 297 298 // The pending injections should have run since active tab permission was 299 // granted. 300 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id())); 301 EXPECT_FALSE(controller()->GetActionForExtension(extension)); 302 } 303 304 TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsCanHaveAllUrlsPref) { 305 const Extension* extension = AddExtension(); 306 ASSERT_TRUE(extension); 307 308 NavigateAndCommit(GURL("https://www.google.com")); 309 EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension)); 310 311 // Enable the extension on all urls. 312 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), true); 313 314 EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension)); 315 // This should carry across navigations, and websites. 316 NavigateAndCommit(GURL("http://www.foo.com")); 317 EXPECT_FALSE(controller()->RequiresUserConsentForScriptInjection(extension)); 318 319 // Turning off the preference should have instant effect. 320 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), false); 321 EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension)); 322 323 // And should also persist across navigations and websites. 324 NavigateAndCommit(GURL("http://www.bar.com")); 325 EXPECT_TRUE(controller()->RequiresUserConsentForScriptInjection(extension)); 326 } 327 328 } // namespace extensions 329