blob: 9512aed91f7e5d1285f454be7b56b4e664a0a790 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_REPORTER_H_
#define CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_REPORTER_H_
#include <map>
#include <optional>
#include <set>
#include <string>
#include <tuple>
#include <variant>
#include <vector>
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/types/pass_key.h"
#include "content/browser/attribution_reporting/attribution_beacon_id.h"
#include "content/common/content_export.h"
#include "content/public/browser/privacy_sandbox_invoking_api.h"
#include "content/public/browser/render_frame_host.h"
#include "content/services/auction_worklet/public/mojom/private_aggregation_request.mojom.h"
#include "net/url_request/referrer_policy.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/mojom/attribution.mojom-forward.h"
#include "third_party/blink/public/common/fenced_frame/redacted_fenced_frame_config.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
class AttributionManager;
class BrowserContext;
class PrivateAggregationManager;
class RenderFrameHostImpl;
// An event to be sent to a preregistered url.
// `type` is the key for the `ReportingUrlMap`, and `data` is sent with the
// request as a POST.
struct DestinationEnumEvent {
std::string type;
std::string data;
bool cross_origin_exposed;
// The equal to operator is defined in order to enable comparison of
// DestinationVariant.
bool operator==(const DestinationEnumEvent& other) const = default;
};
// An event to be sent to a custom url.
// `url` is the custom destination url, and the request is sent as a GET.
// Macros are substituted using the `ReportingMacros`.
struct DestinationURLEvent {
GURL url;
bool cross_origin_exposed;
// The equal to operator is defined in order to enable comparison of
// DestinationVariant.
bool operator==(const DestinationURLEvent& other) const = default;
};
// An event to be sent to a preregistered url as the result of an automatic
// beacon. `type` is the key for the `ReportingUrlMap`, and `data` is sent with
// the request as a POST.
struct AutomaticBeaconEvent {
blink::mojom::AutomaticBeaconType type;
std::string data;
// The equal to operator is defined in order to enable comparison of
// DestinationVariant.
bool operator==(const AutomaticBeaconEvent& other) const = default;
};
// Class that receives report events from fenced frames, and uses a
// per-destination-type maps of events to URLs to send reports. The maps may be
// received after the report event calls, in which case the reports will be
// queued until the corresponding map types have been received.
class CONTENT_EXPORT FencedFrameReporter
: public base::RefCounted<FencedFrameReporter> {
public:
using ReportingUrlMap = base::flat_map<std::string, GURL>;
using ReportingMacros = std::vector<std::pair<std::string, std::string>>;
using FinalizedPrivateAggregationRequests = std::vector<
auction_worklet::mojom::FinalizedPrivateAggregationRequestPtr>;
using DestinationVariant = std::
variant<DestinationEnumEvent, DestinationURLEvent, AutomaticBeaconEvent>;
// TODO(crbug.com/40285398): Once the CL that stops repeating checks for
// fenced frame reporting beacons is landed, this observer will be extended to
// observe whether the beacon is eventually sent or not.
class ObserverForTesting : public base::CheckedObserver {
public:
virtual void OnBeaconQueued(const DestinationVariant& event_variant,
bool is_queued) = 0;
};
void AddObserverForTesting(ObserverForTesting* observer);
void RemoveObserverForTesting(const ObserverForTesting* observer);
// Creates a FencedFrameReporter that only maps kSharedStorageSelectUrl
// destinations, using the passed in map.
//
// `url_loader_factory` is used to send all reports, and must not be null.
//
// `browser_context` is used to help notify Attribution Reporting API
// for the beacons, and to check attestations before sending out the beacons.
//
// `reporting_url_declarer_origin` is used to set the request initiator on
// outgoing beacon network requests. The initiator is the entity that chose
// the destination URLs in order to prevent CSRF.
//
// `main_frame_origin` is the main frame of the page where Shared Storage
// was called.
static scoped_refptr<FencedFrameReporter> CreateForSharedStorage(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
BrowserContext* browser_context,
const std::optional<url::Origin>& reporting_url_declarer_origin,
ReportingUrlMap reporting_url_map,
const url::Origin& main_frame_origin = url::Origin());
// Creates a FencedFrameReporter that maps FLEDGE ReportingDestination types
// (kBuyer, kSeller, kComponentSeller), but that initially considers all three
// map types pending, and just collects reporting strings of those types until
// the corresponding mappings are passed in via OnUrlMappingReady().
//
// `url_loader_factory` is used to send all reports, and must not be null.
//
// `browser_context` is used to help notify Attribution Reporting API
// for the beacons, and to check attestations before sending out the beacons.
//
// `private_aggregation_manager` is used to send private aggregation requests
// for fenced frame events. See comment above declaration of
// `private_aggregation_manager_` for more details.
//
// `main_frame_origin` is the main frame of the page where the auction is
// running. Can be an opaque origin in test iff the test does not have for
// event private aggregation requests.
//
// `winner_origin` is the winning buyer's origin. Can be an opaque origin in
// test iff the test does not have for event private aggregation requests.
//
// `winner_aggregation_coordinator_origin` is the origin of the aggregation
// coordinator for the winning buyer. Set to std::nullopt if the default
// coordinator should be used.
//
// `allowed_reporting_origins` is the winning ad's allowedReportingOrigins. If
// any macro report is attempted to an unlisted origin, all further reports
// after it will be cancelled.
static scoped_refptr<FencedFrameReporter> CreateForFledge(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
BrowserContext* browser_context,
bool direct_seller_is_seller,
PrivateAggregationManager* private_aggregation_manager,
const url::Origin& main_frame_origin,
const url::Origin& winner_origin,
const std::optional<url::Origin>& winner_aggregation_coordinator_origin,
const std::optional<std::vector<url::Origin>>& allowed_reporting_origins =
std::nullopt);
// Don't use this constructor directly, but use factory methods instead.
// See factory methods for details.
FencedFrameReporter(
base::PassKey<FencedFrameReporter> pass_key,
PrivacySandboxInvokingAPI invoking_api,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
BrowserContext* browser_context,
const url::Origin& main_frame_origin,
PrivateAggregationManager* private_aggregation_manager = nullptr,
const std::optional<url::Origin>& winner_origin = std::nullopt,
const std::optional<url::Origin>& winner_aggregation_coordinator_origin =
std::nullopt,
const std::optional<std::vector<url::Origin>>& allowed_reporting_origins =
std::nullopt);
// Called when a mapping for reports of type `reporting_destination` is ready.
// The reporter must currently be considering maps of type
// `reporting_destination` pending - that is:
//
// 1) It must have been created by CreateForFledge()
// 2) `reporting_destination` must be one of kBuyer, kSeller, kDirectSeller or
// kComponentSeller.
// 3) OnUrlMappingReady() must not yet have been invoked with
// `reporting_destination` yet.
//
// When invoked, any pending reports of type `reporting_destination` will be
// sent if there's a matching entry in `reporting_url_map`. Any future reports
// of that type will be immediately sent using the provided map. Errors will
// not be displayed anywhere, as it's unclear where to send them to - the
// originally associated frame may have already been closed.
//
// If it is learned that there are no events types for a particular
// destination, should be called with an empty ReportingUrlMap for that
// destination, so it can discard reports for that destination, and provide
// errors messages for subsequent SendReporter() using that destination.
//
// Reports that use the urls declared in `reporting_url_map` will be sent
// with the request initiator set to `reporting_url_declarer_origin`
//
// `reporting_ad_macros` is std::nullopt unless when `reporting_destination`
// is kBuyer. If it is learned that there are no ad macros for kBuyer, should
// be called with an empty ReportingMacros, so it can discard macro reports,
// and provide errors messages for subsequent SendReporter().
//
// TODO(crbug.com/40253851): Consider investing in outputting error to
// correct frame, if it still exists. `frame_tree_node_id` somewhat does this,
// though it doesn't change across navigations, so could end up displaying an
// error for a page a frame was previously displaying. There may be other
// options.
void OnUrlMappingReady(
blink::FencedFrame::ReportingDestination reporting_destination,
const std::optional<url::Origin>& reporting_url_declarer_origin,
ReportingUrlMap reporting_url_map,
std::optional<ReportingMacros> reporting_ad_macros = std::nullopt);
// Sends a report for the specified event, using the ReportingUrlMap
// associated with `reporting_destination`. If the map for
// `reporting_destination` is pending, queues the report until the mapping
// information is received.
//
// The event is specified with `event_variant`, which is either:
// * a `DestinationEnumEvent`, which contains a `type` and `data`
// * Sends a POST to the url specified by `type` in the ReportingUrlMap,
// with `data` attached.
// * If there's no matching `type`, no beacon is sent.
// * a `DestinationURLEvent`, which contains a `url`
// * Sends a GET to `url`.
// * Substitutes macros from the ReportingMacros.
// * an `AutomaticBeaconEvent`, which contains a `type` and `data`
// * Like the enum variant, the data is sent as a POST to the url specified
// by `type`. However, given that these events are triggered automatically
// by the browser, they are semantically different from enum events.
//
// Returns false and populated `error_message` and `console_message_level` if
// no network request was attempted, unless the reporting URL map for
// `reporting_destination` is pending. In that case, errors are currently
// never reported, even if the reporting URL map results in no request being
// sent.
//
// `initiator_frame_tree_node_id` is used for DevTools support only.
//
// Note: `navigation_id` will only be non-null in the case of an automatic
// beacon sent as a result of a top-level navigation from a fenced frame. It
// will be set to the ID of the navigation request initiated from the fenced
// frame and targeting the new top-level frame. In all other cases (including
// the fence.reportEvent() case), the navigation id will be null.
// Note: `ad_root_origin` will only be set for automatic beacons originating
// from ad components.
bool SendReport(
const DestinationVariant& event_variant,
blink::FencedFrame::ReportingDestination reporting_destination,
RenderFrameHostImpl* request_initiator_frame,
std::string& error_message,
blink::mojom::ConsoleMessageLevel& console_message_level,
FrameTreeNodeId initiator_frame_tree_node_id = FrameTreeNodeId(),
std::optional<int64_t> navigation_id = std::nullopt);
// Called when a mapping for private aggregation requests of non-reserved
// event types is received. Currently it is only called inside
// `InterestGroupAuctionReporter::SendPendingReportsIfNavigated()`, which is
// called after any of the following:
// * the winning ad has been navigated to.
// * reportWin() completes.
// * reportResult() completes.
// The first two cases can have non-empty `private_aggregation_event_map`.
// When invoked, any pending non-reserved event type will trigger sending
// corresponding private aggregation request in
// `private_aggregation_event_map` if it has a matching key. Any future
// reports of that type will be immediately sent using the provided map.
void OnForEventPrivateAggregationRequestsReceived(
std::map<std::string, FinalizedPrivateAggregationRequests>
private_aggregation_event_map);
// Uses `pa_event_type` to send a private aggregation request. The
// non-reserved PA event type is added to `received_pa_events_` because more
// private aggregation requests associated with this event may be received and
// need to be sent after this is called.
void SendPrivateAggregationRequestsForEvent(const std::string& pa_event_type);
// Returns a list of reporting destinations that have at least 1 URL
// registered with them.
const std::vector<blink::FencedFrame::ReportingDestination>
ReportingDestinations();
// Returns a copy of the internal reporting metadata's
// `reporting_url_declarer_origin` for each reporting destination, so it can
// be validated in tests. Only includes reporting destinations for which maps
// have been received - i.e., if OnUrlMappingReady() has not yet been invoked
// for a reporting destination, it is not included in the returned map.
base::flat_map<blink::FencedFrame::ReportingDestination, url::Origin>
GetReportingUrlDeclarerOriginsForTesting();
// Returns a copy of the internal reporting metadata's `reporting_url_map`, so
// it can be validated in tests. Only includes ad beacon maps for which maps
// have been received - i.e., if OnUrlMappingReady() has not yet been invoked
// for a reporting destination, it is not included in the returned map.
base::flat_map<blink::FencedFrame::ReportingDestination, ReportingUrlMap>
GetAdBeaconMapForTesting();
// Returns a copy of the internal reporting metadata's
// `reporting_ad_macros`, so it can be validated in tests. Only includes ad
// macros for which maps have been received - i.e., if OnUrlMappingReady()
// has not yet been invoked for a reporting destination, it is not
// included in the returned map.
base::flat_map<blink::FencedFrame::ReportingDestination, ReportingMacros>
GetAdMacrosForTesting();
// Returns `received_pa_events_`, so that it can be validated in tests. Should
// only be called from tests.
std::set<std::string> GetReceivedPaEventsForTesting() const;
// Returns a copy of `private_aggregation_event_map_`, so that it can be
// validated in tests. Should only be called from tests.
std::map<std::string, FinalizedPrivateAggregationRequests>
GetPrivateAggregationEventMapForTesting();
private:
friend class base::RefCounted<FencedFrameReporter>;
friend class FencedFrameURLMappingTestPeer;
struct AttributionReportingData {
BeaconId beacon_id;
bool is_automatic_beacon;
network::mojom::AttributionSupport attribution_reporting_support;
};
struct PendingEvent {
PendingEvent(
const DestinationVariant& event,
const url::Origin& request_initiator,
const net::ReferrerPolicy request_referrer_policy,
std::optional<AttributionReportingData> attribution_reporting_data,
FrameTreeNodeId initiator_frame_tree_node_id);
PendingEvent(const PendingEvent&);
PendingEvent(PendingEvent&&);
PendingEvent& operator=(const PendingEvent&);
PendingEvent& operator=(PendingEvent&&);
~PendingEvent();
DestinationVariant event;
url::Origin request_initiator;
net::ReferrerPolicy request_referrer_policy;
// The data necessary for attribution reporting. Will be `std::nullopt` if
// attribution reporting is disallowed in the initiator frame.
std::optional<AttributionReportingData> attribution_reporting_data;
FrameTreeNodeId initiator_frame_tree_node_id;
};
// The per-blink::FencedFrame::ReportingDestination reporting information.
struct ReportingDestinationInfo {
explicit ReportingDestinationInfo(
std::optional<url::Origin> reporting_url_declarer_origin = std::nullopt,
std::optional<ReportingUrlMap> reporting_url_map = std::nullopt);
ReportingDestinationInfo(ReportingDestinationInfo&&);
~ReportingDestinationInfo();
ReportingDestinationInfo& operator=(ReportingDestinationInfo&&);
// `reporting_url_declarer_origin`, `reporting_url_map`, and
// `reporting_ad_macros` are set asynchronously, all at once.
// If they are null, any reports that are attempted to be sent of the
// corresponding type will be added to `pending_events` and only sent once
// these are populated.
std::optional<url::Origin> reporting_url_declarer_origin;
std::optional<ReportingUrlMap> reporting_url_map;
std::optional<ReportingMacros> reporting_ad_macros;
// Pending report strings received while `reporting_url_map` was
// std::nullopt. Once the map is received, this is cleared, and reports are
// sent. The events are used for post-impression reporting. They are
// from either `reportEvent()` calls or automatic beacons.
std::vector<PendingEvent> pending_events;
};
~FencedFrameReporter();
// Helper to send a report, used by both SendReport() and OnUrlMappingReady().
bool SendReportInternal(
const ReportingDestinationInfo& reporting_destination_info,
const DestinationVariant& event,
blink::FencedFrame::ReportingDestination reporting_destination,
const url::Origin& request_initiator,
const net::ReferrerPolicy request_referrer_policy,
const std::optional<AttributionReportingData>& attribution_reporting_data,
FrameTreeNodeId initiator_frame_tree_node_id,
std::string& error_message,
blink::mojom::ConsoleMessageLevel& console_message_level,
const std::string& devtools_request_id);
// Helper to send private aggregation requests in
// `private_aggregation_event_map_` with key `pa_event_type`.
void SendPrivateAggregationRequestsForEventInternal(
const std::string& pa_event_type);
// Used by FencedFrameURLMappingTestPeer.
const base::flat_map<blink::FencedFrame::ReportingDestination,
ReportingDestinationInfo>&
reporting_metadata() const {
return reporting_metadata_;
}
// Helper to notify `AttributionDataHostManager` if the report failed to be
// sent.
void NotifyFencedFrameReportingBeaconFailed(
const std::optional<AttributionReportingData>&
attribution_reporting_data);
// Notify the installed observers whether the beacon is queued to be sent or
// not.
void NotifyIsBeaconQueued(const DestinationVariant& event_variant,
bool is_queued);
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
// Bound to the lifetime of the browser context. Could be null in Incognito
// mode or in test.
const raw_ptr<AttributionManager, DanglingUntriaged> attribution_manager_;
const raw_ptr<BrowserContext> browser_context_;
base::flat_map<blink::FencedFrame::ReportingDestination,
ReportingDestinationInfo>
reporting_metadata_;
// True if the "directSeller" alias maps to the Seller destination. False if
// it maps to the "ComponentSeller" destination.
bool direct_seller_is_seller_ = false;
// The origin of the page's main frame. Used for:
// * Private aggregation (Protected Audience only)
// * 3rd party cookie permission check for credentialed automatic beacons
const url::Origin main_frame_origin_;
// Bound to the lifetime of the browser context. Can be nullptr if:
// * It's for non-FLEDGE reporter.
// * In tests that does not trigger private aggregation reports.
// * When feature `kPrivateAggregationApi` is not enabled.
const raw_ptr<PrivateAggregationManager> private_aggregation_manager_;
// The winning buyer's origin. Set to std::nullopt for non-FLEDGE reporter.
const std::optional<url::Origin> winner_origin_;
// The aggregation coordinator origin for the winning buyer. Set to
// std::nullopt for non-FLEDGE reporter or if the default coordinator should
// be used.
const std::optional<url::Origin> winner_aggregation_coordinator_origin_;
// Origins allowed to receive macro expanded reports.
const std::optional<std::vector<url::Origin>> allowed_reporting_origins_;
// Whether there has been an attempt to send a custom destination url with
// macro substitution report to a disallowed origin (according to
// `allowed_reporting_origins_`). Once this occurs, custom destination url
// reports will be disabled for the remainder of the FencedFrameReporter's
// lifetime. This prevents an interest group from encoding cross-site data
// about a user in binary with its choices of allowed/disallowed origins.
bool attempted_custom_url_report_to_disallowed_origin_ = false;
// Private aggregation requests for non-reserved event types registered in
// bidder worklets, keyed by event type.
// OnForEventPrivateAggregationRequestsReceived() builds this map up.
std::map<std::string, FinalizedPrivateAggregationRequests>
private_aggregation_event_map_;
// Fenced frame events for private aggregation API. An event is not removed
// from the set even after corresponding non-reserved private aggregation
// requests are sent, because more requests associated with this event might
// be received and need to be sent later.
std::set<std::string> received_pa_events_;
// Which API created this fenced frame reporter instance.
PrivacySandboxInvokingAPI invoking_api_;
base::ObserverList<ObserverForTesting> observers_;
// Tracks the number of beacons sent during the lifetime of the reporter.
// Logged as a histogram in the destructor.
unsigned int beacons_sent_same_origin_ = 0;
unsigned int beacons_sent_cross_origin_ = 0;
};
} // namespace content
#endif // CONTENT_BROWSER_FENCED_FRAME_FENCED_FRAME_REPORTER_H_