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