1 // Copyright 2021 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/cookies/cookie_partition_key.h"
6
7 #include <string>
8 #include <tuple>
9
10 #include "base/test/scoped_feature_list.h"
11 #include "net/base/features.h"
12 #include "net/cookies/cookie_constants.h"
13 #include "net/cookies/cookie_switches.h"
14 #include "net/cookies/site_for_cookies.h"
15 #include "testing/gtest/include/gtest/gtest.h"
16
17 namespace net {
18
19 using enum CookiePartitionKey::AncestorChainBit;
20
21 class CookiePartitionKeyTest : public testing::TestWithParam<bool> {
22 protected:
23 // testing::Test
SetUp()24 void SetUp() override {
25 scoped_feature_list_.InitWithFeatureState(
26 features::kAncestorChainBitEnabledInPartitionedCookies,
27 AncestorChainBitEnabled());
28 }
29
AncestorChainBitEnabled()30 bool AncestorChainBitEnabled() { return GetParam(); }
31
32 private:
33 base::test::ScopedFeatureList scoped_feature_list_;
34 };
35
36 INSTANTIATE_TEST_SUITE_P(/* no label */,
37 CookiePartitionKeyTest,
38 ::testing::Bool());
39
TEST_P(CookiePartitionKeyTest,TestFromStorage)40 TEST_P(CookiePartitionKeyTest, TestFromStorage) {
41 struct {
42 const std::string top_level_site;
43 bool third_party;
44 const std::optional<CookiePartitionKey> expected_output;
45 } cases[] = {
46 {/*empty site*/
47 "", true, CookiePartitionKey::FromURLForTesting(GURL(""))},
48 /*invalid site*/
49 {"Invalid", true, std::nullopt},
50 /*malformed site*/
51 {"https://toplevelsite.com/", true, std::nullopt},
52 /*valid site: cross site*/
53 {"https://toplevelsite.com", true,
54 CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"))},
55 /*valid site: same site*/
56 {"https://toplevelsite.com", false,
57 CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"),
58 kSameSite)}};
59 for (const auto& tc : cases) {
60 base::expected<std::optional<CookiePartitionKey>, std::string> got =
61 CookiePartitionKey::FromStorage(tc.top_level_site, tc.third_party);
62 EXPECT_EQ(got.has_value(), tc.expected_output.has_value());
63 if (!tc.top_level_site.empty() && tc.expected_output.has_value()) {
64 ASSERT_TRUE(got.has_value()) << "Expected result to have value.";
65 EXPECT_EQ(got.value()->IsThirdParty(), tc.third_party);
66 }
67 }
68
69 {
70 base::CommandLine::ForCurrentProcess()->AppendSwitch(
71 kDisablePartitionedCookiesSwitch);
72 EXPECT_FALSE(
73 CookiePartitionKey::FromStorage("https://toplevelsite.com",
74 /*has_cross_site_ancestor=*/true)
75 .has_value());
76 }
77 }
78
TEST_P(CookiePartitionKeyTest,TestFromUntrustedInput)79 TEST_P(CookiePartitionKeyTest, TestFromUntrustedInput) {
80 const std::string kFullURL = "https://subdomain.toplevelsite.com/index.html";
81 const std::string kValidSite = "https://toplevelsite.com";
82 struct Output {
83 bool third_party;
84 };
85 struct {
86 std::string top_level_site;
87 CookiePartitionKey::AncestorChainBit has_cross_site_ancestor;
88 std::optional<Output> expected_output;
89 } cases[] = {
90 {/*empty site*/
91 "", kCrossSite, std::nullopt},
92 {/*empty site : same site ancestor*/
93 "", kSameSite, std::nullopt},
94 {/*valid site*/
95 kValidSite, kCrossSite, Output{true}},
96 {/*valid site: same site ancestor*/
97 kValidSite, kSameSite, Output{false}},
98 {/*valid site with extra slash: same site ancestor*/
99 kValidSite + "/", kSameSite, Output{false}},
100 {/*invalid site (missing scheme)*/
101 "toplevelsite.com", kCrossSite, std::nullopt},
102 {/*invalid site (missing scheme): same site ancestor*/
103 "toplevelsite.com", kSameSite, std::nullopt},
104 {/*invalid site*/
105 "abc123foobar!!", kCrossSite, std::nullopt},
106 {/*invalid site: same site ancestor*/
107 "abc123foobar!!", kSameSite, std::nullopt},
108 };
109
110 for (const auto& tc : cases) {
111 base::expected<CookiePartitionKey, std::string> got =
112 CookiePartitionKey::FromUntrustedInput(
113 tc.top_level_site, tc.has_cross_site_ancestor == kCrossSite);
114 EXPECT_EQ(got.has_value(), tc.expected_output.has_value());
115 if (tc.expected_output.has_value()) {
116 EXPECT_EQ(got->site().Serialize(), kValidSite);
117 EXPECT_EQ(got->IsThirdParty(), tc.expected_output->third_party);
118 }
119 }
120
121 {
122 base::CommandLine::ForCurrentProcess()->AppendSwitch(
123 kDisablePartitionedCookiesSwitch);
124 EXPECT_FALSE(
125 CookiePartitionKey::FromUntrustedInput("https://toplevelsite.com",
126 /*has_cross_site_ancestor=*/true)
127 .has_value());
128 }
129 }
130
TEST_P(CookiePartitionKeyTest,Serialization)131 TEST_P(CookiePartitionKeyTest, Serialization) {
132 base::UnguessableToken nonce = base::UnguessableToken::Create();
133 struct Output {
134 std::string top_level_site;
135 bool cross_site;
136 };
137 struct {
138 std::optional<CookiePartitionKey> input;
139 std::optional<Output> expected_output;
140 } cases[] = {
141 // No partition key
142 {std::nullopt, Output{kEmptyCookiePartitionKey, true}},
143 // Partition key present
144 {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")),
145 Output{"https://toplevelsite.com", true}},
146 // Local file URL
147 {CookiePartitionKey::FromURLForTesting(GURL("file:///path/to/file.txt")),
148 Output{"file://", true}},
149 // File URL with host
150 {CookiePartitionKey::FromURLForTesting(
151 GURL("file://toplevelsite.com/path/to/file.pdf")),
152 Output{"file://toplevelsite.com", true}},
153 // Opaque origin
154 {CookiePartitionKey::FromURLForTesting(GURL()), std::nullopt},
155 // AncestorChain::kSameSite
156 {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"),
157 kSameSite, std::nullopt),
158 Output{"https://toplevelsite.com", false}},
159 // AncestorChain::kCrossSite
160 {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"),
161 kCrossSite, std::nullopt),
162 Output{"https://toplevelsite.com", true}},
163 // With nonce
164 {CookiePartitionKey::FromNetworkIsolationKey(
165 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
166 SchemefulSite(GURL("https://cookiesite.com")),
167 nonce),
168 SiteForCookies::FromUrl(GURL::EmptyGURL()),
169 SchemefulSite(GURL("https://toplevelsite.com")),
170 /*main_frame_navigation=*/false),
171 std::nullopt},
172 // Same site no nonce from NIK
173 {CookiePartitionKey::FromNetworkIsolationKey(
174 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
175 SchemefulSite(GURL("https://toplevelsite.com"))),
176 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
177 SchemefulSite(GURL("https://toplevelsite.com")),
178 /*main_frame_navigation=*/false),
179 Output{"https://toplevelsite.com", false}},
180 // Different request_site results in cross site ancestor
181 {CookiePartitionKey::FromNetworkIsolationKey(
182 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
183 SchemefulSite(GURL("https://toplevelsite.com"))),
184 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
185 SchemefulSite(GURL("https://differentOrigin.com")),
186 /*main_frame_navigation=*/false),
187 Output{"https://toplevelsite.com", true}},
188 // Different request_site but main_frame_navigation=true results in same
189 // site ancestor
190 {CookiePartitionKey::FromNetworkIsolationKey(
191 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
192 SchemefulSite(GURL("https://toplevelsite.com"))),
193 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
194 SchemefulSite(GURL("https://differentOrigin.com")),
195 /*main_frame_navigation=*/true),
196 Output{"https://toplevelsite.com", false}},
197 // Different request_site and null site_for_cookies but
198 // main_frame_navigation=true results in same
199 // site ancestor
200 {CookiePartitionKey::FromNetworkIsolationKey(
201 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
202 SchemefulSite(GURL("https://toplevelsite.com"))),
203 SiteForCookies::FromUrl(GURL()),
204 SchemefulSite(GURL("https://differentOrigin.com")),
205 /*main_frame_navigation=*/true),
206 Output{"https://toplevelsite.com", false}},
207 // Same site with nonce from NIK
208 {CookiePartitionKey::FromNetworkIsolationKey(
209 NetworkIsolationKey(SchemefulSite(GURL("https://toplevelsite.com")),
210 SchemefulSite(GURL("https://toplevelsite.com")),
211 nonce),
212 SiteForCookies::FromUrl(GURL("https://toplevelsite.com")),
213 SchemefulSite(GURL("https://toplevelsite.com")),
214 /*main_frame_navigation=*/false),
215 std::nullopt},
216 // Invalid partition key
217 {std::make_optional(
218 CookiePartitionKey::FromURLForTesting(GURL("abc123foobar!!"))),
219 std::nullopt},
220 };
221
222 for (const auto& tc : cases) {
223 base::expected<CookiePartitionKey::SerializedCookiePartitionKey,
224 std::string>
225 got = CookiePartitionKey::Serialize(tc.input);
226
227 EXPECT_EQ(tc.expected_output.has_value(), got.has_value());
228 if (got.has_value()) {
229 EXPECT_EQ(tc.expected_output->top_level_site, got->TopLevelSite());
230 EXPECT_EQ(tc.expected_output->cross_site, got->has_cross_site_ancestor());
231 }
232 }
233 }
234
TEST_P(CookiePartitionKeyTest,FromNetworkIsolationKey)235 TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey) {
236 const SchemefulSite kTopLevelSite =
237 SchemefulSite(GURL("https://toplevelsite.com"));
238 const SchemefulSite kCookieSite =
239 SchemefulSite(GURL("https://cookiesite.com"));
240 const base::UnguessableToken kNonce = base::UnguessableToken::Create();
241
242 struct TestCase {
243 const std::string desc;
244 const NetworkIsolationKey network_isolation_key;
245 const std::optional<CookiePartitionKey> expected;
246 const SiteForCookies site_for_cookies;
247 const SchemefulSite request_site;
248 const bool main_frame_navigation;
249 } test_cases[] = {
250 {"Empty", NetworkIsolationKey(), std::nullopt,
251 SiteForCookies::FromUrl(GURL::EmptyGURL()), SchemefulSite(GURL("")),
252 /*main_frame_navigation=*/false},
253 {"WithTopLevelSite", NetworkIsolationKey(kTopLevelSite, kCookieSite),
254 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL()),
255 SiteForCookies::FromUrl(GURL::EmptyGURL()), SchemefulSite(kTopLevelSite),
256 /*main_frame_navigation=*/false},
257 {"WithNonce", NetworkIsolationKey(kTopLevelSite, kCookieSite, kNonce),
258 CookiePartitionKey::FromURLForTesting(kCookieSite.GetURL(), kCrossSite,
259 kNonce),
260 SiteForCookies::FromUrl(GURL::EmptyGURL()), SchemefulSite(kTopLevelSite),
261 /*main_frame_navigation=*/false},
262 {"WithCrossSiteAncestorSameSite",
263 NetworkIsolationKey(kTopLevelSite, kTopLevelSite),
264 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL(), kSameSite,
265 std::nullopt),
266 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
267 SchemefulSite(kTopLevelSite), /*main_frame_navigation=*/false},
268 {"Nonced first party NIK results in kCrossSite partition key",
269 NetworkIsolationKey(kTopLevelSite, kTopLevelSite, kNonce),
270 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL(), kCrossSite,
271 kNonce),
272 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
273 SchemefulSite(kTopLevelSite), /*main_frame_navigation=*/false},
274 {"WithCrossSiteAncestorNotSameSite",
275 NetworkIsolationKey(kTopLevelSite, kTopLevelSite),
276 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL(), kCrossSite,
277 std::nullopt),
278 SiteForCookies::FromUrl(GURL::EmptyGURL()), kCookieSite,
279 /*main_frame_navigation=*/false},
280 {"TestMainFrameNavigationParam",
281 NetworkIsolationKey(kTopLevelSite, kTopLevelSite),
282 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL(), kSameSite,
283 std::nullopt),
284 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
285 SchemefulSite(kCookieSite), /*main_frame_navigation=*/true},
286 {"PresenceOfNonceTakesPriorityOverMainFrameNavigation",
287 NetworkIsolationKey(kTopLevelSite, kTopLevelSite, kNonce),
288 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL(), kCrossSite,
289 kNonce),
290 SiteForCookies::FromUrl(GURL(kTopLevelSite.GetURL())),
291 SchemefulSite(kTopLevelSite), /*main_frame_navigation=*/true},
292 };
293
294 base::test::ScopedFeatureList feature_list;
295 feature_list.InitWithFeatureState(
296 features::kAncestorChainBitEnabledInPartitionedCookies,
297 AncestorChainBitEnabled());
298
299 for (const auto& test_case : test_cases) {
300 SCOPED_TRACE(test_case.desc);
301
302 std::optional<CookiePartitionKey> got =
303 CookiePartitionKey::FromNetworkIsolationKey(
304 test_case.network_isolation_key, test_case.site_for_cookies,
305 test_case.request_site, test_case.main_frame_navigation);
306
307 EXPECT_EQ(test_case.expected, got);
308 if (got) {
309 EXPECT_EQ(test_case.network_isolation_key.GetNonce(), got->nonce());
310 }
311 }
312 }
313
TEST_P(CookiePartitionKeyTest,FromWire)314 TEST_P(CookiePartitionKeyTest, FromWire) {
315 struct TestCase {
316 const GURL url;
317 const std::optional<base::UnguessableToken> nonce;
318 const CookiePartitionKey::AncestorChainBit ancestor_chain_bit;
319 } test_cases[] = {
320 {GURL("https://foo.com"), std::nullopt, kCrossSite},
321 {GURL("https://foo.com"), std::nullopt, kSameSite},
322 {GURL(), std::nullopt, kCrossSite},
323 {GURL("https://foo.com"), base::UnguessableToken::Create(), kCrossSite}};
324
325 for (const auto& test_case : test_cases) {
326 auto want = CookiePartitionKey::FromURLForTesting(
327 test_case.url, test_case.ancestor_chain_bit, test_case.nonce);
328 auto got = CookiePartitionKey::FromWire(
329 want.site(), want.IsThirdParty() ? kCrossSite : kSameSite,
330 want.nonce());
331 EXPECT_EQ(want, got);
332 EXPECT_FALSE(got.from_script());
333 }
334 }
335
TEST_P(CookiePartitionKeyTest,FromStorageKeyComponents)336 TEST_P(CookiePartitionKeyTest, FromStorageKeyComponents) {
337 struct TestCase {
338 const GURL url;
339 const std::optional<base::UnguessableToken> nonce = std::nullopt;
340 const CookiePartitionKey::AncestorChainBit ancestor_chain_bit;
341 } test_cases[] = {
342 {GURL("https://foo.com"), std::nullopt, kCrossSite},
343 {GURL("https://foo.com"), std::nullopt, kSameSite},
344 {GURL(), std::nullopt, kCrossSite},
345 {GURL("https://foo.com"), base::UnguessableToken::Create(), kCrossSite}};
346
347 for (const auto& test_case : test_cases) {
348 auto want = CookiePartitionKey::FromURLForTesting(
349 test_case.url, test_case.ancestor_chain_bit, test_case.nonce);
350 std::optional<CookiePartitionKey> got =
351 CookiePartitionKey::FromStorageKeyComponents(
352 want.site(), want.IsThirdParty() ? kCrossSite : kSameSite,
353 want.nonce());
354 EXPECT_EQ(got, want);
355 }
356 }
357
TEST_P(CookiePartitionKeyTest,FromScript)358 TEST_P(CookiePartitionKeyTest, FromScript) {
359 auto key = CookiePartitionKey::FromScript();
360 EXPECT_TRUE(key);
361 EXPECT_TRUE(key->from_script());
362 EXPECT_TRUE(key->site().opaque());
363 EXPECT_TRUE(key->IsThirdParty());
364
365 auto key2 = CookiePartitionKey::FromScript();
366 EXPECT_TRUE(key2);
367 EXPECT_TRUE(key2->from_script());
368 EXPECT_TRUE(key2->site().opaque());
369 EXPECT_TRUE(key2->IsThirdParty());
370
371 // The keys should not be equal because they get created with different opaque
372 // sites. Test both the '==' and '!=' operators here.
373 EXPECT_FALSE(key == key2);
374 EXPECT_TRUE(key != key2);
375 }
376
TEST_P(CookiePartitionKeyTest,IsSerializeable)377 TEST_P(CookiePartitionKeyTest, IsSerializeable) {
378 EXPECT_FALSE(CookiePartitionKey::FromURLForTesting(GURL()).IsSerializeable());
379 EXPECT_TRUE(
380 CookiePartitionKey::FromURLForTesting(GURL("https://www.example.com"))
381 .IsSerializeable());
382 }
383
TEST_P(CookiePartitionKeyTest,Equality)384 TEST_P(CookiePartitionKeyTest, Equality) {
385 // Same eTLD+1 but different scheme are not equal.
386 EXPECT_NE(CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")),
387 CookiePartitionKey::FromURLForTesting(GURL("http://foo.com")));
388
389 // Different subdomains of the same site are equal.
390 EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://a.foo.com")),
391 CookiePartitionKey::FromURLForTesting(GURL("https://b.foo.com")));
392 }
393
TEST_P(CookiePartitionKeyTest,Equality_WithAncestorChain)394 TEST_P(CookiePartitionKeyTest, Equality_WithAncestorChain) {
395 CookiePartitionKey key1 = CookiePartitionKey::FromURLForTesting(
396 GURL("https://foo.com"), kSameSite, std::nullopt);
397 CookiePartitionKey key2 = CookiePartitionKey::FromURLForTesting(
398 GURL("https://foo.com"), kCrossSite, std::nullopt);
399
400 EXPECT_EQ((key1 == key2), !AncestorChainBitEnabled());
401 EXPECT_EQ(key1, CookiePartitionKey::FromURLForTesting(
402 GURL("https://foo.com"), kSameSite, std::nullopt));
403 }
404
TEST_P(CookiePartitionKeyTest,Equality_WithNonce)405 TEST_P(CookiePartitionKeyTest, Equality_WithNonce) {
406 SchemefulSite top_level_site =
407 SchemefulSite(GURL("https://toplevelsite.com"));
408 SchemefulSite frame_site = SchemefulSite(GURL("https://cookiesite.com"));
409 base::UnguessableToken nonce1 = base::UnguessableToken::Create();
410 base::UnguessableToken nonce2 = base::UnguessableToken::Create();
411 EXPECT_NE(nonce1, nonce2);
412 auto key1 = CookiePartitionKey::FromNetworkIsolationKey(
413 NetworkIsolationKey(top_level_site, frame_site, nonce1), SiteForCookies(),
414 top_level_site, /*main_frame_navigation=*/false);
415 EXPECT_TRUE(key1.has_value());
416
417 auto key2 = CookiePartitionKey::FromNetworkIsolationKey(
418 NetworkIsolationKey(top_level_site, frame_site, nonce2), SiteForCookies(),
419 top_level_site, /*main_frame_navigation=*/false);
420 EXPECT_TRUE(key1.has_value() && key2.has_value());
421 EXPECT_NE(key1, key2);
422
423 auto key3 = CookiePartitionKey::FromNetworkIsolationKey(
424 NetworkIsolationKey(top_level_site, frame_site, nonce1), SiteForCookies(),
425 top_level_site, /*main_frame_navigation=*/false);
426 EXPECT_EQ(key1, key3);
427 // Confirm that nonce is evaluated before main_frame_navigation
428 auto key4 = CookiePartitionKey::FromNetworkIsolationKey(
429 NetworkIsolationKey(top_level_site, frame_site, nonce1), SiteForCookies(),
430 top_level_site, /*main_frame_navigation=*/true);
431 EXPECT_EQ(key1, key4);
432 auto unnonced_key = CookiePartitionKey::FromNetworkIsolationKey(
433 NetworkIsolationKey(top_level_site, frame_site), SiteForCookies(),
434 frame_site, /*main_frame_navigation=*/false);
435 EXPECT_NE(key1, unnonced_key);
436 }
437
TEST_P(CookiePartitionKeyTest,Localhost)438 TEST_P(CookiePartitionKeyTest, Localhost) {
439 SchemefulSite top_level_site(GURL("https://localhost:8000"));
440
441 auto key = CookiePartitionKey::FromNetworkIsolationKey(
442 NetworkIsolationKey(top_level_site, top_level_site), SiteForCookies(),
443 top_level_site, /*main_frame_navigation=*/false);
444 EXPECT_TRUE(key.has_value());
445
446 SchemefulSite frame_site(GURL("https://cookiesite.com"));
447 key = CookiePartitionKey::FromNetworkIsolationKey(
448 NetworkIsolationKey(top_level_site, frame_site), SiteForCookies(),
449 top_level_site, /*main_frame_navigation=*/false);
450 EXPECT_TRUE(key.has_value());
451 }
452
453 } // namespace net
454