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