blob: 190c36f0e9fd1d73e6d6710b349cae1f881738cf [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "content/browser/devtools/protocol/devtools_protocol_test_support.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents.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/content_browser_test_utils.h"
#include "content/public/test/frame_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h"
#include "net/base/features.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/mojom/cookie_manager.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
using ::testing::IsEmpty;
using ::testing::Key;
using ::testing::UnorderedElementsAre;
// This file contains tests for cookie access via HTTP requests.
// See also (tests for cookie access via JavaScript):
// //content/browser/renderer_host/cookie_browsertest.cc
constexpr std::string_view kHostA = "a.test";
constexpr std::string_view kHostB = "b.test";
constexpr std::string_view kHostC = "c.test";
constexpr std::string_view kSameSiteNoneCookieName = "samesite_none_cookie";
constexpr std::string_view kSameSiteStrictCookieName = "samesite_strict_cookie";
constexpr std::string_view kSameSiteLaxCookieName = "samesite_lax_cookie";
constexpr std::string_view kSameSiteUnspecifiedCookieName =
"samesite_unspecified_cookie";
constexpr std::string_view kHostPrefixCookieName = "__Host-prefixed_cookie";
constexpr std::string_view kSecurePrefixCookieName = "__Secure-prefixed_cookie";
constexpr std::string_view kEchoCookiesWithCorsPath = "/echocookieswithcors";
std::string FrameTreeForHostAndUrl(std::string_view host, const GURL& url) {
return base::StrCat({host, "(", url.spec(), ")"});
}
std::string FrameTreeForUrl(const GURL& url) {
return FrameTreeForHostAndUrl(kHostA, url);
}
GURL RedirectUrl(net::EmbeddedTestServer* test_server,
std::string_view host,
const GURL& target_url) {
return test_server->GetURL(host, "/server-redirect?" + target_url.spec());
}
class HttpCookieBrowserTest : public ContentBrowserTest,
public ::testing::WithParamInterface<bool> {
public:
HttpCookieBrowserTest() : https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {
feature_list_.InitWithFeatureState(
net::features::kCookieSameSiteConsidersRedirectChain,
DoesSameSiteConsiderRedirectChain());
}
~HttpCookieBrowserTest() override = default;
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server()->AddDefaultHandlers(GetTestDataFilePath());
embedded_test_server()->AddDefaultHandlers(GetTestDataFilePath());
ASSERT_TRUE(https_server()->Start());
ASSERT_TRUE(embedded_test_server()->Start());
}
bool DoesSameSiteConsiderRedirectChain() { return GetParam(); }
const std::string kSetSameSiteCookiesPath = base::StrCat({
"/set-cookie?",
kSameSiteStrictCookieName,
"=1;SameSite=Strict&",
kSameSiteLaxCookieName,
"=1;SameSite=Lax&",
kSameSiteUnspecifiedCookieName,
"=1&",
kSameSiteNoneCookieName,
"=1;Secure;SameSite=None",
});
void SetSameSiteCookies(std::string_view host) {
BrowserContext* context = shell()->web_contents()->GetBrowserContext();
EXPECT_TRUE(SetCookie(
context, https_server()->GetURL(host, "/"),
base::StrCat({kSameSiteStrictCookieName, "=1; samesite=strict"})));
EXPECT_TRUE(
SetCookie(context, https_server()->GetURL(host, "/"),
base::StrCat({kSameSiteLaxCookieName, "=1; samesite=lax"})));
EXPECT_TRUE(SetCookie(
context, https_server()->GetURL(host, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1; samesite=none; secure"})));
EXPECT_TRUE(
SetCookie(context, https_server()->GetURL(host, "/"),
base::StrCat({kSameSiteUnspecifiedCookieName, "=1"})));
}
// Gets a path that causes the EmbeddedTestServer to attempt to set prefixed
// cookies (some valid, some not).
std::string GetSetPrefixedCookiesPath(std::string_view host) {
return base::StrCat({
"/set-cookie?",
kSecurePrefixCookieName,
"=1;Secure&",
kHostPrefixCookieName,
"=1;Secure;Path=/&",
"__Secure-missing-attr=1&",
"__Host-wrong-path=1;Secure&",
"__Host-wrong-domain=1;Secure;Domain=",
host,
"&",
"__Host-wrong-secure=1;Path=/&",
});
}
// Sets some (valid) prefixed cookies.
void SetPrefixedCookies(std::string_view host,
net::EmbeddedTestServer* test_server = nullptr) {
if (!test_server) {
test_server = https_server();
}
BrowserContext* context = shell()->web_contents()->GetBrowserContext();
ASSERT_TRUE(
SetCookie(context, test_server->GetURL(host, "/"),
base::StrCat({kHostPrefixCookieName, "=1; Secure; Path=/"})));
ASSERT_TRUE(
SetCookie(context, test_server->GetURL(host, "/"),
base::StrCat({kSecurePrefixCookieName, "=1; Secure"})));
}
GURL EchoCookiesUrl(net::EmbeddedTestServer* test_server,
std::string_view host) {
return test_server->GetURL(host, "/echoheader?Cookie");
}
GURL SetSameSiteCookiesUrl(net::EmbeddedTestServer* test_server,
std::string_view host) {
return test_server->GetURL(host, kSetSameSiteCookiesPath);
}
std::string ExtractFrameContent(RenderFrameHost* frame) const {
return EvalJs(frame, "document.body.textContent").ExtractString();
}
uint32_t ClearCookies() {
return DeleteCookies(shell()->web_contents()->GetBrowserContext(),
network::mojom::CookieDeletionFilter());
}
net::EmbeddedTestServer* https_server() { return &https_server_; }
WebContents* web_contents() { return shell()->web_contents(); }
// is_user_initiated_navigation - true if user initiates navigation to 404,
// - false script initiates this navigation
// is_cross_site_navigation - true if testing in a cross-site context,
// - false if testing in a same-site context
// is_user_initiated_reload - true if user initates the reload,
// false if the reload via script
std::string Test404ReloadCookie(bool is_user_initiated_navigation,
bool is_cross_site_navigation,
bool is_user_initiated_reload) {
// Set target as A or B and cookies based on cross_site param
std::string_view target = is_cross_site_navigation ? kHostB : kHostA;
GURL target_URL =
https_server()->GetURL(target, "/echo-cookie-with-status?status=404");
SetSameSiteCookies(target);
// Start at a website A
EXPECT_TRUE(
NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));
// Navigate method based on whether user or script initiated
if (is_user_initiated_navigation) {
EXPECT_TRUE(NavigateToURL(web_contents(), target_URL));
} else {
EXPECT_TRUE(NavigateToURLFromRenderer(web_contents(), target_URL));
}
// Trigger either user or script reload
TestNavigationObserver nav_observer(web_contents());
if (is_user_initiated_reload) {
shell()->Reload();
} else {
ExecuteScriptAsync(
web_contents()->GetPrimaryMainFrame(),
content::JsReplace("window.location.reload($1);", true));
}
nav_observer.Wait();
// Return the cookies rendered on frame
return ExtractFrameContent(web_contents()->GetPrimaryMainFrame());
}
private:
net::test_server::EmbeddedTestServer https_server_;
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendSameSiteCookies) {
SetSameSiteCookies(kHostA);
SetSameSiteCookies(kHostB);
// Main frame browser-initiated navigation sends all SameSite cookies.
ASSERT_TRUE(
NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
// Main frame same-site (A => A) navigation sends all SameSite cookies.
ASSERT_TRUE(
NavigateToURLFromRenderer(web_contents()->GetPrimaryMainFrame(),
EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
// Main frame cross-site (A => B) navigation sends all but Strict cookies.
ASSERT_TRUE(
NavigateToURLFromRenderer(web_contents()->GetPrimaryMainFrame(),
EchoCookiesUrl(https_server(), kHostB)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
// Same-site iframe (A embedded in A) sends all SameSite cookies.
EXPECT_THAT(
content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(https_server(), kHostA)), {0}),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
// Cross-site iframe (B embedded in A) sends only None cookies.
EXPECT_THAT(
content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(https_server(), kHostB)), {0}),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendSameSiteCookies_Redirect) {
SetSameSiteCookies(kHostA);
// Main frame same-site redirect (A->A) sends all SameSite cookies.
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostA,
EchoCookiesUrl(https_server(), kHostA)),
/*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
if (DoesSameSiteConsiderRedirectChain()) {
// Main frame cross-site redirect (B->A) sends Lax but not Strict SameSite
// cookies...
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA)),
/*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
// ... even if the first URL is same-site. (A->B->A)
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostA,
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA))),
/*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
} else {
// If redirect chains are not considered, then cross-site redirects do not
// make the request cross-site.
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA)),
/*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostA,
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA))),
/*expected_commit_url=*/EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
}
// A same-site redirected iframe (A->A embedded in A) sends all SameSite
// cookies.
EXPECT_THAT(
content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(RedirectUrl(https_server(), kHostA,
EchoCookiesUrl(https_server(), kHostA))),
{0}),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
if (DoesSameSiteConsiderRedirectChain()) {
// A cross-site redirected iframe in a same-site context (B->A embedded in
// A) does not send SameSite cookies...
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA))),
{0}),
net::CookieStringIs(
UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// ... even if the first URL is same-site. (A->B->A embedded in A)
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(RedirectUrl(
https_server(), kHostA,
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA)))),
{0}),
net::CookieStringIs(
UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
} else {
// If redirect chains are not considered, then cross-site redirects do not
// make the request cross-site.
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA))),
{0}),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(
RedirectUrl(https_server(), kHostB,
EchoCookiesUrl(https_server(), kHostA))),
{0}),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
}
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetSameSiteCookies) {
// Main frame can set all SameSite cookies.
ASSERT_TRUE(NavigateToURL(
web_contents(), https_server()->GetURL(kHostA, kSetSameSiteCookiesPath)));
EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
// Same-site iframe (A embedded in A) sets all SameSite cookies.
const GURL url_a = SetSameSiteCookiesUrl(https_server(), kHostA);
EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
web_contents(), https_server(), FrameTreeForUrl(url_a),
url::Origin::Create(url_a).GetURL()),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
// Cross-site iframe (B embedded in A) sets only None cookies.
const GURL url_b = SetSameSiteCookiesUrl(https_server(), kHostB);
EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
web_contents(), https_server(), FrameTreeForUrl(url_b),
url::Origin::Create(url_b).GetURL()),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteNoneCookieName)));
ASSERT_EQ(1U, ClearCookies());
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetSameSiteCookies_Redirect) {
// Same-site redirected main frame navigation can set all SameSite cookies.
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostA,
SetSameSiteCookiesUrl(https_server(), kHostA)),
/*expected_commit_url=*/SetSameSiteCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
// Cross-site redirected main frame navigation can set all SameSite cookies.
ASSERT_TRUE(NavigateToURL(
web_contents(),
RedirectUrl(https_server(), kHostB,
SetSameSiteCookiesUrl(https_server(), kHostA)),
/*expected_commit_url=*/SetSameSiteCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
// A same-site redirected iframe sets all SameSite cookies.
EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
web_contents(), https_server(),
FrameTreeForUrl(RedirectUrl(
https_server(), kHostA,
SetSameSiteCookiesUrl(https_server(), kHostA))),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
if (DoesSameSiteConsiderRedirectChain()) {
// A cross-site redirected iframe only sets SameSite=None cookies.
EXPECT_THAT(content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
web_contents(), https_server(),
FrameTreeForUrl(RedirectUrl(
https_server(), kHostB,
SetSameSiteCookiesUrl(https_server(), kHostA))),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteNoneCookieName)));
ASSERT_EQ(1U, ClearCookies());
} else {
EXPECT_THAT(
content::ArrangeFramesAndGetCanonicalCookiesForLeaf(
web_contents(), https_server(),
FrameTreeForUrl(
RedirectUrl(https_server(), kHostB,
SetSameSiteCookiesUrl(https_server(), kHostA))),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kSameSiteStrictCookieName),
net::MatchesCookieWithName(kSameSiteLaxCookieName),
net::MatchesCookieWithName(kSameSiteNoneCookieName),
net::MatchesCookieWithName(kSameSiteUnspecifiedCookieName)));
ASSERT_EQ(4U, ClearCookies());
}
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendPrefixedCookies) {
SetPrefixedCookies(kHostA);
// Main frame browser-initiated navigation sends all prefixed cookies.
ASSERT_TRUE(
NavigateToURL(web_contents(), EchoCookiesUrl(https_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSecurePrefixCookieName), Key(kHostPrefixCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
SendPrefixedCookies_OmitsIfInsecure) {
SetPrefixedCookies(kHostA);
// Main frame browser-initiated navigation omits all prefixed cookies on
// insecure connections.
ASSERT_TRUE(NavigateToURL(web_contents(),
EchoCookiesUrl(embedded_test_server(), kHostA)));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(IsEmpty()));
}
// embedded_test_server() uses http, which is insecure, but localhost is
// allowed to set prefixed cookies anyway.
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SendPrefixedCookiesLocalhost) {
SetPrefixedCookies("localhost", embedded_test_server());
// Main frame browser-initiated navigation sends all prefixed cookies.
ASSERT_TRUE(NavigateToURL(
web_contents(), EchoCookiesUrl(embedded_test_server(), "localhost")));
EXPECT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(
Key(kSecurePrefixCookieName), Key(kHostPrefixCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest, SetPrefixedCookies) {
// Main frame can set cookies with all prefixes.
ASSERT_TRUE(NavigateToURL(
web_contents(),
https_server()->GetURL(kHostA, GetSetPrefixedCookiesPath(kHostA))));
EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
https_server()->GetURL(kHostA, "/")),
UnorderedElementsAre(
net::MatchesCookieWithName(kHostPrefixCookieName),
net::MatchesCookieWithName(kSecurePrefixCookieName)));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
SetPrefixedCookies_DisallowedIfInsecure) {
// Main frame cannot set cookies with any prefix over an insecure connection.
ASSERT_TRUE(NavigateToURL(web_contents(),
embedded_test_server()->GetURL(
kHostA, GetSetPrefixedCookiesPath(kHostA))));
EXPECT_THAT(GetCanonicalCookies(web_contents()->GetBrowserContext(),
embedded_test_server()->GetURL(kHostA, "/")),
IsEmpty());
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
ScriptNavigationSameSite404ScriptReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
/* is_cross_site_navigation= */ false,
/* is_user_initiated_reload= */ false),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
ScriptNavigationSameSite404UserReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
/* is_cross_site_navigation= */ false,
/* is_user_initiated_reload= */ true),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
ScriptNavigationCrossSite404ScriptReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
/* is_cross_site_navigation= */ true,
/* is_user_initiated_reload= */ false),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
ScriptNavigationCrossSite404UserReload) {
EXPECT_THAT(Test404ReloadCookie(/* is_user_initiated_navigation= */ false,
/* is_cross_site_navigation= */ true,
/* is_user_initiated_reload= */ true),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteLaxCookieName), Key(kSameSiteNoneCookieName),
Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
UserNavigationSameSite404ScriptReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
/* is_cross_site_navigation= */ false,
/* is_user_initiated_reload= */ false),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
UserNavigationSameSite404UserReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
/* is_cross_site_navigation= */ false,
/* is_user_initiated_reload= */ true),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
UserNavigationCrossSite404ScriptReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
/* is_cross_site_navigation= */ true,
/* is_user_initiated_reload= */ false),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
IN_PROC_BROWSER_TEST_P(HttpCookieBrowserTest,
UserNavigationCrossSite404UserReload) {
EXPECT_THAT(
Test404ReloadCookie(/* is_user_initiated_navigation= */ true,
/* is_cross_site_navigation= */ true,
/* is_user_initiated_reload= */ true),
net::CookieStringIs(UnorderedElementsAre(
Key(kSameSiteStrictCookieName), Key(kSameSiteLaxCookieName),
Key(kSameSiteNoneCookieName), Key(kSameSiteUnspecifiedCookieName))));
}
// Responds to a request to /echocookieswithcors with the cookies that were sent
// with the request. We can't use the default handler /echoheader?Cookie here,
// because it doesn't send the appropriate Access-Control-Allow-Origin and
// Access-Control-Allow-Credentials headers (which are required for this to
// work for cross-origin requests in the tests).
std::unique_ptr<net::test_server::HttpResponse>
HandleEchoCookiesWithCorsRequest(const net::test_server::HttpRequest& request) {
if (request.relative_url != kEchoCookiesWithCorsPath) {
return nullptr;
}
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
std::string content;
// Get the 'Cookie' header that was sent in the request.
if (auto it = request.headers.find(net::HttpRequestHeaders::kCookie);
it != request.headers.end()) {
content = it->second;
}
http_response->set_code(net::HTTP_OK);
http_response->set_content_type("text/plain");
// Set the cors enabled headers.
if (auto it = request.headers.find(net::HttpRequestHeaders::kOrigin);
it != request.headers.end()) {
http_response->AddCustomHeader("Access-Control-Allow-Headers",
"credentials");
http_response->AddCustomHeader("Access-Control-Allow-Origin", it->second);
http_response->AddCustomHeader("Origin", it->second);
http_response->AddCustomHeader("Vary", "Origin");
http_response->AddCustomHeader("Access-Control-Allow-Methods", "POST");
http_response->AddCustomHeader("Access-Control-Allow-Credentials", "true");
}
http_response->set_content(content);
return http_response;
}
class ThirdPartyCookiesHttpCookieBrowserTest : public ContentBrowserTest {
public:
ThirdPartyCookiesHttpCookieBrowserTest()
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS) {}
~ThirdPartyCookiesHttpCookieBrowserTest() override = default;
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
https_server()->SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
https_server()->AddDefaultHandlers(GetTestDataFilePath());
https_server()->RegisterRequestHandler(
base::BindRepeating(&HandleEchoCookiesWithCorsRequest));
ASSERT_TRUE(https_server()->Start());
}
WebContents* web_contents() const { return shell()->web_contents(); }
net::EmbeddedTestServer* https_server() { return &https_server_; }
GURL EchoCookiesUrl(std::string_view host) {
return https_server()->GetURL(host, "/echoheader?Cookie");
}
std::string ExtractFrameContent(RenderFrameHost* frame) const {
return EvalJs(frame, "document.body.textContent").ExtractString();
}
std::string ExtractCookieFromDocument(RenderFrameHost* frame) const {
return EvalJs(frame, "document.cookie").ExtractString();
}
std::string PostWithCredentials(RenderFrameHost* frame, const GURL& url) {
constexpr char script[] = R"JS(
fetch($1, {method: 'POST', 'credentials' : 'include'}
).then((result) => result.text());
)JS";
return EvalJs(frame, JsReplace(script, url)).ExtractString();
}
EvalJsResult Fetch(RenderFrameHost* frame,
const GURL& url,
std::string_view mode,
std::string_view credentials) {
constexpr char script[] = R"JS(
fetch($1, {mode: $2, credentials: $3}).then(result => result.text());
)JS";
return EvalJs(frame, JsReplace(script, url, mode, credentials));
}
bool CookieStoreEmpty(RenderFrameHost* frame) {
constexpr char script[] = R"JS(
(async () => {
let cookies = await cookieStore.getAll();
return cookies.length == 0;
})();
)JS";
return EvalJs(frame, script).ExtractBool();
}
EvalJsResult NavigateToURLWithPOST(RenderFrameHost* frame,
std::string_view host) {
TestNavigationObserver observer(web_contents());
constexpr char script[] = R"JS(
let form = document.createElement('form');
form.setAttribute('method', 'POST');
form.setAttribute('action', $1);
document.body.appendChild(form);
form.submit();
)JS";
EvalJsResult result =
EvalJs(frame, JsReplace(script, EchoCookiesUrl(host)));
observer.WaitForNavigationFinished();
EXPECT_TRUE(WaitForLoadStop(web_contents()));
return result;
}
EvalJsResult ReadCookiesViaFetchWithRedirect(
RenderFrameHost* frame,
std::string_view intermediate_host,
std::string_view destination_host) {
constexpr char script[] = "fetch($1).then((result) => result.text());";
GURL redirect_url = RedirectUrl(https_server(), intermediate_host,
EchoCookiesUrl(destination_host));
return EvalJs(frame, JsReplace(script, redirect_url));
}
private:
net::test_server::EmbeddedTestServer https_server_;
};
class ThirdPartyCookiesBlockedHttpCookieBrowserTest
: public ThirdPartyCookiesHttpCookieBrowserTest {
public:
ThirdPartyCookiesBlockedHttpCookieBrowserTest() {
feature_list_.InitWithFeatures(
{
net::features::kForceThirdPartyCookieBlocking,
},
{});
}
~ThirdPartyCookiesBlockedHttpCookieBrowserTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
SameSiteNoneCookieNavigateCrossSiteEmbedToSameSiteUrl) {
ASSERT_TRUE(base::FeatureList::IsEnabled(
net::features::kForceThirdPartyCookieBlocking));
// Set SameSite=None cookie on kHostA.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
// Confirm cross-site iframe (kHostB embedded in kHostA) does not
// send SameSite=None cookie to iframe.
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
net::CookieStringIs(UnorderedElementsAre()));
// Navigate embedded iframe from kHostB to kHostA and confirm that
// SameSite=None cookie is sent.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostA)));
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
SameSiteNoneCookieCrossSitePostRequest) {
// Set and confirm SameSite=None cookie on top-level-site kHostB.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
// Perform 'Post' to cross-site (kHostB) and confirm no cookie present in
// method response. Since there is no redirect action at the same time as
// the post, the cookie will be blocked as the request is being made
// cross-site.
EXPECT_THAT(PostWithCredentials(
web_contents()->GetPrimaryMainFrame(),
https_server()->GetURL(kHostB, kEchoCookiesWithCorsPath)),
"");
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
SameSiteNoneCookieCrossSiteSubresourceNavigationPost) {
// Set and confirm SameSite=None cookie on top-level-site kHostB.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Starting at kHostA create a form that has an action value that causes a
// navigation to a new top-level-site (kHostB). Submit the form to trigger the
// navigation and confirm that no error has occurred in the response.
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
ASSERT_TRUE(
NavigateToURLWithPOST(web_contents()->GetPrimaryMainFrame(), kHostB)
.is_ok());
// Confirm that navigation from subresource occurred and cookies are still
// available.
EXPECT_THAT(web_contents()->GetLastCommittedURL().host(), kHostB);
EXPECT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
RedirectCrossSiteSubresourceToSameSiteUrl) {
// Set and confirm SameSite=None cookie on top-level-site kHostA.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Perform redirect from cross-site subresource and ensure that no cookie was
// sent even though it was redirected to the top-level-site.
EXPECT_EQ(ReadCookiesViaFetchWithRedirect(
web_contents()->GetPrimaryMainFrame(), kHostB, kHostA),
"None");
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
SameSiteNoneCookieBlockedOnABEmbeddedIframe) {
// Set and confirm SameSite=None cookie on top-level-site kHostA is
// present in the cookie header, document.cookie and cookie store.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
// Confirm in cookie header.
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Confirm in document.cookie.
ASSERT_THAT(
ExtractCookieFromDocument(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Confirm in cookie store.
ASSERT_TRUE(
GetCookies(web_contents()->GetBrowserContext(), EchoCookiesUrl(kHostA))
.starts_with(kSameSiteNoneCookieName));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));
// Embed an iframe containing A in B and check cookie header.
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
"None");
// Check document.cookie.
EXPECT_TRUE(ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0))
.empty());
// Check cookie store.
EXPECT_TRUE(
CookieStoreEmpty(ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)));
}
IN_PROC_BROWSER_TEST_F(
ThirdPartyCookiesBlockedHttpCookieBrowserTest,
SameSiteNoneCookieBlockedInCrossSiteFetchRequestFromTopLevelFrame) {
// Set and confirm SameSite=None cookie on Site A.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// From site B make a fetch call (with credentials) from site B to site A; and
// check if cookies are present on the request.
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostB)));
EXPECT_THAT(Fetch(web_contents()->GetPrimaryMainFrame(),
https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath),
"cors", "include")
.ExtractString(),
net::CookieStringIs(IsEmpty()));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
TestCrossSitePartitionKeyNotAvailable) {
// Set cookie for site A kSameSite ancestor.
// Create initial frame tree A->B (B is an iframe) and check cookie.
// Navigate iframe with site B to A and confirm that cookie is present.
// Set kSameSite cookie
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing B in A to create initial frame tree A->B.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
"None");
// Navigate embedded iframe B to A
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostA)));
// Extract cookie from A
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
TestSubresourceRedirects) {
// Initial frame tree A->B (B is an iframe).
// A cookie is set for site C.
// iframe B is navigated to site C.
// Frame tree becomes A->C (C is an iframe).
// Check if cookie set for C is present in C.
// Embed an iframe containing B in A to create initial frame tree A->B.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostB)), {0}),
"None");
// Set SameSite=None partitioned cookie for kHostC from embedded iframe B.
net::CookiePartitionKey partition_key =
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kCrossSite);
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
net::CookieOptions::SameSiteCookieContext(
net::CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE),
partition_key));
// confirm that there is a cookie with kHostC url in the mojom cookie manager
// and that the cookie is partitioned and third party.
std::vector<net::CanonicalCookie> cookies = GetCanonicalCookies(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostC, "/"),
net::CookiePartitionKeyCollection(partition_key));
ASSERT_EQ(cookies.size(), 1u);
ASSERT_TRUE(cookies[0].IsPartitioned());
ASSERT_TRUE(cookies[0].PartitionKey()->IsThirdParty());
// Navigate embedded iframe B to C
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostC)));
// Extract cookie from C
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
TestTopLevelRedirects) {
// Navigate to Site A and set cookie on site A.
// Redirect from site A to site B and back to site A.
// Confirm cookie is present on site A after redirection.
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
// Check to make sure that there are no cookies set on kHostA.
ASSERT_THAT(ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
"None");
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Perform redirect from site A to site B and back to site A.
ASSERT_TRUE(
NavigateToURL(web_contents(),
RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
EchoCookiesUrl(kHostA)));
EXPECT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(
ThirdPartyCookiesBlockedHttpCookieBrowserTest,
TestSameSiteEmbeddedResourceToCrossSiteEmbeddedResource) {
// Initial frame tree A1->A2 (A2 is an iframe)
// A cookie is set from top-level A1 for site B with kCrossSite ancestor chain
// bit. iframe A2 is navigated to site B. Frame tree becomes A1->B (B is an
// iframe). Check if cookie set from A1 is present in B.
// Embed an iframe containing A in A to create initial frame tree A->A.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"None");
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostB, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kCrossSite)));
// Navigate embedded iframe A2 to B.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostB)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
CrossSiteToSameSiteIframeRedirects) {
// Set partitioned kSameSite ancestor cookie on top level site A.
// Embed an iframe of site A and confirm cookie is accessible from iframe.
// Navigate the iframe to a cross-domain (site B) and redirect back to A.
// Confirm that cookie is accessible from the iframe.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing A in A to create initial frame tree A->A.
// Confirm that partitioned cookie is accessible from the iframe.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"samesite_none_cookie=1");
// Navigate the iframe from A to B to A.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
/*expected_commit_url=*/EchoCookiesUrl(kHostA)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
RedirectCrossSiteThroughSameSiteIframe) {
// Set partitioned kSameSite ancestor cookie on top level site A.
// Embed an iframe of site A and confirm cookie is accessible from iframe.
// Navigate the iframe to a cross-domain (site B) and redirect back to a
// different page with domain of A and then redirect back to the original
// site. Confirm that cookie is accessible from the header in the final
// redirect.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing A in A to create initial frame tree A->A.
// Confirm that partitioned cookie is accessible from the iframe.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"samesite_none_cookie=1");
// Navigate the iframe from A to B redirecting to A redirecting to A.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
RedirectUrl(https_server(), kHostB,
RedirectUrl(https_server(), kHostA, EchoCookiesUrl(kHostA))),
/*expected_commit_url=*/EchoCookiesUrl(kHostA)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
RedirectTwoCrossSitesThroughSameSiteIframe) {
// Set partitioned kSameSite ancestor cookie on top level site A.
// Embed an iframe of site A and confirm cookie is accessible from iframe.
// Navigate the iframe to a cross-domain (site B) and redirect to a second
// cross-domain (site C) and then redirect back to A.
// Confirm that cookie is accessible from the header in the final
// redirect.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing A in A to create initial frame tree A->A.
// Confirm that partitioned cookie is accessible from the iframe.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"samesite_none_cookie=1");
// Navigate the iframe from A to B redirecting to C and then back to A.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
RedirectUrl(https_server(), kHostB,
RedirectUrl(https_server(), kHostC,
RedirectUrl(https_server(), kHostA,
EchoCookiesUrl(kHostA)))),
EchoCookiesUrl(kHostA)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(
ThirdPartyCookiesBlockedHttpCookieBrowserTest,
RedirectCrossSiteIframeToSameSiteThenNavigateToSameSite) {
// Set partitioned kSameSite ancestor cookie on top level site A.
// Embed an iframe of site A and confirm cookie is accessible from iframe.
// Navigate the iframe to a cross-domain (site B) and redirect back to site A.
// Then navigate to site A again.
// Confirm that cookie is accessible from the header in the final navigation.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing A in A to create initial frame tree A->A.
// Confirm that partitioned cookie is accessible from the iframe.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"samesite_none_cookie=1");
// Navigate the iframe from A to B redirecting to A.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
RedirectUrl(https_server(), kHostB, EchoCookiesUrl(kHostA)),
/*expected_commit_url=*/EchoCookiesUrl(kHostA)));
// Navigate to A from the iframe A that was redirected to site A from B.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostA)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(ThirdPartyCookiesBlockedHttpCookieBrowserTest,
CrossSiteToSameSiteIframeNavigation) {
// Set partitioned kSameSite ancestor cookie on top level site A.
// Embed an iframe of site A and confirm cookie is accessible from iframe.
// Navigate the iframe to a cross-domain (site B).
// Then navigate the iframe back to site A.
// Confirm that cookie is accessible from the iframe.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat(
{kSameSiteNoneCookieName, "=1;Secure;SameSite=None;Partitioned"}),
net::CookieOptions::SameSiteCookieContext::MakeInclusive(),
net::CookiePartitionKey::FromURLForTesting(
https_server()->GetURL(kHostA, "/"),
net::CookiePartitionKey::AncestorChainBit::kSameSite)));
// Embed an iframe containing A in A to create initial frame tree A->A.
// Confirm that partitioned cookie is accessible from the iframe.
ASSERT_EQ(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForUrl(EchoCookiesUrl(kHostA)), {0}),
"samesite_none_cookie=1");
// Navigate the iframe from A to B. Then B to A.
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostB)));
ASSERT_TRUE(NavigateToURLFromRenderer(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0),
EchoCookiesUrl(kHostA)));
// Confirm that the cookie is in the header sent to the iframe.
EXPECT_THAT(
ExtractFrameContent(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
ExtractCookieFromDocument(
ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0)),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
class DevToolsOverridesThirdPartyCookiesBrowserTest
: public ThirdPartyCookiesHttpCookieBrowserTest {
public:
DevToolsOverridesThirdPartyCookiesBrowserTest() = default;
~DevToolsOverridesThirdPartyCookiesBrowserTest() override = default;
void SetUpOnMainThread() override {
ThirdPartyCookiesHttpCookieBrowserTest::SetUpOnMainThread();
web_contents_devtools_client.AttachToWebContents(shell()->web_contents());
web_contents_devtools_client.SendCommandAsync("Network.enable");
}
void TearDownOnMainThread() override {
web_contents_devtools_client.DetachProtocolClient();
frame_devtools_client.DetachProtocolClient();
}
protected:
void NavigateToPageWith3pIFrame(std::string_view host) {
frame_devtools_client.DetachProtocolClient();
GURL main_url(https_server()->GetURL(host, "/page_with_blank_iframe.html"));
ASSERT_TRUE(content::NavigateToURL(web_contents(), main_url));
EXPECT_TRUE(
NavigateIframeToURL(web_contents(), "test_iframe",
https_server()->GetURL(kHostB, "/empty.html")));
frame_devtools_client.AttachToFrameTreeHost(GetFrame());
frame_devtools_client.SendCommandSync("Network.enable");
}
content::RenderFrameHost* GetFrame() {
return ChildFrameAt(web_contents()->GetPrimaryMainFrame(), 0);
}
std::string SetCookieFromJS(content::RenderFrameHost* render_frame_host,
std::string cookie) {
content::EvalJsResult result = content::EvalJs(
render_frame_host,
"document.cookie = '" + cookie + "; SameSite=None; Secure'",
content::EXECUTE_SCRIPT_NO_USER_GESTURE);
return result.ExtractString();
}
std::string ReadCookiesFromJS(content::RenderFrameHost* render_frame_host) {
std::string res = content::EvalJs(render_frame_host, "document.cookie",
content::EXECUTE_SCRIPT_NO_USER_GESTURE)
.ExtractString();
return res;
}
void SendSetCookieControls(bool enable_third_party_cookie_restriction,
bool disable_third_party_cookie_metadata,
bool disable_third_party_cookie_heuristics) {
base::Value::Dict command_params;
web_contents_devtools_client.SendCommandSync("Network.enable");
command_params.Set("enableThirdPartyCookieRestriction",
enable_third_party_cookie_restriction);
command_params.Set("disableThirdPartyCookieMetadata",
disable_third_party_cookie_metadata);
command_params.Set("disableThirdPartyCookieHeuristics",
disable_third_party_cookie_heuristics);
web_contents_devtools_client.SendCommandSync("Network.setCookieControls",
std::move(command_params));
}
content::TestDevToolsProtocolClient web_contents_devtools_client;
content::TestDevToolsProtocolClient frame_devtools_client;
};
IN_PROC_BROWSER_TEST_F(DevToolsOverridesThirdPartyCookiesBrowserTest,
DevToolsForceDisableTPCFromJS) {
// Third-party access should work initially
NavigateToPageWith3pIFrame(kHostA);
SetCookieFromJS(GetFrame(), "cookieAllowed=true");
EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "cookieAllowed=true");
// Turning on third-party cookie restriction
SendSetCookieControls(/*enable_third_party_cookie_restriction=*/true,
/*disable_third_party_cookie_metadata=*/false,
/*disable_third_party_cookie_heuristics=*/false);
// Refreshing so that RCM is re-created with new controls
NavigateToPageWith3pIFrame("a.test");
// Both of these should get blocked now
SetCookieFromJS(GetFrame(), "cookieAllowed=false");
EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "");
// Disabling the network domain should return to normal (unblocked) cookie
// state
frame_devtools_client.SendCommandSync("Network.disable");
web_contents_devtools_client.SendCommandSync("Network.disable");
// The cookie should be the same as before proving that the last
// SetCookieFromJS didn't update the cookie
EXPECT_EQ(ReadCookiesFromJS(GetFrame()), "cookieAllowed=true");
}
IN_PROC_BROWSER_TEST_F(DevToolsOverridesThirdPartyCookiesBrowserTest,
DevToolsForceDisableTPC) {
// Set SameSite=None cookie on top-level-site kHostA.
ASSERT_TRUE(SetCookie(
web_contents()->GetBrowserContext(), https_server()->GetURL(kHostA, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1;Secure;SameSite=None;"})));
ASSERT_TRUE(NavigateToURL(web_contents(), EchoCookiesUrl(kHostA)));
ASSERT_THAT(
ExtractFrameContent(web_contents()->GetPrimaryMainFrame()),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Embed an iframe containing A in B and check 3pc is allowed.
ASSERT_THAT(
content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
// Apply devtools overrides to enable 3pc restriction.
SendSetCookieControls(/*enable_third_party_cookie_restriction=*/true,
/*disable_third_party_cookie_metadata=*/false,
/*disable_third_party_cookie_heuristics=*/false);
// 3pc should be blocked due to devtools overrides.
EXPECT_THAT(content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
"None");
EXPECT_THAT(Fetch(web_contents()->GetPrimaryMainFrame(),
https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath),
"cors", "include")
.ExtractString(),
net::CookieStringIs(IsEmpty()));
web_contents_devtools_client.SendCommandAsync("Network.disable");
// The override should stop working and 3pc is re-allowed after devtools is
// disabled.
EXPECT_THAT(
content::ArrangeFramesAndGetContentFromLeaf(
web_contents(), https_server(),
FrameTreeForHostAndUrl(kHostB, EchoCookiesUrl(kHostA)), {0}),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
EXPECT_THAT(
Fetch(web_contents()->GetPrimaryMainFrame(),
https_server()->GetURL(kHostA, kEchoCookiesWithCorsPath), "cors",
"include")
.ExtractString(),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
INSTANTIATE_TEST_SUITE_P(/* no label */,
HttpCookieBrowserTest,
::testing::Bool());
} // namespace
} // namespace content