Portals navigation throttle for 1P mode.
When enabled, this provides browser-side enforcement precluding navigations
in a portal to any origin other than the origin of its host.
Bug: 1013389
Change-Id: Ia7b2a94463b3fb4ba68fcf63f0e6e56342ca20ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1854150
Commit-Queue: Jeremy Roman <[email protected]>
Reviewed-by: Kinuko Yasuda <[email protected]>
Reviewed-by: Kevin McNee <[email protected]>
Cr-Commit-Position: refs/heads/master@{#708249}
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
index c654fea0..3d393cff 100644
--- a/content/browser/BUILD.gn
+++ b/content/browser/BUILD.gn
@@ -1314,6 +1314,8 @@
"picture_in_picture/picture_in_picture_window_controller_impl.h",
"portal/portal.cc",
"portal/portal.h",
+ "portal/portal_navigation_throttle.cc",
+ "portal/portal_navigation_throttle.h",
"presentation/presentation_service_impl.cc",
"presentation/presentation_service_impl.h",
"process_internals/process_internals_handler_impl.cc",
diff --git a/content/browser/frame_host/navigation_throttle_runner.cc b/content/browser/frame_host/navigation_throttle_runner.cc
index 8727dc0..972ba307 100644
--- a/content/browser/frame_host/navigation_throttle_runner.cc
+++ b/content/browser/frame_host/navigation_throttle_runner.cc
@@ -14,6 +14,7 @@
#include "content/browser/frame_host/navigator_delegate.h"
#include "content/browser/frame_host/origin_policy_throttle.h"
#include "content/browser/frame_host/webui_navigation_throttle.h"
+#include "content/browser/portal/portal_navigation_throttle.h"
#include "content/public/browser/navigation_handle.h"
namespace content {
@@ -116,6 +117,9 @@
// Handle Origin Policy (if enabled)
AddThrottle(OriginPolicyThrottle::MaybeCreateThrottleFor(handle_));
+ // Block certain requests that are not permitted for portals.
+ AddThrottle(PortalNavigationThrottle::MaybeCreateThrottleFor(handle_));
+
for (auto& throttle :
devtools_instrumentation::CreateNavigationThrottles(handle_)) {
AddThrottle(std::move(throttle));
diff --git a/content/browser/portal/portal_navigation_throttle.cc b/content/browser/portal/portal_navigation_throttle.cc
new file mode 100644
index 0000000..200203c
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle.cc
@@ -0,0 +1,70 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "content/browser/portal/portal_navigation_throttle.h"
+
+#include "base/feature_list.h"
+#include "base/memory/ptr_util.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/portal/portal.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/browser/navigation_handle.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+#include "url/origin.h"
+
+namespace content {
+
+// static
+std::unique_ptr<PortalNavigationThrottle>
+PortalNavigationThrottle::MaybeCreateThrottleFor(
+ NavigationHandle* navigation_handle) {
+ if (!IsEnabled() || !navigation_handle->IsInMainFrame())
+ return nullptr;
+
+ return base::WrapUnique(new PortalNavigationThrottle(navigation_handle));
+}
+
+// static
+bool PortalNavigationThrottle::IsEnabled() {
+ return Portal::IsEnabled() &&
+ !base::FeatureList::IsEnabled(blink::features::kPortalsCrossOrigin);
+}
+
+PortalNavigationThrottle::PortalNavigationThrottle(
+ NavigationHandle* navigation_handle)
+ : NavigationThrottle(navigation_handle) {}
+
+PortalNavigationThrottle::~PortalNavigationThrottle() = default;
+
+const char* PortalNavigationThrottle::GetNameForLogging() {
+ return "PortalNavigationThrottle";
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillStartRequest() {
+ return WillStartOrRedirectRequest();
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillRedirectRequest() {
+ return WillStartOrRedirectRequest();
+}
+
+NavigationThrottle::ThrottleCheckResult
+PortalNavigationThrottle::WillStartOrRedirectRequest() {
+ WebContentsImpl* web_contents =
+ static_cast<WebContentsImpl*>(navigation_handle()->GetWebContents());
+ Portal* portal = web_contents->portal();
+ if (!portal)
+ return PROCEED;
+
+ url::Origin origin = url::Origin::Create(navigation_handle()->GetURL());
+ url::Origin first_party_origin =
+ portal->owner_render_frame_host()->GetLastCommittedOrigin();
+
+ return origin == first_party_origin ? PROCEED : BLOCK_REQUEST;
+}
+
+} // namespace content
diff --git a/content/browser/portal/portal_navigation_throttle.h b/content/browser/portal/portal_navigation_throttle.h
new file mode 100644
index 0000000..50572869
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle.h
@@ -0,0 +1,47 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
+#define CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
+
+#include <memory>
+
+#include "base/auto_reset.h"
+#include "content/common/content_export.h"
+#include "content/public/browser/navigation_throttle.h"
+
+namespace content {
+
+// When enabled, restricts navigation within a portal main frame to only the
+// origin of its host. This allows a more limited testing mode of the portals
+// feature, in which third-party (cross-origin) content cannot be loaded.
+//
+// This throttle is enabled only when portals are enabled but third-party
+// portals are not, and provides enforcement of that state.
+//
+// Note: This has complicated interactions with portal activation, which are not
+// yet resolved. See https://crbug.com/1013389.
+class CONTENT_EXPORT PortalNavigationThrottle : public NavigationThrottle {
+ public:
+ static std::unique_ptr<PortalNavigationThrottle> MaybeCreateThrottleFor(
+ NavigationHandle* navigation_handle);
+
+ ~PortalNavigationThrottle() override;
+
+ // NavigationThrottle
+ const char* GetNameForLogging() override;
+ ThrottleCheckResult WillStartRequest() override;
+ ThrottleCheckResult WillRedirectRequest() override;
+
+ private:
+ static bool IsEnabled();
+
+ PortalNavigationThrottle(NavigationHandle* navigation_handle);
+
+ ThrottleCheckResult WillStartOrRedirectRequest();
+};
+
+} // namespace content
+
+#endif // CONTENT_BROWSER_PORTAL_PORTAL_NAVIGATION_THROTTLE_H_
diff --git a/content/browser/portal/portal_navigation_throttle_browsertest.cc b/content/browser/portal/portal_navigation_throttle_browsertest.cc
new file mode 100644
index 0000000..04caffb
--- /dev/null
+++ b/content/browser/portal/portal_navigation_throttle_browsertest.cc
@@ -0,0 +1,251 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/bind.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_feature_list.h"
+#include "content/browser/frame_host/render_frame_host_impl.h"
+#include "content/browser/portal/portal.h"
+#include "content/browser/portal/portal_created_observer.h"
+#include "content/browser/portal/portal_navigation_throttle.h"
+#include "content/browser/web_contents/web_contents_impl.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "net/base/escape.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/blink/public/common/features.h"
+#include "url/gurl.h"
+
+namespace content {
+namespace {
+
+// TODO(jbroman): Perhaps this would be a useful utility generally.
+GURL GetServerRedirectURL(const net::EmbeddedTestServer* server,
+ const std::string& hostname,
+ const GURL& destination) {
+ return server->GetURL(
+ hostname,
+ "/server-redirect?" +
+ net::EscapeQueryParamValue(destination.spec(), /*use_plus=*/false));
+}
+
+class PortalNavigationThrottleBrowserTest : public ContentBrowserTest {
+ protected:
+ void SetUp() override {
+ scoped_feature_list_.InitWithFeatures(
+ /*enabled_features=*/{blink::features::kPortals},
+ /*disabled_features=*/{blink::features::kPortalsCrossOrigin});
+ ContentBrowserTest::SetUp();
+ }
+
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ContentBrowserTest::SetUpOnMainThread();
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &net::test_server::HandlePrefixedRequest, "/notreached",
+ base::BindRepeating(
+ [](const net::test_server::HttpRequest& r)
+ -> std::unique_ptr<net::test_server::HttpResponse> {
+ ADD_FAILURE() << "/notreached was requested";
+ return nullptr;
+ })));
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+
+ WebContentsImpl* GetWebContents() {
+ return static_cast<WebContentsImpl*>(shell()->web_contents());
+ }
+ RenderFrameHostImpl* GetMainFrame() {
+ return GetWebContents()->GetMainFrame();
+ }
+
+ Portal* InsertAndWaitForPortal(const GURL& url,
+ bool expected_to_succeed = true) {
+ TestNavigationObserver navigation_observer(url);
+ navigation_observer.StartWatchingNewWebContents();
+ PortalCreatedObserver portal_created_observer(GetMainFrame());
+ EXPECT_TRUE(ExecJs(
+ GetMainFrame(),
+ base::StringPrintf("var portal = document.createElement('portal');\n"
+ "portal.src = '%s';\n"
+ "document.body.appendChild(portal);",
+ url.spec().c_str())));
+ Portal* portal = portal_created_observer.WaitUntilPortalCreated();
+ navigation_observer.StopWatchingNewWebContents();
+ navigation_observer.Wait();
+ EXPECT_EQ(navigation_observer.last_navigation_succeeded(),
+ expected_to_succeed);
+ return portal;
+ }
+
+ bool NavigatePortalViaSrcAttribute(Portal* portal,
+ const GURL& url,
+ int number_of_navigations) {
+ TestNavigationObserver navigation_observer(portal->GetPortalContents(),
+ number_of_navigations);
+ EXPECT_TRUE(
+ ExecJs(GetMainFrame(),
+ base::StringPrintf(
+ "document.querySelector('body > portal').src = '%s';",
+ url.spec().c_str())));
+ navigation_observer.WaitForNavigationFinished();
+ return navigation_observer.last_navigation_succeeded();
+ }
+
+ bool NavigatePortalViaLocationHref(Portal* portal,
+ const GURL& url,
+ int number_of_navigations) {
+ TestNavigationObserver navigation_observer(portal->GetPortalContents(),
+ number_of_navigations);
+ EXPECT_TRUE(ExecJs(
+ portal->GetPortalContents(),
+ base::StringPrintf("location.href = '%s';", url.spec().c_str())));
+ navigation_observer.WaitForNavigationFinished();
+ return navigation_observer.last_navigation_succeeded();
+ }
+
+ private:
+ base::test::ScopedFeatureList scoped_feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ SameOriginInitialNavigation) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+ EXPECT_NE(portal, nullptr);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ CrossOriginInitialNavigation) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("not.portal.test", "/title2.html"),
+ /*expected_to_succeed=*/false);
+ EXPECT_NE(portal, nullptr);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ SameOriginNavigation) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("portal.test", "/title3.html");
+ EXPECT_TRUE(NavigatePortalViaSrcAttribute(portal, destination_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ SameOriginNavigationTriggeredByPortal) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("portal.test", "/title3.html");
+ EXPECT_TRUE(NavigatePortalViaLocationHref(portal, destination_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ SameOriginNavigationWithServerRedirect) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("portal.test", "/title3.html");
+ GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+ "portal.test", destination_url);
+ EXPECT_TRUE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ CrossOriginNavigation) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("not.portal.test", "/notreached");
+ EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, destination_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ CrossOriginNavigationTriggeredByPortal) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("not.portal.test", "/notreached");
+ EXPECT_FALSE(NavigatePortalViaLocationHref(portal, destination_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ CrossOriginNavigationViaRedirect) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("not.portal.test", "/notreached");
+ GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+ "portal.test", destination_url);
+ EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(),
+ destination_url);
+}
+
+IN_PROC_BROWSER_TEST_F(PortalNavigationThrottleBrowserTest,
+ CrossOriginRedirectLeadingBack) {
+ ASSERT_TRUE(NavigateToURL(
+ GetWebContents(),
+ embedded_test_server()->GetURL("portal.test", "/title1.html")));
+ Portal* portal = InsertAndWaitForPortal(
+ embedded_test_server()->GetURL("portal.test", "/title2.html"));
+
+ GURL destination_url =
+ embedded_test_server()->GetURL("portal.test", "/notreached");
+ GURL redirect_url = GetServerRedirectURL(embedded_test_server(),
+ "not.portal.test", destination_url);
+ EXPECT_FALSE(NavigatePortalViaSrcAttribute(portal, redirect_url, 1));
+ EXPECT_EQ(portal->GetPortalContents()->GetLastCommittedURL(), redirect_url);
+}
+
+} // namespace
+} // namespace content