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 "testing/gtest/include/gtest/gtest.h"
13
14 namespace net {
15
16 class CookiePartitionKeyTest : public testing::TestWithParam<bool> {
17 protected:
18 // testing::Test
SetUp()19 void SetUp() override {
20 scoped_feature_list_.InitWithFeatureState(features::kPartitionedCookies,
21 PartitionedCookiesEnabled());
22 }
23
PartitionedCookiesEnabled()24 bool PartitionedCookiesEnabled() { return GetParam(); }
25
26 private:
27 base::test::ScopedFeatureList scoped_feature_list_;
28 };
29
30 INSTANTIATE_TEST_SUITE_P(/* no label */,
31 CookiePartitionKeyTest,
32 ::testing::Bool());
33
TEST_P(CookiePartitionKeyTest,Serialization)34 TEST_P(CookiePartitionKeyTest, Serialization) {
35 base::UnguessableToken nonce = base::UnguessableToken::Create();
36 struct {
37 absl::optional<CookiePartitionKey> input;
38 bool expected_ret;
39 std::string expected_output;
40 } cases[] = {
41 // No partition key
42 {absl::nullopt, true, kEmptyCookiePartitionKey},
43 // Partition key present
44 {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")),
45 true, "https://toplevelsite.com"},
46 // Local file URL
47 {CookiePartitionKey::FromURLForTesting(GURL("file:///path/to/file.txt")),
48 true, "file://"},
49 // File URL with host
50 {CookiePartitionKey::FromURLForTesting(
51 GURL("file://toplevelsite.com/path/to/file.pdf")),
52 true, "file://toplevelsite.com"},
53 // Opaque origin
54 {CookiePartitionKey::FromURLForTesting(GURL()), false, ""},
55 // With nonce
56 {CookiePartitionKey::FromNetworkIsolationKey(NetworkIsolationKey(
57 SchemefulSite(GURL("https://toplevelsite.com")),
58 SchemefulSite(GURL("https://cookiesite.com")), nonce)),
59 false, ""},
60 // Invalid partition key
61 {absl::make_optional(
62 CookiePartitionKey::FromURLForTesting(GURL("abc123foobar!!"))),
63 false, ""},
64 };
65
66 for (const auto& tc : cases) {
67 std::string got;
68 if (PartitionedCookiesEnabled()) {
69 EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Serialize(tc.input, got));
70 EXPECT_EQ(tc.expected_output, got);
71 } else {
72 // Serialize should only return true for unpartitioned cookies if the
73 // feature is disabled.
74 EXPECT_NE(tc.input.has_value(),
75 CookiePartitionKey::Serialize(tc.input, got));
76 }
77 }
78 }
79
TEST_P(CookiePartitionKeyTest,Deserialization)80 TEST_P(CookiePartitionKeyTest, Deserialization) {
81 struct {
82 std::string input;
83 bool expected_ret;
84 absl::optional<CookiePartitionKey> expected_output;
85 } cases[] = {
86 {kEmptyCookiePartitionKey, true, absl::nullopt},
87 {"https://toplevelsite.com", true,
88 CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"))},
89 {"abc123foobar!!", false, absl::nullopt},
90 };
91
92 for (const auto& tc : cases) {
93 absl::optional<CookiePartitionKey> got;
94 if (PartitionedCookiesEnabled()) {
95 EXPECT_EQ(tc.expected_ret,
96 CookiePartitionKey::Deserialize(tc.input, got));
97 if (tc.expected_output.has_value()) {
98 EXPECT_TRUE(got.has_value());
99 EXPECT_EQ(tc.expected_output.value(), got.value());
100 } else {
101 EXPECT_FALSE(got.has_value());
102 }
103 } else {
104 // Deserialize should only return true for unpartitioned cookies if the
105 // feature is disabled.
106 EXPECT_EQ(tc.input == kEmptyCookiePartitionKey,
107 CookiePartitionKey::Deserialize(tc.input, got));
108 }
109 }
110 }
111
TEST_P(CookiePartitionKeyTest,FromNetworkIsolationKey)112 TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey) {
113 const SchemefulSite kTopLevelSite =
114 SchemefulSite(GURL("https://toplevelsite.com"));
115 const SchemefulSite kCookieSite =
116 SchemefulSite(GURL("https://cookiesite.com"));
117 const base::UnguessableToken kNonce = base::UnguessableToken::Create();
118
119 struct TestCase {
120 const std::string desc;
121 const NetworkIsolationKey network_isolation_key;
122 const absl::optional<CookiePartitionKey> expected;
123 } test_cases[] = {
124 {
125 "Empty",
126 NetworkIsolationKey(),
127 absl::nullopt,
128 },
129 {
130 "WithTopLevelSite",
131 NetworkIsolationKey(kTopLevelSite, kCookieSite),
132 CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL()),
133 },
134 {
135 "WithNonce",
136 NetworkIsolationKey(kTopLevelSite, kCookieSite, kNonce),
137 CookiePartitionKey::FromURLForTesting(kCookieSite.GetURL(), kNonce),
138 },
139 };
140
141 for (const auto& test_case : test_cases) {
142 SCOPED_TRACE(test_case.desc);
143
144 base::test::ScopedFeatureList feature_list;
145 std::vector<base::test::FeatureRef> enabled_features;
146 std::vector<base::test::FeatureRef> disabled_features;
147 if (PartitionedCookiesEnabled()) {
148 enabled_features.push_back(features::kPartitionedCookies);
149 } else {
150 disabled_features.push_back(features::kPartitionedCookies);
151 }
152 feature_list.InitWithFeatures(enabled_features, disabled_features);
153
154 absl::optional<CookiePartitionKey> got =
155 CookiePartitionKey::FromNetworkIsolationKey(
156 test_case.network_isolation_key);
157
158 if (PartitionedCookiesEnabled()) {
159 EXPECT_EQ(test_case.expected, got);
160 if (got) {
161 EXPECT_EQ(test_case.network_isolation_key.GetNonce(), got->nonce());
162 }
163 } else {
164 EXPECT_FALSE(got);
165 }
166 }
167 }
168
TEST_P(CookiePartitionKeyTest,FromWire)169 TEST_P(CookiePartitionKeyTest, FromWire) {
170 struct TestCase {
171 const GURL url;
172 const absl::optional<base::UnguessableToken> nonce;
173 } test_cases[] = {
174 {GURL("https://foo.com"), absl::nullopt},
175 {GURL(), absl::nullopt},
176 {GURL("https://foo.com"),
177 absl::make_optional(base::UnguessableToken::Create())},
178 };
179
180 for (const auto& test_case : test_cases) {
181 auto want =
182 CookiePartitionKey::FromURLForTesting(test_case.url, test_case.nonce);
183 auto got = CookiePartitionKey::FromWire(want.site(), want.nonce());
184 EXPECT_EQ(want, got);
185 EXPECT_FALSE(got.from_script());
186 }
187 }
188
TEST_P(CookiePartitionKeyTest,FromStorageKeyComponents)189 TEST_P(CookiePartitionKeyTest, FromStorageKeyComponents) {
190 struct TestCase {
191 const GURL url;
192 const absl::optional<base::UnguessableToken> nonce = absl::nullopt;
193 } test_cases[] = {
194 {GURL("https://foo.com")},
195 {GURL()},
196 {GURL("https://foo.com"), base::UnguessableToken::Create()},
197 };
198
199 for (const auto& test_case : test_cases) {
200 auto want =
201 CookiePartitionKey::FromURLForTesting(test_case.url, test_case.nonce);
202 absl::optional<CookiePartitionKey> got =
203 CookiePartitionKey::FromStorageKeyComponents(want.site(), want.nonce());
204 if (PartitionedCookiesEnabled()) {
205 EXPECT_EQ(got, want);
206 } else {
207 EXPECT_FALSE(got);
208 }
209 }
210 }
211
TEST_P(CookiePartitionKeyTest,FromScript)212 TEST_P(CookiePartitionKeyTest, FromScript) {
213 auto key = CookiePartitionKey::FromScript();
214 EXPECT_TRUE(key);
215 EXPECT_TRUE(key->from_script());
216 EXPECT_TRUE(key->site().opaque());
217
218 auto key2 = CookiePartitionKey::FromScript();
219 EXPECT_TRUE(key2);
220 EXPECT_TRUE(key2->from_script());
221 EXPECT_TRUE(key2->site().opaque());
222
223 // The keys should not be equal because they get created with different opaque
224 // sites. Test both the '==' and '!=' operators here.
225 EXPECT_FALSE(key == key2);
226 EXPECT_TRUE(key != key2);
227 }
228
TEST_P(CookiePartitionKeyTest,IsSerializeable)229 TEST_P(CookiePartitionKeyTest, IsSerializeable) {
230 EXPECT_FALSE(CookiePartitionKey::FromURLForTesting(GURL()).IsSerializeable());
231 EXPECT_EQ(PartitionedCookiesEnabled(), CookiePartitionKey::FromURLForTesting(
232 GURL("https://www.example.com"))
233 .IsSerializeable());
234 }
235
TEST_P(CookiePartitionKeyTest,Equality)236 TEST_P(CookiePartitionKeyTest, Equality) {
237 // Same eTLD+1 but different scheme are not equal.
238 EXPECT_NE(CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")),
239 CookiePartitionKey::FromURLForTesting(GURL("http://foo.com")));
240
241 // Different subdomains of the same site are equal.
242 EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://a.foo.com")),
243 CookiePartitionKey::FromURLForTesting(GURL("https://b.foo.com")));
244 }
245
TEST_P(CookiePartitionKeyTest,Equality_WithNonce)246 TEST_P(CookiePartitionKeyTest, Equality_WithNonce) {
247 SchemefulSite top_level_site =
248 SchemefulSite(GURL("https://toplevelsite.com"));
249 SchemefulSite frame_site = SchemefulSite(GURL("https://cookiesite.com"));
250 base::UnguessableToken nonce1 = base::UnguessableToken::Create();
251 base::UnguessableToken nonce2 = base::UnguessableToken::Create();
252 EXPECT_NE(nonce1, nonce2);
253 auto key1 = CookiePartitionKey::FromNetworkIsolationKey(
254 NetworkIsolationKey(top_level_site, frame_site, nonce1));
255 EXPECT_EQ(PartitionedCookiesEnabled(), key1.has_value());
256 if (!PartitionedCookiesEnabled()) {
257 return;
258 }
259
260 auto key2 = CookiePartitionKey::FromNetworkIsolationKey(
261 NetworkIsolationKey(top_level_site, frame_site, nonce2));
262 EXPECT_TRUE(key1.has_value() && key2.has_value());
263 EXPECT_NE(key1, key2);
264
265 auto key3 = CookiePartitionKey::FromNetworkIsolationKey(
266 NetworkIsolationKey(top_level_site, frame_site, nonce1));
267 EXPECT_EQ(key1, key3);
268
269 auto unnonced_key = CookiePartitionKey::FromNetworkIsolationKey(
270 NetworkIsolationKey(top_level_site, frame_site));
271 EXPECT_NE(key1, unnonced_key);
272 }
273
TEST_P(CookiePartitionKeyTest,Localhost)274 TEST_P(CookiePartitionKeyTest, Localhost) {
275 SchemefulSite top_level_site(GURL("https://localhost:8000"));
276
277 auto key = CookiePartitionKey::FromNetworkIsolationKey(
278 NetworkIsolationKey(top_level_site, top_level_site));
279 EXPECT_EQ(PartitionedCookiesEnabled(), key.has_value());
280
281 SchemefulSite frame_site(GURL("https://cookiesite.com"));
282 key = CookiePartitionKey::FromNetworkIsolationKey(
283 NetworkIsolationKey(top_level_site, frame_site));
284 EXPECT_EQ(PartitionedCookiesEnabled(), key.has_value());
285 }
286
287 // Test that creating nonced partition keys works with both types of
288 // NetworkIsolationKey modes. See https://crbug.com/1442260.
TEST_P(CookiePartitionKeyTest,NetworkIsolationKeyMode)289 TEST_P(CookiePartitionKeyTest, NetworkIsolationKeyMode) {
290 if (!PartitionedCookiesEnabled()) {
291 return;
292 }
293
294 const net::SchemefulSite kTopFrameSite(GURL("https://a.com"));
295 const net::SchemefulSite kFrameSite(GURL("https://b.com"));
296 const auto kNonce = base::UnguessableToken::Create();
297
298 { // Frame site mode.
299 base::test::ScopedFeatureList feature_list;
300 feature_list.InitWithFeatures(
301 {}, {net::features::kEnableCrossSiteFlagNetworkIsolationKey});
302
303 const auto key = CookiePartitionKey::FromNetworkIsolationKey(
304 NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce));
305 EXPECT_TRUE(key);
306 EXPECT_EQ(key->site(), kFrameSite);
307 EXPECT_EQ(key->nonce().value(), kNonce);
308 }
309
310 { // Cross-site flag mode.
311 base::test::ScopedFeatureList feature_list;
312 feature_list.InitWithFeatures(
313 {net::features::kEnableCrossSiteFlagNetworkIsolationKey}, {});
314
315 const auto key = CookiePartitionKey::FromNetworkIsolationKey(
316 NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce));
317 EXPECT_TRUE(key);
318 EXPECT_EQ(key->site(), kFrameSite);
319 EXPECT_EQ(key->nonce().value(), kNonce);
320 }
321 }
322
323 } // namespace net
324