| // 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 <memory> |
| |
| #include "base/command_line.h" |
| #include "base/path_service.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/net/storage_test_utils.h" |
| #include "chrome/browser/policy/policy_test_utils.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/policy_constants.h" |
| #include "content/public/common/content_paths.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_utils.h" |
| #include "net/base/features.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace policy { |
| |
| namespace { |
| const char kURL[] = "http://example.com"; |
| } // namespace |
| |
| // Test fixture that enables (if param is true) and disables (if param is false) |
| // Schemeful Same-Site to test the Legacy SameSite cookie policy, which controls |
| // behavior of SameSite-by-default, Cookies-without-SameSite-must-be-Secure, and |
| // Schemeful Same-Site. |
| class SameSiteCookiesPolicyTest : public PolicyTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| SameSiteCookiesPolicyTest() |
| : http_server_(net::EmbeddedTestServer::TYPE_HTTP), |
| https_server_(net::EmbeddedTestServer::TYPE_HTTPS) { |
| if (IsSchemefulSameSiteEnabled()) { |
| // No need to explicitly enable, since it's enabled by default. |
| feature_list_.Init(); |
| } else { |
| feature_list_.InitAndDisableFeature(net::features::kSchemefulSameSite); |
| } |
| } |
| |
| ~SameSiteCookiesPolicyTest() override = default; |
| |
| void SetUpOnMainThread() override { |
| PolicyTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| https_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES); |
| http_server_.AddDefaultHandlers(GetChromeTestDataDir()); |
| ASSERT_TRUE(https_server_.Start()); |
| ASSERT_TRUE(http_server_.Start()); |
| } |
| |
| GURL GetURL(const std::string& host, const std::string& path, bool secure) { |
| if (secure) |
| return https_server_.GetURL(host, path); |
| |
| return http_server_.GetURL(host, path); |
| } |
| |
| GURL GetURL(const std::string& host, bool secure) { |
| return GetURL(host, "/", secure); |
| } |
| |
| content::RenderFrameHost* GetChildFrame() { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| return ChildFrameAt(web_contents->GetMainFrame(), 0); |
| } |
| |
| content::RenderFrameHost* GetMainFrame() { |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| return web_contents->GetMainFrame(); |
| } |
| |
| void NavigateToHttpPageWithFrame(const std::string& host) { |
| GURL main_url(http_server_.GetURL(host, "/iframe.html")); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url)); |
| } |
| |
| void NavigateFrameToHttps(const std::string& host, const std::string& path) { |
| GURL page = https_server_.GetURL(host, path); |
| content::WebContents* web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| EXPECT_TRUE(NavigateIframeToURL(web_contents, "test", page)); |
| } |
| |
| void ExpectFrameContent(content::RenderFrameHost* frame, |
| const std::string& expected) { |
| storage::test::ExpectFrameContent(frame, expected); |
| } |
| |
| bool IsSchemefulSameSiteEnabled() { return GetParam(); } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| net::test_server::EmbeddedTestServer http_server_; |
| net::test_server::EmbeddedTestServer https_server_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(SameSiteCookiesPolicyTest, |
| AllowLegacyCookieAccessForDomain) { |
| GURL legacy_allowed_domain_url(kURL); |
| GURL other_domain_url("http://other-domain.example"); |
| |
| // Set a policy to allow Legacy cookie access for one domain only. |
| base::Value policy_value(base::Value::Type::LIST); |
| policy_value.Append(legacy_allowed_domain_url.host()); |
| |
| PolicyMap policies; |
| // Set a policy to allow Legacy access for the given domain only. |
| SetPolicy(&policies, key::kLegacySameSiteCookieBehaviorEnabledForDomainList, |
| std::move(policy_value)); |
| UpdateProviderPolicy(policies); |
| |
| Profile* profile = browser()->profile(); |
| |
| // No cookies at startup |
| ASSERT_TRUE(content::GetCookies(profile, legacy_allowed_domain_url).empty()); |
| ASSERT_TRUE(content::GetCookies(profile, other_domain_url).empty()); |
| |
| // Set a cookie from a same-site context. The cookie does not specify |
| // SameSite, so it may default to Lax if the SameSite features are enabled. |
| // Since the context used is same-site, it should always work. |
| EXPECT_TRUE(content::SetCookie(profile, legacy_allowed_domain_url, |
| "samesite-unspecified=1", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::SAME_SITE_LAX))); |
| EXPECT_EQ("samesite-unspecified=1", |
| content::GetCookies(profile, legacy_allowed_domain_url)); |
| // Do the same on the other domain... |
| EXPECT_TRUE(content::SetCookie(profile, other_domain_url, |
| "samesite-unspecified=1", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::SAME_SITE_LAX))); |
| EXPECT_EQ("samesite-unspecified=1", |
| content::GetCookies(profile, other_domain_url)); |
| |
| // Overwrite the cookie from a cross-site context. Because we have a policy |
| // that allows Legacy access for one domain but not the other, this will work |
| // on the policy-specified domain even if SameSite features are enabled, but |
| // it will not work for the other domain. (It works regardless, if they are |
| // disabled.) |
| EXPECT_TRUE(content::SetCookie( |
| profile, legacy_allowed_domain_url, "samesite-unspecified=2", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE))); |
| EXPECT_EQ("samesite-unspecified=2", |
| content::GetCookies(profile, legacy_allowed_domain_url)); |
| EXPECT_EQ("samesite-unspecified=2", |
| content::GetCookies(profile, legacy_allowed_domain_url, |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::CROSS_SITE))); |
| // When Schemeful Same-Site is enabled a context downgrade to an insufficient |
| // context should still be allowed with legacy access. This'll always work if |
| // Schemeful Same-Site is disabled because the schemeless context is Lax |
| // which is sufficient. |
| EXPECT_TRUE(content::SetCookie( |
| profile, legacy_allowed_domain_url, "samesite-lax=1; SameSite=Lax", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX, |
| net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE))); |
| // Similarly when we try to get the cookie. |
| EXPECT_THAT( |
| content::GetCookies(profile, legacy_allowed_domain_url, |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::SAME_SITE_LAX, |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::CROSS_SITE)), |
| testing::HasSubstr("samesite-lax=1")); |
| |
| // Setting the cookie from a cross-site context on the domain without the |
| // policy does not work, because it defaults to Lax. |
| EXPECT_FALSE(content::SetCookie( |
| profile, other_domain_url, "samesite-unspecified=2", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE))); |
| // If we get the cookie from a same-site context, the old value is still |
| // present. |
| EXPECT_EQ("samesite-unspecified=1", |
| content::GetCookies(profile, other_domain_url)); |
| // The cookie cannot be accessed at all from a cross-site context. |
| EXPECT_EQ("", |
| content::GetCookies(profile, other_domain_url, |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::CROSS_SITE))); |
| |
| // Setting a Lax cookie from a downgraded context only works if Schemeful |
| // Same-Site is not enabled. |
| EXPECT_EQ(!IsSchemefulSameSiteEnabled(), |
| content::SetCookie(profile, other_domain_url, |
| "samesite-lax=1; SameSite=Lax", |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::SAME_SITE_LAX, |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::CROSS_SITE))); |
| // We should be able to get the cookie which was previously added. |
| EXPECT_EQ(IsSchemefulSameSiteEnabled() |
| ? "samesite-unspecified=1" |
| : "samesite-unspecified=1; samesite-lax=1", |
| content::GetCookies(profile, other_domain_url)); |
| // But no cookies should be returned for a downgrade to an insufficient |
| // context, since SameSite-by-default is active which requires a minimum of |
| // a Lax context. |
| EXPECT_EQ(IsSchemefulSameSiteEnabled() |
| ? "" |
| : "samesite-unspecified=1; samesite-lax=1", |
| content::GetCookies(profile, other_domain_url, |
| net::CookieOptions::SameSiteCookieContext( |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::SAME_SITE_LAX, |
| net::CookieOptions::SameSiteCookieContext:: |
| ContextType::CROSS_SITE))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameSiteCookiesPolicyTest, |
| AllowCrossSchemeFrameLegacyCookies) { |
| PolicyMap policies; |
| // Set a policy to force legacy access for our cookies. |
| base::Value policy_value(base::Value::Type::LIST); |
| policy_value.Append(GURL("http://a.test").host()); |
| SetPolicy(&policies, key::kLegacySameSiteCookieBehaviorEnabledForDomainList, |
| std::move(policy_value)); |
| PolicyTest::UpdateProviderPolicy(policies); |
| |
| // Set a cookie that will only be sent with legacy behavior. |
| content::SetCookie(browser()->profile(), GetURL("a.test", false), |
| "strictcookie=1;SameSite=Strict"); |
| |
| // Construct a cross-scheme same domain iframe (Main frame http://a.test, |
| // iframe https://a.test). |
| // |
| // Start by navigating to an insecure page with an iframe. |
| NavigateToHttpPageWithFrame("a.test"); |
| storage::test::ExpectCookiesOnHost(browser()->profile(), |
| GetURL("a.test", false /* secure */), |
| "strictcookie=1"); |
| |
| // Then navigate the frame to a secure page and check to see if the cookie is |
| // sent. |
| NavigateFrameToHttps("a.test", "/echoheader?cookie"); |
| // The legacy cookie should have been sent. |
| ExpectFrameContent(GetChildFrame(), "strictcookie=1"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameSiteCookiesPolicyTest, |
| DisallowCrossSchemeFrameNonLegacyCookies) { |
| // Don't set a policy, this results in the cookies having behavior dependent |
| // on the base::Feature state. |
| |
| // Set a cookie that will only be sent with legacy behavior. |
| content::SetCookie(browser()->profile(), GetURL("a.test", false), |
| "strictcookie=1;SameSite=Strict"); |
| |
| // Construct a cross-scheme same domain iframe (Main frame http://a.test, |
| // iframe https://a.test). |
| // |
| // Start by navigating to an insecure page with an iframe. The cookie will |
| // always be present because it is a same-schemeful-site context. |
| NavigateToHttpPageWithFrame("a.test"); |
| storage::test::ExpectCookiesOnHost(browser()->profile(), |
| GetURL("a.test", false /* secure */), |
| "strictcookie=1"); |
| |
| // Then navigate the frame to a secure page and check to see if the cookie is |
| // sent. |
| NavigateFrameToHttps("a.test", "/echoheader?cookie"); |
| // The cookie will be sent only if Schemeful Same-Site is not active. |
| ExpectFrameContent(GetChildFrame(), |
| IsSchemefulSameSiteEnabled() ? "None" : "strictcookie=1"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameSiteCookiesPolicyTest, |
| AllowStrictOnCrossSchemeNavigation) { |
| PolicyMap policies; |
| // Set a policy to force legacy access for our cookies. |
| base::Value policy_value(base::Value::Type::LIST); |
| policy_value.Append(GURL("http://a.test").host()); |
| SetPolicy(&policies, key::kLegacySameSiteCookieBehaviorEnabledForDomainList, |
| std::move(policy_value)); |
| PolicyTest::UpdateProviderPolicy(policies); |
| |
| // Set a cookie that will only be sent with legacy behavior. |
| content::SetCookie(browser()->profile(), GetURL("a.test", true), |
| "strictcookie=1;SameSite=Strict"); |
| |
| // Just go somewhere on http://a.test. Doesn't matter where. |
| NavigateToHttpPageWithFrame("a.test"); |
| |
| GURL secure_echo_url = GetURL("a.test", "/echoheader?cookie", true); |
| ASSERT_TRUE(NavigateToURLFromRenderer(GetMainFrame(), secure_echo_url)); |
| |
| ExpectFrameContent(GetMainFrame(), "strictcookie=1"); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(SameSiteCookiesPolicyTest, |
| DisallowStrictOnCrossSchemeNavigation) { |
| // Don't set a policy, this results in the cookies having behavior dependent |
| // on the base::Feature state. |
| |
| // Set a cookie that will only be sent with legacy behavior. |
| content::SetCookie(browser()->profile(), GetURL("a.test", false), |
| "strictcookie=1;SameSite=Strict"); |
| |
| // Just go somewhere on http://a.test. Doesn't matter where. |
| NavigateToHttpPageWithFrame("a.test"); |
| |
| GURL secure_echo_url = GetURL("a.test", "/echoheader?cookie", true); |
| ASSERT_TRUE(NavigateToURLFromRenderer(GetMainFrame(), secure_echo_url)); |
| |
| ExpectFrameContent(GetMainFrame(), |
| IsSchemefulSameSiteEnabled() ? "None" : "strictcookie=1"); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, SameSiteCookiesPolicyTest, ::testing::Bool()); |
| |
| } // namespace policy |