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 36 namespace { 37 38 const char kExtensionId[] = "abc"; 39 40 const 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 55 namespace extensions { 56 57 class 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 200 TEST_F(ActivityLogTest, Construct) { 201 ASSERT_TRUE(GetDatabaseEnabled()); 202 ASSERT_FALSE(GetWatchdogActive()); 203 } 204 205 TEST_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 233 TEST_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 283 TEST_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 348 TEST_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 385 TEST_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