| // 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 <string> |
| #include <tuple> |
| |
| #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<std::tuple<bool, bool>> { |
| 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<CookiePartitionKey> 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<CookiePartitionKey> 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<CookiePartitionKey> 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<CookiePartitionKey> 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<base::test::FeatureRef> enabled_features; |
| std::vector<base::test::FeatureRef> 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<CookiePartitionKey> 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<base::UnguessableToken> 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<base::UnguessableToken> 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<CookiePartitionKey> 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 |