blob: 01fe26ba0cf72bcc319482f4dd6684c120a1d75c [file] [log] [blame]
[email protected]78cd68e2014-05-22 20:33:521// 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"
[email protected]b33c8c22014-05-29 19:51:0810#include "chrome/browser/extensions/extension_util.h"
[email protected]23a85362014-07-07 23:26:1911#include "chrome/browser/extensions/permissions_updater.h"
[email protected]78cd68e2014-05-22 20:33:5212#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 "content/public/browser/navigation_controller.h"
16#include "content/public/browser/navigation_entry.h"
17#include "content/public/browser/web_contents.h"
18#include "extensions/browser/extension_registry.h"
19#include "extensions/common/extension.h"
20#include "extensions/common/extension_builder.h"
21#include "extensions/common/feature_switch.h"
[email protected]b33c8c22014-05-29 19:51:0822#include "extensions/common/id_util.h"
[email protected]78cd68e2014-05-22 20:33:5223#include "extensions/common/manifest.h"
[email protected]23a85362014-07-07 23:26:1924#include "extensions/common/user_script.h"
[email protected]78cd68e2014-05-22 20:33:5225#include "extensions/common/value_builder.h"
26
27namespace extensions {
28
29namespace {
30
31const 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.
40class 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
[email protected]23a85362014-07-07 23:26:1948 // Returns true if the |extension| requires user consent before injecting
49 // a script.
50 bool RequiresUserConsent(const Extension* extension) const;
51
52 // Request an injection for the given |extension|.
53 void RequestInjection(const Extension* extension);
[email protected]78cd68e2014-05-22 20:33:5254
55 // Returns the number of times a given extension has had a script execute.
56 size_t GetExecutionCountForExtension(const std::string& extension_id) const;
57
[email protected]23a85362014-07-07 23:26:1958 ActiveScriptController* controller() const {
59 return active_script_controller_;
60 }
[email protected]78cd68e2014-05-22 20:33:5261
62 private:
[email protected]23a85362014-07-07 23:26:1963 // Returns a closure to use as a script execution for a given extension.
64 base::Closure GetExecutionCallbackForExtension(
65 const std::string& extension_id);
66
[email protected]78cd68e2014-05-22 20:33:5267 // Increment the number of executions for the given |extension_id|.
68 void IncrementExecutionCount(const std::string& extension_id);
69
70 virtual void SetUp() OVERRIDE;
71
72 // Since ActiveScriptController's behavior is behind a flag, override the
73 // feature switch.
74 FeatureSwitch::ScopedOverride feature_override_;
75
76 // The associated ActiveScriptController.
77 ActiveScriptController* active_script_controller_;
78
79 // The map of observed executions, keyed by extension id.
80 std::map<std::string, int> extension_executions_;
81};
82
83ActiveScriptControllerUnitTest::ActiveScriptControllerUnitTest()
84 : feature_override_(FeatureSwitch::scripts_require_action(),
85 FeatureSwitch::OVERRIDE_ENABLED),
86 active_script_controller_(NULL) {
87}
88
89ActiveScriptControllerUnitTest::~ActiveScriptControllerUnitTest() {
90}
91
92const Extension* ActiveScriptControllerUnitTest::AddExtension() {
[email protected]b33c8c22014-05-29 19:51:0893 const std::string kId = id_util::GenerateId("all_hosts_extension");
[email protected]78cd68e2014-05-22 20:33:5294 scoped_refptr<const Extension> extension =
95 ExtensionBuilder()
96 .SetManifest(
97 DictionaryBuilder()
98 .Set("name", "all_hosts_extension")
99 .Set("description", "an extension")
100 .Set("manifest_version", 2)
101 .Set("version", "1.0.0")
102 .Set("permissions",
103 ListBuilder().Append(kAllHostsPermission)))
104 .SetLocation(Manifest::INTERNAL)
105 .SetID(kId)
106 .Build();
107
108 ExtensionRegistry::Get(profile())->AddEnabled(extension);
[email protected]23a85362014-07-07 23:26:19109 PermissionsUpdater(profile()).InitializePermissions(extension);
[email protected]78cd68e2014-05-22 20:33:52110 return extension;
111}
112
[email protected]23a85362014-07-07 23:26:19113bool ActiveScriptControllerUnitTest::RequiresUserConsent(
114 const Extension* extension) const {
115 PermissionsData::AccessType access_type =
116 controller()->RequiresUserConsentForScriptInjectionForTesting(
117 extension, UserScript::PROGRAMMATIC_SCRIPT);
118 // We should never downright refuse access in these tests.
119 DCHECK_NE(PermissionsData::ACCESS_DENIED, access_type);
120 return access_type == PermissionsData::ACCESS_WITHHELD;
121}
122
123void ActiveScriptControllerUnitTest::RequestInjection(
124 const Extension* extension) {
125 controller()->RequestScriptInjectionForTesting(
126 extension,
127 GetExecutionCallbackForExtension(extension->id()));
[email protected]78cd68e2014-05-22 20:33:52128}
129
130size_t ActiveScriptControllerUnitTest::GetExecutionCountForExtension(
131 const std::string& extension_id) const {
132 std::map<std::string, int>::const_iterator iter =
133 extension_executions_.find(extension_id);
134 if (iter != extension_executions_.end())
135 return iter->second;
136 return 0u;
137}
138
[email protected]23a85362014-07-07 23:26:19139base::Closure ActiveScriptControllerUnitTest::GetExecutionCallbackForExtension(
140 const std::string& extension_id) {
141 // We use base unretained here, but if this ever gets executed outside of
142 // this test's lifetime, we have a major problem anyway.
143 return base::Bind(&ActiveScriptControllerUnitTest::IncrementExecutionCount,
144 base::Unretained(this),
145 extension_id);
146}
147
[email protected]78cd68e2014-05-22 20:33:52148void ActiveScriptControllerUnitTest::IncrementExecutionCount(
149 const std::string& extension_id) {
150 ++extension_executions_[extension_id];
151}
152
153void ActiveScriptControllerUnitTest::SetUp() {
154 ChromeRenderViewHostTestHarness::SetUp();
155
156 TabHelper::CreateForWebContents(web_contents());
157 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents());
158 // None of these should ever be NULL.
159 DCHECK(tab_helper);
160 DCHECK(tab_helper->location_bar_controller());
161 active_script_controller_ =
162 tab_helper->location_bar_controller()->active_script_controller();
163 DCHECK(active_script_controller_);
164}
165
166// Test that extensions with all_hosts require permission to execute, and, once
167// that permission is granted, do execute.
168TEST_F(ActiveScriptControllerUnitTest, RequestPermissionAndExecute) {
169 const Extension* extension = AddExtension();
170 ASSERT_TRUE(extension);
171
172 NavigateAndCommit(GURL("https://www.google.com"));
173
174 // Ensure that there aren't any executions pending.
175 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id()));
176 ASSERT_FALSE(controller()->GetActionForExtension(extension));
177
178 // Since the extension requests all_hosts, we should require user consent.
[email protected]23a85362014-07-07 23:26:19179 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]78cd68e2014-05-22 20:33:52180
181 // Request an injection. There should be an action visible, but no executions.
[email protected]23a85362014-07-07 23:26:19182 RequestInjection(extension);
[email protected]78cd68e2014-05-22 20:33:52183 EXPECT_TRUE(controller()->GetActionForExtension(extension));
184 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
185
186 // Click to accept the extension executing.
187 controller()->OnClicked(extension);
188
189 // The extension should execute, and the action should go away.
190 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
191 EXPECT_FALSE(controller()->GetActionForExtension(extension));
192
193 // Since we already executed on the given page, we shouldn't need permission
194 // for a second time.
[email protected]23a85362014-07-07 23:26:19195 EXPECT_FALSE(RequiresUserConsent(extension));
[email protected]78cd68e2014-05-22 20:33:52196
197 // Reloading should clear those permissions, and we should again require user
198 // consent.
199 Reload();
[email protected]23a85362014-07-07 23:26:19200 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]78cd68e2014-05-22 20:33:52201
202 // Grant access.
[email protected]23a85362014-07-07 23:26:19203 RequestInjection(extension);
[email protected]78cd68e2014-05-22 20:33:52204 controller()->OnClicked(extension);
205 EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id()));
206 EXPECT_FALSE(controller()->GetActionForExtension(extension));
207
208 // Navigating to another site should also clear the permissions.
209 NavigateAndCommit(GURL("https://www.foo.com"));
[email protected]23a85362014-07-07 23:26:19210 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]78cd68e2014-05-22 20:33:52211}
212
213// Test that injections that are not executed by the time the user navigates are
214// ignored and never execute.
215TEST_F(ActiveScriptControllerUnitTest, PendingInjectionsRemovedAtNavigation) {
216 const Extension* extension = AddExtension();
217 ASSERT_TRUE(extension);
218
219 NavigateAndCommit(GURL("https://www.google.com"));
220
221 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id()));
222
223 // Request an injection. There should be an action visible, but no executions.
[email protected]23a85362014-07-07 23:26:19224 RequestInjection(extension);
[email protected]78cd68e2014-05-22 20:33:52225 EXPECT_TRUE(controller()->GetActionForExtension(extension));
226 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
227
[email protected]8d5cb212014-06-04 09:00:39228 // Reload. This should remove the pending injection, and we should not
[email protected]78cd68e2014-05-22 20:33:52229 // execute anything.
[email protected]8d5cb212014-06-04 09:00:39230 Reload();
[email protected]78cd68e2014-05-22 20:33:52231 EXPECT_FALSE(controller()->GetActionForExtension(extension));
232 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
233
234 // Request and accept a new injection.
[email protected]23a85362014-07-07 23:26:19235 RequestInjection(extension);
[email protected]78cd68e2014-05-22 20:33:52236 controller()->OnClicked(extension);
237
238 // The extension should only have executed once, even though a grand total
239 // of two executions were requested.
240 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
241 EXPECT_FALSE(controller()->GetActionForExtension(extension));
242}
243
244// Test that queueing multiple pending injections, and then accepting, triggers
245// them all.
246TEST_F(ActiveScriptControllerUnitTest, MultiplePendingInjection) {
247 const Extension* extension = AddExtension();
248 ASSERT_TRUE(extension);
249 NavigateAndCommit(GURL("https://www.google.com"));
250
251 ASSERT_EQ(0u, GetExecutionCountForExtension(extension->id()));
252
253 const size_t kNumInjections = 3u;
254 // Queue multiple pending injections.
[email protected]23a85362014-07-07 23:26:19255 for (size_t i = 0u; i < kNumInjections; ++i)
256 RequestInjection(extension);
257
[email protected]78cd68e2014-05-22 20:33:52258 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
267TEST_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.
[email protected]23a85362014-07-07 23:26:19281 EXPECT_FALSE(RequiresUserConsent(extension));
[email protected]78cd68e2014-05-22 20:33:52282
[email protected]11814f52014-05-23 06:50:35283 // Also test that granting active tab runs any pending tasks.
[email protected]8d5cb212014-06-04 09:00:39284 Reload();
[email protected]11814f52014-05-23 06:50:35285 // Navigating should mean we need permission again.
[email protected]23a85362014-07-07 23:26:19286 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]11814f52014-05-23 06:50:35287
[email protected]23a85362014-07-07 23:26:19288 RequestInjection(extension);
[email protected]11814f52014-05-23 06:50:35289 EXPECT_TRUE(controller()->GetActionForExtension(extension));
290 EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
291
292 // Grant active tab.
293 active_tab_permission_granter->GrantIfRequested(extension);
294
295 // The pending injections should have run since active tab permission was
296 // granted.
297 EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
298 EXPECT_FALSE(controller()->GetActionForExtension(extension));
[email protected]78cd68e2014-05-22 20:33:52299}
300
[email protected]b33c8c22014-05-29 19:51:08301TEST_F(ActiveScriptControllerUnitTest, ActiveScriptsCanHaveAllUrlsPref) {
302 const Extension* extension = AddExtension();
303 ASSERT_TRUE(extension);
304
305 NavigateAndCommit(GURL("https://www.google.com"));
[email protected]23a85362014-07-07 23:26:19306 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]b33c8c22014-05-29 19:51:08307
308 // Enable the extension on all urls.
309 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), true);
310
[email protected]23a85362014-07-07 23:26:19311 EXPECT_FALSE(RequiresUserConsent(extension));
[email protected]b33c8c22014-05-29 19:51:08312 // This should carry across navigations, and websites.
313 NavigateAndCommit(GURL("http://www.foo.com"));
[email protected]23a85362014-07-07 23:26:19314 EXPECT_FALSE(RequiresUserConsent(extension));
[email protected]b33c8c22014-05-29 19:51:08315
316 // Turning off the preference should have instant effect.
317 util::SetAllowedScriptingOnAllUrls(extension->id(), profile(), false);
[email protected]23a85362014-07-07 23:26:19318 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]b33c8c22014-05-29 19:51:08319
320 // And should also persist across navigations and websites.
321 NavigateAndCommit(GURL("http://www.bar.com"));
[email protected]23a85362014-07-07 23:26:19322 EXPECT_TRUE(RequiresUserConsent(extension));
[email protected]b33c8c22014-05-29 19:51:08323}
324
[email protected]78cd68e2014-05-22 20:33:52325} // namespace extensions