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