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