| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/first_party_sets/first_party_sets_handler_impl.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/files/file.h" |
| #include "base/logging.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/task/thread_pool.h" |
| #include "base/types/optional_util.h" |
| #include "base/values.h" |
| #include "content/browser/first_party_sets/first_party_set_parser.h" |
| #include "content/browser/first_party_sets/first_party_sets_loader.h" |
| #include "content/browser/first_party_sets/first_party_sets_site_data_remover.h" |
| #include "content/browser/first_party_sets/local_set_declaration.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/first_party_sets_handler.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_features.h" |
| #include "net/first_party_sets/first_party_set_metadata.h" |
| #include "net/first_party_sets/first_party_sets_context_config.h" |
| #include "net/first_party_sets/global_first_party_sets.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| |
| namespace net { |
| class SchemefulSite; |
| } // namespace net |
| |
| namespace content { |
| |
| namespace { |
| |
| constexpr base::FilePath::CharType kFirstPartySetsDatabase[] = |
| FILE_PATH_LITERAL("first_party_sets.db"); |
| |
| using ClearSiteDataOutcomeType = |
| FirstPartySetsHandlerImpl::ClearSiteDataOutcomeType; |
| |
| // `failed_data_types` is a bitmask used to indicate data types from |
| // BrowsingDataRemover::DataType enum that were failed to remove. |
| ClearSiteDataOutcomeType ComputeClearSiteDataOutcome( |
| uint64_t failed_data_types) { |
| ClearSiteDataOutcomeType outcome = ClearSiteDataOutcomeType::kSuccess; |
| if (failed_data_types & BrowsingDataRemover::DATA_TYPE_COOKIES) { |
| outcome = ClearSiteDataOutcomeType::kCookieFailed; |
| } |
| if (failed_data_types & BrowsingDataRemover::DATA_TYPE_DOM_STORAGE) { |
| outcome = outcome == ClearSiteDataOutcomeType::kCookieFailed |
| ? ClearSiteDataOutcomeType::kCookieAndStorageFailed |
| : ClearSiteDataOutcomeType::kStorageFailed; |
| } |
| return outcome; |
| } |
| |
| void RecordClearSiteDataOutcome(ClearSiteDataOutcomeType outcome) { |
| base::UmaHistogramEnumeration( |
| "FirstPartySets.Initialization.ClearSiteDataOutcomeType", outcome); |
| } |
| |
| // Global FirstPartySetsHandler instance for testing. |
| FirstPartySetsHandler* g_test_instance = nullptr; |
| |
| } // namespace |
| |
| // static |
| void FirstPartySetsHandler::SetInstanceForTesting( |
| FirstPartySetsHandler* test_instance) { |
| g_test_instance = test_instance; |
| } |
| |
| // static |
| FirstPartySetsHandler* FirstPartySetsHandler::GetInstance() { |
| if (g_test_instance) |
| return g_test_instance; |
| |
| return FirstPartySetsHandlerImpl::GetInstance(); |
| } |
| |
| // static |
| FirstPartySetsHandlerImpl* FirstPartySetsHandlerImpl::GetInstance() { |
| static base::NoDestructor<FirstPartySetsHandlerImpl> instance( |
| GetContentClient()->browser()->IsFirstPartySetsEnabled(), |
| GetContentClient()->browser()->WillProvidePublicFirstPartySets()); |
| return instance.get(); |
| } |
| |
| // static |
| std::pair<absl::optional<FirstPartySetsHandler::ParseError>, |
| std::vector<FirstPartySetsHandler::ParseWarning>> |
| FirstPartySetsHandler::ValidateEnterprisePolicy( |
| const base::Value::Dict& policy) { |
| FirstPartySetParser::PolicyParseResult parsed_or_error = |
| FirstPartySetParser::ParseSetsFromEnterprisePolicy(policy); |
| if (!parsed_or_error.has_value()) { |
| return {parsed_or_error.error().first, parsed_or_error.error().second}; |
| } |
| return {absl::nullopt, parsed_or_error.value().second}; |
| } |
| |
| // static |
| FirstPartySetsHandlerImpl FirstPartySetsHandlerImpl::CreateForTesting( |
| bool enabled, |
| bool embedder_will_provide_public_sets) { |
| return FirstPartySetsHandlerImpl(enabled, embedder_will_provide_public_sets); |
| } |
| |
| void FirstPartySetsHandlerImpl::GetContextConfigForPolicy( |
| const base::Value::Dict* policy, |
| base::OnceCallback<void(net::FirstPartySetsContextConfig)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!policy || !enabled_) { |
| std::move(callback).Run(net::FirstPartySetsContextConfig()); |
| return; |
| } |
| if (global_sets_.has_value()) { |
| std::move(callback).Run(GetContextConfigForPolicyInternal(*policy)); |
| return; |
| } |
| // Add to the deque of callbacks that will be processed once the list |
| // of First-Party Sets has been fully initialized. |
| on_sets_ready_callbacks_.push_back( |
| base::BindOnce( |
| &FirstPartySetsHandlerImpl::GetContextConfigForPolicyInternal, |
| // base::Unretained(this) is safe here because this is a static |
| // singleton. |
| base::Unretained(this), policy->Clone()) |
| .Then(std::move(callback))); |
| } |
| |
| net::FirstPartySetsContextConfig |
| FirstPartySetsHandlerImpl::ComputeEnterpriseContextConfig( |
| const net::GlobalFirstPartySets& global_sets, |
| const FirstPartySetParser::ParsedPolicySetLists& policy) { |
| return global_sets.ComputeConfig( |
| /*replacement_sets=*/policy.replacements, |
| /*addition_sets=*/ |
| policy.additions); |
| } |
| |
| FirstPartySetsHandlerImpl::FirstPartySetsHandlerImpl( |
| bool enabled, |
| bool embedder_will_provide_public_sets) |
| : enabled_(enabled), |
| embedder_will_provide_public_sets_(enabled && |
| embedder_will_provide_public_sets) { |
| sets_loader_ = std::make_unique<FirstPartySetsLoader>( |
| base::BindOnce(&FirstPartySetsHandlerImpl::SetCompleteSets, |
| // base::Unretained(this) is safe here because |
| // this is a static singleton. |
| base::Unretained(this))); |
| } |
| |
| FirstPartySetsHandlerImpl::~FirstPartySetsHandlerImpl() = default; |
| |
| absl::optional<net::GlobalFirstPartySets> FirstPartySetsHandlerImpl::GetSets( |
| SetsReadyOnceCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(IsEnabled()); |
| if (global_sets_.has_value()) |
| return global_sets_->Clone(); |
| |
| if (!callback.is_null()) { |
| // base::Unretained(this) is safe here because this is a static singleton. |
| on_sets_ready_callbacks_.push_back( |
| base::BindOnce(&FirstPartySetsHandlerImpl::GetGlobalSetsSync, |
| base::Unretained(this)) |
| .Then(std::move(callback))); |
| } |
| |
| return absl::nullopt; |
| } |
| |
| void FirstPartySetsHandlerImpl::Init(const base::FilePath& user_data_dir, |
| const LocalSetDeclaration& local_set) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!initialized_); |
| |
| initialized_ = true; |
| SetDatabase(user_data_dir); |
| |
| if (IsEnabled()) { |
| sets_loader_->SetManuallySpecifiedSet(local_set); |
| if (!embedder_will_provide_public_sets_) { |
| sets_loader_->SetComponentSets(base::Version(), base::File()); |
| } |
| } else { |
| SetCompleteSets(net::GlobalFirstPartySets()); |
| } |
| } |
| |
| bool FirstPartySetsHandlerImpl::IsEnabled() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return enabled_; |
| } |
| |
| void FirstPartySetsHandlerImpl::SetPublicFirstPartySets( |
| const base::Version& version, |
| base::File sets_file) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(enabled_); |
| DCHECK(embedder_will_provide_public_sets_); |
| |
| // TODO(crbug.com/1219656): Use the version to compute sets diff. |
| sets_loader_->SetComponentSets(version, std::move(sets_file)); |
| } |
| |
| void FirstPartySetsHandlerImpl::GetPersistedGlobalSetsForTesting( |
| const std::string& browser_context_id, |
| base::OnceCallback<void(absl::optional<net::GlobalFirstPartySets>)> |
| callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!browser_context_id.empty()); |
| if (db_helper_.is_null()) { |
| std::move(callback).Run(absl::nullopt); |
| return; |
| } |
| db_helper_ |
| .AsyncCall(&FirstPartySetsHandlerDatabaseHelper::GetPersistedGlobalSets) |
| .WithArgs(browser_context_id) |
| .Then(std::move(callback)); |
| } |
| |
| void FirstPartySetsHandlerImpl::HasBrowserContextClearedForTesting( |
| const std::string& browser_context_id, |
| base::OnceCallback<void(absl::optional<bool>)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!browser_context_id.empty()); |
| if (db_helper_.is_null()) { |
| std::move(callback).Run(absl::nullopt); |
| return; |
| } |
| db_helper_ |
| .AsyncCall(&FirstPartySetsHandlerDatabaseHelper:: |
| HasEntryInBrowserContextsClearedForTesting) // IN-TEST |
| .WithArgs(browser_context_id) |
| .Then(std::move(callback)); |
| } |
| |
| void FirstPartySetsHandlerImpl::SetCompleteSets( |
| net::GlobalFirstPartySets sets) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!global_sets_.has_value()); |
| global_sets_ = std::move(sets); |
| |
| if (IsEnabled()) |
| InvokePendingQueries(); |
| } |
| |
| void FirstPartySetsHandlerImpl::SetDatabase( |
| const base::FilePath& user_data_dir) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(db_helper_.is_null()); |
| |
| if (user_data_dir.empty()) { |
| VLOG(1) << "Empty path. Failed initializing First-Party Sets database."; |
| return; |
| } |
| db_helper_.emplace(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| user_data_dir.Append(kFirstPartySetsDatabase)); |
| } |
| |
| void FirstPartySetsHandlerImpl::InvokePendingQueries() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(enabled_); |
| base::circular_deque<base::OnceClosure> queue; |
| queue.swap(on_sets_ready_callbacks_); |
| while (!queue.empty()) { |
| base::OnceCallback callback = std::move(queue.front()); |
| queue.pop_front(); |
| std::move(callback).Run(); |
| } |
| } |
| |
| absl::optional<net::FirstPartySetEntry> FirstPartySetsHandlerImpl::FindEntry( |
| const net::SchemefulSite& site, |
| const net::FirstPartySetsContextConfig& config) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!base::FeatureList::IsEnabled(features::kFirstPartySets) || |
| !global_sets_.has_value()) { |
| return absl::nullopt; |
| } |
| return global_sets_->FindEntry(site, config); |
| } |
| |
| net::GlobalFirstPartySets FirstPartySetsHandlerImpl::GetGlobalSetsSync() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(global_sets_.has_value()); |
| return global_sets_->Clone(); |
| } |
| |
| void FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContext( |
| base::RepeatingCallback<BrowserContext*()> browser_context_getter, |
| const std::string& browser_context_id, |
| net::FirstPartySetsContextConfig context_config, |
| base::OnceCallback<void(net::FirstPartySetsContextConfig, |
| net::FirstPartySetsCacheFilter)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| if (!enabled_ || !features::kFirstPartySetsClearSiteDataOnChangedSets.Get()) { |
| std::move(callback).Run(std::move(context_config), |
| net::FirstPartySetsCacheFilter()); |
| return; |
| } |
| |
| if (global_sets_.has_value()) { |
| ClearSiteDataOnChangedSetsForContextInternal( |
| browser_context_getter, browser_context_id, std::move(context_config), |
| std::move(callback)); |
| return; |
| } |
| |
| // base::Unretained(this) is safe because this is a static singleton. |
| on_sets_ready_callbacks_.push_back(base::BindOnce( |
| &FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContextInternal, |
| base::Unretained(this), browser_context_getter, browser_context_id, |
| std::move(context_config), std::move(callback))); |
| } |
| |
| void FirstPartySetsHandlerImpl::ClearSiteDataOnChangedSetsForContextInternal( |
| base::RepeatingCallback<BrowserContext*()> browser_context_getter, |
| const std::string& browser_context_id, |
| net::FirstPartySetsContextConfig context_config, |
| base::OnceCallback<void(net::FirstPartySetsContextConfig, |
| net::FirstPartySetsCacheFilter)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(global_sets_.has_value()); |
| DCHECK(!browser_context_id.empty()); |
| DCHECK(enabled_ && features::kFirstPartySetsClearSiteDataOnChangedSets.Get()); |
| |
| if (db_helper_.is_null()) { |
| VLOG(1) << "Invalid First-Party Sets database. Failed to clear site data " |
| "for browser_context_id=" |
| << browser_context_id; |
| std::move(callback).Run(std::move(context_config), |
| net::FirstPartySetsCacheFilter()); |
| return; |
| } |
| |
| // Extract the callback into a variable and pass it into DB async call args, |
| // to prevent the case that `context_config` gets used after it's moved. This |
| // is because C++ does not have a defined evaluation order for function |
| // parameters. |
| base::OnceCallback<void(std::pair<std::vector<net::SchemefulSite>, |
| net::FirstPartySetsCacheFilter>)> |
| on_get_sites_to_clear = base::BindOnce( |
| &FirstPartySetsHandlerImpl::OnGetSitesToClear, |
| // base::Unretained(this) is safe here because this |
| // is a static singleton. |
| base::Unretained(this), browser_context_getter, browser_context_id, |
| context_config.Clone(), std::move(callback)); |
| |
| db_helper_ |
| .AsyncCall(&FirstPartySetsHandlerDatabaseHelper:: |
| UpdateAndGetSitesToClearForContext) |
| .WithArgs(browser_context_id, global_sets_->Clone(), |
| std::move(context_config)) |
| .Then(std::move(on_get_sites_to_clear)); |
| } |
| |
| void FirstPartySetsHandlerImpl::OnGetSitesToClear( |
| base::RepeatingCallback<BrowserContext*()> browser_context_getter, |
| const std::string& browser_context_id, |
| net::FirstPartySetsContextConfig context_config, |
| base::OnceCallback<void(net::FirstPartySetsContextConfig, |
| net::FirstPartySetsCacheFilter)> callback, |
| std::pair<std::vector<net::SchemefulSite>, net::FirstPartySetsCacheFilter> |
| sites_to_clear) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| BrowserContext* browser_context = browser_context_getter.Run(); |
| if (!browser_context) { |
| DVLOG(1) << "Invalid Browser Context. Failed to clear site data for " |
| "browser_context_id=" |
| << browser_context_id; |
| |
| std::move(callback).Run(std::move(context_config), |
| net::FirstPartySetsCacheFilter()); |
| return; |
| } |
| |
| FirstPartySetsSiteDataRemover::RemoveSiteData( |
| *browser_context->GetBrowsingDataRemover(), |
| std::move(sites_to_clear.first), |
| base::BindOnce( |
| &FirstPartySetsHandlerImpl::DidClearSiteDataOnChangedSetsForContext, |
| // base::Unretained(this) is safe here because |
| // this is a static singleton. |
| base::Unretained(this), browser_context_id, std::move(context_config), |
| std::move(sites_to_clear.second), std::move(callback))); |
| } |
| |
| void FirstPartySetsHandlerImpl::DidClearSiteDataOnChangedSetsForContext( |
| const std::string& browser_context_id, |
| net::FirstPartySetsContextConfig context_config, |
| net::FirstPartySetsCacheFilter cache_filter, |
| base::OnceCallback<void(net::FirstPartySetsContextConfig, |
| net::FirstPartySetsCacheFilter)> callback, |
| uint64_t failed_data_types) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(!db_helper_.is_null()); |
| |
| ClearSiteDataOutcomeType outcome = |
| ComputeClearSiteDataOutcome(failed_data_types); |
| RecordClearSiteDataOutcome(outcome); |
| if (outcome == ClearSiteDataOutcomeType::kSuccess) { |
| db_helper_ |
| .AsyncCall( |
| &FirstPartySetsHandlerDatabaseHelper::UpdateClearStatusForContext) |
| .WithArgs(browser_context_id); |
| } |
| |
| db_helper_.AsyncCall(&FirstPartySetsHandlerDatabaseHelper::PersistSets) |
| .WithArgs(browser_context_id, global_sets_->Clone(), |
| context_config.Clone()); |
| std::move(callback).Run(std::move(context_config), std::move(cache_filter)); |
| } |
| |
| void FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadata( |
| const net::SchemefulSite& site, |
| const net::SchemefulSite* top_frame_site, |
| const std::set<net::SchemefulSite>& party_context, |
| const net::FirstPartySetsContextConfig& config, |
| base::OnceCallback<void(net::FirstPartySetMetadata)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!global_sets_.has_value()) { |
| on_sets_ready_callbacks_.push_back(base::BindOnce( |
| &FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadataInternal, |
| base::Unretained(this), site, base::OptionalFromPtr(top_frame_site), |
| party_context, config.Clone(), std::move(callback))); |
| return; |
| } |
| |
| std::move(callback).Run(global_sets_->ComputeMetadata(site, top_frame_site, |
| party_context, config)); |
| } |
| |
| void FirstPartySetsHandlerImpl::ComputeFirstPartySetMetadataInternal( |
| const net::SchemefulSite& site, |
| const absl::optional<net::SchemefulSite>& top_frame_site, |
| const std::set<net::SchemefulSite>& party_context, |
| const net::FirstPartySetsContextConfig& config, |
| base::OnceCallback<void(net::FirstPartySetMetadata)> callback) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(global_sets_.has_value()); |
| std::move(callback).Run(global_sets_->ComputeMetadata( |
| site, base::OptionalToPtr(top_frame_site), party_context, config)); |
| } |
| |
| net::FirstPartySetsContextConfig |
| FirstPartySetsHandlerImpl::GetContextConfigForPolicyInternal( |
| const base::Value::Dict& policy) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| FirstPartySetParser::PolicyParseResult parsed_or_error = |
| FirstPartySetParser::ParseSetsFromEnterprisePolicy(policy); |
| // Provide empty customization if the policy is malformed. |
| return parsed_or_error.has_value() |
| ? FirstPartySetsHandlerImpl::ComputeEnterpriseContextConfig( |
| global_sets_.value(), parsed_or_error.value().first) |
| : net::FirstPartySetsContextConfig(); |
| } |
| |
| } // namespace content |