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