blob: e21bbe6e45fc004b5a96181ad78c6ed49a85573e [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/tpcd/metadata/browser/manager.h"
#include <cstdint>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include "base/base64.h"
#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/hash/sha1.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/rand_util.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_enums.mojom-shared.h"
#include "components/content_settings/core/common/content_settings_rules.h"
#include "components/content_settings/core/common/content_settings_utils.h"
#include "components/content_settings/core/common/features.h"
#include "components/content_settings/core/common/host_indexed_content_settings.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/tpcd/metadata/browser/parser.h"
#include "components/tpcd/metadata/browser/prefs.h"
#include "components/tpcd/metadata/common/proto/metadata.pb.h"
namespace tpcd::metadata {
namespace {
uint32_t ElectedDtrp(const MetadataEntry& metadata_entry) {
return metadata_entry.has_dtrp_override() ? metadata_entry.dtrp_override()
: metadata_entry.dtrp();
}
} // namespace
// static
Manager* Manager::GetInstance(Parser* parser, Delegate& delegate) {
// TODO(dcheng): This is probably the wrong layer for caching the Manager,
// since it's never ideal to pass ctor arguments into a factory method and
// ignore them on subsequent calls.
static base::NoDestructor<Manager> instance(parser, delegate);
return instance.get();
}
Manager::Manager(Parser* parser, Delegate& delegate)
: parser_(parser), delegate_(delegate) {
CHECK(parser_);
rand_generator_ = std::make_unique<RandGenerator>();
parser_->AddObserver(this);
if (!parser_->GetMetadata().empty()) {
OnMetadataReady();
}
}
Manager::~Manager() {
parser_->RemoveObserver(this);
}
uint32_t Manager::RandGenerator::Generate() const {
base::RandomBitGenerator generator;
std::uniform_int_distribution<uint32_t> distribution(Parser::kMinDtrp + 1,
Parser::kMaxDtrp);
uint32_t rand = distribution(generator);
return rand;
}
bool Manager::IsAllowed(const GURL& url,
const GURL& first_party_url,
content_settings::SettingInfo* out_info) const {
base::AutoLock lock(grants_lock_);
return CONTENT_SETTING_ALLOW ==
GetContentSetting(grants_, url, first_party_url, out_info);
}
void Manager::SetGrants(const ContentSettingsForOneType& grants) {
auto indices = content_settings::HostIndexedContentSettings::Create(grants);
if (indices.empty()) {
base::AutoLock lock(grants_lock_);
grants_ = content_settings::HostIndexedContentSettings();
} else {
CHECK_EQ(indices.size(), 1u);
base::AutoLock lock(grants_lock_);
grants_ = std::move(indices.front());
}
delegate_->SetTpcdMetadataGrants(grants);
}
ContentSettingsForOneType Manager::BuildGrantsWithPredicate(
base::FunctionRef<bool(const MetadataEntry&)> predicate) {
base::flat_set<std::string> remove_keys;
if (base::FeatureList::IsEnabled(net::features::kTpcdMetadataStageControl)) {
const base::Value::Dict& dict =
delegate_->GetLocalState().GetDict(prefs::kCohorts);
for (const auto itr : dict) {
remove_keys.insert(itr.first);
}
}
ContentSettingsForOneType grants;
for (const auto& metadata_entry : parser_->GetMetadata()) {
const auto primary_pattern = ContentSettingsPattern::FromString(
metadata_entry.primary_pattern_spec());
const auto secondary_pattern = ContentSettingsPattern::FromString(
metadata_entry.secondary_pattern_spec());
// This is unlikely to occurred as it is validated before the component is
// installed by the component installer.
if (!primary_pattern.IsValid() || !secondary_pattern.IsValid()) {
continue;
}
base::Value value(ContentSetting::CONTENT_SETTING_ALLOW);
content_settings::RuleMetaData rule_metadata =
content_settings::RuleMetaData();
rule_metadata.set_tpcd_metadata_rule_source(
Parser::ToRuleSource(metadata_entry.source()));
uint32_t elected_dtrp = ElectedDtrp(metadata_entry);
rule_metadata.set_tpcd_metadata_elected_dtrp(elected_dtrp);
std::optional<content_settings::mojom::TpcdMetadataCohort> cohort;
if (!metadata_entry.has_dtrp() ||
!base::FeatureList::IsEnabled(
net::features::kTpcdMetadataStageControl)) {
cohort = content_settings::mojom::TpcdMetadataCohort::DEFAULT;
}
std::string key_hash = helpers::GenerateKeyHash(metadata_entry);
// Get the cohort from the prefs if available.
if (!cohort.has_value() && !predicate(metadata_entry)) {
const base::Value::Dict& dict =
delegate_->GetLocalState().GetDict(prefs::kCohorts);
const std::optional<int> stored_int = dict.FindInt(key_hash);
if (stored_int.has_value()) {
auto stored_cohort =
static_cast<content_settings::mojom::TpcdMetadataCohort>(
stored_int.value());
if (content_settings::mojom::IsKnownEnumValue(stored_cohort)) {
cohort = stored_cohort;
remove_keys.erase(key_hash);
}
}
}
if (!cohort.has_value()) {
cohort = rand_generator_->Generate() <= elected_dtrp
? content_settings::mojom::TpcdMetadataCohort::
GRACE_PERIOD_FORCED_OFF
: content_settings::mojom::TpcdMetadataCohort::
GRACE_PERIOD_FORCED_ON;
ScopedDictPrefUpdate update(&delegate_->GetLocalState(), prefs::kCohorts);
update->Set(key_hash, static_cast<int32_t>(cohort.value()));
if (predicate(metadata_entry)) {
remove_keys.erase(key_hash);
}
}
CHECK(cohort.has_value());
rule_metadata.set_tpcd_metadata_cohort(cohort.value());
grants.emplace_back(primary_pattern, secondary_pattern, std::move(value),
content_settings::ProviderType::kNone,
/*incognito=*/false, std::move(rule_metadata));
}
if (base::FeatureList::IsEnabled(net::features::kTpcdMetadataStageControl)) {
ScopedDictPrefUpdate update(&delegate_->GetLocalState(), prefs::kCohorts);
for (const auto& key : remove_keys) {
update->Remove(key);
}
}
return grants;
}
void Manager::OnMetadataReady() {
if (!base::FeatureList::IsEnabled(net::features::kTpcdMetadataGrants)) {
return;
}
SetGrants(
BuildGrantsWithPredicate([](const MetadataEntry&) { return false; }));
}
ContentSettingsForOneType Manager::GetGrants() const {
if (!base::FeatureList::IsEnabled(net::features::kTpcdMetadataGrants)) {
return ContentSettingsForOneType();
}
base::AutoLock lock(grants_lock_);
return GetContentSettingForOneType(grants_);
}
void Manager::ResetCohorts() {
if (!base::FeatureList::IsEnabled(net::features::kTpcdMetadataStageControl)) {
return;
}
auto reset_cohort = [&](const MetadataEntry& metadata_entry) -> bool {
return metadata_entry.has_dtrp();
};
SetGrants(BuildGrantsWithPredicate(reset_cohort));
}
namespace helpers {
std::string GenerateKeyHash(const MetadataEntry& metadata_entry) {
std::string key = base::StrCat(
{metadata_entry.primary_pattern_spec(),
/*Non-url delimiter:*/ "\\", metadata_entry.secondary_pattern_spec(),
base::NumberToString(ElectedDtrp(metadata_entry))});
// The hash from SHA1 is faster to obtain than SHA256 and will be forever
// unchanged if key is unchanged and provides lesser collision risk than MD5.
return base::Base64Encode(base::SHA1HashString(key));
}
} // namespace helpers
} // namespace tpcd::metadata