| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/sync/sync_encryption_keys_tab_helper.h" |
| |
| #include "base/memory/scoped_refptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/signin/chrome_signin_client_factory.h" |
| #include "chrome/browser/signin/test_signin_client_builder.h" |
| #include "chrome/browser/sync/sync_service_factory.h" |
| #include "chrome/common/sync_encryption_keys_extension.mojom.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "components/site_isolation/features.h" |
| #include "content/public/browser/child_process_security_policy.h" |
| #include "content/public/browser/site_isolation_policy.h" |
| #include "content/public/browser/web_contents_observer.h" |
| #include "content/public/test/navigation_simulator.h" |
| #include "content/public/test/prerender_test_util.h" |
| #include "content/public/test/test_renderer_host.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_response_headers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| namespace { |
| |
| class SyncEncryptionKeysTabHelperTest : public ChromeRenderViewHostTestHarness { |
| public: |
| SyncEncryptionKeysTabHelperTest() { |
| // Avoid the disabling of site isolation due to memory constraints, required |
| // on Android so that ApplyGlobalIsolatedOrigins() takes effect regardless |
| // of available memory when running the test (otherwise low-memory bots may |
| // run into test failures). |
| feature_list_.InitAndEnableFeatureWithParameters( |
| site_isolation::features::kSiteIsolationMemoryThresholds, |
| {{site_isolation::features:: |
| kStrictSiteIsolationMemoryThresholdParamName, |
| "0"}, |
| {site_isolation::features:: |
| kPartialSiteIsolationMemoryThresholdParamName, |
| "0"}}); |
| } |
| |
| ~SyncEncryptionKeysTabHelperTest() override = default; |
| |
| SyncEncryptionKeysTabHelperTest(const SyncEncryptionKeysTabHelperTest&) = |
| delete; |
| SyncEncryptionKeysTabHelperTest& operator=( |
| const SyncEncryptionKeysTabHelperTest&) = delete; |
| |
| protected: |
| // content::RenderViewHostTestHarness: |
| void SetUp() override { |
| content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins(); |
| ChromeRenderViewHostTestHarness::SetUp(); |
| SyncEncryptionKeysTabHelper::CreateForWebContents(web_contents()); |
| } |
| |
| void TearDown() override { |
| ChromeRenderViewHostTestHarness::TearDown(); |
| // Undo content::SiteIsolationPolicy::ApplyGlobalIsolatedOrigins(). |
| content::ChildProcessSecurityPolicy::GetInstance() |
| ->ClearIsolatedOriginsForTesting(); |
| } |
| |
| bool HasEncryptionKeysApi(content::RenderFrameHost* rfh) { |
| auto* tab_helper = |
| SyncEncryptionKeysTabHelper::FromWebContents(web_contents()); |
| return tab_helper->HasEncryptionKeysApiForTesting(rfh); |
| } |
| |
| bool HasEncryptionKeysApiInMainFrame() { |
| return HasEncryptionKeysApi(main_rfh()); |
| } |
| |
| content::WebContentsTester* web_contents_tester() { |
| return content::WebContentsTester::For(web_contents()); |
| } |
| |
| TestingProfile::TestingFactories GetTestingFactories() const override { |
| return {{SyncServiceFactory::GetInstance(), |
| SyncServiceFactory::GetDefaultFactory()}, |
| {ChromeSigninClientFactory::GetInstance(), |
| base::BindRepeating(&signin::BuildTestSigninClient)}}; |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| TEST_F(SyncEncryptionKeysTabHelperTest, ShouldExposeMojoApiToAllowedOrigin) { |
| ASSERT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| EXPECT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| TEST_F(SyncEncryptionKeysTabHelperTest, |
| ShouldNotExposeMojoApiToUnallowedOrigin) { |
| web_contents_tester()->NavigateAndCommit(GURL("http://page.com")); |
| EXPECT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| // TODO(https://crbug.com/1394191): flaky on android bots. |
| TEST_F(SyncEncryptionKeysTabHelperTest, |
| DISABLED_ShouldNotExposeMojoApiIfNavigatedAway) { |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| ASSERT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| web_contents_tester()->NavigateAndCommit(GURL("http://page.com")); |
| EXPECT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| TEST_F(SyncEncryptionKeysTabHelperTest, |
| ShouldExposeMojoApiEvenIfSubframeNavigatedAway) { |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| content::RenderFrameHost* subframe = |
| content::RenderFrameHostTester::For(main_rfh())->AppendChild("subframe"); |
| ASSERT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| content::NavigationSimulator::CreateRendererInitiated(GURL("http://page.com"), |
| subframe) |
| ->Commit(); |
| // For the receiver set to be fully updated, a mainframe navigation is needed. |
| // Otherwise the test passes regardless of whether the logic is buggy. |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| EXPECT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| TEST_F(SyncEncryptionKeysTabHelperTest, |
| ShouldNotExposeMojoApiIfNavigationFailed) { |
| auto* render_frame_host = |
| content::NavigationSimulator::NavigateAndFailFromBrowser( |
| web_contents(), GaiaUrls::GetInstance()->gaia_url(), |
| net::ERR_ABORTED); |
| EXPECT_FALSE(HasEncryptionKeysApi(render_frame_host)); |
| EXPECT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| // TODO(https://crbug.com/1394191): flaky on android bots. |
| TEST_F(SyncEncryptionKeysTabHelperTest, |
| DISABLED_ShouldNotExposeMojoApiIfNavigatedAwayToErrorPage) { |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| ASSERT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| auto* render_frame_host = |
| content::NavigationSimulator::NavigateAndFailFromBrowser( |
| web_contents(), GURL("http://page.com"), net::ERR_ABORTED); |
| EXPECT_FALSE(HasEncryptionKeysApi(render_frame_host)); |
| // `net::ERR_ABORTED` doesn't update the main frame and the previous main |
| // frame is still available. So, EncryptionKeysApi is still valid on the main |
| // frame. |
| EXPECT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| render_frame_host = content::NavigationSimulator::NavigateAndFailFromBrowser( |
| web_contents(), GURL("http://page.com"), net::ERR_TIMED_OUT); |
| EXPECT_FALSE(HasEncryptionKeysApi(render_frame_host)); |
| // `net::ERR_TIMED_OUT` commits the error page that is the main frame now. |
| EXPECT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| } |
| |
| class SyncEncryptionKeysTabHelperPrerenderingTest |
| : public SyncEncryptionKeysTabHelperTest { |
| public: |
| SyncEncryptionKeysTabHelperPrerenderingTest() = default; |
| |
| private: |
| content::test::ScopedPrerenderFeatureList prerender_feature_list_; |
| }; |
| |
| // Tests that EncryptionKeys works based on a main frame. A prerendered page |
| // also creates EncryptionKeys as it's a main frame. But, if EncryptionKeys |
| // is accessed thrugh Mojo in prerendering, it causes canceling prerendering. |
| // See the browser test, 'ShouldNotBindEncryptionKeysApiInPrerendering', from |
| // 'sync_encryption_keys_tab_helper_browsertest.cc' for the details about |
| // canceling prerendering. |
| TEST_F(SyncEncryptionKeysTabHelperPrerenderingTest, |
| CreateEncryptionKeysInPrerendering) { |
| content::test::ScopedPrerenderWebContentsDelegate web_contents_delegate( |
| *web_contents()); |
| |
| // Load a page. |
| ASSERT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| web_contents_tester()->NavigateAndCommit(GaiaUrls::GetInstance()->gaia_url()); |
| ASSERT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| // If prerendering happens in the gaia url, EncryptionKeys is created for |
| // the prerendering and the current EncryptionKeys for a primary page also |
| // exists. |
| const GURL kPrerenderingUrl(GaiaUrls::GetInstance()->gaia_url().spec() + |
| "?prerendering"); |
| auto* prerender_rfh = content::WebContentsTester::For(web_contents()) |
| ->AddPrerenderAndCommitNavigation(kPrerenderingUrl); |
| DCHECK_NE(prerender_rfh, nullptr); |
| EXPECT_TRUE(HasEncryptionKeysApi(prerender_rfh)); |
| EXPECT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| content::test::PrerenderHostObserver host_observer(*web_contents(), |
| kPrerenderingUrl); |
| |
| // Activate the prerendered page. |
| auto* activated_rfh = |
| content::NavigationSimulator::NavigateAndCommitFromDocument( |
| kPrerenderingUrl, web_contents()->GetPrimaryMainFrame()); |
| host_observer.WaitForActivation(); |
| EXPECT_TRUE(host_observer.was_activated()); |
| // The EncryptionKeys exists for the activated page. |
| EXPECT_TRUE(HasEncryptionKeysApi(activated_rfh)); |
| |
| // If the prerendering happens to the cross origin, the prerendering would be |
| // canceled. |
| const GURL kCrossOriginPrerenderingUrl(GURL("http://page.com")); |
| int frame_tree_node_id = content::WebContentsTester::For(web_contents()) |
| ->AddPrerender(kCrossOriginPrerenderingUrl); |
| ASSERT_EQ(frame_tree_node_id, content::RenderFrameHost::kNoFrameTreeNodeId); |
| // EncryptionKeys is still valid in a primary page. |
| EXPECT_TRUE(HasEncryptionKeysApiInMainFrame()); |
| |
| // Load a page that doesn't allow EncryptionKeys. |
| auto* rfh = content::NavigationSimulator::NavigateAndCommitFromDocument( |
| kCrossOriginPrerenderingUrl, web_contents()->GetPrimaryMainFrame()); |
| EXPECT_FALSE(HasEncryptionKeysApi(rfh)); |
| EXPECT_FALSE(HasEncryptionKeysApiInMainFrame()); |
| } |
| } // namespace |