1 // Copyright 2017 The Chromium Authors. All rights reserved.
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_service.h"
6
7 #include <memory>
8 #include <string>
9
10 #include "base/bind.h"
11 #include "base/memory/ptr_util.h"
12 #include "base/test/scoped_feature_list.h"
13 #include "base/time/tick_clock.h"
14 #include "base/values.h"
15 #include "net/base/features.h"
16 #include "net/base/network_isolation_key.h"
17 #include "net/reporting/mock_persistent_reporting_store.h"
18 #include "net/reporting/reporting_browsing_data_remover.h"
19 #include "net/reporting/reporting_cache.h"
20 #include "net/reporting/reporting_context.h"
21 #include "net/reporting/reporting_endpoint.h"
22 #include "net/reporting/reporting_policy.h"
23 #include "net/reporting/reporting_report.h"
24 #include "net/reporting/reporting_service.h"
25 #include "net/reporting/reporting_test_util.h"
26 #include "net/test/test_with_task_environment.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29
30 namespace net {
31 namespace {
32
33 using CommandType = MockPersistentReportingStore::Command::Type;
34
35 // The tests are parametrized on a boolean value which represents whether to use
36 // a MockPersistentReportingStore (if false, no store is used).
37 class ReportingServiceTest : public ::testing::TestWithParam<bool>,
38 public WithTaskEnvironment {
39 protected:
40 const GURL kUrl_ = GURL("https://origin/path");
41 const GURL kUrl2_ = GURL("https://origin2/path");
42 const url::Origin kOrigin_ = url::Origin::Create(kUrl_);
43 const url::Origin kOrigin2_ = url::Origin::Create(kUrl2_);
44 const GURL kEndpoint_ = GURL("https://endpoint/");
45 const std::string kUserAgent_ = "Mozilla/1.0";
46 const std::string kGroup_ = "group";
47 const std::string kType_ = "type";
48 const NetworkIsolationKey kNik_ = NetworkIsolationKey(kOrigin_, kOrigin_);
49 const NetworkIsolationKey kNik2_ = NetworkIsolationKey(kOrigin2_, kOrigin2_);
50 const ReportingEndpointGroupKey kGroupKey_ =
51 ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_);
52 const ReportingEndpointGroupKey kGroupKey2_ =
53 ReportingEndpointGroupKey(kNik2_, kOrigin2_, kGroup_);
54
ReportingServiceTest()55 ReportingServiceTest() {
56 feature_list_.InitAndEnableFeature(
57 features::kPartitionNelAndReportingByNetworkIsolationKey);
58 Init();
59 }
60
61 // Initializes, or re-initializes, |service_| and its dependencies.
Init()62 void Init() {
63 if (GetParam())
64 store_ = std::make_unique<MockPersistentReportingStore>();
65 else
66 store_ = nullptr;
67
68 auto test_context = std::make_unique<TestReportingContext>(
69 &clock_, &tick_clock_, ReportingPolicy(), store_.get());
70 context_ = test_context.get();
71
72 service_ = ReportingService::CreateForTesting(std::move(test_context));
73 }
74
75 // If the store exists, simulate finishing loading the store, which should
76 // make the rest of the test run synchronously.
FinishLoading(bool load_success)77 void FinishLoading(bool load_success) {
78 if (store_)
79 store_->FinishLoading(load_success);
80 }
81
store()82 MockPersistentReportingStore* store() { return store_.get(); }
context()83 TestReportingContext* context() { return context_; }
service()84 ReportingService* service() { return service_.get(); }
85
86 private:
87 base::test::ScopedFeatureList feature_list_;
88
89 base::SimpleTestClock clock_;
90 base::SimpleTestTickClock tick_clock_;
91
92 std::unique_ptr<MockPersistentReportingStore> store_;
93 TestReportingContext* context_;
94 std::unique_ptr<ReportingService> service_;
95 };
96
TEST_P(ReportingServiceTest,QueueReport)97 TEST_P(ReportingServiceTest, QueueReport) {
98 service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
99 std::make_unique<base::DictionaryValue>(), 0);
100 FinishLoading(true /* load_success */);
101
102 std::vector<const ReportingReport*> reports;
103 context()->cache()->GetReports(&reports);
104 ASSERT_EQ(1u, reports.size());
105 EXPECT_EQ(kUrl_, reports[0]->url);
106 EXPECT_EQ(kNik_, reports[0]->network_isolation_key);
107 EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
108 EXPECT_EQ(kGroup_, reports[0]->group);
109 EXPECT_EQ(kType_, reports[0]->type);
110 }
111
TEST_P(ReportingServiceTest,QueueReportSanitizeUrl)112 TEST_P(ReportingServiceTest, QueueReportSanitizeUrl) {
113 // Same as kUrl_ but with username, password, and fragment.
114 GURL url = GURL("https://username:password@origin/path#fragment");
115 service()->QueueReport(url, kNik_, kUserAgent_, kGroup_, kType_,
116 std::make_unique<base::DictionaryValue>(), 0);
117 FinishLoading(true /* load_success */);
118
119 std::vector<const ReportingReport*> reports;
120 context()->cache()->GetReports(&reports);
121 ASSERT_EQ(1u, reports.size());
122 EXPECT_EQ(kUrl_, reports[0]->url);
123 EXPECT_EQ(kNik_, reports[0]->network_isolation_key);
124 EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
125 EXPECT_EQ(kGroup_, reports[0]->group);
126 EXPECT_EQ(kType_, reports[0]->type);
127 }
128
TEST_P(ReportingServiceTest,DontQueueReportInvalidUrl)129 TEST_P(ReportingServiceTest, DontQueueReportInvalidUrl) {
130 GURL url = GURL("https://");
131 // This does not trigger an attempt to load from the store because the url
132 // is immediately rejected as invalid.
133 service()->QueueReport(url, kNik_, kUserAgent_, kGroup_, kType_,
134 std::make_unique<base::DictionaryValue>(), 0);
135
136 std::vector<const ReportingReport*> reports;
137 context()->cache()->GetReports(&reports);
138 ASSERT_EQ(0u, reports.size());
139 }
140
TEST_P(ReportingServiceTest,QueueReportNetworkIsolationKeyDisabled)141 TEST_P(ReportingServiceTest, QueueReportNetworkIsolationKeyDisabled) {
142 base::test::ScopedFeatureList feature_list;
143 feature_list.InitAndDisableFeature(
144 features::kPartitionNelAndReportingByNetworkIsolationKey);
145
146 // Re-create the store, so it reads the new feature value.
147 Init();
148
149 service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
150 std::make_unique<base::DictionaryValue>(), 0);
151 FinishLoading(true /* load_success */);
152
153 std::vector<const ReportingReport*> reports;
154 context()->cache()->GetReports(&reports);
155 ASSERT_EQ(1u, reports.size());
156
157 // NetworkIsolationKey should be empty, instead of kNik_;
158 EXPECT_EQ(NetworkIsolationKey(), reports[0]->network_isolation_key);
159 EXPECT_NE(kNik_, reports[0]->network_isolation_key);
160
161 EXPECT_EQ(kUrl_, reports[0]->url);
162 EXPECT_EQ(kUserAgent_, reports[0]->user_agent);
163 EXPECT_EQ(kGroup_, reports[0]->group);
164 EXPECT_EQ(kType_, reports[0]->type);
165 }
166
TEST_P(ReportingServiceTest,ProcessHeader)167 TEST_P(ReportingServiceTest, ProcessHeader) {
168 service()->ProcessHeader(kUrl_, kNik_,
169 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
170 "\"}],"
171 "\"group\":\"" +
172 kGroup_ +
173 "\","
174 "\"max_age\":86400}");
175 FinishLoading(true /* load_success */);
176
177 EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
178 EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
179 ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_), kEndpoint_));
180 }
181
TEST_P(ReportingServiceTest,ProcessHeaderPathAbsolute)182 TEST_P(ReportingServiceTest, ProcessHeaderPathAbsolute) {
183 service()->ProcessHeader(kUrl_, kNik_,
184 "{\"endpoints\":[{\"url\":\"/path-absolute\"}],"
185 "\"group\":\"" +
186 kGroup_ +
187 "\","
188 "\"max_age\":86400}");
189 FinishLoading(true /* load_success */);
190
191 EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
192 }
193
TEST_P(ReportingServiceTest,ProcessHeader_TooLong)194 TEST_P(ReportingServiceTest, ProcessHeader_TooLong) {
195 const std::string header_too_long =
196 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
197 "\"}],"
198 "\"group\":\"" +
199 kGroup_ +
200 "\","
201 "\"max_age\":86400," +
202 "\"junk\":\"" + std::string(32 * 1024, 'a') + "\"}";
203 // This does not trigger an attempt to load from the store because the header
204 // is immediately rejected as invalid.
205 service()->ProcessHeader(kUrl_, kNik_, header_too_long);
206
207 EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
208 }
209
TEST_P(ReportingServiceTest,ProcessHeader_TooDeep)210 TEST_P(ReportingServiceTest, ProcessHeader_TooDeep) {
211 const std::string header_too_deep = "{\"endpoints\":[{\"url\":\"" +
212 kEndpoint_.spec() +
213 "\"}],"
214 "\"group\":\"" +
215 kGroup_ +
216 "\","
217 "\"max_age\":86400," +
218 "\"junk\":[[[[[[[[[[]]]]]]]]]]}";
219 // This does not trigger an attempt to load from the store because the header
220 // is immediately rejected as invalid.
221 service()->ProcessHeader(kUrl_, kNik_, header_too_deep);
222
223 EXPECT_EQ(0u, context()->cache()->GetEndpointCount());
224 }
225
TEST_P(ReportingServiceTest,ProcessHeaderNetworkIsolationKeyDisabled)226 TEST_P(ReportingServiceTest, ProcessHeaderNetworkIsolationKeyDisabled) {
227 base::test::ScopedFeatureList feature_list;
228 feature_list.InitAndDisableFeature(
229 features::kPartitionNelAndReportingByNetworkIsolationKey);
230
231 // Re-create the store, so it reads the new feature value.
232 Init();
233
234 service()->ProcessHeader(kUrl_, kNik_,
235 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
236 "\"}],"
237 "\"group\":\"" +
238 kGroup_ +
239 "\","
240 "\"max_age\":86400}");
241 FinishLoading(true /* load_success */);
242
243 EXPECT_EQ(1u, context()->cache()->GetEndpointCount());
244 EXPECT_FALSE(context()->cache()->GetEndpointForTesting(
245 ReportingEndpointGroupKey(kNik_, kOrigin_, kGroup_), kEndpoint_));
246 EXPECT_TRUE(context()->cache()->GetEndpointForTesting(
247 ReportingEndpointGroupKey(NetworkIsolationKey(), kOrigin_, kGroup_),
248 kEndpoint_));
249 }
250
TEST_P(ReportingServiceTest,WriteToStore)251 TEST_P(ReportingServiceTest, WriteToStore) {
252 if (!store())
253 return;
254
255 MockPersistentReportingStore::CommandList expected_commands;
256
257 // This first call to any public method triggers a load. The load will block
258 // until we call FinishLoading.
259 service()->ProcessHeader(kUrl_, kNik_,
260 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
261 "\"}],"
262 "\"group\":\"" +
263 kGroup_ +
264 "\","
265 "\"max_age\":86400}");
266 expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
267 EXPECT_THAT(store()->GetAllCommands(),
268 testing::UnorderedElementsAreArray(expected_commands));
269
270 // Unblock the load. The will let the remaining calls to the service complete
271 // without blocking.
272 FinishLoading(true /* load_success */);
273 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
274 kGroupKey_, kEndpoint_);
275 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
276 kGroupKey_);
277 EXPECT_THAT(store()->GetAllCommands(),
278 testing::UnorderedElementsAreArray(expected_commands));
279
280 service()->ProcessHeader(kUrl2_, kNik2_,
281 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
282 "\"}],"
283 "\"group\":\"" +
284 kGroup_ +
285 "\","
286 "\"max_age\":86400}");
287 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
288 kGroupKey2_, kEndpoint_);
289 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
290 kGroupKey2_);
291 EXPECT_THAT(store()->GetAllCommands(),
292 testing::UnorderedElementsAreArray(expected_commands));
293
294 service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
295 std::make_unique<base::DictionaryValue>(), 0);
296 expected_commands.emplace_back(
297 CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
298 EXPECT_THAT(store()->GetAllCommands(),
299 testing::UnorderedElementsAreArray(expected_commands));
300
301 service()->RemoveBrowsingData(ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
302 base::BindRepeating([](const GURL& url) {
303 return url.host() == "origin";
304 }));
305 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
306 kGroupKey_, kEndpoint_);
307 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
308 kGroupKey_);
309 expected_commands.emplace_back(CommandType::FLUSH);
310 EXPECT_THAT(store()->GetAllCommands(),
311 testing::UnorderedElementsAreArray(expected_commands));
312
313 service()->RemoveAllBrowsingData(
314 ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
315 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
316 kGroupKey2_, kEndpoint_);
317 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
318 kGroupKey2_);
319 expected_commands.emplace_back(CommandType::FLUSH);
320 EXPECT_THAT(store()->GetAllCommands(),
321 testing::UnorderedElementsAreArray(expected_commands));
322 }
323
TEST_P(ReportingServiceTest,WaitUntilLoadFinishesBeforeWritingToStore)324 TEST_P(ReportingServiceTest, WaitUntilLoadFinishesBeforeWritingToStore) {
325 if (!store())
326 return;
327
328 MockPersistentReportingStore::CommandList expected_commands;
329
330 // This first call to any public method triggers a load. The load will block
331 // until we call FinishLoading.
332 service()->ProcessHeader(kUrl_, kNik_,
333 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
334 "\"}],"
335 "\"group\":\"" +
336 kGroup_ +
337 "\","
338 "\"max_age\":86400}");
339 expected_commands.emplace_back(CommandType::LOAD_REPORTING_CLIENTS);
340 EXPECT_THAT(store()->GetAllCommands(),
341 testing::UnorderedElementsAreArray(expected_commands));
342
343 service()->ProcessHeader(kUrl2_, kNik2_,
344 "{\"endpoints\":[{\"url\":\"" + kEndpoint_.spec() +
345 "\"}],"
346 "\"group\":\"" +
347 kGroup_ +
348 "\","
349 "\"max_age\":86400}");
350 EXPECT_THAT(store()->GetAllCommands(),
351 testing::UnorderedElementsAreArray(expected_commands));
352
353 service()->QueueReport(kUrl_, kNik_, kUserAgent_, kGroup_, kType_,
354 std::make_unique<base::DictionaryValue>(), 0);
355 EXPECT_THAT(store()->GetAllCommands(),
356 testing::UnorderedElementsAreArray(expected_commands));
357
358 service()->RemoveBrowsingData(ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS,
359 base::BindRepeating([](const GURL& url) {
360 return url.host() == "origin";
361 }));
362 EXPECT_THAT(store()->GetAllCommands(),
363 testing::UnorderedElementsAreArray(expected_commands));
364
365 service()->RemoveAllBrowsingData(
366 ReportingBrowsingDataRemover::DATA_TYPE_CLIENTS);
367 EXPECT_THAT(store()->GetAllCommands(),
368 testing::UnorderedElementsAreArray(expected_commands));
369
370 // Unblock the load. The will let the remaining calls to the service complete
371 // without blocking.
372 FinishLoading(true /* load_success */);
373 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
374 kGroupKey_, kEndpoint_);
375 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT,
376 kGroupKey2_, kEndpoint_);
377 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
378 kGroupKey_);
379 expected_commands.emplace_back(CommandType::ADD_REPORTING_ENDPOINT_GROUP,
380 kGroupKey2_);
381 expected_commands.emplace_back(
382 CommandType::UPDATE_REPORTING_ENDPOINT_GROUP_ACCESS_TIME, kGroupKey_);
383 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
384 kGroupKey_, kEndpoint_);
385 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
386 kGroupKey_);
387 expected_commands.emplace_back(CommandType::FLUSH);
388 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT,
389 kGroupKey2_, kEndpoint_);
390 expected_commands.emplace_back(CommandType::DELETE_REPORTING_ENDPOINT_GROUP,
391 kGroupKey2_);
392 expected_commands.emplace_back(CommandType::FLUSH);
393 EXPECT_THAT(store()->GetAllCommands(),
394 testing::UnorderedElementsAreArray(expected_commands));
395 }
396
397 INSTANTIATE_TEST_SUITE_P(ReportingServiceStoreTest,
398 ReportingServiceTest,
399 ::testing::Bool());
400 } // namespace
401 } // namespace net
402