• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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