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 "base/command_line.h"
6#include "base/memory/scoped_ptr.h"
7#include "base/message_loop/message_loop.h"
8#include "base/run_loop.h"
9#include "base/synchronization/waitable_event.h"
10#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
11#include "chrome/browser/extensions/activity_log/activity_log.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/test_extension_system.h"
14#include "chrome/browser/prerender/prerender_handle.h"
15#include "chrome/browser/prerender/prerender_manager.h"
16#include "chrome/browser/prerender/prerender_manager_factory.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/test/base/chrome_render_view_host_test_harness.h"
20#include "chrome/test/base/testing_profile.h"
21#include "content/public/browser/web_contents.h"
22#include "content/public/test/test_browser_thread_bundle.h"
23#include "extensions/browser/extension_registry.h"
24#include "extensions/browser/uninstall_reason.h"
25#include "extensions/common/dom_action_types.h"
26#include "extensions/common/extension_builder.h"
27#include "sql/statement.h"
28#include "testing/gtest/include/gtest/gtest.h"
29
30#if defined(OS_CHROMEOS)
31#include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
32#include "chrome/browser/chromeos/settings/cros_settings.h"
33#include "chrome/browser/chromeos/settings/device_settings_service.h"
34#endif
35
36namespace {
37
38const char kExtensionId[] = "abc";
39
40const char* kUrlApiCalls[] = {
41    "HTMLButtonElement.formAction", "HTMLEmbedElement.src",
42    "HTMLFormElement.action",       "HTMLFrameElement.src",
43    "HTMLHtmlElement.manifest",     "HTMLIFrameElement.src",
44    "HTMLImageElement.longDesc",    "HTMLImageElement.src",
45    "HTMLImageElement.lowsrc",      "HTMLInputElement.formAction",
46    "HTMLInputElement.src",         "HTMLLinkElement.href",
47    "HTMLMediaElement.src",         "HTMLMediaElement.currentSrc",
48    "HTMLModElement.cite",          "HTMLObjectElement.data",
49    "HTMLQuoteElement.cite",        "HTMLScriptElement.src",
50    "HTMLSourceElement.src",        "HTMLTrackElement.src",
51    "HTMLVideoElement.poster"};
52
53}  // namespace
54
55namespace extensions {
56
57class ActivityLogTest : public ChromeRenderViewHostTestHarness {
58 protected:
59  virtual void SetUp() OVERRIDE {
60    ChromeRenderViewHostTestHarness::SetUp();
61#if defined OS_CHROMEOS
62    test_user_manager_.reset(new chromeos::ScopedTestUserManager());
63#endif
64    CommandLine command_line(CommandLine::NO_PROGRAM);
65    CommandLine::ForCurrentProcess()->AppendSwitch(
66        switches::kEnableExtensionActivityLogging);
67    CommandLine::ForCurrentProcess()->AppendSwitch(
68        switches::kEnableExtensionActivityLogTesting);
69    extension_service_ = static_cast<TestExtensionSystem*>(
70        ExtensionSystem::Get(profile()))->CreateExtensionService
71            (&command_line, base::FilePath(), false);
72    base::RunLoop().RunUntilIdle();
73  }
74
75  virtual void TearDown() OVERRIDE {
76#if defined OS_CHROMEOS
77    test_user_manager_.reset();
78#endif
79    base::RunLoop().RunUntilIdle();
80    ChromeRenderViewHostTestHarness::TearDown();
81  }
82
83  static void RetrieveActions_LogAndFetchActions0(
84      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
85    ASSERT_EQ(0, static_cast<int>(i->size()));
86  }
87
88  static void RetrieveActions_LogAndFetchActions2(
89      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
90    ASSERT_EQ(2, static_cast<int>(i->size()));
91  }
92
93  void SetPolicy(bool log_arguments) {
94    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
95    if (log_arguments)
96      activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
97    else
98      activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
99  }
100
101  bool GetDatabaseEnabled() {
102    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
103    return activity_log->IsDatabaseEnabled();
104  }
105
106  bool GetWatchdogActive() {
107    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
108    return activity_log->IsWatchdogAppActive();
109  }
110
111  static void Arguments_Prerender(
112      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
113    ASSERT_EQ(1U, i->size());
114    scoped_refptr<Action> last = i->front();
115
116    ASSERT_EQ("odlameecjipmbmbejkplpemijjgpljce", last->extension_id());
117    ASSERT_EQ(Action::ACTION_CONTENT_SCRIPT, last->action_type());
118    ASSERT_EQ("[\"script\"]",
119              ActivityLogPolicy::Util::Serialize(last->args()));
120    ASSERT_EQ("http://www.google.com/", last->SerializePageUrl());
121    ASSERT_EQ("{\"prerender\":true}",
122              ActivityLogPolicy::Util::Serialize(last->other()));
123    ASSERT_EQ("", last->api_name());
124    ASSERT_EQ("", last->page_title());
125    ASSERT_EQ("", last->SerializeArgUrl());
126  }
127
128  static void RetrieveActions_ArgUrlExtraction(
129      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
130    const base::DictionaryValue* other = NULL;
131    int dom_verb = -1;
132
133    ASSERT_EQ(4U, i->size());
134    scoped_refptr<Action> action = i->at(0);
135    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
136    ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
137              ActivityLogPolicy::Util::Serialize(action->args()));
138    ASSERT_EQ("http://api.google.com/", action->arg_url().spec());
139    // Test that the dom_verb field was changed to XHR (from METHOD).  This
140    // could be tested on all retrieved XHR actions but it would be redundant,
141    // so just test once.
142    other = action->other();
143    ASSERT_TRUE(other);
144    ASSERT_TRUE(other->GetInteger(activity_log_constants::kActionDomVerb,
145                                  &dom_verb));
146    ASSERT_EQ(DomActionType::XHR, dom_verb);
147
148    action = i->at(1);
149    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
150    ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
151              ActivityLogPolicy::Util::Serialize(action->args()));
152    ASSERT_EQ("http://www.google.com/api/", action->arg_url().spec());
153
154    action = i->at(2);
155    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
156    ASSERT_EQ("[\"POST\",\"/api/\"]",
157              ActivityLogPolicy::Util::Serialize(action->args()));
158    ASSERT_FALSE(action->arg_url().is_valid());
159
160    action = i->at(3);
161    ASSERT_EQ("windows.create", action->api_name());
162    ASSERT_EQ("[{\"url\":\"\\u003Carg_url>\"}]",
163              ActivityLogPolicy::Util::Serialize(action->args()));
164    ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
165  }
166
167  static void RetrieveActions_ArgUrlApiCalls(
168      scoped_ptr<std::vector<scoped_refptr<Action> > > actions) {
169    size_t api_calls_size = arraysize(kUrlApiCalls);
170    const base::DictionaryValue* other = NULL;
171    int dom_verb = -1;
172
173    ASSERT_EQ(api_calls_size, actions->size());
174
175    for (size_t i = 0; i < actions->size(); i++) {
176      scoped_refptr<Action> action = actions->at(i);
177      ASSERT_EQ(kExtensionId, action->extension_id());
178      ASSERT_EQ(Action::ACTION_DOM_ACCESS, action->action_type());
179      ASSERT_EQ(kUrlApiCalls[i], action->api_name());
180      ASSERT_EQ("[\"\\u003Carg_url>\"]",
181                ActivityLogPolicy::Util::Serialize(action->args()));
182      ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
183      other = action->other();
184      ASSERT_TRUE(other);
185      ASSERT_TRUE(
186          other->GetInteger(activity_log_constants::kActionDomVerb, &dom_verb));
187      ASSERT_EQ(DomActionType::SETTER, dom_verb);
188    }
189  }
190
191  ExtensionService* extension_service_;
192
193#if defined OS_CHROMEOS
194  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
195  chromeos::ScopedTestCrosSettings test_cros_settings_;
196  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
197#endif
198};
199
200TEST_F(ActivityLogTest, Construct) {
201  ASSERT_TRUE(GetDatabaseEnabled());
202  ASSERT_FALSE(GetWatchdogActive());
203}
204
205TEST_F(ActivityLogTest, LogAndFetchActions) {
206  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
207  scoped_ptr<base::ListValue> args(new base::ListValue());
208  ASSERT_TRUE(GetDatabaseEnabled());
209
210  // Write some API calls
211  scoped_refptr<Action> action = new Action(kExtensionId,
212                                            base::Time::Now(),
213                                            Action::ACTION_API_CALL,
214                                            "tabs.testMethod");
215  activity_log->LogAction(action);
216  action = new Action(kExtensionId,
217                      base::Time::Now(),
218                      Action::ACTION_DOM_ACCESS,
219                      "document.write");
220  action->set_page_url(GURL("http://www.google.com"));
221  activity_log->LogAction(action);
222
223  activity_log->GetFilteredActions(
224      kExtensionId,
225      Action::ACTION_ANY,
226      "",
227      "",
228      "",
229      0,
230      base::Bind(ActivityLogTest::RetrieveActions_LogAndFetchActions2));
231}
232
233TEST_F(ActivityLogTest, LogPrerender) {
234  scoped_refptr<const Extension> extension =
235      ExtensionBuilder()
236          .SetManifest(DictionaryBuilder()
237                       .Set("name", "Test extension")
238                       .Set("version", "1.0.0")
239                       .Set("manifest_version", 2))
240          .Build();
241  extension_service_->AddExtension(extension.get());
242  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
243  ASSERT_TRUE(GetDatabaseEnabled());
244  GURL url("http://www.google.com");
245
246  prerender::PrerenderManager* prerender_manager =
247      prerender::PrerenderManagerFactory::GetForProfile(
248          Profile::FromBrowserContext(profile()));
249
250  prerender_manager->OnCookieStoreLoaded();
251
252  const gfx::Size kSize(640, 480);
253  scoped_ptr<prerender::PrerenderHandle> prerender_handle(
254      prerender_manager->AddPrerenderFromLocalPredictor(
255          url,
256          web_contents()->GetController().GetDefaultSessionStorageNamespace(),
257          kSize));
258
259  const std::vector<content::WebContents*> contentses =
260      prerender_manager->GetAllPrerenderingContents();
261  ASSERT_EQ(1U, contentses.size());
262  content::WebContents *contents = contentses[0];
263  ASSERT_TRUE(prerender_manager->IsWebContentsPrerendering(contents, NULL));
264
265  ScriptExecutionObserver::ExecutingScriptsMap executing_scripts;
266  executing_scripts[extension->id()].insert("script");
267
268  static_cast<ScriptExecutionObserver*>(activity_log)
269      ->OnScriptsExecuted(contents, executing_scripts, url);
270
271  activity_log->GetFilteredActions(
272      extension->id(),
273      Action::ACTION_ANY,
274      "",
275      "",
276      "",
277      0,
278      base::Bind(ActivityLogTest::Arguments_Prerender));
279
280  prerender_manager->CancelAllPrerenders();
281}
282
283TEST_F(ActivityLogTest, ArgUrlExtraction) {
284  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
285  scoped_ptr<base::ListValue> args(new base::ListValue());
286
287  base::Time now = base::Time::Now();
288
289  // Submit a DOM API call which should have its URL extracted into the arg_url
290  // field.
291  scoped_refptr<Action> action = new Action(kExtensionId,
292                                            now,
293                                            Action::ACTION_DOM_ACCESS,
294                                            "XMLHttpRequest.open");
295  action->set_page_url(GURL("http://www.google.com/"));
296  action->mutable_args()->AppendString("POST");
297  action->mutable_args()->AppendString("http://api.google.com/");
298  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
299                                      DomActionType::METHOD);
300  activity_log->LogAction(action);
301
302  // Submit a DOM API call with a relative URL in the argument, which should be
303  // resolved relative to the page URL.
304  action = new Action(kExtensionId,
305                      now - base::TimeDelta::FromSeconds(1),
306                      Action::ACTION_DOM_ACCESS,
307                      "XMLHttpRequest.open");
308  action->set_page_url(GURL("http://www.google.com/"));
309  action->mutable_args()->AppendString("POST");
310  action->mutable_args()->AppendString("/api/");
311  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
312                                      DomActionType::METHOD);
313  activity_log->LogAction(action);
314
315  // Submit a DOM API call with a relative URL but no base page URL against
316  // which to resolve.
317  action = new Action(kExtensionId,
318                      now - base::TimeDelta::FromSeconds(2),
319                      Action::ACTION_DOM_ACCESS,
320                      "XMLHttpRequest.open");
321  action->mutable_args()->AppendString("POST");
322  action->mutable_args()->AppendString("/api/");
323  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
324                                      DomActionType::METHOD);
325  activity_log->LogAction(action);
326
327  // Submit an API call with an embedded URL.
328  action = new Action(kExtensionId,
329                      now - base::TimeDelta::FromSeconds(3),
330                      Action::ACTION_API_CALL,
331                      "windows.create");
332  action->set_args(
333      ListBuilder()
334          .Append(DictionaryBuilder().Set("url", "http://www.google.co.uk"))
335          .Build());
336  activity_log->LogAction(action);
337
338  activity_log->GetFilteredActions(
339      kExtensionId,
340      Action::ACTION_ANY,
341      "",
342      "",
343      "",
344      -1,
345      base::Bind(ActivityLogTest::RetrieveActions_ArgUrlExtraction));
346}
347
348TEST_F(ActivityLogTest, UninstalledExtension) {
349  scoped_refptr<const Extension> extension =
350      ExtensionBuilder()
351          .SetManifest(DictionaryBuilder()
352                       .Set("name", "Test extension")
353                       .Set("version", "1.0.0")
354                       .Set("manifest_version", 2))
355          .Build();
356
357  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
358  scoped_ptr<base::ListValue> args(new base::ListValue());
359  ASSERT_TRUE(GetDatabaseEnabled());
360
361  // Write some API calls
362  scoped_refptr<Action> action = new Action(extension->id(),
363                                            base::Time::Now(),
364                                            Action::ACTION_API_CALL,
365                                            "tabs.testMethod");
366  activity_log->LogAction(action);
367  action = new Action(extension->id(),
368                      base::Time::Now(),
369                      Action::ACTION_DOM_ACCESS,
370                      "document.write");
371  action->set_page_url(GURL("http://www.google.com"));
372
373  activity_log->OnExtensionUninstalled(
374      NULL, extension.get(), extensions::UNINSTALL_REASON_FOR_TESTING);
375  activity_log->GetFilteredActions(
376      extension->id(),
377      Action::ACTION_ANY,
378      "",
379      "",
380      "",
381      -1,
382      base::Bind(ActivityLogTest::RetrieveActions_LogAndFetchActions0));
383}
384
385TEST_F(ActivityLogTest, ArgUrlApiCalls) {
386  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
387  scoped_ptr<base::ListValue> args(new base::ListValue());
388  base::Time now = base::Time::Now();
389  int api_calls_size = arraysize(kUrlApiCalls);
390  scoped_refptr<Action> action;
391
392  for (int i = 0; i < api_calls_size; i++) {
393    action = new Action(kExtensionId,
394                        now - base::TimeDelta::FromSeconds(i),
395                        Action::ACTION_DOM_ACCESS,
396                        kUrlApiCalls[i]);
397    action->mutable_args()->AppendString("http://www.google.co.uk");
398    action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
399                                        DomActionType::SETTER);
400    activity_log->LogAction(action);
401  }
402
403  activity_log->GetFilteredActions(
404      kExtensionId,
405      Action::ACTION_ANY,
406      "",
407      "",
408      "",
409      -1,
410      base::Bind(ActivityLogTest::RetrieveActions_ArgUrlApiCalls));
411}
412
413}  // namespace extensions
414