1 // Copyright (c) 2012 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 "chrome/browser/extensions/activity_log/activity_log.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10
11 #include "base/bind.h"
12 #include "base/command_line.h"
13 #include "base/run_loop.h"
14 #include "base/stl_util.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "build/build_config.h"
17 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
18 #include "chrome/browser/extensions/activity_log/activity_log_task_runner.h"
19 #include "chrome/browser/extensions/extension_service.h"
20 #include "chrome/browser/extensions/test_extension_system.h"
21 #include "chrome/browser/prefetch/no_state_prefetch/prerender_manager_factory.h"
22 #include "chrome/browser/prefetch/no_state_prefetch/prerender_test_utils.h"
23 #include "chrome/common/chrome_constants.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
26 #include "chrome/test/base/testing_profile.h"
27 #include "components/no_state_prefetch/browser/prerender_handle.h"
28 #include "components/no_state_prefetch/browser/prerender_manager.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/test/browser_task_environment.h"
31 #include "content/public/test/mock_render_process_host.h"
32 #include "extensions/browser/extension_registry.h"
33 #include "extensions/browser/renderer_startup_helper.h"
34 #include "extensions/browser/uninstall_reason.h"
35 #include "extensions/common/dom_action_types.h"
36 #include "extensions/common/extension_builder.h"
37 #include "mojo/public/cpp/bindings/associated_receiver_set.h"
38 #include "testing/gtest/include/gtest/gtest.h"
39
40 namespace {
41
42 const char kExtensionId[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
43
44 const char* const kUrlApiCalls[] = {
45 "HTMLButtonElement.formAction", "HTMLEmbedElement.src",
46 "HTMLFormElement.action", "HTMLFrameElement.src",
47 "HTMLHtmlElement.manifest", "HTMLIFrameElement.src",
48 "HTMLImageElement.longDesc", "HTMLImageElement.src",
49 "HTMLImageElement.lowsrc", "HTMLInputElement.formAction",
50 "HTMLInputElement.src", "HTMLLinkElement.href",
51 "HTMLMediaElement.src", "HTMLMediaElement.currentSrc",
52 "HTMLModElement.cite", "HTMLObjectElement.data",
53 "HTMLQuoteElement.cite", "HTMLScriptElement.src",
54 "HTMLSourceElement.src", "HTMLTrackElement.src",
55 "HTMLVideoElement.poster"};
56
57 } // namespace
58
59 namespace extensions {
60
61 // Class that implements the binding of a new Renderer mojom interface and
62 // can receive callbacks on it for testing validation.
63 class InterceptingRendererStartupHelper : public RendererStartupHelper,
64 public mojom::Renderer {
65 public:
InterceptingRendererStartupHelper(content::BrowserContext * browser_context)66 explicit InterceptingRendererStartupHelper(
67 content::BrowserContext* browser_context)
68 : RendererStartupHelper(browser_context) {}
69
70 protected:
BindNewRendererRemote(content::RenderProcessHost * process)71 mojo::PendingAssociatedRemote<mojom::Renderer> BindNewRendererRemote(
72 content::RenderProcessHost* process) override {
73 mojo::AssociatedRemote<mojom::Renderer> remote;
74 receivers_.Add(this, remote.BindNewEndpointAndPassDedicatedReceiver());
75 return remote.Unbind();
76 }
77
78 private:
79 // mojom::Renderer implementation:
ActivateExtension(const std::string & extension_id)80 void ActivateExtension(const std::string& extension_id) override {}
SetActivityLoggingEnabled(bool enabled)81 void SetActivityLoggingEnabled(bool enabled) override {}
82
83 mojo::AssociatedReceiverSet<mojom::Renderer> receivers_;
84 };
85
86 class ActivityLogTest : public ChromeRenderViewHostTestHarness {
87 protected:
enable_activity_logging_switch() const88 virtual bool enable_activity_logging_switch() const { return true; }
SetUp()89 void SetUp() override {
90 ChromeRenderViewHostTestHarness::SetUp();
91
92 SetActivityLogTaskRunnerForTesting(
93 base::ThreadTaskRunnerHandle::Get().get());
94
95 base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
96 if (enable_activity_logging_switch()) {
97 base::CommandLine::ForCurrentProcess()->AppendSwitch(
98 switches::kEnableExtensionActivityLogging);
99 }
100 base::CommandLine::ForCurrentProcess()->AppendSwitch(
101 switches::kEnableExtensionActivityLogTesting);
102 extension_service_ = static_cast<TestExtensionSystem*>(
103 ExtensionSystem::Get(profile()))->CreateExtensionService
104 (&command_line, base::FilePath(), false);
105
106 RendererStartupHelperFactory::GetForBrowserContext(profile())
107 ->OnRenderProcessHostCreated(
108 static_cast<content::RenderProcessHost*>(process()));
109
110 base::RunLoop().RunUntilIdle();
111 }
112
BuildFakeRendererStartupHelper(content::BrowserContext * context)113 static std::unique_ptr<KeyedService> BuildFakeRendererStartupHelper(
114 content::BrowserContext* context) {
115 return std::make_unique<InterceptingRendererStartupHelper>(context);
116 }
117
GetTestingFactories() const118 TestingProfile::TestingFactories GetTestingFactories() const override {
119 return {{RendererStartupHelperFactory::GetInstance(),
120 base::BindRepeating(&BuildFakeRendererStartupHelper)}};
121 }
122
TearDown()123 void TearDown() override {
124 base::RunLoop().RunUntilIdle();
125 SetActivityLogTaskRunnerForTesting(nullptr);
126 ChromeRenderViewHostTestHarness::TearDown();
127 }
128
RetrieveActions_LogAndFetchActions0(std::unique_ptr<std::vector<scoped_refptr<Action>>> i)129 static void RetrieveActions_LogAndFetchActions0(
130 std::unique_ptr<std::vector<scoped_refptr<Action>>> i) {
131 ASSERT_EQ(0, static_cast<int>(i->size()));
132 }
133
RetrieveActions_LogAndFetchActions1(std::unique_ptr<std::vector<scoped_refptr<Action>>> i)134 static void RetrieveActions_LogAndFetchActions1(
135 std::unique_ptr<std::vector<scoped_refptr<Action>>> i) {
136 ASSERT_EQ(1, static_cast<int>(i->size()));
137 }
138
RetrieveActions_LogAndFetchActions2(std::unique_ptr<std::vector<scoped_refptr<Action>>> i)139 static void RetrieveActions_LogAndFetchActions2(
140 std::unique_ptr<std::vector<scoped_refptr<Action>>> i) {
141 ASSERT_EQ(2, static_cast<int>(i->size()));
142 }
143
SetPolicy(bool log_arguments)144 void SetPolicy(bool log_arguments) {
145 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
146 if (log_arguments)
147 activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
148 else
149 activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
150 }
151
GetDatabaseEnabled()152 bool GetDatabaseEnabled() {
153 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
154 return activity_log->IsDatabaseEnabled();
155 }
156
GetWatchdogActive()157 bool GetWatchdogActive() {
158 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
159 return activity_log->IsWatchdogAppActive();
160 }
161
Arguments_Prerender(std::unique_ptr<std::vector<scoped_refptr<Action>>> i)162 static void Arguments_Prerender(
163 std::unique_ptr<std::vector<scoped_refptr<Action>>> i) {
164 ASSERT_EQ(1U, i->size());
165 scoped_refptr<Action> last = i->front();
166
167 ASSERT_EQ("odlameecjipmbmbejkplpemijjgpljce", last->extension_id());
168 ASSERT_EQ(Action::ACTION_CONTENT_SCRIPT, last->action_type());
169 ASSERT_EQ("[\"script\"]",
170 ActivityLogPolicy::Util::Serialize(last->args()));
171 ASSERT_EQ("http://www.google.com/", last->SerializePageUrl());
172 ASSERT_EQ("{\"prerender\":true}",
173 ActivityLogPolicy::Util::Serialize(last->other()));
174 ASSERT_EQ("", last->api_name());
175 ASSERT_EQ("", last->page_title());
176 ASSERT_EQ("", last->SerializeArgUrl());
177 }
178
RetrieveActions_ArgUrlExtraction(std::unique_ptr<std::vector<scoped_refptr<Action>>> i)179 static void RetrieveActions_ArgUrlExtraction(
180 std::unique_ptr<std::vector<scoped_refptr<Action>>> i) {
181 const base::DictionaryValue* other = NULL;
182 int dom_verb = -1;
183
184 ASSERT_EQ(4U, i->size());
185 scoped_refptr<Action> action = i->at(0);
186 ASSERT_EQ("XMLHttpRequest.open", action->api_name());
187 ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
188 ActivityLogPolicy::Util::Serialize(action->args()));
189 ASSERT_EQ("http://api.google.com/", action->arg_url().spec());
190 // Test that the dom_verb field was changed to XHR (from METHOD). This
191 // could be tested on all retrieved XHR actions but it would be redundant,
192 // so just test once.
193 other = action->other();
194 ASSERT_TRUE(other);
195 ASSERT_TRUE(other->GetInteger(activity_log_constants::kActionDomVerb,
196 &dom_verb));
197 ASSERT_EQ(DomActionType::XHR, dom_verb);
198
199 action = i->at(1);
200 ASSERT_EQ("XMLHttpRequest.open", action->api_name());
201 ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
202 ActivityLogPolicy::Util::Serialize(action->args()));
203 ASSERT_EQ("http://www.google.com/api/", action->arg_url().spec());
204
205 action = i->at(2);
206 ASSERT_EQ("XMLHttpRequest.open", action->api_name());
207 ASSERT_EQ("[\"POST\",\"/api/\"]",
208 ActivityLogPolicy::Util::Serialize(action->args()));
209 ASSERT_FALSE(action->arg_url().is_valid());
210
211 action = i->at(3);
212 ASSERT_EQ("windows.create", action->api_name());
213 ASSERT_EQ("[{\"url\":\"\\u003Carg_url>\"}]",
214 ActivityLogPolicy::Util::Serialize(action->args()));
215 ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
216 }
217
RetrieveActions_ArgUrlApiCalls(std::unique_ptr<std::vector<scoped_refptr<Action>>> actions)218 static void RetrieveActions_ArgUrlApiCalls(
219 std::unique_ptr<std::vector<scoped_refptr<Action>>> actions) {
220 size_t api_calls_size = base::size(kUrlApiCalls);
221 const base::DictionaryValue* other = NULL;
222 int dom_verb = -1;
223
224 ASSERT_EQ(api_calls_size, actions->size());
225
226 for (size_t i = 0; i < actions->size(); i++) {
227 scoped_refptr<Action> action = actions->at(i);
228 ASSERT_EQ(kExtensionId, action->extension_id());
229 ASSERT_EQ(Action::ACTION_DOM_ACCESS, action->action_type());
230 ASSERT_EQ(kUrlApiCalls[i], action->api_name());
231 ASSERT_EQ("[\"\\u003Carg_url>\"]",
232 ActivityLogPolicy::Util::Serialize(action->args()));
233 ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
234 other = action->other();
235 ASSERT_TRUE(other);
236 ASSERT_TRUE(
237 other->GetInteger(activity_log_constants::kActionDomVerb, &dom_verb));
238 ASSERT_EQ(DomActionType::SETTER, dom_verb);
239 }
240 }
241
242 ExtensionService* extension_service_;
243 };
244
TEST_F(ActivityLogTest,Construct)245 TEST_F(ActivityLogTest, Construct) {
246 ASSERT_TRUE(GetDatabaseEnabled());
247 ASSERT_FALSE(GetWatchdogActive());
248 }
249
TEST_F(ActivityLogTest,LogAndFetchActions)250 TEST_F(ActivityLogTest, LogAndFetchActions) {
251 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
252 ASSERT_TRUE(GetDatabaseEnabled());
253
254 // Write some API calls
255 scoped_refptr<Action> action = new Action(kExtensionId,
256 base::Time::Now(),
257 Action::ACTION_API_CALL,
258 "tabs.testMethod");
259 activity_log->LogAction(action);
260 action = new Action(kExtensionId,
261 base::Time::Now(),
262 Action::ACTION_DOM_ACCESS,
263 "document.write");
264 action->set_page_url(GURL("http://www.google.com"));
265 activity_log->LogAction(action);
266
267 activity_log->GetFilteredActions(
268 kExtensionId, Action::ACTION_ANY, "", "", "", 0,
269 base::BindOnce(ActivityLogTest::RetrieveActions_LogAndFetchActions2));
270 }
271
TEST_F(ActivityLogTest,LogPrerender)272 TEST_F(ActivityLogTest, LogPrerender) {
273 scoped_refptr<const Extension> extension =
274 ExtensionBuilder()
275 .SetManifest(DictionaryBuilder()
276 .Set("name", "Test extension")
277 .Set("version", "1.0.0")
278 .Set("manifest_version", 2)
279 .Build())
280 .Build();
281 extension_service_->AddExtension(extension.get());
282 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
283 EXPECT_TRUE(activity_log->ShouldLog(extension->id()));
284 ASSERT_TRUE(GetDatabaseEnabled());
285 GURL url("http://www.google.com");
286
287 prerender::PrerenderManager* prerender_manager =
288 prerender::PrerenderManagerFactory::GetForBrowserContext(profile());
289
290 const gfx::Size kSize(640, 480);
291 std::unique_ptr<prerender::PrerenderHandle> prerender_handle(
292 prerender_manager->AddPrerenderFromOmnibox(
293 url,
294 web_contents()->GetController().GetDefaultSessionStorageNamespace(),
295 kSize));
296
297 const std::vector<content::WebContents*> contentses =
298 prerender_manager->GetAllNoStatePrefetchingContentsForTesting();
299 ASSERT_EQ(1U, contentses.size());
300 content::WebContents *contents = contentses[0];
301 ASSERT_TRUE(prerender_manager->IsWebContentsPrerendering(contents));
302
303 activity_log->OnScriptsExecuted(contents, {{extension->id(), {"script"}}},
304 url);
305
306 activity_log->GetFilteredActions(
307 extension->id(), Action::ACTION_ANY, "", "", "", 0,
308 base::BindOnce(ActivityLogTest::Arguments_Prerender));
309
310 prerender_manager->CancelAllPrerenders();
311 }
312
TEST_F(ActivityLogTest,ArgUrlExtraction)313 TEST_F(ActivityLogTest, ArgUrlExtraction) {
314 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
315 base::Time now = base::Time::Now();
316
317 // Submit a DOM API call which should have its URL extracted into the arg_url
318 // field.
319 EXPECT_TRUE(activity_log->ShouldLog(kExtensionId));
320 scoped_refptr<Action> action = new Action(kExtensionId,
321 now,
322 Action::ACTION_DOM_ACCESS,
323 "XMLHttpRequest.open");
324 action->set_page_url(GURL("http://www.google.com/"));
325 action->mutable_args()->AppendString("POST");
326 action->mutable_args()->AppendString("http://api.google.com/");
327 action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
328 DomActionType::METHOD);
329 activity_log->LogAction(action);
330
331 // Submit a DOM API call with a relative URL in the argument, which should be
332 // resolved relative to the page URL.
333 action = new Action(kExtensionId,
334 now - base::TimeDelta::FromSeconds(1),
335 Action::ACTION_DOM_ACCESS,
336 "XMLHttpRequest.open");
337 action->set_page_url(GURL("http://www.google.com/"));
338 action->mutable_args()->AppendString("POST");
339 action->mutable_args()->AppendString("/api/");
340 action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
341 DomActionType::METHOD);
342 activity_log->LogAction(action);
343
344 // Submit a DOM API call with a relative URL but no base page URL against
345 // which to resolve.
346 action = new Action(kExtensionId,
347 now - base::TimeDelta::FromSeconds(2),
348 Action::ACTION_DOM_ACCESS,
349 "XMLHttpRequest.open");
350 action->mutable_args()->AppendString("POST");
351 action->mutable_args()->AppendString("/api/");
352 action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
353 DomActionType::METHOD);
354 activity_log->LogAction(action);
355
356 // Submit an API call with an embedded URL.
357 action = new Action(kExtensionId,
358 now - base::TimeDelta::FromSeconds(3),
359 Action::ACTION_API_CALL,
360 "windows.create");
361 action->set_args(
362 ListBuilder()
363 .Append(
364 DictionaryBuilder().Set("url", "http://www.google.co.uk").Build())
365 .Build());
366 activity_log->LogAction(action);
367
368 activity_log->GetFilteredActions(
369 kExtensionId, Action::ACTION_ANY, "", "", "", -1,
370 base::BindOnce(ActivityLogTest::RetrieveActions_ArgUrlExtraction));
371 }
372
TEST_F(ActivityLogTest,UninstalledExtension)373 TEST_F(ActivityLogTest, UninstalledExtension) {
374 scoped_refptr<const Extension> extension =
375 ExtensionBuilder()
376 .SetManifest(DictionaryBuilder()
377 .Set("name", "Test extension")
378 .Set("version", "1.0.0")
379 .Set("manifest_version", 2)
380 .Build())
381 .Build();
382
383 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
384 ASSERT_TRUE(GetDatabaseEnabled());
385
386 // Write some API calls
387 scoped_refptr<Action> action = new Action(extension->id(),
388 base::Time::Now(),
389 Action::ACTION_API_CALL,
390 "tabs.testMethod");
391 activity_log->LogAction(action);
392 action = new Action(extension->id(),
393 base::Time::Now(),
394 Action::ACTION_DOM_ACCESS,
395 "document.write");
396 action->set_page_url(GURL("http://www.google.com"));
397
398 activity_log->OnExtensionUninstalled(
399 NULL, extension.get(), extensions::UNINSTALL_REASON_FOR_TESTING);
400 activity_log->GetFilteredActions(
401 extension->id(), Action::ACTION_ANY, "", "", "", -1,
402 base::BindOnce(ActivityLogTest::RetrieveActions_LogAndFetchActions0));
403 }
404
TEST_F(ActivityLogTest,ArgUrlApiCalls)405 TEST_F(ActivityLogTest, ArgUrlApiCalls) {
406 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
407 base::Time now = base::Time::Now();
408 int api_calls_size = base::size(kUrlApiCalls);
409 scoped_refptr<Action> action;
410
411 for (int i = 0; i < api_calls_size; i++) {
412 action = new Action(kExtensionId,
413 now - base::TimeDelta::FromSeconds(i),
414 Action::ACTION_DOM_ACCESS,
415 kUrlApiCalls[i]);
416 action->mutable_args()->AppendString("http://www.google.co.uk");
417 action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
418 DomActionType::SETTER);
419 activity_log->LogAction(action);
420 }
421
422 activity_log->GetFilteredActions(
423 kExtensionId, Action::ACTION_ANY, "", "", "", -1,
424 base::BindOnce(ActivityLogTest::RetrieveActions_ArgUrlApiCalls));
425 }
426
TEST_F(ActivityLogTest,DeleteActivitiesByExtension)427 TEST_F(ActivityLogTest, DeleteActivitiesByExtension) {
428 const std::string kOtherExtensionId = std::string(32, 'c');
429
430 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
431 ASSERT_TRUE(GetDatabaseEnabled());
432
433 scoped_refptr<Action> action =
434 base::MakeRefCounted<Action>(kExtensionId, base::Time::Now(),
435 Action::ACTION_API_CALL, "tabs.testMethod");
436 activity_log->LogAction(action);
437
438 action =
439 base::MakeRefCounted<Action>(kOtherExtensionId, base::Time::Now(),
440 Action::ACTION_DOM_ACCESS, "document.write");
441 action->set_page_url(GURL("http://www.google.com"));
442 activity_log->LogAction(action);
443
444 activity_log->RemoveExtensionData(kExtensionId);
445 activity_log->GetFilteredActions(
446 kExtensionId, Action::ACTION_ANY, "", "", "", 0,
447 base::BindOnce(ActivityLogTest::RetrieveActions_LogAndFetchActions0));
448 activity_log->GetFilteredActions(
449 kOtherExtensionId, Action::ACTION_ANY, "", "", "", 0,
450 base::BindOnce(ActivityLogTest::RetrieveActions_LogAndFetchActions1));
451 }
452
453 class ActivityLogTestWithoutSwitch : public ActivityLogTest {
454 public:
ActivityLogTestWithoutSwitch()455 ActivityLogTestWithoutSwitch() {}
~ActivityLogTestWithoutSwitch()456 ~ActivityLogTestWithoutSwitch() override {}
enable_activity_logging_switch() const457 bool enable_activity_logging_switch() const override { return false; }
458 };
459
TEST_F(ActivityLogTestWithoutSwitch,TestShouldLog)460 TEST_F(ActivityLogTestWithoutSwitch, TestShouldLog) {
461 static_cast<TestExtensionSystem*>(
462 ExtensionSystem::Get(profile()))->SetReady();
463 ActivityLog* activity_log = ActivityLog::GetInstance(profile());
464 scoped_refptr<const Extension> empty_extension =
465 ExtensionBuilder("Test").Build();
466 extension_service_->AddExtension(empty_extension.get());
467 // Since the command line switch for logging isn't enabled and there's no
468 // watchdog app active, the activity log shouldn't log anything.
469 EXPECT_FALSE(activity_log->ShouldLog(empty_extension->id()));
470 const char kAllowlistedExtensionId[] = "eplckmlabaanikjjcgnigddmagoglhmp";
471 scoped_refptr<const Extension> activity_log_extension =
472 ExtensionBuilder("Test").SetID(kAllowlistedExtensionId).Build();
473 extension_service_->AddExtension(activity_log_extension.get());
474 // Loading a watchdog app means the activity log should log other extension
475 // activities...
476 EXPECT_TRUE(activity_log->ShouldLog(empty_extension->id()));
477 // ... but not those of the watchdog app...
478 EXPECT_FALSE(activity_log->ShouldLog(activity_log_extension->id()));
479 // ... or activities from the browser/extensions page, represented by an empty
480 // extension ID.
481 EXPECT_FALSE(activity_log->ShouldLog(std::string()));
482 extension_service_->DisableExtension(activity_log_extension->id(),
483 disable_reason::DISABLE_USER_ACTION);
484 // Disabling the watchdog app means that we're back to never logging anything.
485 EXPECT_FALSE(activity_log->ShouldLog(empty_extension->id()));
486 }
487
488 } // namespace extensions
489