910e62b5创建于 1月15日历史提交
// 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/private_aggregation/private_aggregation_manager_impl.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <map>
#include <memory>
#include <numeric>
#include <optional>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/checked_math.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/task/updateable_sequenced_task_runner.h"
#include "base/time/time.h"
#include "content/browser/aggregation_service/aggregatable_report.h"
#include "content/browser/aggregation_service/aggregation_service.h"
#include "content/browser/private_aggregation/private_aggregation_budget_key.h"
#include "content/browser/private_aggregation/private_aggregation_budgeter.h"
#include "content/browser/private_aggregation/private_aggregation_caller_api.h"
#include "content/browser/private_aggregation/private_aggregation_host.h"
#include "content/browser/private_aggregation/private_aggregation_pending_contributions.h"
#include "content/browser/private_aggregation/private_aggregation_utils.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/private_aggregation_data_model.h"
#include "content/public/browser/storage_partition.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/features_generated.h"
#include "third_party/blink/public/mojom/aggregation_service/aggregatable_report.mojom.h"
#include "third_party/blink/public/mojom/private_aggregation/private_aggregation_host.mojom.h"
#include "url/origin.h"

namespace content {

namespace {

void RecordBudgeterResultHistogram(
    PrivateAggregationBudgeter::RequestResult request_result) {
  base::UmaHistogramEnumeration(
      "PrivacySandbox.PrivateAggregation.Budgeter.RequestResult3",
      request_result);
}

void RecordManagerResultHistogram(
    PrivateAggregationManagerImpl::RequestResult request_result) {
  base::UmaHistogramEnumeration(
      "PrivacySandbox.PrivateAggregation.Manager.RequestResult",
      request_result);
}

}  // namespace

struct PrivateAggregationManagerImpl::InProgressBudgetRequest {
  PrivateAggregationHost::ReportRequestGenerator report_request_generator;
  PrivateAggregationPendingContributions pending_contributions;
  const PrivateAggregationBudgetKey budget_key;
  const PrivateAggregationHost::NullReportBehavior null_report_behavior;
  std::optional<PrivateAggregationBudgeter::RequestResult>
      inspect_request_result;
};

PrivateAggregationManagerImpl::PrivateAggregationManagerImpl(
    bool exclusively_run_in_memory,
    const base::FilePath& user_data_directory,
    StoragePartitionImpl* storage_partition)
    : PrivateAggregationManagerImpl(
          std::make_unique<PrivateAggregationBudgeter>(
              // This uses BLOCK_SHUTDOWN as some data deletion operations may
              // be running when the browser is closed, and we want to ensure
              // all data is deleted correctly. Additionally, we use
              // MUST_USE_FOREGROUND to avoid priority inversions if a task is
              // already running when the priority is increased.
              base::ThreadPool::CreateUpdateableSequencedTaskRunner(
                  base::TaskTraits(base::TaskPriority::BEST_EFFORT,
                                   base::MayBlock(),
                                   base::TaskShutdownBehavior::BLOCK_SHUTDOWN,
                                   base::ThreadPolicy::MUST_USE_FOREGROUND)),
              exclusively_run_in_memory,
              /*path_to_db_dir=*/user_data_directory),
          std::make_unique<PrivateAggregationHost>(
              /*on_report_request_details_received=*/base::BindRepeating(
                  &PrivateAggregationManagerImpl::
                      OnReportRequestDetailsReceivedFromHost,
                  base::Unretained(this)),
              storage_partition ? storage_partition->browser_context()
                                : nullptr),
          storage_partition) {}

PrivateAggregationManagerImpl::PrivateAggregationManagerImpl(
    std::unique_ptr<PrivateAggregationBudgeter> budgeter,
    std::unique_ptr<PrivateAggregationHost> host,
    StoragePartitionImpl* storage_partition)
    : budgeter_(std::move(budgeter)),
      host_(std::move(host)),
      storage_partition_(storage_partition) {
  CHECK(budgeter_);
  CHECK(host_);
}

PrivateAggregationManagerImpl::~PrivateAggregationManagerImpl() = default;

bool PrivateAggregationManagerImpl::BindNewReceiver(
    url::Origin worklet_origin,
    url::Origin top_frame_origin,
    PrivateAggregationCallerApi caller_api,
    std::optional<std::string> context_id,
    std::optional<base::TimeDelta> timeout,
    std::optional<url::Origin> aggregation_coordinator_origin,
    size_t filtering_id_max_bytes,
    std::optional<size_t> max_contributions,
    mojo::PendingReceiver<blink::mojom::PrivateAggregationHost>
        pending_receiver) {
  return host_->BindNewReceiver(
      std::move(worklet_origin), std::move(top_frame_origin), caller_api,
      std::move(context_id), std::move(timeout),
      std::move(aggregation_coordinator_origin), filtering_id_max_bytes,
      std::move(max_contributions), std::move(pending_receiver));
}

void PrivateAggregationManagerImpl::ClearBudgetData(
    base::Time delete_begin,
    base::Time delete_end,
    StoragePartition::StorageKeyMatcherFunction filter,
    base::OnceClosure done) {
  budgeter_->ClearData(delete_begin, delete_end, std::move(filter),
                       std::move(done));
}

bool PrivateAggregationManagerImpl::IsDebugModeAllowed(
    const url::Origin& top_frame_origin,
    const url::Origin& reporting_origin) {
  return host_->IsDebugModeAllowed(top_frame_origin, reporting_origin);
}

void PrivateAggregationManagerImpl::OnReportRequestDetailsReceivedFromHost(
    PrivateAggregationHost::ReportRequestGenerator report_request_generator,
    PrivateAggregationPendingContributions::Wrapper contributions_wrapper,
    PrivateAggregationBudgetKey budget_key,
    PrivateAggregationHost::NullReportBehavior null_report_behavior) {
  if (base::FeatureList::IsEnabled(
          blink::features::kPrivateAggregationApiErrorReporting)) {
    if (contributions_wrapper.GetPendingContributions().IsEmpty()) {
      // An empty non-deterministic report should've been dropped already.
      CHECK_EQ(null_report_behavior,
               PrivateAggregationHost::NullReportBehavior::kSendNullReport);
      RecordManagerResultHistogram(RequestResult::kSentWithoutContributions);
      OnContributionsFinalized(std::move(report_request_generator),
                               /*contributions=*/{}, budget_key.caller_api());
      return;
    }

    BudgetRequestId budget_request_id =
        BudgetRequestId(num_requests_processed_++);

    auto emplace_pair = in_progress_budget_requests_.emplace(
        budget_request_id,
        InProgressBudgetRequest{
            .report_request_generator = std::move(report_request_generator),
            .pending_contributions =
                std::move(contributions_wrapper.GetPendingContributions()),
            .budget_key = std::move(budget_key),
            .null_report_behavior = null_report_behavior});

    InProgressBudgetRequest& current_request = emplace_pair.first->second;

    budgeter_->InspectBudgetAndLock(
        current_request.pending_contributions.unconditional_contributions(),
        current_request.budget_key, /*result_callback=*/
        // Unretained is safe as the `budgeter_` is owned by `this`.
        base::BindOnce(
            &PrivateAggregationManagerImpl::OnTestBudgetAndLockReturned,
            base::Unretained(this), budget_request_id));

    return;
  }

  std::vector<blink::mojom::AggregatableReportHistogramContribution>
      contributions = std::move(contributions_wrapper.GetContributionsVector());

  base::CheckedNumeric<int> budget_needed = std::accumulate(
      contributions.begin(), contributions.end(),
      /*init=*/base::CheckedNumeric<int>(0), /*op=*/
      [](base::CheckedNumeric<int> running_sum,
         const blink::mojom::AggregatableReportHistogramContribution&
             contribution) { return running_sum + contribution.value; });

  PrivateAggregationCallerApi caller_api = budget_key.caller_api();

  if (!budget_needed.IsValid()) {
    OnConsumeBudgetReturned(std::move(report_request_generator),
                            std::move(contributions), caller_api,
                            null_report_behavior,
                            PrivateAggregationBudgeter::RequestResult::
                                kRequestedMoreThanTotalBudget);
    return;
  }

  // No need to request budget if none is needed.
  if (budget_needed.ValueOrDie() == 0) {
    RecordManagerResultHistogram(RequestResult::kSentWithoutContributions);
    CHECK(contributions.empty());
    OnContributionsFinalized(std::move(report_request_generator),
                             std::move(contributions), caller_api);
    return;
  }

  CHECK(!contributions.empty());
  int minimum_value_for_metrics =
      std::ranges::min(
          contributions, /*comp=*/{}, /*proj=*/
          &blink::mojom::AggregatableReportHistogramContribution::value)
          .value;

  budgeter_->ConsumeBudget(
      budget_needed.ValueOrDie(), std::move(budget_key),
      minimum_value_for_metrics, /*on_done=*/
      // Unretained is safe as the `budgeter_` is owned by `this`.
      base::BindOnce(
          &PrivateAggregationManagerImpl::OnConsumeBudgetReturned,
          base::Unretained(this), std::move(report_request_generator),
          std::move(contributions), caller_api, null_report_behavior));
}

AggregationService* PrivateAggregationManagerImpl::GetAggregationService() {
  CHECK(storage_partition_);
  return AggregationService::GetService(storage_partition_->browser_context());
}

void PrivateAggregationManagerImpl::OnTestBudgetAndLockReturned(
    BudgetRequestId budget_request_id,
    PrivateAggregationBudgeter::InspectBudgetCallResult result) {
  CHECK(base::FeatureList::IsEnabled(
      blink::features::kPrivateAggregationApiErrorReporting));
  CHECK(in_progress_budget_requests_.contains(budget_request_id));
  InProgressBudgetRequest& current_request =
      in_progress_budget_requests_.at(budget_request_id);

  current_request.inspect_request_result = result.query_result.overall_result;
  CHECK(current_request.inspect_request_result.has_value());

  PrivateAggregationPendingContributions::NullReportBehavior
      pending_contributions_null_report_behavior =
          current_request.null_report_behavior ==
                  PrivateAggregationHost::NullReportBehavior::kSendNullReport
              ? PrivateAggregationPendingContributions::NullReportBehavior::
                    kSendNullReport
              : PrivateAggregationPendingContributions::NullReportBehavior::
                    kDontSendReport;

  const std::vector<blink::mojom::AggregatableReportHistogramContribution>&
      final_unmerged_contributions =
          current_request.pending_contributions
              .CompileFinalUnmergedContributions(
                  std::move(result.query_result.result_for_each_contribution),
                  result.pending_report_limit_result,
                  pending_contributions_null_report_behavior);

  if (!result.lock.has_value()) {
    // A fatal error occurred, generate a fictitious budget query result denying
    // the conditional contributions (if present).
    result.query_result.result_for_each_contribution =
        std::vector<PrivateAggregationBudgeter::ResultForContribution>(
            final_unmerged_contributions.size(),
            PrivateAggregationBudgeter::ResultForContribution::kDenied);

    OnConsumeBudgetWithLockReturned(budget_request_id,
                                    std::move(result.query_result));
    return;
  }

  budgeter_->ConsumeBudget(
      std::move(result.lock).value(), final_unmerged_contributions,
      current_request.budget_key, /*result_callback=*/
      // Unretained is safe as the `budgeter_` is owned by `this`.
      base::BindOnce(
          &PrivateAggregationManagerImpl::OnConsumeBudgetWithLockReturned,
          base::Unretained(this), budget_request_id));
}

void PrivateAggregationManagerImpl::OnConsumeBudgetWithLockReturned(
    BudgetRequestId budget_request_id,
    PrivateAggregationBudgeter::BudgetQueryResult result) {
  CHECK(base::FeatureList::IsEnabled(
      blink::features::kPrivateAggregationApiErrorReporting));
  CHECK(in_progress_budget_requests_.contains(budget_request_id));
  InProgressBudgetRequest current_request = std::move(
      in_progress_budget_requests_.extract(budget_request_id).mapped());

  PrivateAggregationBudgeter::RequestResult overall_result_both_phases =
      PrivateAggregationBudgeter::CombineRequestResults(
          /*inspect_budget_result=*/current_request.inspect_request_result
              .value(),
          /*consume_budget_result=*/result.overall_result);

  RecordBudgeterResultHistogram(overall_result_both_phases);

  std::vector<blink::mojom::AggregatableReportHistogramContribution>
      final_contributions = std::move(current_request.pending_contributions)
                                .TakeFinalContributions(std::move(
                                    result.result_for_each_contribution));

  if (final_contributions.empty()) {
    switch (current_request.null_report_behavior) {
      case PrivateAggregationHost::NullReportBehavior::kDontSendReport:
        RecordManagerResultHistogram(RequestResult::kNotSent);
        return;
      case PrivateAggregationHost::NullReportBehavior::kSendNullReport:
        // This distinguishes from the case where we have an empty report due to
        // untriggered conditional contributions (and no other contributions).
        RecordManagerResultHistogram(
            overall_result_both_phases !=
                    PrivateAggregationBudgeter::RequestResult::kApproved
                ? RequestResult::kSentButContributionsClearedDueToBudgetDenial
                : RequestResult::kSentWithoutContributions);
        break;
    }
  } else {
    RecordManagerResultHistogram(RequestResult::kSentWithContributions);
  }

  OnContributionsFinalized(std::move(current_request.report_request_generator),
                           std::move(final_contributions),
                           current_request.budget_key.caller_api());
}

void PrivateAggregationManagerImpl::OnConsumeBudgetReturned(
    PrivateAggregationHost::ReportRequestGenerator report_request_generator,
    std::vector<blink::mojom::AggregatableReportHistogramContribution>
        contributions,
    PrivateAggregationCallerApi caller_api,
    PrivateAggregationHost::NullReportBehavior null_report_behavior,
    PrivateAggregationBudgeter::RequestResult request_result) {
  CHECK(!base::FeatureList::IsEnabled(
      blink::features::kPrivateAggregationApiErrorReporting));
  RecordBudgeterResultHistogram(request_result);

  // TODO(crbug.com/355271550): Consider allowing a subset of contributions to
  // be sent if there's insufficient budget for them all.
  if (request_result == PrivateAggregationBudgeter::RequestResult::kApproved) {
    CHECK(!contributions.empty());
    RecordManagerResultHistogram(RequestResult::kSentWithContributions);
  } else {
    switch (null_report_behavior) {
      case PrivateAggregationHost::NullReportBehavior::kDontSendReport:
        RecordManagerResultHistogram(RequestResult::kNotSent);
        return;
      case PrivateAggregationHost::NullReportBehavior::kSendNullReport:
        RecordManagerResultHistogram(
            RequestResult::kSentButContributionsClearedDueToBudgetDenial);
        contributions.clear();
        break;
    }
  }

  OnContributionsFinalized(std::move(report_request_generator),
                           std::move(contributions), caller_api);
}

void PrivateAggregationManagerImpl::OnContributionsFinalized(
    PrivateAggregationHost::ReportRequestGenerator report_request_generator,
    std::vector<blink::mojom::AggregatableReportHistogramContribution>
        contributions,
    PrivateAggregationCallerApi caller_api) {
  AggregationService* aggregation_service = GetAggregationService();
  if (!aggregation_service) {
    return;
  }

  AggregatableReportRequest report_request =
      std::move(report_request_generator).Run(std::move(contributions));

  // If the request has debug mode enabled, immediately send a duplicate of the
  // requested report to a special debug reporting endpoint.
  if (report_request.shared_info().debug_mode ==
      AggregatableReportSharedInfo::DebugMode::kEnabled) {
    std::string immediate_debug_reporting_path =
        private_aggregation::GetReportingPath(
            caller_api,
            /*is_immediate_debug_report=*/true);

    std::optional<AggregatableReportRequest> debug_request =
        AggregatableReportRequest::Create(
            report_request.payload_contents(),
            report_request.shared_info().Clone(),
            AggregatableReportRequest::DelayType::Unscheduled,
            std::move(immediate_debug_reporting_path),
            report_request.debug_key(), report_request.additional_fields());
    CHECK(debug_request.has_value());

    aggregation_service->AssembleAndSendReport(
        std::move(debug_request.value()));
  }

  aggregation_service->ScheduleReport(std::move(report_request));
}

void PrivateAggregationManagerImpl::GetAllDataKeys(
    base::OnceCallback<void(std::set<DataKey>)> callback) {
  budgeter_->GetAllDataKeys(base::BindOnce(
      &PrivateAggregationManagerImpl::OnBudgeterGetAllDataKeysReturned,
      weak_factory_.GetWeakPtr(), std::move(callback)));
}

void PrivateAggregationManagerImpl::OnBudgeterGetAllDataKeysReturned(
    base::OnceCallback<void(std::set<DataKey>)> callback,
    std::set<DataKey> all_keys) {
  AggregationService* aggregation_service = GetAggregationService();
  if (!aggregation_service) {
    std::move(callback).Run(std::move(all_keys));
    return;
  }

  aggregation_service->GetPendingReportReportingOrigins(base::BindOnce(
      [](base::OnceCallback<void(std::set<DataKey>)> callback,
         std::set<DataKey> all_keys, std::set<url::Origin> pending_origins) {
        std::ranges::transform(
            std::make_move_iterator(pending_origins.begin()),
            std::make_move_iterator(pending_origins.end()),
            std::inserter(all_keys, all_keys.begin()), [](url::Origin elem) {
              return PrivateAggregationDataModel::DataKey(std::move(elem));
            });
        std::move(callback).Run(std::move(all_keys));
      },
      std::move(callback), std::move(all_keys)));
}

void PrivateAggregationManagerImpl::RemovePendingDataKey(
    const DataKey& data_key,
    base::OnceClosure callback) {
  base::RepeatingClosure barrier = base::BarrierClosure(2, std::move(callback));
  budgeter_->DeleteByDataKey(data_key, barrier);
  AggregationService* aggregation_service = GetAggregationService();
  if (!aggregation_service) {
    std::move(barrier).Run();
    return;
  }

  aggregation_service->ClearData(
      /*delete_begin=*/base::Time::Min(), /*delete_end=*/base::Time::Max(),
      /*filter=*/
      base::BindRepeating(
          std::equal_to<blink::StorageKey>(),
          blink::StorageKey::CreateFirstParty(data_key.reporting_origin())),
      /*done=*/std::move(barrier));
}

}  // namespace content