// Copyright 2021 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include #include #include "base/test/scoped_feature_list.h" #include "net/base/features.h" #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_partition_key.h" #include "testing/gtest/include/gtest/gtest.h" namespace net { class CookiePartitionKeyTest : public testing::TestWithParam> { protected: // testing::Test void SetUp() override { scoped_feature_list_[0].InitWithFeatureState(features::kPartitionedCookies, PartitionedCookiesEnabled()); scoped_feature_list_[1].InitWithFeatureState( features::kNoncedPartitionedCookies, NoncedPartitionedCookiesEnabled()); } bool PartitionedCookiesEnabled() { return std::get<0>(GetParam()); } bool NoncedPartitionedCookiesEnabled() { return std::get<1>(GetParam()); } private: base::test::ScopedFeatureList scoped_feature_list_[2]; }; INSTANTIATE_TEST_SUITE_P(/* no label */, CookiePartitionKeyTest, ::testing::Values(std::make_tuple(false, false), std::make_tuple(false, true), std::make_tuple(true, true))); TEST_P(CookiePartitionKeyTest, Serialization) { base::UnguessableToken nonce = base::UnguessableToken::Create(); struct { absl::optional input; bool expected_ret; std::string expected_output; } cases[] = { // No partition key {absl::nullopt, true, kEmptyCookiePartitionKey}, // Partition key present {CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")), true, "https://toplevelsite.com"}, // Local file URL {CookiePartitionKey::FromURLForTesting(GURL("file:///path/to/file.txt")), true, "file://"}, // File URL with host {CookiePartitionKey::FromURLForTesting( GURL("file://toplevelsite.com/path/to/file.pdf")), true, "file://toplevelsite.com"}, // Opaque origin {CookiePartitionKey::FromURLForTesting(GURL()), false, ""}, // With nonce {CookiePartitionKey::FromNetworkIsolationKey(NetworkIsolationKey( SchemefulSite(GURL("https://toplevelsite.com")), SchemefulSite(GURL("https://cookiesite.com")), nonce)), false, ""}, // Invalid partition key {absl::make_optional( CookiePartitionKey::FromURLForTesting(GURL("abc123foobar!!"))), false, ""}, }; for (const auto& tc : cases) { std::string got; if (PartitionedCookiesEnabled()) { EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Serialize(tc.input, got)); EXPECT_EQ(tc.expected_output, got); } else { // Serialize should only return true for unpartitioned cookies if the // feature is disabled. EXPECT_NE(tc.input.has_value(), CookiePartitionKey::Serialize(tc.input, got)); } } } TEST_P(CookiePartitionKeyTest, Deserialization) { struct { std::string input; bool expected_ret; absl::optional expected_output; } cases[] = { {kEmptyCookiePartitionKey, true, absl::nullopt}, {"https://toplevelsite.com", true, CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com"))}, {"abc123foobar!!", false, absl::nullopt}, }; for (const auto& tc : cases) { absl::optional got; if (PartitionedCookiesEnabled()) { EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Deserialize(tc.input, got)); if (tc.expected_output.has_value()) { EXPECT_TRUE(got.has_value()); EXPECT_EQ(tc.expected_output.value(), got.value()); } else { EXPECT_FALSE(got.has_value()); } } else { // Deserialize should only return true for unpartitioned cookies if the // feature is disabled. EXPECT_EQ(tc.input == kEmptyCookiePartitionKey, CookiePartitionKey::Deserialize(tc.input, got)); } } } TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey) { const SchemefulSite kTopLevelSite = SchemefulSite(GURL("https://toplevelsite.com")); const SchemefulSite kCookieSite = SchemefulSite(GURL("https://cookiesite.com")); const base::UnguessableToken kNonce = base::UnguessableToken::Create(); struct TestCase { const std::string desc; const NetworkIsolationKey network_isolation_key; bool allow_nonced_partition_keys; const absl::optional expected; } test_cases[] = { { "Empty", NetworkIsolationKey(), /*allow_nonced_partition_keys=*/false, absl::nullopt, }, { "WithTopLevelSite", NetworkIsolationKey(kTopLevelSite, kCookieSite), /*allow_nonced_partition_keys=*/false, CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL()), }, { "WithNonce", NetworkIsolationKey(kTopLevelSite, kCookieSite, kNonce), /*allow_nonced_partition_keys=*/false, CookiePartitionKey::FromURLForTesting(kCookieSite.GetURL(), kNonce), }, { "NoncedAllowed_KeyWithoutNonce", NetworkIsolationKey(kTopLevelSite, kCookieSite), /*allow_nonced_partition_keys=*/true, CookiePartitionKey::FromURLForTesting(kTopLevelSite.GetURL()), }, { "NoncedAllowed_KeyWithoutNonce", NetworkIsolationKey(kTopLevelSite, kCookieSite, kNonce), /*allow_nonced_partition_keys=*/true, CookiePartitionKey::FromURLForTesting(kCookieSite.GetURL(), kNonce), }, }; for (const auto& test_case : test_cases) { SCOPED_TRACE(test_case.desc); base::test::ScopedFeatureList feature_list; std::vector enabled_features; std::vector disabled_features; if (PartitionedCookiesEnabled()) { enabled_features.push_back(features::kPartitionedCookies); } else { disabled_features.push_back(features::kPartitionedCookies); } if (test_case.allow_nonced_partition_keys) { enabled_features.push_back(features::kNoncedPartitionedCookies); } else { disabled_features.push_back(features::kNoncedPartitionedCookies); } feature_list.InitWithFeatures(enabled_features, disabled_features); absl::optional got = CookiePartitionKey::FromNetworkIsolationKey( test_case.network_isolation_key); if (PartitionedCookiesEnabled()) { EXPECT_EQ(test_case.expected, got); } else if (test_case.allow_nonced_partition_keys) { EXPECT_EQ(test_case.network_isolation_key.GetNonce().has_value(), got.has_value()); if (got) EXPECT_EQ(test_case.expected, got); } else { EXPECT_FALSE(got); } } } TEST_P(CookiePartitionKeyTest, FromWire) { struct TestCase { const GURL url; const absl::optional nonce; } test_cases[] = { {GURL("https://foo.com"), absl::nullopt}, {GURL(), absl::nullopt}, {GURL("https://foo.com"), absl::make_optional(base::UnguessableToken::Create())}, }; for (const auto& test_case : test_cases) { auto want = CookiePartitionKey::FromURLForTesting(test_case.url, test_case.nonce); auto got = CookiePartitionKey::FromWire(want.site(), want.nonce()); EXPECT_EQ(want, got); EXPECT_FALSE(got.from_script()); } } TEST_P(CookiePartitionKeyTest, FromStorageKeyComponents) { struct TestCase { const GURL url; const absl::optional nonce = absl::nullopt; } test_cases[] = { {GURL("https://foo.com")}, {GURL()}, {GURL("https://foo.com"), base::UnguessableToken::Create()}, }; for (const auto& test_case : test_cases) { auto want = CookiePartitionKey::FromURLForTesting(test_case.url, test_case.nonce); absl::optional got = CookiePartitionKey::FromStorageKeyComponents(want.site(), want.nonce()); if (PartitionedCookiesEnabled() || (NoncedPartitionedCookiesEnabled() && test_case.nonce)) { EXPECT_EQ(got, want); } else { EXPECT_FALSE(got); } } } TEST_P(CookiePartitionKeyTest, FromScript) { auto key = CookiePartitionKey::FromScript(); EXPECT_TRUE(key); EXPECT_TRUE(key->from_script()); EXPECT_TRUE(key->site().opaque()); auto key2 = CookiePartitionKey::FromScript(); EXPECT_TRUE(key2); EXPECT_TRUE(key2->from_script()); EXPECT_TRUE(key2->site().opaque()); // The keys should not be equal because they get created with different opaque // sites. Test both the '==' and '!=' operators here. EXPECT_FALSE(key == key2); EXPECT_TRUE(key != key2); } TEST_P(CookiePartitionKeyTest, IsSerializeable) { EXPECT_FALSE(CookiePartitionKey::FromURLForTesting(GURL()).IsSerializeable()); EXPECT_EQ(PartitionedCookiesEnabled(), CookiePartitionKey::FromURLForTesting( GURL("https://www.example.com")) .IsSerializeable()); } TEST_P(CookiePartitionKeyTest, Equality) { // Same eTLD+1 but different scheme are not equal. EXPECT_NE(CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")), CookiePartitionKey::FromURLForTesting(GURL("http://foo.com"))); // Different subdomains of the same site are equal. EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://a.foo.com")), CookiePartitionKey::FromURLForTesting(GURL("https://b.foo.com"))); } TEST_P(CookiePartitionKeyTest, Equality_WithNonce) { SchemefulSite top_level_site = SchemefulSite(GURL("https://toplevelsite.com")); SchemefulSite frame_site = SchemefulSite(GURL("https://cookiesite.com")); base::UnguessableToken nonce1 = base::UnguessableToken::Create(); base::UnguessableToken nonce2 = base::UnguessableToken::Create(); EXPECT_NE(nonce1, nonce2); auto key1 = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, frame_site, nonce1)); bool partitioned_cookies_enabled = PartitionedCookiesEnabled() || NoncedPartitionedCookiesEnabled(); EXPECT_EQ(partitioned_cookies_enabled, key1.has_value()); if (!partitioned_cookies_enabled) return; auto key2 = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, frame_site, nonce2)); EXPECT_TRUE(key1.has_value() && key2.has_value()); EXPECT_NE(key1, key2); auto key3 = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, frame_site, nonce1)); EXPECT_EQ(key1, key3); auto unnonced_key = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, frame_site)); EXPECT_NE(key1, unnonced_key); } TEST_P(CookiePartitionKeyTest, Localhost) { SchemefulSite top_level_site(GURL("https://localhost:8000")); auto key = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, top_level_site)); EXPECT_EQ(PartitionedCookiesEnabled(), key.has_value()); SchemefulSite frame_site(GURL("https://cookiesite.com")); key = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(top_level_site, frame_site)); EXPECT_EQ(PartitionedCookiesEnabled(), key.has_value()); } // Test that creating nonced partition keys works with both types of // NetworkIsolationKey modes. See https://crbug.com/1442260. TEST_P(CookiePartitionKeyTest, NetworkIsolationKeyMode) { if (!PartitionedCookiesEnabled()) { return; } const net::SchemefulSite kTopFrameSite(GURL("https://a.com")); const net::SchemefulSite kFrameSite(GURL("https://b.com")); const auto kNonce = base::UnguessableToken::Create(); { // Frame site mode. base::test::ScopedFeatureList feature_list; feature_list.InitWithFeatures( {}, {net::features::kEnableCrossSiteFlagNetworkIsolationKey}); const auto key = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce)); EXPECT_TRUE(key); EXPECT_EQ(key->site(), kFrameSite); EXPECT_EQ(key->nonce().value(), kNonce); } { // Cross-site flag mode. base::test::ScopedFeatureList feature_list; feature_list.InitWithFeatures( {net::features::kEnableCrossSiteFlagNetworkIsolationKey}, {}); const auto key = CookiePartitionKey::FromNetworkIsolationKey( NetworkIsolationKey(kTopFrameSite, kFrameSite, kNonce)); EXPECT_TRUE(key); EXPECT_EQ(key->site(), kFrameSite); EXPECT_EQ(key->nonce().value(), kNonce); } } } // namespace net