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