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