| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/first_party_sets/public_sets.h" |
| |
| #include <set> |
| #include <string> |
| |
| #include "base/containers/flat_map.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/first_party_sets/first_party_set_entry.h" |
| #include "net/first_party_sets/first_party_sets_context_config.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| |
| using ::testing::IsEmpty; |
| using ::testing::Optional; |
| using ::testing::Pair; |
| using ::testing::UnorderedElementsAre; |
| |
| namespace net { |
| |
| const SchemefulSite kPrimary(GURL("https://primary.test")); |
| const SchemefulSite kPrimary2(GURL("https://primary2.test")); |
| const SchemefulSite kPrimary3(GURL("https://primary3.test")); |
| const SchemefulSite kAssociated1(GURL("https://associated1.test")); |
| const SchemefulSite kAssociated1Cctld(GURL("https://associated1.cctld")); |
| const SchemefulSite kAssociated1Cctld2(GURL("https://associated1.cctld2")); |
| const SchemefulSite kAssociated2(GURL("https://associated2.test")); |
| const SchemefulSite kAssociated3(GURL("https://associated3.test")); |
| const SchemefulSite kAssociated4(GURL("https://associated4.test")); |
| const SchemefulSite kService(GURL("https://service.test")); |
| |
| class PublicSetsTest : public ::testing::Test { |
| public: |
| PublicSetsTest() = default; |
| |
| FirstPartySetsContextConfig* config() { return &fps_context_config_; } |
| |
| // A helper to repeatedly call `PublicSets::FindEntry` and accumulate the |
| // non-nullopt results. |
| base::flat_map<SchemefulSite, FirstPartySetEntry> FindEntries( |
| const PublicSets& public_sets, |
| const base::flat_set<SchemefulSite>& sites) { |
| std::vector<std::pair<SchemefulSite, FirstPartySetEntry>> results; |
| for (const SchemefulSite& site : sites) { |
| if (absl::optional<FirstPartySetEntry> entry = |
| public_sets.FindEntry(site, config()); |
| entry.has_value()) { |
| results.emplace_back(site, *entry); |
| } |
| } |
| return results; |
| } |
| |
| private: |
| FirstPartySetsContextConfig fps_context_config_; |
| }; |
| |
| TEST_F(PublicSetsTest, FindEntry_Nonexistent) { |
| SchemefulSite example(GURL("https://example.test")); |
| |
| EXPECT_THAT(PublicSets().FindEntry(example, config()), absl::nullopt); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_Exists) { |
| SchemefulSite example(GURL("https://example.test")); |
| SchemefulSite decoy_site(GURL("https://decoy.test")); |
| FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt); |
| FirstPartySetEntry decoy_entry(example, SiteType::kAssociated, 1); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, entry}, |
| {decoy_site, decoy_entry}, |
| }, |
| {}) |
| .FindEntry(example, config()), |
| Optional(entry)); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_ExistsWhenNormalized) { |
| SchemefulSite https_example(GURL("https://example.test")); |
| SchemefulSite wss_example(GURL("wss://example.test")); |
| FirstPartySetEntry entry(https_example, SiteType::kPrimary, absl::nullopt); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {https_example, entry}, |
| }, |
| {}) |
| .FindEntry(wss_example, config()), |
| Optional(entry)); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_ExistsViaOverride) { |
| SchemefulSite example(GURL("https://example.test")); |
| FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt); |
| FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1); |
| |
| config()->SetCustomizations({{example, override_entry}}); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, public_entry}, |
| }, |
| {}) |
| .FindEntry(example, config()), |
| Optional(override_entry)); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_RemovedViaOverride) { |
| SchemefulSite example(GURL("https://example.test")); |
| FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt); |
| |
| config()->SetCustomizations({{example, absl::nullopt}}); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, public_entry}, |
| }, |
| {}) |
| .FindEntry(example, config()), |
| absl::nullopt); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_ExistsViaAlias) { |
| SchemefulSite example(GURL("https://example.test")); |
| SchemefulSite example_cctld(GURL("https://example.cctld")); |
| FirstPartySetEntry entry(example, SiteType::kPrimary, absl::nullopt); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, entry}, |
| }, |
| {{example_cctld, example}}) |
| .FindEntry(example_cctld, config()), |
| Optional(entry)); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_ExistsViaOverrideWithDecoyAlias) { |
| SchemefulSite example(GURL("https://example.test")); |
| SchemefulSite example_cctld(GURL("https://example.cctld")); |
| FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt); |
| FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1); |
| |
| config()->SetCustomizations({{example_cctld, override_entry}}); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, public_entry}, |
| }, |
| {{example_cctld, example}}) |
| .FindEntry(example_cctld, config()), |
| Optional(override_entry)); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_RemovedViaOverrideWithDecoyAlias) { |
| SchemefulSite example(GURL("https://example.test")); |
| SchemefulSite example_cctld(GURL("https://example.cctld")); |
| FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt); |
| |
| config()->SetCustomizations({{example_cctld, absl::nullopt}}); |
| |
| EXPECT_THAT(PublicSets( |
| { |
| {example, public_entry}, |
| }, |
| {{example_cctld, example}}) |
| .FindEntry(example_cctld, config()), |
| absl::nullopt); |
| } |
| |
| TEST_F(PublicSetsTest, FindEntry_AliasesIgnoredForConfig) { |
| SchemefulSite example(GURL("https://example.test")); |
| SchemefulSite example_cctld(GURL("https://example.cctld")); |
| FirstPartySetEntry public_entry(example, SiteType::kPrimary, absl::nullopt); |
| FirstPartySetEntry override_entry(example, SiteType::kAssociated, 1); |
| |
| config()->SetCustomizations({{example, override_entry}}); |
| |
| // FindEntry should ignore aliases when using the customizations. Public |
| // aliases only apply to sites in the public sets. |
| EXPECT_THAT(PublicSets( |
| { |
| {example, public_entry}, |
| }, |
| {{example_cctld, example}}) |
| .FindEntry(example_cctld, config()), |
| public_entry); |
| } |
| |
| class PopulatedPublicSetsTest : public PublicSetsTest { |
| public: |
| PopulatedPublicSetsTest() |
| : public_sets_( |
| { |
| {kPrimary, FirstPartySetEntry(kPrimary, |
| SiteType::kPrimary, |
| absl::nullopt)}, |
| {kAssociated1, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)}, |
| {kAssociated2, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 1)}, |
| {kService, FirstPartySetEntry(kPrimary, |
| SiteType::kService, |
| absl::nullopt)}, |
| {kPrimary2, FirstPartySetEntry(kPrimary2, |
| SiteType::kPrimary, |
| absl::nullopt)}, |
| {kAssociated3, |
| FirstPartySetEntry(kPrimary2, SiteType::kAssociated, 0)}, |
| }, |
| { |
| {kAssociated1Cctld, kAssociated1}, |
| }) {} |
| |
| base::flat_map<SchemefulSite, FirstPartySetEntry> FindEntries( |
| const base::flat_set<SchemefulSite>& sites) { |
| return PublicSetsTest::FindEntries(public_sets(), sites); |
| } |
| |
| PublicSets& public_sets() { return public_sets_; } |
| |
| private: |
| PublicSets public_sets_; |
| }; |
| |
| TEST_F(PopulatedPublicSetsTest, |
| ApplyManuallySpecifiedSet_DeduplicatesPrimaryPrimary) { |
| // kPrimary overlaps as primary of both sets, so the existing set should be |
| // wiped out. |
| public_sets().ApplyManuallySpecifiedSet( |
| kPrimary, |
| { |
| {kPrimary, |
| FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)}, |
| {kAssociated4, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)}, |
| }, |
| {}); |
| |
| EXPECT_THAT( |
| FindEntries({ |
| kPrimary, |
| kAssociated1, |
| kAssociated2, |
| kAssociated4, |
| kService, |
| kAssociated1Cctld, |
| }), |
| UnorderedElementsAre( |
| Pair(kPrimary, |
| FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)), |
| Pair(kAssociated4, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 0)))); |
| } |
| |
| TEST_F(PopulatedPublicSetsTest, |
| ApplyManuallySpecifiedSet_DeduplicatesPrimaryNonprimary) { |
| // kPrimary overlaps as a primary of the public set and non-primary of the CLI |
| // set, so the existing set should be wiped out. |
| public_sets().ApplyManuallySpecifiedSet( |
| kPrimary3, |
| { |
| {kPrimary3, |
| FirstPartySetEntry(kPrimary3, SiteType::kPrimary, absl::nullopt)}, |
| {kPrimary, FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)}, |
| }, |
| {}); |
| |
| EXPECT_THAT( |
| FindEntries({ |
| kPrimary, |
| kAssociated1, |
| kAssociated2, |
| kAssociated4, |
| kService, |
| kPrimary3, |
| kAssociated1Cctld, |
| }), |
| UnorderedElementsAre( |
| Pair(kPrimary3, FirstPartySetEntry(kPrimary3, SiteType::kPrimary, |
| absl::nullopt)), |
| Pair(kPrimary, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)))); |
| } |
| |
| TEST_F(PopulatedPublicSetsTest, |
| ApplyManuallySpecifiedSet_DeduplicatesNonprimaryPrimary) { |
| // kAssociated1 overlaps as a non-primary of the public set and primary of the |
| // CLI set, so the CLI set should steal it and wipe out its alias, but |
| // otherwise leave the set intact. |
| public_sets().ApplyManuallySpecifiedSet( |
| kAssociated1, |
| { |
| {kAssociated1, |
| FirstPartySetEntry(kAssociated1, SiteType::kPrimary, absl::nullopt)}, |
| {kAssociated4, |
| FirstPartySetEntry(kAssociated1, SiteType::kAssociated, 0)}, |
| }, |
| {}); |
| |
| EXPECT_THAT( |
| FindEntries({ |
| kPrimary, |
| kAssociated1, |
| kAssociated2, |
| kAssociated4, |
| kService, |
| kPrimary3, |
| kAssociated1Cctld, |
| }), |
| UnorderedElementsAre( |
| Pair(kPrimary, |
| FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)), |
| Pair(kAssociated2, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 1)), |
| Pair(kService, |
| FirstPartySetEntry(kPrimary, SiteType::kService, absl::nullopt)), |
| Pair(kAssociated1, |
| FirstPartySetEntry(kAssociated1, SiteType::kPrimary, |
| absl::nullopt)), |
| Pair(kAssociated4, |
| FirstPartySetEntry(kAssociated1, SiteType::kAssociated, 0)))); |
| } |
| |
| TEST_F(PopulatedPublicSetsTest, |
| ApplyManuallySpecifiedSet_DeduplicatesNonprimaryNonprimary) { |
| // kAssociated1 overlaps as a non-primary of the public set and non-primary of |
| // the CLI set, so the CLI set should steal it and wipe out its alias. |
| public_sets().ApplyManuallySpecifiedSet( |
| kPrimary3, |
| { |
| {kPrimary3, |
| FirstPartySetEntry(kPrimary3, SiteType::kPrimary, absl::nullopt)}, |
| {kAssociated1, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)}, |
| }, |
| {}); |
| |
| EXPECT_THAT( |
| FindEntries({ |
| kPrimary, |
| kAssociated1, |
| kAssociated2, |
| kAssociated4, |
| kService, |
| kPrimary3, |
| kAssociated1Cctld, |
| }), |
| UnorderedElementsAre( |
| Pair(kPrimary, |
| FirstPartySetEntry(kPrimary, SiteType::kPrimary, absl::nullopt)), |
| Pair(kAssociated2, |
| FirstPartySetEntry(kPrimary, SiteType::kAssociated, 1)), |
| Pair(kService, |
| FirstPartySetEntry(kPrimary, SiteType::kService, absl::nullopt)), |
| Pair(kPrimary3, FirstPartySetEntry(kPrimary3, SiteType::kPrimary, |
| absl::nullopt)), |
| Pair(kAssociated1, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)))); |
| } |
| |
| TEST_F(PopulatedPublicSetsTest, |
| ApplyManuallySpecifiedSet_PrunesInducedSingletons) { |
| // Steal kAssociated3, so that kPrimary2 becomes a singleton, and verify that |
| // kPrimary2 is no longer considered in a set. |
| public_sets().ApplyManuallySpecifiedSet( |
| kPrimary3, |
| { |
| {kPrimary3, |
| FirstPartySetEntry(kPrimary3, SiteType::kPrimary, absl::nullopt)}, |
| {kAssociated3, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)}, |
| }, |
| {}); |
| |
| EXPECT_THAT(FindEntries({kPrimary2}), IsEmpty()); |
| } |
| |
| TEST_F(PopulatedPublicSetsTest, ApplyManuallySpecifiedSet_RespectsManualAlias) { |
| // Both the public sets and the locally-defined set define an alias for |
| // kAssociated1, but both define a different set for that site too. Only the |
| // locally-defined alias should be observable. |
| public_sets().ApplyManuallySpecifiedSet( |
| kPrimary3, |
| { |
| {kPrimary3, |
| FirstPartySetEntry(kPrimary3, SiteType::kPrimary, absl::nullopt)}, |
| {kAssociated1, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)}, |
| }, |
| {{kAssociated1Cctld2, kAssociated1}}); |
| |
| EXPECT_THAT( |
| FindEntries({ |
| kAssociated1, |
| kAssociated1Cctld, |
| kAssociated1Cctld2, |
| }), |
| UnorderedElementsAre( |
| Pair(kAssociated1, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)), |
| Pair(kAssociated1Cctld2, |
| FirstPartySetEntry(kPrimary3, SiteType::kAssociated, 0)))); |
| } |
| |
| } // namespace net |