blob: 341f369fa8abb2037860aa1c673f23837095b7f6 [file] [log] [blame]
rdevlin.cronin5f6bebcd2016-09-26 23:11:361// Copyright 2016 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
clamy64040222017-08-03 18:01:475#include "extensions/browser/extension_navigation_throttle.h"
Jinho Bangb5216cec2018-01-17 19:43:116
7#include <memory>
rdevlin.cronin5f6bebcd2016-09-26 23:11:368#include "base/strings/stringprintf.h"
9#include "chrome/test/base/chrome_render_view_host_test_harness.h"
10#include "components/crx_file/id_util.h"
11#include "content/public/browser/content_browser_client.h"
12#include "content/public/browser/navigation_handle.h"
13#include "content/public/common/content_client.h"
clamy64040222017-08-03 18:01:4714#include "content/public/test/navigation_simulator.h"
rdevlin.cronin5f6bebcd2016-09-26 23:11:3615#include "content/public/test/test_renderer_host.h"
16#include "content/public/test/web_contents_tester.h"
rdevlin.cronin5f6bebcd2016-09-26 23:11:3617#include "extensions/browser/extension_registry.h"
18#include "extensions/common/extension.h"
19#include "extensions/common/extension_builder.h"
20#include "extensions/common/value_builder.h"
21#include "testing/gtest/include/gtest/gtest.h"
22#include "url/gurl.h"
23
24using content::NavigationThrottle;
25
26namespace extensions {
27
28namespace {
29
30const char kAccessible[] = "accessible.html";
31const char kPrivate[] = "private.html";
32const char kAccessibleDir[] = "accessible_dir/*";
33const char kAccessibleDirResource[] = "accessible_dir/foo.html";
34
35class MockBrowserClient : public content::ContentBrowserClient {
36 public:
37 MockBrowserClient() {}
38 ~MockBrowserClient() override {}
39
40 // Only construct an ExtensionNavigationThrottle so that we can test it in
41 // isolation.
avid6d88b912017-01-13 00:16:0042 std::vector<std::unique_ptr<NavigationThrottle>> CreateThrottlesForNavigation(
rdevlin.cronin5f6bebcd2016-09-26 23:11:3643 content::NavigationHandle* handle) override {
avid6d88b912017-01-13 00:16:0044 std::vector<std::unique_ptr<NavigationThrottle>> throttles;
Jinho Bangb5216cec2018-01-17 19:43:1145 throttles.push_back(std::make_unique<ExtensionNavigationThrottle>(handle));
rdevlin.cronin5f6bebcd2016-09-26 23:11:3646 return throttles;
47 }
48};
49
50} // namespace
51
52class ExtensionNavigationThrottleUnitTest
53 : public ChromeRenderViewHostTestHarness {
54 public:
55 ExtensionNavigationThrottleUnitTest() {}
56 void SetUp() override {
57 ChromeRenderViewHostTestHarness::SetUp();
58 original_client_ = content::SetBrowserClientForTesting(&client_);
59 AddExtension();
60 }
61
62 void TearDown() override {
63 content::SetBrowserClientForTesting(original_client_);
64 ChromeRenderViewHostTestHarness::TearDown();
65 }
66
nick7a719cd2017-05-23 20:51:2667 // Checks that trying to navigate the given |host| to |extension_url| results
68 // in the |expected_will_start_result|, and also that navigating to
69 // |extension_url| via http redirect will cancel the request unless
70 // |expected_will_start_result| is PROCEED.
71 void CheckTestCase(
72 content::RenderFrameHost* host,
73 const GURL& extension_url,
Lucas Garron75d2c9f92017-09-12 12:28:2174 NavigationThrottle::ThrottleAction expected_will_start_result) {
nick7a719cd2017-05-23 20:51:2675 // First subtest: direct navigation to |extension_url|.
rdevlin.cronin5f6bebcd2016-09-26 23:11:3676 std::unique_ptr<content::NavigationHandle> handle =
nick7a719cd2017-05-23 20:51:2677 content::NavigationHandle::CreateNavigationHandleForTesting(
78 extension_url, host);
79 EXPECT_EQ(expected_will_start_result,
John Abd-El-Malek028a4fd2018-01-04 15:39:5980 handle->CallWillStartRequestForTesting())
nick7a719cd2017-05-23 20:51:2681 << extension_url;
82
83 // Reset the handle for a second subtest: server redirect to
84 // |extension_url|.
85 GURL http_url("https://example.com");
86 handle = content::NavigationHandle::CreateNavigationHandleForTesting(
87 http_url, host);
88
89 // TODO(nick): https://crbug.com/695421 Once PlzNavigate is enabled 100%, it
90 // should be possible to support return values other than PROCEED and CANCEL
91 // from ExtensionNavigationThrottle::WillRedirectRequest.
Lucas Garron75d2c9f92017-09-12 12:28:2192 NavigationThrottle::ThrottleAction expected_will_redirect_result =
nick7a719cd2017-05-23 20:51:2693 (expected_will_start_result == NavigationThrottle::PROCEED)
94 ? NavigationThrottle::PROCEED
95 : NavigationThrottle::CANCEL;
96 EXPECT_EQ(NavigationThrottle::PROCEED,
John Abd-El-Malek028a4fd2018-01-04 15:39:5997 handle->CallWillStartRequestForTesting())
nick7a719cd2017-05-23 20:51:2698 << http_url;
99 EXPECT_EQ(expected_will_redirect_result,
100 handle->CallWillRedirectRequestForTesting(
101 extension_url,
102 /*new_method_is_post=*/false, http_url,
103 /*new_is_external_protocol=*/false))
104 << extension_url;
rdevlin.cronin5f6bebcd2016-09-26 23:11:36105 }
106
107 const Extension* extension() { return extension_.get(); }
108 content::WebContentsTester* web_contents_tester() {
109 return content::WebContentsTester::For(web_contents());
110 }
111 content::RenderFrameHostTester* render_frame_host_tester(
112 content::RenderFrameHost* host) {
113 return content::RenderFrameHostTester::For(host);
114 }
115
116 private:
117 // Constructs an extension with accessible.html and accessible_dir/* as
118 // accessible resources.
119 void AddExtension() {
120 DictionaryBuilder manifest;
121 manifest.Set("name", "ext")
122 .Set("description", "something")
123 .Set("version", "0.1")
124 .Set("manifest_version", 2)
125 .Set("web_accessible_resources",
126 ListBuilder().Append(kAccessible).Append(kAccessibleDir).Build());
127 extension_ = ExtensionBuilder()
128 .SetManifest(manifest.Build())
129 .SetID(crx_file::id_util::GenerateId("foo"))
130 .Build();
131 ASSERT_TRUE(extension_);
132 ExtensionRegistry::Get(browser_context())->AddEnabled(extension_.get());
133 }
134
135 scoped_refptr<const Extension> extension_;
136 MockBrowserClient client_;
137 content::ContentBrowserClient* original_client_;
138
139 DISALLOW_COPY_AND_ASSIGN(ExtensionNavigationThrottleUnitTest);
140};
141
142// Tests the basic case of an external web page embedding an extension resource.
143TEST_F(ExtensionNavigationThrottleUnitTest, ExternalWebPage) {
144 web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
145 content::RenderFrameHost* child =
146 render_frame_host_tester(main_rfh())->AppendChild("child");
147
148 // Only resources specified in web_accessible_resources should be allowed.
149 CheckTestCase(child, extension()->GetResourceURL(kPrivate),
150 NavigationThrottle::BLOCK_REQUEST);
151 CheckTestCase(child, extension()->GetResourceURL(kAccessible),
152 NavigationThrottle::PROCEED);
153 CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
154 NavigationThrottle::PROCEED);
155}
156
157// Tests that the owning extension can access any of its resources.
158TEST_F(ExtensionNavigationThrottleUnitTest, SameExtension) {
159 web_contents_tester()->NavigateAndCommit(
160 extension()->GetResourceURL("trusted.html"));
161 content::RenderFrameHost* child =
162 render_frame_host_tester(main_rfh())->AppendChild("child");
163
164 // All resources should be allowed.
165 CheckTestCase(child, extension()->GetResourceURL(kPrivate),
166 NavigationThrottle::PROCEED);
167 CheckTestCase(child, extension()->GetResourceURL(kAccessible),
168 NavigationThrottle::PROCEED);
169 CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
170 NavigationThrottle::PROCEED);
171}
172
173// Tests that if any of the ancestors are an external web page, we restrict
174// the resources.
175TEST_F(ExtensionNavigationThrottleUnitTest, WebPageAncestor) {
176 web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
177 content::RenderFrameHost* child =
178 render_frame_host_tester(main_rfh())->AppendChild("subframe1");
179 GURL url = extension()->GetResourceURL(kAccessible);
clamy64040222017-08-03 18:01:47180 child =
181 content::NavigationSimulator::NavigateAndCommitFromDocument(url, child);
rdevlin.cronin5f6bebcd2016-09-26 23:11:36182 content::RenderFrameHost* grand_child =
183 render_frame_host_tester(child)->AppendChild("grandchild");
184
185 // Even though the immediate parent is a trusted frame, we should restrict
186 // to web_accessible_resources since the grand parent is external.
187 CheckTestCase(grand_child, extension()->GetResourceURL(kPrivate),
188 NavigationThrottle::BLOCK_REQUEST);
189 CheckTestCase(grand_child, extension()->GetResourceURL(kAccessible),
190 NavigationThrottle::PROCEED);
191 CheckTestCase(grand_child,
192 extension()->GetResourceURL(kAccessibleDirResource),
193 NavigationThrottle::PROCEED);
194}
195
196// Tests that requests to disabled or non-existent extensions are blocked.
nick6cfe5c72017-05-22 22:00:42197TEST_F(ExtensionNavigationThrottleUnitTest, DisabledExtensionChildFrame) {
rdevlin.cronin5f6bebcd2016-09-26 23:11:36198 web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
199 content::RenderFrameHost* child =
200 render_frame_host_tester(main_rfh())->AppendChild("child");
201
202 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
203 registry->RemoveEnabled(extension()->id());
204 registry->AddDisabled(extension());
205
206 // Since the extension is disabled, all requests should be blocked.
207 CheckTestCase(child, extension()->GetResourceURL(kPrivate),
208 NavigationThrottle::BLOCK_REQUEST);
209 CheckTestCase(child, extension()->GetResourceURL(kAccessible),
210 NavigationThrottle::BLOCK_REQUEST);
211 CheckTestCase(child, extension()->GetResourceURL(kAccessibleDirResource),
212 NavigationThrottle::BLOCK_REQUEST);
213
214 std::string second_id = crx_file::id_util::GenerateId("bar");
215 ASSERT_NE(second_id, extension()->id());
nick6cfe5c72017-05-22 22:00:42216 GURL unknown_url(base::StringPrintf("chrome-extension://%s/accessible.html",
rdevlin.cronin5f6bebcd2016-09-26 23:11:36217 second_id.c_str()));
218 // Requests to non-existent extensions should be blocked.
nick6cfe5c72017-05-22 22:00:42219 CheckTestCase(child, unknown_url, NavigationThrottle::BLOCK_REQUEST);
220
221 // Test blob and filesystem URLs with disabled/unknown extensions.
222 GURL disabled_blob(base::StringPrintf("blob:chrome-extension://%s/SOMEGUID",
223 extension()->id().c_str()));
224 GURL unknown_blob(base::StringPrintf("blob:chrome-extension://%s/SOMEGUID",
225 second_id.c_str()));
226 CheckTestCase(child, disabled_blob, NavigationThrottle::BLOCK_REQUEST);
227 CheckTestCase(child, unknown_blob, NavigationThrottle::BLOCK_REQUEST);
228 GURL disabled_filesystem(
229 base::StringPrintf("filesystem:chrome-extension://%s/temporary/foo.html",
230 extension()->id().c_str()));
231 GURL unknown_filesystem(
232 base::StringPrintf("filesystem:chrome-extension://%s/temporary/foo.html",
233 second_id.c_str()));
234 CheckTestCase(child, disabled_filesystem, NavigationThrottle::BLOCK_REQUEST);
235 CheckTestCase(child, unknown_filesystem, NavigationThrottle::BLOCK_REQUEST);
236}
237
238// Tests that requests to disabled or non-existent extensions are blocked.
239TEST_F(ExtensionNavigationThrottleUnitTest, DisabledExtensionMainFrame) {
240 web_contents_tester()->NavigateAndCommit(GURL("http://example.com"));
241
242 ExtensionRegistry* registry = ExtensionRegistry::Get(browser_context());
243 registry->RemoveEnabled(extension()->id());
244 registry->AddDisabled(extension());
245
246 // Since the extension is disabled, all requests should be blocked.
247 CheckTestCase(main_rfh(), extension()->GetResourceURL(kPrivate),
248 NavigationThrottle::BLOCK_REQUEST);
249 CheckTestCase(main_rfh(), extension()->GetResourceURL(kAccessible),
250 NavigationThrottle::BLOCK_REQUEST);
251 CheckTestCase(main_rfh(), extension()->GetResourceURL(kAccessibleDirResource),
252 NavigationThrottle::BLOCK_REQUEST);
253
254 std::string second_id = crx_file::id_util::GenerateId("bar");
255
256 ASSERT_NE(second_id, extension()->id());
257 GURL unknown_url(base::StringPrintf("chrome-extension://%s/accessible.html",
258 second_id.c_str()));
259 // Requests to non-existent extensions should be blocked.
260 CheckTestCase(main_rfh(), unknown_url, NavigationThrottle::BLOCK_REQUEST);
261
262 // Test blob and filesystem URLs with disabled/unknown extensions.
263 GURL disabled_blob(base::StringPrintf("blob:chrome-extension://%s/SOMEGUID",
264 extension()->id().c_str()));
265 GURL unknown_blob(base::StringPrintf("blob:chrome-extension://%s/SOMEGUID",
266 second_id.c_str()));
267 CheckTestCase(main_rfh(), disabled_blob, NavigationThrottle::BLOCK_REQUEST);
268 CheckTestCase(main_rfh(), unknown_blob, NavigationThrottle::BLOCK_REQUEST);
269 GURL disabled_filesystem(
270 base::StringPrintf("filesystem:chrome-extension://%s/temporary/foo.html",
271 extension()->id().c_str()));
272 GURL unknown_filesystem(
273 base::StringPrintf("filesystem:chrome-extension://%s/temporary/foo.html",
274 second_id.c_str()));
275 CheckTestCase(main_rfh(), disabled_filesystem,
276 NavigationThrottle::BLOCK_REQUEST);
277 CheckTestCase(main_rfh(), unknown_filesystem,
278 NavigationThrottle::BLOCK_REQUEST);
rdevlin.cronin5f6bebcd2016-09-26 23:11:36279}
280
281} // namespace extensions