1 // Copyright 2017 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/reporting/reporting_delivery_agent.h"
6
7 #include <algorithm>
8 #include <map>
9 #include <set>
10 #include <string>
11 #include <utility>
12 #include <vector>
13
14 #include "base/check.h"
15 #include "base/containers/contains.h"
16 #include "base/functional/bind.h"
17 #include "base/json/json_writer.h"
18 #include "base/memory/raw_ptr.h"
19 #include "base/metrics/histogram_functions.h"
20 #include "base/numerics/safe_conversions.h"
21 #include "base/time/tick_clock.h"
22 #include "base/timer/timer.h"
23 #include "base/values.h"
24 #include "net/base/isolation_info.h"
25 #include "net/base/network_anonymization_key.h"
26 #include "net/base/url_util.h"
27 #include "net/reporting/reporting_cache.h"
28 #include "net/reporting/reporting_cache_observer.h"
29 #include "net/reporting/reporting_context.h"
30 #include "net/reporting/reporting_delegate.h"
31 #include "net/reporting/reporting_endpoint_manager.h"
32 #include "net/reporting/reporting_report.h"
33 #include "net/reporting/reporting_uploader.h"
34 #include "url/gurl.h"
35 #include "url/origin.h"
36
37 namespace net {
38
39 namespace {
40
41 using ReportList = std::vector<const ReportingReport*>;
42 using ReportingUploadHeaderType =
43 ReportingDeliveryAgent::ReportingUploadHeaderType;
44
RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type)45 void RecordReportingUploadHeaderType(ReportingUploadHeaderType header_type) {
46 base::UmaHistogramEnumeration("Net.Reporting.UploadHeaderType", header_type);
47 }
48
SerializeReports(const ReportList & reports,base::TimeTicks now)49 std::string SerializeReports(const ReportList& reports, base::TimeTicks now) {
50 base::Value::List reports_value;
51
52 for (const ReportingReport* report : reports) {
53 base::Value::Dict report_value;
54
55 report_value.Set("age", base::saturated_cast<int>(
56 (now - report->queued).InMilliseconds()));
57 report_value.Set("type", report->type);
58 report_value.Set("url", report->url.spec());
59 report_value.Set("user_agent", report->user_agent);
60 report_value.Set("body", report->body.Clone());
61
62 reports_value.Append(std::move(report_value));
63 }
64
65 std::string json_out;
66 bool json_written = base::JSONWriter::Write(reports_value, &json_out);
67 DCHECK(json_written);
68 return json_out;
69 }
70
CompareReportGroupKeys(const ReportingReport * lhs,const ReportingReport * rhs)71 bool CompareReportGroupKeys(const ReportingReport* lhs,
72 const ReportingReport* rhs) {
73 return lhs->GetGroupKey() < rhs->GetGroupKey();
74 }
75
76 // Each Delivery corresponds to one upload URLRequest.
77 class Delivery {
78 public:
79 // The target of a delivery. All reports uploaded together must share the
80 // same values for these parameters.
81 // Note that |origin| here (which matches the report's |origin|) is not
82 // necessarily the same as the |origin| of the ReportingEndpoint's group key
83 // (if the endpoint is configured to include subdomains). Reports with
84 // different group keys can be in the same delivery, as long as the NIK,
85 // report origin and reporting source are the same, and they all get assigned
86 // to the same endpoint URL.
87 // |isolation_info| is the IsolationInfo struct associated with the reporting
88 // endpoint, and is used to determine appropriate credentials for the upload.
89 // |network_anonymization_key| is the NIK from the ReportingEndpoint, which
90 // may have been cleared in the ReportingService if reports are not being
91 // partitioned by NIK. (This is why a separate parameter is used here, rather
92 // than simply using the computed NIK from |isolation_info|.)
93 struct Target {
Targetnet::__anon6ecaebbd0111::Delivery::Target94 Target(const IsolationInfo& isolation_info,
95 const NetworkAnonymizationKey& network_anonymization_key,
96 const url::Origin& origin,
97 const GURL& endpoint_url,
98 const absl::optional<base::UnguessableToken> reporting_source)
99 : isolation_info(isolation_info),
100 network_anonymization_key(network_anonymization_key),
101 origin(origin),
102 endpoint_url(endpoint_url),
103 reporting_source(reporting_source) {
104 DCHECK(network_anonymization_key.IsEmpty() ||
105 network_anonymization_key ==
106 isolation_info.network_anonymization_key());
107 }
108
109 ~Target() = default;
110
operator <net::__anon6ecaebbd0111::Delivery::Target111 bool operator<(const Target& other) const {
112 // Note that sorting by NIK here is required for V0 reports; V1 reports
113 // should not need this (but it doesn't hurt). We can remove that as a
114 // comparison key when V0 reporting endpoints are removed.
115 return std::tie(network_anonymization_key, origin, endpoint_url,
116 reporting_source) <
117 std::tie(other.network_anonymization_key, other.origin,
118 other.endpoint_url, other.reporting_source);
119 }
120
121 IsolationInfo isolation_info;
122 NetworkAnonymizationKey network_anonymization_key;
123 url::Origin origin;
124 GURL endpoint_url;
125 absl::optional<base::UnguessableToken> reporting_source;
126 };
127
Delivery(const Target & target)128 explicit Delivery(const Target& target) : target_(target) {}
129
130 ~Delivery() = default;
131
132 // Add the reports in [reports_begin, reports_end) into this delivery.
133 // Modify the report counter for the |endpoint| to which this delivery is
134 // destined.
AddReports(const ReportingEndpoint & endpoint,const ReportList::const_iterator reports_begin,const ReportList::const_iterator reports_end)135 void AddReports(const ReportingEndpoint& endpoint,
136 const ReportList::const_iterator reports_begin,
137 const ReportList::const_iterator reports_end) {
138 DCHECK(reports_begin != reports_end);
139 DCHECK(endpoint.group_key.network_anonymization_key ==
140 network_anonymization_key());
141 DCHECK(IsSubdomainOf(target_.origin.host() /* subdomain */,
142 endpoint.group_key.origin.host() /* superdomain */));
143 for (auto it = reports_begin; it != reports_end; ++it) {
144 DCHECK_EQ((*reports_begin)->GetGroupKey(), (*it)->GetGroupKey());
145 DCHECK((*it)->network_anonymization_key == network_anonymization_key());
146 DCHECK_EQ(url::Origin::Create((*it)->url), target_.origin);
147 DCHECK_EQ((*it)->group, endpoint.group_key.group_name);
148 // Report origin is equal to, or a subdomain of, the endpoint
149 // configuration's origin.
150 DCHECK(IsSubdomainOf((*it)->url.host_piece() /* subdomain */,
151 endpoint.group_key.origin.host() /* superdomain */));
152 }
153
154 reports_per_group_[endpoint.group_key] +=
155 std::distance(reports_begin, reports_end);
156 reports_.insert(reports_.end(), reports_begin, reports_end);
157 }
158
159 // Records statistics for reports after an upload has completed.
160 // Either removes successfully delivered reports, or increments the failure
161 // counter if delivery was unsuccessful.
ProcessOutcome(ReportingCache * cache,bool success)162 void ProcessOutcome(ReportingCache* cache, bool success) {
163 for (const auto& group_name_and_count : reports_per_group_) {
164 cache->IncrementEndpointDeliveries(group_name_and_count.first,
165 target_.endpoint_url,
166 group_name_and_count.second, success);
167 }
168 if (success) {
169 ReportingUploadHeaderType upload_type =
170 target_.reporting_source.has_value()
171 ? ReportingUploadHeaderType::kReportingEndpoints
172 : ReportingUploadHeaderType::kReportTo;
173 for (size_t i = 0; i < reports_.size(); ++i) {
174 RecordReportingUploadHeaderType(upload_type);
175 }
176 cache->RemoveReports(reports_, /* delivery_success */ true);
177 } else {
178 cache->IncrementReportsAttempts(reports_);
179 }
180 }
181
network_anonymization_key() const182 const NetworkAnonymizationKey& network_anonymization_key() const {
183 return target_.network_anonymization_key;
184 }
endpoint_url() const185 const GURL& endpoint_url() const { return target_.endpoint_url; }
reports() const186 const ReportList& reports() const { return reports_; }
187
188 private:
189 const Target target_;
190 ReportList reports_;
191
192 // Used to track statistics for each ReportingEndpoint.
193 // The endpoint is uniquely identified by the key in conjunction with
194 // |target_.endpoint_url|. See ProcessOutcome().
195 std::map<ReportingEndpointGroupKey, int> reports_per_group_;
196 };
197
198 class ReportingDeliveryAgentImpl : public ReportingDeliveryAgent,
199 public ReportingCacheObserver {
200 public:
ReportingDeliveryAgentImpl(ReportingContext * context,const RandIntCallback & rand_callback)201 ReportingDeliveryAgentImpl(ReportingContext* context,
202 const RandIntCallback& rand_callback)
203 : context_(context),
204 timer_(std::make_unique<base::OneShotTimer>()),
205 endpoint_manager_(
206 ReportingEndpointManager::Create(&context->policy(),
207 &context->tick_clock(),
208 context->delegate(),
209 context->cache(),
210 rand_callback)) {
211 context_->AddCacheObserver(this);
212 }
213
214 ReportingDeliveryAgentImpl(const ReportingDeliveryAgentImpl&) = delete;
215 ReportingDeliveryAgentImpl& operator=(const ReportingDeliveryAgentImpl&) =
216 delete;
217
218 // ReportingDeliveryAgent implementation:
219
~ReportingDeliveryAgentImpl()220 ~ReportingDeliveryAgentImpl() override {
221 context_->RemoveCacheObserver(this);
222 }
223
SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer)224 void SetTimerForTesting(std::unique_ptr<base::OneShotTimer> timer) override {
225 DCHECK(!timer_->IsRunning());
226 timer_ = std::move(timer);
227 }
228
SendReportsForSource(base::UnguessableToken reporting_source)229 void SendReportsForSource(base::UnguessableToken reporting_source) override {
230 DCHECK(!reporting_source.is_empty());
231 ReportList reports =
232 cache()->GetReportsToDeliverForSource(reporting_source);
233 if (reports.empty())
234 return;
235 DoSendReports(std::move(reports));
236 }
237
238 // ReportingCacheObserver implementation:
OnReportsUpdated()239 void OnReportsUpdated() override {
240 if (CacheHasReports() && !timer_->IsRunning()) {
241 SendReports();
242 StartTimer();
243 }
244 }
245
246 private:
CacheHasReports()247 bool CacheHasReports() {
248 ReportList reports;
249 context_->cache()->GetReports(&reports);
250 return !reports.empty();
251 }
252
StartTimer()253 void StartTimer() {
254 timer_->Start(FROM_HERE, policy().delivery_interval,
255 base::BindOnce(&ReportingDeliveryAgentImpl::OnTimerFired,
256 base::Unretained(this)));
257 }
258
OnTimerFired()259 void OnTimerFired() {
260 if (CacheHasReports()) {
261 SendReports();
262 StartTimer();
263 }
264 }
265
SendReports()266 void SendReports() {
267 ReportList reports = cache()->GetReportsToDeliver();
268 if (reports.empty())
269 return;
270 DoSendReports(std::move(reports));
271 }
272
DoSendReports(ReportList reports)273 void DoSendReports(ReportList reports) {
274 // First determine which origins we're allowed to upload reports about.
275 std::set<url::Origin> report_origins;
276 for (const ReportingReport* report : reports) {
277 report_origins.insert(url::Origin::Create(report->url));
278 }
279 delegate()->CanSendReports(
280 std::move(report_origins),
281 base::BindOnce(&ReportingDeliveryAgentImpl::OnSendPermissionsChecked,
282 weak_factory_.GetWeakPtr(), std::move(reports)));
283 }
284
OnSendPermissionsChecked(ReportList reports,std::set<url::Origin> allowed_report_origins)285 void OnSendPermissionsChecked(ReportList reports,
286 std::set<url::Origin> allowed_report_origins) {
287 DCHECK(!reports.empty());
288 std::map<Delivery::Target, std::unique_ptr<Delivery>> deliveries;
289
290 // Sort by group key
291 std::sort(reports.begin(), reports.end(), &CompareReportGroupKeys);
292
293 // Iterate over "buckets" of reports with the same group key.
294 for (auto bucket_it = reports.begin(); bucket_it != reports.end();) {
295 auto bucket_start = bucket_it;
296 // Set the iterator to the beginning of the next group bucket.
297 bucket_it = std::upper_bound(bucket_it, reports.end(), *bucket_it,
298 &CompareReportGroupKeys);
299
300 // Skip this group if we don't have origin permissions for this origin.
301 const ReportingEndpointGroupKey& report_group_key =
302 (*bucket_start)->GetGroupKey();
303 if (!base::Contains(allowed_report_origins, report_group_key.origin))
304 continue;
305
306 // Skip this group if there is already a pending upload for it.
307 // We don't allow multiple concurrent uploads for the same group.
308 if (base::Contains(pending_groups_, report_group_key))
309 continue;
310
311 // Find an endpoint to deliver these reports to.
312 const ReportingEndpoint endpoint =
313 endpoint_manager_->FindEndpointForDelivery(report_group_key);
314 // TODO(chlily): Remove reports for which there are no valid delivery
315 // endpoints.
316 if (!endpoint)
317 continue;
318
319 pending_groups_.insert(report_group_key);
320
321 IsolationInfo isolation_info =
322 cache()->GetIsolationInfoForEndpoint(endpoint);
323
324 // Add the reports to the appropriate delivery.
325 Delivery::Target target(isolation_info,
326 report_group_key.network_anonymization_key,
327 report_group_key.origin, endpoint.info.url,
328 endpoint.group_key.reporting_source);
329 auto delivery_it = deliveries.find(target);
330 if (delivery_it == deliveries.end()) {
331 bool inserted;
332 auto new_delivery = std::make_unique<Delivery>(target);
333 std::tie(delivery_it, inserted) = deliveries.insert(
334 std::make_pair(std::move(target), std::move(new_delivery)));
335 DCHECK(inserted);
336 }
337 delivery_it->second->AddReports(endpoint, bucket_start, bucket_it);
338 }
339
340 // Keep track of which of these reports we don't queue for delivery; we'll
341 // need to mark them as not-pending.
342 std::set<const ReportingReport*> undelivered_reports(reports.begin(),
343 reports.end());
344
345 // Start an upload for each delivery.
346 for (auto& target_and_delivery : deliveries) {
347 const Delivery::Target& target = target_and_delivery.first;
348 std::unique_ptr<Delivery>& delivery = target_and_delivery.second;
349
350 int max_depth = 0;
351 for (const ReportingReport* report : delivery->reports()) {
352 undelivered_reports.erase(report);
353 max_depth = std::max(report->depth, max_depth);
354 }
355
356 std::string upload_data =
357 SerializeReports(delivery->reports(), tick_clock().NowTicks());
358
359 // TODO: Calculate actual max depth.
360 uploader()->StartUpload(
361 target.origin, target.endpoint_url, target.isolation_info,
362 upload_data, max_depth,
363 /*eligible_for_credentials=*/target.reporting_source.has_value(),
364 base::BindOnce(&ReportingDeliveryAgentImpl::OnUploadComplete,
365 weak_factory_.GetWeakPtr(), std::move(delivery)));
366 }
367
368 cache()->ClearReportsPending(
369 {undelivered_reports.begin(), undelivered_reports.end()});
370 }
371
OnUploadComplete(std::unique_ptr<Delivery> delivery,ReportingUploader::Outcome outcome)372 void OnUploadComplete(std::unique_ptr<Delivery> delivery,
373 ReportingUploader::Outcome outcome) {
374 bool success = outcome == ReportingUploader::Outcome::SUCCESS;
375 delivery->ProcessOutcome(cache(), success);
376
377 endpoint_manager_->InformOfEndpointRequest(
378 delivery->network_anonymization_key(), delivery->endpoint_url(),
379 success);
380
381 // TODO(chlily): This leaks information across NIKs. If the endpoint URL is
382 // configured for both NIK1 and NIK2, and it responds with a 410 on a NIK1
383 // connection, then the change in configuration will be detectable on a NIK2
384 // connection.
385 // TODO(rodneyding): Handle Remove endpoint for Reporting-Endpoints header.
386 if (outcome == ReportingUploader::Outcome::REMOVE_ENDPOINT)
387 cache()->RemoveEndpointsForUrl(delivery->endpoint_url());
388
389 for (const ReportingReport* report : delivery->reports()) {
390 pending_groups_.erase(report->GetGroupKey());
391 }
392
393 cache()->ClearReportsPending(delivery->reports());
394 }
395
policy() const396 const ReportingPolicy& policy() const { return context_->policy(); }
tick_clock() const397 const base::TickClock& tick_clock() const { return context_->tick_clock(); }
delegate()398 ReportingDelegate* delegate() { return context_->delegate(); }
cache()399 ReportingCache* cache() { return context_->cache(); }
uploader()400 ReportingUploader* uploader() { return context_->uploader(); }
401
402 raw_ptr<ReportingContext> context_;
403
404 std::unique_ptr<base::OneShotTimer> timer_;
405
406 // Tracks endpoint groups for which there is a pending delivery running.
407 std::set<ReportingEndpointGroupKey> pending_groups_;
408
409 std::unique_ptr<ReportingEndpointManager> endpoint_manager_;
410
411 base::WeakPtrFactory<ReportingDeliveryAgentImpl> weak_factory_{this};
412 };
413
414 } // namespace
415
416 // static
Create(ReportingContext * context,const RandIntCallback & rand_callback)417 std::unique_ptr<ReportingDeliveryAgent> ReportingDeliveryAgent::Create(
418 ReportingContext* context,
419 const RandIntCallback& rand_callback) {
420 return std::make_unique<ReportingDeliveryAgentImpl>(context, rand_callback);
421 }
422
423 ReportingDeliveryAgent::~ReportingDeliveryAgent() = default;
424
425 } // namespace net
426