xref: /aosp_15_r20/external/cronet/net/first_party_sets/global_first_party_sets.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
1 // Copyright 2022 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/first_party_sets/global_first_party_sets.h"
6 
7 #include <optional>
8 #include <set>
9 #include <tuple>
10 
11 #include "base/containers/contains.h"
12 #include "base/containers/flat_map.h"
13 #include "base/containers/flat_set.h"
14 #include "base/functional/function_ref.h"
15 #include "base/ranges/algorithm.h"
16 #include "base/types/optional_util.h"
17 #include "net/base/schemeful_site.h"
18 #include "net/first_party_sets/addition_overlaps_union_find.h"
19 #include "net/first_party_sets/first_party_set_entry.h"
20 #include "net/first_party_sets/first_party_set_entry_override.h"
21 #include "net/first_party_sets/first_party_set_metadata.h"
22 #include "net/first_party_sets/first_party_sets_context_config.h"
23 #include "net/first_party_sets/local_set_declaration.h"
24 
25 namespace net {
26 
27 namespace {
28 
29 using FlattenedSets = base::flat_map<SchemefulSite, FirstPartySetEntry>;
30 using SingleSet = base::flat_map<SchemefulSite, FirstPartySetEntry>;
31 
32 // Converts a list of First-Party Sets from a SingleSet to a FlattenedSet
33 // representation.
SetListToFlattenedSets(const std::vector<SingleSet> & set_list)34 FlattenedSets SetListToFlattenedSets(const std::vector<SingleSet>& set_list) {
35   FlattenedSets sets;
36   for (const auto& set : set_list) {
37     for (const auto& site_and_entry : set) {
38       bool inserted = sets.emplace(site_and_entry).second;
39       CHECK(inserted);
40     }
41   }
42   return sets;
43 }
44 
45 // Adds all sets in a list of First-Party Sets into `site_to_entry` which maps
46 // from a site to its entry.
UpdateCustomizations(const std::vector<SingleSet> & set_list,std::vector<std::pair<SchemefulSite,FirstPartySetEntryOverride>> & site_to_entry)47 void UpdateCustomizations(
48     const std::vector<SingleSet>& set_list,
49     std::vector<std::pair<SchemefulSite, FirstPartySetEntryOverride>>&
50         site_to_entry) {
51   for (const auto& set : set_list) {
52     for (const auto& site_and_entry : set) {
53       site_to_entry.emplace_back(site_and_entry);
54     }
55   }
56 }
57 
ProjectKey(const std::pair<SchemefulSite,FirstPartySetEntryOverride> & p)58 const SchemefulSite& ProjectKey(
59     const std::pair<SchemefulSite, FirstPartySetEntryOverride>& p) {
60   return p.first;
61 }
62 
63 }  // namespace
64 
65 GlobalFirstPartySets::GlobalFirstPartySets() = default;
66 
GlobalFirstPartySets(base::Version public_sets_version,base::flat_map<SchemefulSite,FirstPartySetEntry> entries,base::flat_map<SchemefulSite,SchemefulSite> aliases)67 GlobalFirstPartySets::GlobalFirstPartySets(
68     base::Version public_sets_version,
69     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
70     base::flat_map<SchemefulSite, SchemefulSite> aliases)
71     : GlobalFirstPartySets(
72           public_sets_version,
73           public_sets_version.IsValid()
74               ? std::move(entries)
75               : base::flat_map<SchemefulSite, FirstPartySetEntry>(),
76           public_sets_version.IsValid()
77               ? std::move(aliases)
78               : base::flat_map<SchemefulSite, SchemefulSite>(),
79           FirstPartySetsContextConfig(),
80           base::flat_map<SchemefulSite, SchemefulSite>()) {}
81 
GlobalFirstPartySets(base::Version public_sets_version,base::flat_map<SchemefulSite,FirstPartySetEntry> entries,base::flat_map<SchemefulSite,SchemefulSite> aliases,FirstPartySetsContextConfig manual_config,base::flat_map<SchemefulSite,SchemefulSite> manual_aliases)82 GlobalFirstPartySets::GlobalFirstPartySets(
83     base::Version public_sets_version,
84     base::flat_map<SchemefulSite, FirstPartySetEntry> entries,
85     base::flat_map<SchemefulSite, SchemefulSite> aliases,
86     FirstPartySetsContextConfig manual_config,
87     base::flat_map<SchemefulSite, SchemefulSite> manual_aliases)
88     : public_sets_version_(std::move(public_sets_version)),
89       entries_(std::move(entries)),
90       aliases_(std::move(aliases)),
91       manual_config_(std::move(manual_config)),
92       manual_aliases_(std::move(manual_aliases)) {
93   if (!public_sets_version_.IsValid()) {
94     CHECK(entries_.empty());
95     CHECK(aliases_.empty());
96   }
97 
98   CHECK(base::ranges::all_of(aliases_, [&](const auto& pair) {
99     return entries_.contains(pair.second);
100   }));
101   CHECK(!ContainsSingleton());
102 }
103 
104 GlobalFirstPartySets::GlobalFirstPartySets(GlobalFirstPartySets&&) = default;
105 GlobalFirstPartySets& GlobalFirstPartySets::operator=(GlobalFirstPartySets&&) =
106     default;
107 
108 GlobalFirstPartySets::~GlobalFirstPartySets() = default;
109 
110 bool GlobalFirstPartySets::operator==(const GlobalFirstPartySets& other) const =
111     default;
112 
113 bool GlobalFirstPartySets::operator!=(const GlobalFirstPartySets& other) const =
114     default;
115 
Clone() const116 GlobalFirstPartySets GlobalFirstPartySets::Clone() const {
117   return GlobalFirstPartySets(public_sets_version_, entries_, aliases_,
118                               manual_config_.Clone(), manual_aliases_);
119 }
120 
FindEntry(const SchemefulSite & site,const FirstPartySetsContextConfig & config) const121 std::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
122     const SchemefulSite& site,
123     const FirstPartySetsContextConfig& config) const {
124   return FindEntry(site, &config);
125 }
126 
FindEntry(const SchemefulSite & site,const FirstPartySetsContextConfig * config) const127 std::optional<FirstPartySetEntry> GlobalFirstPartySets::FindEntry(
128     const SchemefulSite& site,
129     const FirstPartySetsContextConfig* config) const {
130   // Check if `site` can be found in the customizations first.
131   if (config) {
132     if (const auto override = config->FindOverride(site);
133         override.has_value()) {
134       return override->IsDeletion() ? std::nullopt
135                                     : std::make_optional(override->GetEntry());
136     }
137   }
138 
139   // Now see if it's in the manual config (with or without a manual alias).
140   if (const auto manual_override = manual_config_.FindOverride(site);
141       manual_override.has_value()) {
142     return manual_override->IsDeletion()
143                ? std::nullopt
144                : std::make_optional(manual_override->GetEntry());
145   }
146 
147   // Finally, look up in `entries_`, applying an alias if applicable.
148   const auto canonical_it = aliases_.find(site);
149   const SchemefulSite& canonical_site =
150       canonical_it == aliases_.end() ? site : canonical_it->second;
151   if (const auto entry_it = entries_.find(canonical_site);
152       entry_it != entries_.end()) {
153     return entry_it->second;
154   }
155 
156   return std::nullopt;
157 }
158 
159 base::flat_map<SchemefulSite, FirstPartySetEntry>
FindEntries(const base::flat_set<SchemefulSite> & sites,const FirstPartySetsContextConfig & config) const160 GlobalFirstPartySets::FindEntries(
161     const base::flat_set<SchemefulSite>& sites,
162     const FirstPartySetsContextConfig& config) const {
163   std::vector<std::pair<SchemefulSite, FirstPartySetEntry>> sites_to_entries;
164   for (const SchemefulSite& site : sites) {
165     const std::optional<FirstPartySetEntry> entry = FindEntry(site, config);
166     if (entry.has_value()) {
167       sites_to_entries.emplace_back(site, entry.value());
168     }
169   }
170   return sites_to_entries;
171 }
172 
ComputeMetadata(const SchemefulSite & site,const SchemefulSite * top_frame_site,const FirstPartySetsContextConfig & fps_context_config) const173 FirstPartySetMetadata GlobalFirstPartySets::ComputeMetadata(
174     const SchemefulSite& site,
175     const SchemefulSite* top_frame_site,
176     const FirstPartySetsContextConfig& fps_context_config) const {
177   std::optional<FirstPartySetEntry> top_frame_entry =
178       top_frame_site ? FindEntry(*top_frame_site, fps_context_config)
179                      : std::nullopt;
180 
181   return FirstPartySetMetadata(
182       base::OptionalToPtr(FindEntry(site, fps_context_config)),
183       base::OptionalToPtr(top_frame_entry));
184 }
185 
ApplyManuallySpecifiedSet(const LocalSetDeclaration & local_set_declaration)186 void GlobalFirstPartySets::ApplyManuallySpecifiedSet(
187     const LocalSetDeclaration& local_set_declaration) {
188   CHECK(manual_config_.empty());
189   CHECK(manual_aliases_.empty());
190   if (local_set_declaration.empty()) {
191     // Nothing to do.
192     return;
193   }
194 
195   base::flat_map<SchemefulSite, SchemefulSite> manual_aliases =
196       local_set_declaration.aliases();
197 
198   base::flat_map<SchemefulSite, FirstPartySetEntry> manual_entries =
199       local_set_declaration.entries();
200   for (const auto& [alias, canonical] : manual_aliases) {
201     manual_entries.emplace(alias, manual_entries.find(canonical)->second);
202   }
203 
204   // We handle the manually-specified set the same way as we handle
205   // replacement enterprise policy sets.
206   manual_config_ = ComputeConfig(SetsMutation(
207       /*replacement_sets=*/{manual_entries},
208       /*addition_sets=*/{}));
209   manual_aliases_ = std::move(manual_aliases);
210 
211   CHECK(!ContainsSingleton());
212 }
213 
UnsafeSetManualConfig(FirstPartySetsContextConfig manual_config)214 void GlobalFirstPartySets::UnsafeSetManualConfig(
215     FirstPartySetsContextConfig manual_config) {
216   CHECK(manual_config_.empty());
217   manual_config_ = std::move(manual_config);
218 }
219 
ComputeConfig(const SetsMutation & mutation) const220 FirstPartySetsContextConfig GlobalFirstPartySets::ComputeConfig(
221     const SetsMutation& mutation) const {
222   const std::vector<SingleSet>& replacement_sets = mutation.replacements();
223   const std::vector<SingleSet>& addition_sets = mutation.additions();
224   if (base::ranges::all_of(replacement_sets,
225                            [](const SingleSet& set) { return set.empty(); }) &&
226       base::ranges::all_of(addition_sets,
227                            [](const SingleSet& set) { return set.empty(); })) {
228     // Nothing to do.
229     return FirstPartySetsContextConfig();
230   }
231 
232   // Maps a site to its override.
233   std::vector<std::pair<SchemefulSite, FirstPartySetEntryOverride>>
234       site_to_entry;
235 
236   std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>
237       normalized_additions = NormalizeAdditionSets(addition_sets);
238 
239   // Create flattened versions of the sets for easier lookup.
240   FlattenedSets flattened_replacements =
241       SetListToFlattenedSets(replacement_sets);
242   FlattenedSets flattened_additions =
243       SetListToFlattenedSets(normalized_additions);
244 
245   // All of the custom sets are automatically inserted into site_to_entry.
246   UpdateCustomizations(replacement_sets, site_to_entry);
247   UpdateCustomizations(normalized_additions, site_to_entry);
248 
249   // Maps old primary site to new entry.
250   base::flat_map<SchemefulSite, FirstPartySetEntry>
251       addition_intersected_primaries;
252   for (const auto& [new_member, new_entry] : flattened_additions) {
253     if (const auto entry = FindEntry(new_member, /*config=*/nullptr);
254         entry.has_value()) {
255       // Found an overlap with the existing list of sets.
256       addition_intersected_primaries.emplace(entry->primary(), new_entry);
257     }
258   }
259 
260   // Maps an existing primary site to the members it lost due to replacement.
261   base::flat_map<SchemefulSite, base::flat_set<SchemefulSite>>
262       potential_singletons;
263   for (const auto& [member, set_entry] : flattened_replacements) {
264     if (member == set_entry.primary())
265       continue;
266     if (const auto existing_entry = FindEntry(member, /*config=*/nullptr);
267         existing_entry.has_value() && existing_entry->primary() != member &&
268         !addition_intersected_primaries.contains(existing_entry->primary()) &&
269         !flattened_additions.contains(existing_entry->primary()) &&
270         !flattened_replacements.contains(existing_entry->primary())) {
271       potential_singletons[existing_entry->primary()].insert(member);
272     }
273   }
274 
275   // Find the existing primary sites that have left their existing sets, and
276   // whose existing members should be removed from their set (excluding any
277   // custom sets that those members are involved in).
278   base::flat_set<SchemefulSite> replaced_existing_primaries;
279   for (const auto& [site, unused_primary] : flattened_replacements) {
280     if (const auto entry = FindEntry(site, /*config=*/nullptr);
281         entry.has_value() && entry->primary() == site) {
282       // Site was an primary in the existing sets.
283       bool inserted = replaced_existing_primaries.emplace(site).second;
284       CHECK(inserted);
285     }
286   }
287 
288   if (!addition_intersected_primaries.empty() ||
289       !potential_singletons.empty() || !replaced_existing_primaries.empty()) {
290     // Find out which potential singletons are actually singletons; delete
291     // members whose primaries left; and reparent the sets that intersected with
292     // an addition set.
293     // Note: use a null config here, to avoid taking unrelated policy sets into
294     // account.
295     ForEachEffectiveSetEntry(
296         /*config=*/nullptr,
297         [&](const SchemefulSite& member, const FirstPartySetEntry& set_entry) {
298           // Reparent all sites in any intersecting addition sets.
299           if (const auto entry =
300                   addition_intersected_primaries.find(set_entry.primary());
301               entry != addition_intersected_primaries.end() &&
302               !flattened_replacements.contains(member)) {
303             site_to_entry.emplace_back(
304                 member, FirstPartySetEntry(entry->second.primary(),
305                                            member == entry->second.primary()
306                                                ? SiteType::kPrimary
307                                                : SiteType::kAssociated,
308                                            std::nullopt));
309           }
310           if (member == set_entry.primary())
311             return true;
312           // Remove non-singletons from the potential list.
313           if (const auto entry = potential_singletons.find(set_entry.primary());
314               entry != potential_singletons.end() &&
315               !entry->second.contains(member)) {
316             // This primary lost members, but it still has at least one
317             // (`member`), so it's not a singleton.
318             potential_singletons.erase(entry);
319           }
320           // Remove members from sets whose primary left.
321           if (replaced_existing_primaries.contains(set_entry.primary()) &&
322               !flattened_replacements.contains(member) &&
323               !addition_intersected_primaries.contains(set_entry.primary())) {
324             site_to_entry.emplace_back(member, FirstPartySetEntryOverride());
325           }
326 
327           return true;
328         });
329 
330     // Any primary remaining in `potential_singleton` is a real singleton, so
331     // delete it:
332     for (const auto& [primary, members] : potential_singletons) {
333       site_to_entry.emplace_back(primary, FirstPartySetEntryOverride());
334     }
335   }
336 
337   // For every pre-existing alias that would now refer to a site in the overlay,
338   // which is not already contained in the overlay, we explicitly ignore that
339   // alias.
340   ForEachAlias([&](const SchemefulSite& alias, const SchemefulSite& canonical) {
341     if (base::Contains(site_to_entry, canonical, ProjectKey) &&
342         !base::Contains(site_to_entry, alias, ProjectKey)) {
343       site_to_entry.emplace_back(alias, FirstPartySetEntryOverride());
344     }
345   });
346 
347   return FirstPartySetsContextConfig(std::move(site_to_entry));
348 }
349 
350 std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>
NormalizeAdditionSets(const std::vector<base::flat_map<SchemefulSite,FirstPartySetEntry>> & addition_sets) const351 GlobalFirstPartySets::NormalizeAdditionSets(
352     const std::vector<base::flat_map<SchemefulSite, FirstPartySetEntry>>&
353         addition_sets) const {
354   if (base::ranges::all_of(addition_sets,
355                            [](const SingleSet& set) { return set.empty(); })) {
356     // Nothing to do.
357     return {};
358   }
359 
360   // Find all the addition sets that intersect with any given public set.
361   base::flat_map<SchemefulSite, base::flat_set<size_t>> addition_set_overlaps;
362   for (size_t set_idx = 0; set_idx < addition_sets.size(); set_idx++) {
363     for (const auto& site_and_entry : addition_sets[set_idx]) {
364       if (const auto entry =
365               FindEntry(site_and_entry.first, /*config=*/nullptr);
366           entry.has_value()) {
367         addition_set_overlaps[entry->primary()].insert(set_idx);
368       }
369     }
370   }
371 
372   // Union together all transitively-overlapping addition sets.
373   AdditionOverlapsUnionFind union_finder(addition_sets.size());
374   for (const auto& [public_site, addition_set_indices] :
375        addition_set_overlaps) {
376     for (size_t representative : addition_set_indices) {
377       union_finder.Union(*addition_set_indices.begin(), representative);
378     }
379   }
380 
381   // Now build the new addition sets, with all transitive overlaps eliminated.
382   std::vector<SingleSet> normalized_additions;
383   for (const auto& [rep, children] : union_finder.SetsMapping()) {
384     SingleSet normalized = addition_sets[rep];
385     const SchemefulSite& rep_primary =
386         addition_sets[rep].begin()->second.primary();
387     for (size_t child_set_idx : children) {
388       for (const auto& child_site_and_entry : addition_sets[child_set_idx]) {
389         bool inserted =
390             normalized
391                 .emplace(child_site_and_entry.first,
392                          FirstPartySetEntry(rep_primary, SiteType::kAssociated,
393                                             std::nullopt))
394                 .second;
395         CHECK(inserted);
396       }
397     }
398     normalized_additions.push_back(normalized);
399   }
400   return normalized_additions;
401 }
402 
ForEachPublicSetEntry(base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const403 bool GlobalFirstPartySets::ForEachPublicSetEntry(
404     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
405     const {
406   for (const auto& [site, entry] : entries_) {
407     if (!f(site, entry))
408       return false;
409   }
410   for (const auto& [alias, canonical] : aliases_) {
411     auto it = entries_.find(canonical);
412     CHECK(it != entries_.end());
413     if (!f(alias, it->second))
414       return false;
415   }
416   return true;
417 }
418 
ForEachManualConfigEntry(base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntryOverride &)> f) const419 bool GlobalFirstPartySets::ForEachManualConfigEntry(
420     base::FunctionRef<bool(const SchemefulSite&,
421                            const FirstPartySetEntryOverride&)> f) const {
422   return manual_config_.ForEachCustomizationEntry(f);
423 }
424 
ForEachEffectiveSetEntry(const FirstPartySetsContextConfig & config,base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const425 bool GlobalFirstPartySets::ForEachEffectiveSetEntry(
426     const FirstPartySetsContextConfig& config,
427     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
428     const {
429   return ForEachEffectiveSetEntry(&config, f);
430 }
431 
ForEachEffectiveSetEntry(const FirstPartySetsContextConfig * config,base::FunctionRef<bool (const SchemefulSite &,const FirstPartySetEntry &)> f) const432 bool GlobalFirstPartySets::ForEachEffectiveSetEntry(
433     const FirstPartySetsContextConfig* config,
434     base::FunctionRef<bool(const SchemefulSite&, const FirstPartySetEntry&)> f)
435     const {
436   // Policy sets have highest precedence:
437   if (config != nullptr) {
438     if (!config->ForEachCustomizationEntry(
439             [&](const SchemefulSite& site,
440                 const FirstPartySetEntryOverride& override) {
441               if (!override.IsDeletion())
442                 return f(site, override.GetEntry());
443               return true;
444             })) {
445       return false;
446     }
447   }
448 
449   // Then the manual set:
450   if (!manual_config_.ForEachCustomizationEntry(
451           [&](const SchemefulSite& site,
452               const FirstPartySetEntryOverride& override) {
453             if (!override.IsDeletion() && (!config || !config->Contains(site)))
454               return f(site, override.GetEntry());
455             return true;
456           })) {
457     return false;
458   }
459 
460   // Finally, the public sets.
461   return ForEachPublicSetEntry([&](const SchemefulSite& site,
462                                    const FirstPartySetEntry& entry) {
463     if ((!config || !config->Contains(site)) && !manual_config_.Contains(site))
464       return f(site, entry);
465     return true;
466   });
467 }
468 
ForEachAlias(base::FunctionRef<void (const SchemefulSite &,const SchemefulSite &)> f) const469 void GlobalFirstPartySets::ForEachAlias(
470     base::FunctionRef<void(const SchemefulSite&, const SchemefulSite&)> f)
471     const {
472   for (const auto& [alias, site] : manual_aliases_) {
473     f(alias, site);
474   }
475   for (const auto& [alias, site] : aliases_) {
476     if (manual_config_.Contains(alias)) {
477       continue;
478     }
479     f(alias, site);
480   }
481 }
482 
ContainsSingleton() const483 bool GlobalFirstPartySets::ContainsSingleton() const {
484   std::set<SchemefulSite> possible_singletons;
485   std::set<SchemefulSite> not_singletons;
486 
487   ForEachEffectiveSetEntry(
488       nullptr,
489       [&](const SchemefulSite& site, const FirstPartySetEntry& entry) -> bool {
490         if (!not_singletons.contains(entry.primary())) {
491           if (site == entry.primary()) {
492             possible_singletons.insert(entry.primary());
493           } else {
494             not_singletons.insert(entry.primary());
495             possible_singletons.erase(entry.primary());
496           }
497         }
498 
499         return true;
500       });
501 
502   return !possible_singletons.empty();
503 }
504 
operator <<(std::ostream & os,const GlobalFirstPartySets & sets)505 std::ostream& operator<<(std::ostream& os, const GlobalFirstPartySets& sets) {
506   os << "{entries = {";
507   for (const auto& [site, entry] : sets.entries_) {
508     os << "{" << site.Serialize() << ": " << entry << "}, ";
509   }
510   os << "}, aliases = {";
511   for (const auto& [alias, canonical] : sets.aliases_) {
512     os << "{" << alias.Serialize() << ": " << canonical.Serialize() << "}, ";
513   }
514   os << "}, manual_config = {";
515   sets.ForEachManualConfigEntry(
516       [&](const net::SchemefulSite& site,
517           const FirstPartySetEntryOverride& override) {
518         os << "{" << site.Serialize() << ": " << override << "},";
519         return true;
520       });
521   os << "}, manual_aliases = {";
522   for (const auto& [alias, canonical] : sets.manual_aliases_) {
523     os << "{" << alias.Serialize() << ": " << canonical.Serialize() << "}, ";
524   }
525   os << "}}";
526   return os;
527 }
528 
529 }  // namespace net
530