blob: 992e0d59fd3944a47116840a3a5926ba7662887c [file] [log] [blame]
// Copyright (c) 2021 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/path_service.h"
#include "base/strings/strcat.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.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 {
using testing::Key;
using testing::Pair;
using testing::UnorderedElementsAre;
const char* kSamePartySameSiteLaxCookieName = "sameparty_samesite_lax_cookie";
const char* kSamePartySameSiteNoneCookieName = "sameparty_samesite_none_cookie";
const char* kSamePartySameSiteUnspecifiedCookieName =
"sameparty_samesite_unspecified_cookie";
const char* kSameSiteNoneCookieName = "samesite_none_cookie";
class CookieStoreSamePartyTest : public InProcessBrowserTest {
public:
explicit CookieStoreSamePartyTest(bool enable_fps)
: https_server_(net::EmbeddedTestServer::TYPE_HTTPS),
enable_fps_(enable_fps) {
if (!enable_fps) {
feature_list_.InitAndDisableFeature(features::kFirstPartySets);
}
// If FPS is to be enabled, that happens in `SetUpCommandLine` when the
// `kUseFirstPartySet` switch is provided.
}
CookieStoreSamePartyTest(const CookieStoreSamePartyTest&) = delete;
CookieStoreSamePartyTest& operator=(const CookieStoreSamePartyTest&) = delete;
void SetUpCommandLine(base::CommandLine* command_line) override {
InProcessBrowserTest::SetUpCommandLine(command_line);
if (enable_fps_) {
command_line->AppendSwitchASCII(network::switches::kUseFirstPartySet,
"https://a.test,https://b.test");
}
}
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
base::FilePath path;
base::PathService::Get(content::DIR_TEST_DATA, &path);
https_server_.ServeFilesFromDirectory(path);
https_server_.AddDefaultHandlers(GetChromeTestDataDir());
https_server_.SetSSLConfig(net::EmbeddedTestServer::CERT_TEST_NAMES);
ASSERT_TRUE(https_server_.Start());
}
content::WebContents* contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
content::RenderFrameHost* GetDescendantFrame(
const std::vector<int>& indices) {
content::RenderFrameHost* frame = contents()->GetMainFrame();
for (int index : indices) {
frame = ChildFrameAt(frame, index);
}
return frame;
}
std::string GetCookiesViaCookieStore(content::RenderFrameHost* frame) {
std::string script = R"(
(async () => {
const cookies = await cookieStore.getAll();
return cookies.map(c => `${c.name}=${c.value}`).join(';');
})();
)";
return content::EvalJs(frame, script).ExtractString();
}
std::string GetSamePartyAttributesViaCookieStore(
content::RenderFrameHost* frame) {
return content::EvalJs(frame, R"(
(async () => {
const cookies = await cookieStore.getAll();
return cookies.map(c => `${c.name}=${c.sameParty}`).join(';');
})();
)")
.ExtractString();
}
void SetSamePartyCookies(const std::string& host) {
Profile* profile = browser()->profile();
ASSERT_TRUE(content::SetCookie(
profile, https_server().GetURL(host, "/"),
base::StrCat({kSamePartySameSiteLaxCookieName,
"=1; samesite=lax; secure; sameparty"})));
ASSERT_TRUE(content::SetCookie(
profile, https_server().GetURL(host, "/"),
base::StrCat({kSamePartySameSiteNoneCookieName,
"=1; samesite=none; secure; sameparty"})));
ASSERT_TRUE(content::SetCookie(
profile, https_server().GetURL(host, "/"),
base::StrCat({kSamePartySameSiteUnspecifiedCookieName,
"=1; secure; sameparty"})));
ASSERT_TRUE(content::SetCookie(
profile, https_server().GetURL(host, "/"),
base::StrCat({kSameSiteNoneCookieName, "=1; samesite=none; secure"})));
}
std::string SetSamePartyCookieString(base::StringPiece name,
bool same_party,
base::StringPiece same_site) {
return content::JsReplace(
R"(
(async () => {
await cookieStore.set({name: $1, value: '1',
sameParty: $2, sameSite: $3});
return true;
})();
)",
name, same_party, same_site);
}
void SetSamePartyCookiesViaCookieStore(content::RenderFrameHost* frame,
bool is_same_party_context,
bool site_is_in_fps) {
if (is_same_party_context) {
ASSERT_TRUE(site_is_in_fps);
}
{
content::EvalJsResult result = content::EvalJs(
frame, SetSamePartyCookieString(kSamePartySameSiteLaxCookieName, true,
"lax"));
if (!is_same_party_context || !site_is_in_fps) {
// If the site is in an FPS and the context isn't same-party, the set
// will fail; otherwise, if the context is cross-site, the set will
// fail. We assume any call is from a cross-site context, so therefore
// any call where the site is not in an FPS should fail.
ASSERT_FALSE(result.error.empty());
} else {
ASSERT_TRUE(result.ExtractBool());
}
}
{
content::EvalJsResult result = content::EvalJs(
frame, SetSamePartyCookieString(kSamePartySameSiteNoneCookieName,
true, "none"));
if (site_is_in_fps && !is_same_party_context) {
ASSERT_FALSE(result.error.empty());
} else {
ASSERT_TRUE(result.ExtractBool());
}
}
// Can't test unspecified SameSite here, since the CookieStore supplies
// 'Strict' when SameSite is not provided explicitly.
ASSERT_TRUE(
content::EvalJs(frame, SetSamePartyCookieString(kSameSiteNoneCookieName,
false, "none"))
.ExtractBool());
}
const net::test_server::EmbeddedTestServer& https_server() const {
return https_server_;
}
private:
net::test_server::EmbeddedTestServer https_server_;
bool enable_fps_;
base::test::ScopedFeatureList feature_list_;
};
class CookieStoreSamePartyEnabledTest : public CookieStoreSamePartyTest {
public:
CookieStoreSamePartyEnabledTest() : CookieStoreSamePartyTest(true) {}
};
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
ReadCookies_SamePartyContext) {
SetSamePartyCookies("b.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(b.test)"}))));
EXPECT_THAT(GetCookiesViaCookieStore(GetDescendantFrame({0})),
net::CookieStringIs(UnorderedElementsAre(
Key(kSamePartySameSiteLaxCookieName),
Key(kSamePartySameSiteNoneCookieName),
Key(kSamePartySameSiteUnspecifiedCookieName),
Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
ReadCookies_CrossPartyContext) {
SetSamePartyCookies("c.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test)"}))));
// c.test isn't in the FPS, so SameParty is ignored, so we get SameSite=None
// cookies.
EXPECT_THAT(GetCookiesViaCookieStore(GetDescendantFrame({0})),
net::CookieStringIs(
UnorderedElementsAre(Key(kSamePartySameSiteNoneCookieName),
Key(kSameSiteNoneCookieName))));
ASSERT_EQ(4U, content::DeleteCookies(browser()->profile(),
network::mojom::CookieDeletionFilter()));
SetSamePartyCookies("b.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test(b.test))"}))));
// SameParty is not ignored, so we get the SameSite=None cookie.
EXPECT_THAT(
GetCookiesViaCookieStore(GetDescendantFrame({0, 0})),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
ReadAttribute_SamePartyContext) {
SetSamePartyCookies("b.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(b.test)"}))));
EXPECT_THAT(GetSamePartyAttributesViaCookieStore(GetDescendantFrame({0})),
net::CookieStringIs(UnorderedElementsAre(
Pair(kSamePartySameSiteLaxCookieName, "true"),
Pair(kSamePartySameSiteNoneCookieName, "true"),
Pair(kSamePartySameSiteUnspecifiedCookieName, "true"),
Pair(kSameSiteNoneCookieName, "false"))));
}
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
ReadAttribute_CrossPartyContext) {
SetSamePartyCookies("c.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test)"}))));
EXPECT_THAT(GetSamePartyAttributesViaCookieStore(GetDescendantFrame({0})),
net::CookieStringIs(UnorderedElementsAre(
Pair(kSamePartySameSiteNoneCookieName, "true"),
Pair(kSameSiteNoneCookieName, "false"))));
ASSERT_EQ(4U, content::DeleteCookies(browser()->profile(),
network::mojom::CookieDeletionFilter()));
SetSamePartyCookies("b.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test(b.test))"}))));
EXPECT_THAT(GetSamePartyAttributesViaCookieStore(GetDescendantFrame({0, 0})),
net::CookieStringIs(UnorderedElementsAre(
Pair(kSameSiteNoneCookieName, "false"))));
}
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
WriteCookies_SamePartyContext) {
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(b.test)"}))));
SetSamePartyCookiesViaCookieStore(GetDescendantFrame({0}),
/*is_same_party_context=*/true,
/*site_is_in_fps=*/true);
EXPECT_THAT(content::GetCookies(browser()->profile(), GURL("https://b.test")),
net::CookieStringIs(
UnorderedElementsAre(Key(kSamePartySameSiteLaxCookieName),
Key(kSamePartySameSiteNoneCookieName),
Key(kSameSiteNoneCookieName))));
}
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyEnabledTest,
WriteCookies_CrossPartyContext) {
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test)"}))));
SetSamePartyCookiesViaCookieStore(GetDescendantFrame({0}),
/*is_same_party_context=*/false,
/*site_is_in_fps=*/false);
EXPECT_THAT(content::GetCookies(browser()->profile(), GURL("https://c.test")),
net::CookieStringIs(
UnorderedElementsAre(Key(kSamePartySameSiteNoneCookieName),
Key(kSameSiteNoneCookieName))));
ASSERT_GE(2U, content::DeleteCookies(browser()->profile(),
network::mojom::CookieDeletionFilter()));
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test(b.test))"}))));
SetSamePartyCookiesViaCookieStore(GetDescendantFrame({0, 0}),
/*is_same_party_context=*/false,
/*site_is_in_fps=*/true);
EXPECT_THAT(
content::GetCookies(browser()->profile(), GURL("https://b.test")),
net::CookieStringIs(UnorderedElementsAre(Key(kSameSiteNoneCookieName))));
}
class CookieStoreSamePartyDisabledTest : public CookieStoreSamePartyTest {
public:
CookieStoreSamePartyDisabledTest() : CookieStoreSamePartyTest(false) {}
};
IN_PROC_BROWSER_TEST_F(CookieStoreSamePartyDisabledTest,
ReadAttribute_CrossPartyContext) {
SetSamePartyCookies("c.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test)"}))));
EXPECT_THAT(GetSamePartyAttributesViaCookieStore(GetDescendantFrame({0})),
net::CookieStringIs(UnorderedElementsAre(
Pair(kSamePartySameSiteNoneCookieName, "undefined"),
Pair(kSameSiteNoneCookieName, "undefined"))));
ASSERT_EQ(4U, content::DeleteCookies(browser()->profile(),
network::mojom::CookieDeletionFilter()));
SetSamePartyCookies("b.test");
ASSERT_TRUE(NavigateToURL(
contents(),
https_server().GetURL("a.test",
base::StrCat({"/cross_site_iframe_factory.html?",
"a.test(c.test(b.test))"}))));
EXPECT_THAT(GetSamePartyAttributesViaCookieStore(GetDescendantFrame({0, 0})),
net::CookieStringIs(UnorderedElementsAre(
Pair(kSamePartySameSiteNoneCookieName, "undefined"),
Pair(kSameSiteNoneCookieName, "undefined"))));
}
} // namespace