1 // Copyright (c) 2011 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 <set>
8 #include <vector>
9
10 #include "base/command_line.h"
11 #include "base/json/json_string_value_serializer.h"
12 #include "base/lazy_instance.h"
13 #include "base/logging.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/threading/thread_checker.h"
17 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
18 #include "chrome/browser/extensions/activity_log/counting_policy.h"
19 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
20 #include "chrome/browser/extensions/activity_log/uma_policy.h"
21 #include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
22 #include "chrome/browser/extensions/extension_tab_util.h"
23 #include "chrome/browser/prefs/pref_service_syncable.h"
24 #include "chrome/browser/prerender/prerender_manager.h"
25 #include "chrome/browser/prerender/prerender_manager_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/ui/browser.h"
28 #include "chrome/common/chrome_constants.h"
29 #include "chrome/common/chrome_switches.h"
30 #include "chrome/common/pref_names.h"
31 #include "content/public/browser/browser_thread.h"
32 #include "content/public/browser/web_contents.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_registry_factory.h"
35 #include "extensions/browser/extension_system.h"
36 #include "extensions/browser/extension_system_provider.h"
37 #include "extensions/browser/extensions_browser_client.h"
38 #include "extensions/common/extension.h"
39 #include "extensions/common/one_shot_event.h"
40 #include "third_party/re2/re2/re2.h"
41 #include "url/gurl.h"
42
43 namespace constants = activity_log_constants;
44
45 namespace extensions {
46
47 namespace {
48
49 using constants::kArgUrlPlaceholder;
50 using content::BrowserThread;
51
52 // If DOM API methods start with this string, we flag them as being of type
53 // DomActionType::XHR.
54 const char kDomXhrPrefix[] = "XMLHttpRequest.";
55
56 // Specifies a possible action to take to get an extracted URL in the ApiInfo
57 // structure below.
58 enum Transformation {
59 NONE,
60 DICT_LOOKUP,
61 LOOKUP_TAB_ID,
62 };
63
64 // Information about specific Chrome and DOM APIs, such as which contain
65 // arguments that should be extracted into the arg_url field of an Action.
66 struct ApiInfo {
67 // The lookup key consists of the action_type and api_name in the Action
68 // object.
69 Action::ActionType action_type;
70 const char* api_name;
71
72 // If non-negative, an index into args might contain a URL to be extracted
73 // into arg_url.
74 int arg_url_index;
75
76 // A transformation to apply to the data found at index arg_url_index in the
77 // argument list.
78 //
79 // If NONE, the data is expected to be a string which is treated as a URL.
80 //
81 // If LOOKUP_TAB_ID, the data is either an integer which is treated as a tab
82 // ID and translated (in the context of a provided Profile), or a list of tab
83 // IDs which are translated.
84 //
85 // If DICT_LOOKUP, the data is expected to be a dictionary, and
86 // arg_url_dict_path is a path (list of keys delimited by ".") where a URL
87 // string is to be found.
88 Transformation arg_url_transform;
89 const char* arg_url_dict_path;
90 };
91
92 static const ApiInfo kApiInfoTable[] = {
93 // Tabs APIs that require tab ID translation
94 {Action::ACTION_API_CALL, "tabs.connect", 0, LOOKUP_TAB_ID, NULL},
95 {Action::ACTION_API_CALL, "tabs.detectLanguage", 0, LOOKUP_TAB_ID, NULL},
96 {Action::ACTION_API_CALL, "tabs.duplicate", 0, LOOKUP_TAB_ID, NULL},
97 {Action::ACTION_API_CALL, "tabs.executeScript", 0, LOOKUP_TAB_ID, NULL},
98 {Action::ACTION_API_CALL, "tabs.get", 0, LOOKUP_TAB_ID, NULL},
99 {Action::ACTION_API_CALL, "tabs.insertCSS", 0, LOOKUP_TAB_ID, NULL},
100 {Action::ACTION_API_CALL, "tabs.move", 0, LOOKUP_TAB_ID, NULL},
101 {Action::ACTION_API_CALL, "tabs.reload", 0, LOOKUP_TAB_ID, NULL},
102 {Action::ACTION_API_CALL, "tabs.remove", 0, LOOKUP_TAB_ID, NULL},
103 {Action::ACTION_API_CALL, "tabs.sendMessage", 0, LOOKUP_TAB_ID, NULL},
104 {Action::ACTION_API_CALL, "tabs.update", 0, LOOKUP_TAB_ID, NULL},
105 {Action::ACTION_API_EVENT, "tabs.onUpdated", 0, LOOKUP_TAB_ID, NULL},
106 {Action::ACTION_API_EVENT, "tabs.onMoved", 0, LOOKUP_TAB_ID, NULL},
107 {Action::ACTION_API_EVENT, "tabs.onDetached", 0, LOOKUP_TAB_ID, NULL},
108 {Action::ACTION_API_EVENT, "tabs.onAttached", 0, LOOKUP_TAB_ID, NULL},
109 {Action::ACTION_API_EVENT, "tabs.onRemoved", 0, LOOKUP_TAB_ID, NULL},
110 {Action::ACTION_API_EVENT, "tabs.onReplaced", 0, LOOKUP_TAB_ID, NULL},
111
112 // Other APIs that accept URLs as strings
113 {Action::ACTION_API_CALL, "bookmarks.create", 0, DICT_LOOKUP, "url"},
114 {Action::ACTION_API_CALL, "bookmarks.update", 1, DICT_LOOKUP, "url"},
115 {Action::ACTION_API_CALL, "cookies.get", 0, DICT_LOOKUP, "url"},
116 {Action::ACTION_API_CALL, "cookies.getAll", 0, DICT_LOOKUP, "url"},
117 {Action::ACTION_API_CALL, "cookies.remove", 0, DICT_LOOKUP, "url"},
118 {Action::ACTION_API_CALL, "cookies.set", 0, DICT_LOOKUP, "url"},
119 {Action::ACTION_API_CALL, "downloads.download", 0, DICT_LOOKUP, "url"},
120 {Action::ACTION_API_CALL, "history.addUrl", 0, DICT_LOOKUP, "url"},
121 {Action::ACTION_API_CALL, "history.deleteUrl", 0, DICT_LOOKUP, "url"},
122 {Action::ACTION_API_CALL, "history.getVisits", 0, DICT_LOOKUP, "url"},
123 {Action::ACTION_API_CALL, "webstore.install", 0, NONE, NULL},
124 {Action::ACTION_API_CALL, "windows.create", 0, DICT_LOOKUP, "url"},
125 {Action::ACTION_DOM_ACCESS, "Document.location", 0, NONE, NULL},
126 {Action::ACTION_DOM_ACCESS, "HTMLAnchorElement.href", 0, NONE, NULL},
127 {Action::ACTION_DOM_ACCESS, "HTMLButtonElement.formAction", 0, NONE, NULL},
128 {Action::ACTION_DOM_ACCESS, "HTMLEmbedElement.src", 0, NONE, NULL},
129 {Action::ACTION_DOM_ACCESS, "HTMLFormElement.action", 0, NONE, NULL},
130 {Action::ACTION_DOM_ACCESS, "HTMLFrameElement.src", 0, NONE, NULL},
131 {Action::ACTION_DOM_ACCESS, "HTMLHtmlElement.manifest", 0, NONE, NULL},
132 {Action::ACTION_DOM_ACCESS, "HTMLIFrameElement.src", 0, NONE, NULL},
133 {Action::ACTION_DOM_ACCESS, "HTMLImageElement.longDesc", 0, NONE, NULL},
134 {Action::ACTION_DOM_ACCESS, "HTMLImageElement.src", 0, NONE, NULL},
135 {Action::ACTION_DOM_ACCESS, "HTMLImageElement.lowsrc", 0, NONE, NULL},
136 {Action::ACTION_DOM_ACCESS, "HTMLInputElement.formAction", 0, NONE, NULL},
137 {Action::ACTION_DOM_ACCESS, "HTMLInputElement.src", 0, NONE, NULL},
138 {Action::ACTION_DOM_ACCESS, "HTMLLinkElement.href", 0, NONE, NULL},
139 {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.src", 0, NONE, NULL},
140 {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.currentSrc", 0, NONE, NULL},
141 {Action::ACTION_DOM_ACCESS, "HTMLModElement.cite", 0, NONE, NULL},
142 {Action::ACTION_DOM_ACCESS, "HTMLObjectElement.data", 0, NONE, NULL},
143 {Action::ACTION_DOM_ACCESS, "HTMLQuoteElement.cite", 0, NONE, NULL},
144 {Action::ACTION_DOM_ACCESS, "HTMLScriptElement.src", 0, NONE, NULL},
145 {Action::ACTION_DOM_ACCESS, "HTMLSourceElement.src", 0, NONE, NULL},
146 {Action::ACTION_DOM_ACCESS, "HTMLTrackElement.src", 0, NONE, NULL},
147 {Action::ACTION_DOM_ACCESS, "HTMLVideoElement.poster", 0, NONE, NULL},
148 {Action::ACTION_DOM_ACCESS, "Location.assign", 0, NONE, NULL},
149 {Action::ACTION_DOM_ACCESS, "Location.replace", 0, NONE, NULL},
150 {Action::ACTION_DOM_ACCESS, "Window.location", 0, NONE, NULL},
151 {Action::ACTION_DOM_ACCESS, "XMLHttpRequest.open", 1, NONE, NULL}};
152
153 // A singleton class which provides lookups into the kApiInfoTable data
154 // structure. It inserts all data into a map on first lookup.
155 class ApiInfoDatabase {
156 public:
GetInstance()157 static ApiInfoDatabase* GetInstance() {
158 return Singleton<ApiInfoDatabase>::get();
159 }
160
161 // Retrieves an ApiInfo record for the given Action type. Returns either a
162 // pointer to the record, or NULL if no such record was found.
Lookup(Action::ActionType action_type,const std::string & api_name) const163 const ApiInfo* Lookup(Action::ActionType action_type,
164 const std::string& api_name) const {
165 std::map<std::string, const ApiInfo*>::const_iterator i =
166 api_database_.find(api_name);
167 if (i == api_database_.end())
168 return NULL;
169 if (i->second->action_type != action_type)
170 return NULL;
171 return i->second;
172 }
173
174 private:
ApiInfoDatabase()175 ApiInfoDatabase() {
176 for (size_t i = 0; i < arraysize(kApiInfoTable); i++) {
177 const ApiInfo* info = &kApiInfoTable[i];
178 api_database_[info->api_name] = info;
179 }
180 }
~ApiInfoDatabase()181 virtual ~ApiInfoDatabase() {}
182
183 // The map is keyed by API name only, since API names aren't be repeated
184 // across multiple action types in kApiInfoTable. However, the action type
185 // should still be checked before returning a positive match.
186 std::map<std::string, const ApiInfo*> api_database_;
187
188 friend struct DefaultSingletonTraits<ApiInfoDatabase>;
189 DISALLOW_COPY_AND_ASSIGN(ApiInfoDatabase);
190 };
191
192 // Gets the URL for a given tab ID. Helper method for ExtractUrls. Returns
193 // true if able to perform the lookup. The URL is stored to *url, and
194 // *is_incognito is set to indicate whether the URL is for an incognito tab.
GetUrlForTabId(int tab_id,Profile * profile,GURL * url,bool * is_incognito)195 bool GetUrlForTabId(int tab_id,
196 Profile* profile,
197 GURL* url,
198 bool* is_incognito) {
199 content::WebContents* contents = NULL;
200 Browser* browser = NULL;
201 bool found = ExtensionTabUtil::GetTabById(
202 tab_id,
203 profile,
204 true, // Search incognito tabs, too.
205 &browser,
206 NULL,
207 &contents,
208 NULL);
209
210 if (found) {
211 *url = contents->GetURL();
212 *is_incognito = browser->profile()->IsOffTheRecord();
213 return true;
214 } else {
215 return false;
216 }
217 }
218
219 // Resolves an argument URL relative to a base page URL. If the page URL is
220 // not valid, then only absolute argument URLs are supported.
ResolveUrl(const GURL & base,const std::string & arg,GURL * arg_out)221 bool ResolveUrl(const GURL& base, const std::string& arg, GURL* arg_out) {
222 if (base.is_valid())
223 *arg_out = base.Resolve(arg);
224 else
225 *arg_out = GURL(arg);
226
227 return arg_out->is_valid();
228 }
229
230 // Performs processing of the Action object to extract URLs from the argument
231 // list and translate tab IDs to URLs, according to the API call metadata in
232 // kApiInfoTable. Mutates the Action object in place. There is a small chance
233 // that the tab id->URL translation could be wrong, if the tab has already been
234 // navigated by the time of invocation.
235 //
236 // Any extracted URL is stored into the arg_url field of the action, and the
237 // URL in the argument list is replaced with the marker value "<arg_url>". For
238 // APIs that take a list of tab IDs, extracts the first valid URL into arg_url
239 // and overwrites the other tab IDs in the argument list with the translated
240 // URL.
ExtractUrls(scoped_refptr<Action> action,Profile * profile)241 void ExtractUrls(scoped_refptr<Action> action, Profile* profile) {
242 const ApiInfo* api_info = ApiInfoDatabase::GetInstance()->Lookup(
243 action->action_type(), action->api_name());
244 if (api_info == NULL)
245 return;
246
247 int url_index = api_info->arg_url_index;
248
249 if (!action->args() || url_index < 0 ||
250 static_cast<size_t>(url_index) >= action->args()->GetSize())
251 return;
252
253 // Do not overwrite an existing arg_url value in the Action, so that callers
254 // have the option of doing custom arg_url extraction.
255 if (action->arg_url().is_valid())
256 return;
257
258 GURL arg_url;
259 bool arg_incognito = action->page_incognito();
260
261 switch (api_info->arg_url_transform) {
262 case NONE: {
263 // No translation needed; just extract the URL directly from a raw string
264 // or from a dictionary. Succeeds if we can find a string in the
265 // argument list and that the string resolves to a valid URL.
266 std::string url_string;
267 if (action->args()->GetString(url_index, &url_string) &&
268 ResolveUrl(action->page_url(), url_string, &arg_url)) {
269 action->mutable_args()->Set(url_index,
270 new base::StringValue(kArgUrlPlaceholder));
271 }
272 break;
273 }
274
275 case DICT_LOOKUP: {
276 CHECK(api_info->arg_url_dict_path);
277 // Look up the URL from a dictionary at the specified location. Succeeds
278 // if we can find a dictionary in the argument list, the dictionary
279 // contains the specified key, and the corresponding value resolves to a
280 // valid URL.
281 base::DictionaryValue* dict = NULL;
282 std::string url_string;
283 if (action->mutable_args()->GetDictionary(url_index, &dict) &&
284 dict->GetString(api_info->arg_url_dict_path, &url_string) &&
285 ResolveUrl(action->page_url(), url_string, &arg_url)) {
286 dict->SetString(api_info->arg_url_dict_path, kArgUrlPlaceholder);
287 }
288 break;
289 }
290
291 case LOOKUP_TAB_ID: {
292 // Translation of tab IDs to URLs has been requested. There are two
293 // cases to consider: either a single integer or a list of integers (when
294 // multiple tabs are manipulated).
295 int tab_id;
296 base::ListValue* tab_list = NULL;
297 if (action->args()->GetInteger(url_index, &tab_id)) {
298 // Single tab ID to translate.
299 GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito);
300 if (arg_url.is_valid()) {
301 action->mutable_args()->Set(
302 url_index, new base::StringValue(kArgUrlPlaceholder));
303 }
304 } else if (action->mutable_args()->GetList(url_index, &tab_list)) {
305 // A list of possible IDs to translate. Work through in reverse order
306 // so the last one translated is left in arg_url.
307 int extracted_index = -1; // Which list item is copied to arg_url?
308 for (int i = tab_list->GetSize() - 1; i >= 0; --i) {
309 if (tab_list->GetInteger(i, &tab_id) &&
310 GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito)) {
311 if (!arg_incognito)
312 tab_list->Set(i, new base::StringValue(arg_url.spec()));
313 extracted_index = i;
314 }
315 }
316 if (extracted_index >= 0) {
317 tab_list->Set(
318 extracted_index, new base::StringValue(kArgUrlPlaceholder));
319 }
320 }
321 break;
322 }
323
324 default:
325 NOTREACHED();
326 }
327
328 if (arg_url.is_valid()) {
329 action->set_arg_incognito(arg_incognito);
330 action->set_arg_url(arg_url);
331 }
332 }
333
334 } // namespace
335
336 // SET THINGS UP. --------------------------------------------------------------
337
338 static base::LazyInstance<BrowserContextKeyedAPIFactory<ActivityLog> >
339 g_factory = LAZY_INSTANCE_INITIALIZER;
340
GetFactoryInstance()341 BrowserContextKeyedAPIFactory<ActivityLog>* ActivityLog::GetFactoryInstance() {
342 return g_factory.Pointer();
343 }
344
345 // static
GetInstance(content::BrowserContext * context)346 ActivityLog* ActivityLog::GetInstance(content::BrowserContext* context) {
347 return ActivityLog::GetFactoryInstance()->Get(
348 Profile::FromBrowserContext(context));
349 }
350
351 // Use GetInstance instead of directly creating an ActivityLog.
ActivityLog(content::BrowserContext * context)352 ActivityLog::ActivityLog(content::BrowserContext* context)
353 : database_policy_(NULL),
354 database_policy_type_(ActivityLogPolicy::POLICY_INVALID),
355 uma_policy_(NULL),
356 profile_(Profile::FromBrowserContext(context)),
357 db_enabled_(false),
358 testing_mode_(false),
359 has_threads_(true),
360 extension_registry_observer_(this),
361 watchdog_apps_active_(0) {
362 // This controls whether logging statements are printed & which policy is set.
363 testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
364 switches::kEnableExtensionActivityLogTesting);
365
366 // Check if the watchdog extension is previously installed and active.
367 // It was originally a boolean, but we've had to move to an integer. Handle
368 // the legacy case.
369 // TODO(felt): In M34, remove the legacy code & old pref.
370 if (profile_->GetPrefs()->GetBoolean(prefs::kWatchdogExtensionActiveOld))
371 profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, 1);
372 watchdog_apps_active_ =
373 profile_->GetPrefs()->GetInteger(prefs::kWatchdogExtensionActive);
374
375 observers_ = new ObserverListThreadSafe<Observer>;
376
377 // Check that the right threads exist for logging to the database.
378 // If not, we shouldn't try to do things that require them.
379 if (!BrowserThread::IsMessageLoopValid(BrowserThread::DB) ||
380 !BrowserThread::IsMessageLoopValid(BrowserThread::FILE) ||
381 !BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
382 has_threads_ = false;
383 }
384
385 db_enabled_ = has_threads_
386 && (CommandLine::ForCurrentProcess()->
387 HasSwitch(switches::kEnableExtensionActivityLogging)
388 || watchdog_apps_active_);
389
390 ExtensionSystem::Get(profile_)->ready().Post(
391 FROM_HERE,
392 base::Bind(&ActivityLog::StartObserving, base::Unretained(this)));
393
394 if (!profile_->IsOffTheRecord())
395 uma_policy_ = new UmaPolicy(profile_);
396
397 ChooseDatabasePolicy();
398 }
399
SetDatabasePolicy(ActivityLogPolicy::PolicyType policy_type)400 void ActivityLog::SetDatabasePolicy(
401 ActivityLogPolicy::PolicyType policy_type) {
402 if (database_policy_type_ == policy_type)
403 return;
404 if (!IsDatabaseEnabled() && !IsWatchdogAppActive())
405 return;
406
407 // Deleting the old policy takes place asynchronously, on the database
408 // thread. Initializing a new policy below similarly happens
409 // asynchronously. Since the two operations are both queued for the
410 // database, the queue ordering should ensure that the deletion completes
411 // before database initialization occurs.
412 //
413 // However, changing policies at runtime is still not recommended, and
414 // likely only should be done for unit tests.
415 if (database_policy_)
416 database_policy_->Close();
417
418 switch (policy_type) {
419 case ActivityLogPolicy::POLICY_FULLSTREAM:
420 database_policy_ = new FullStreamUIPolicy(profile_);
421 break;
422 case ActivityLogPolicy::POLICY_COUNTS:
423 database_policy_ = new CountingPolicy(profile_);
424 break;
425 default:
426 NOTREACHED();
427 }
428 database_policy_->Init();
429 database_policy_type_ = policy_type;
430 }
431
~ActivityLog()432 ActivityLog::~ActivityLog() {
433 if (uma_policy_)
434 uma_policy_->Close();
435 if (database_policy_)
436 database_policy_->Close();
437 }
438
439 // MAINTAIN STATUS. ------------------------------------------------------------
440
StartObserving()441 void ActivityLog::StartObserving() {
442 extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
443 }
444
ChooseDatabasePolicy()445 void ActivityLog::ChooseDatabasePolicy() {
446 if (!(IsDatabaseEnabled() || IsWatchdogAppActive()))
447 return;
448 if (testing_mode_)
449 SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
450 else
451 SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
452 }
453
IsDatabaseEnabled()454 bool ActivityLog::IsDatabaseEnabled() {
455 // Make sure we are not enabled when there are no threads.
456 DCHECK(has_threads_ || !db_enabled_);
457 return db_enabled_;
458 }
459
IsWatchdogAppActive()460 bool ActivityLog::IsWatchdogAppActive() {
461 return (watchdog_apps_active_ > 0);
462 }
463
SetWatchdogAppActiveForTesting(bool active)464 void ActivityLog::SetWatchdogAppActiveForTesting(bool active) {
465 watchdog_apps_active_ = active ? 1 : 0;
466 }
467
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)468 void ActivityLog::OnExtensionLoaded(content::BrowserContext* browser_context,
469 const Extension* extension) {
470 if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
471 if (has_threads_)
472 db_enabled_ = true;
473 watchdog_apps_active_++;
474 profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
475 watchdog_apps_active_);
476 if (watchdog_apps_active_ == 1)
477 ChooseDatabasePolicy();
478 }
479
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)480 void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context,
481 const Extension* extension,
482 UnloadedExtensionInfo::Reason reason) {
483 if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
484 watchdog_apps_active_--;
485 profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
486 watchdog_apps_active_);
487 if (watchdog_apps_active_ == 0 &&
488 !CommandLine::ForCurrentProcess()->HasSwitch(
489 switches::kEnableExtensionActivityLogging)) {
490 db_enabled_ = false;
491 }
492 }
493
494 // OnExtensionUnloaded will also be called right before this.
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension,extensions::UninstallReason reason)495 void ActivityLog::OnExtensionUninstalled(
496 content::BrowserContext* browser_context,
497 const Extension* extension,
498 extensions::UninstallReason reason) {
499 if (ActivityLogAPI::IsExtensionWhitelisted(extension->id()) &&
500 !CommandLine::ForCurrentProcess()->HasSwitch(
501 switches::kEnableExtensionActivityLogging) &&
502 watchdog_apps_active_ == 0) {
503 DeleteDatabase();
504 } else if (database_policy_) {
505 database_policy_->RemoveExtensionData(extension->id());
506 }
507 }
508
AddObserver(ActivityLog::Observer * observer)509 void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
510 observers_->AddObserver(observer);
511 }
512
RemoveObserver(ActivityLog::Observer * observer)513 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
514 observers_->RemoveObserver(observer);
515 }
516
517 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)518 void ActivityLog::RegisterProfilePrefs(
519 user_prefs::PrefRegistrySyncable* registry) {
520 registry->RegisterIntegerPref(
521 prefs::kWatchdogExtensionActive,
522 false,
523 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
524 registry->RegisterBooleanPref(
525 prefs::kWatchdogExtensionActiveOld,
526 false,
527 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
528 }
529
530 // LOG ACTIONS. ----------------------------------------------------------------
531
LogAction(scoped_refptr<Action> action)532 void ActivityLog::LogAction(scoped_refptr<Action> action) {
533 if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
534 return;
535
536 // Perform some preprocessing of the Action data: convert tab IDs to URLs and
537 // mask out incognito URLs if appropriate.
538 ExtractUrls(action, profile_);
539
540 // Mark DOM XHR requests as such, for easier processing later.
541 if (action->action_type() == Action::ACTION_DOM_ACCESS &&
542 StartsWithASCII(action->api_name(), kDomXhrPrefix, true) &&
543 action->other()) {
544 base::DictionaryValue* other = action->mutable_other();
545 int dom_verb = -1;
546 if (other->GetInteger(constants::kActionDomVerb, &dom_verb) &&
547 dom_verb == DomActionType::METHOD) {
548 other->SetInteger(constants::kActionDomVerb, DomActionType::XHR);
549 }
550 }
551
552 if (uma_policy_)
553 uma_policy_->ProcessAction(action);
554 if (IsDatabaseEnabled() && database_policy_)
555 database_policy_->ProcessAction(action);
556 if (IsWatchdogAppActive())
557 observers_->Notify(&Observer::OnExtensionActivity, action);
558 if (testing_mode_)
559 VLOG(1) << action->PrintForDebug();
560 }
561
OnScriptsExecuted(const content::WebContents * web_contents,const ExecutingScriptsMap & extension_ids,const GURL & on_url)562 void ActivityLog::OnScriptsExecuted(
563 const content::WebContents* web_contents,
564 const ExecutingScriptsMap& extension_ids,
565 const GURL& on_url) {
566 Profile* profile =
567 Profile::FromBrowserContext(web_contents->GetBrowserContext());
568 ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
569 for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
570 it != extension_ids.end(); ++it) {
571 const Extension* extension =
572 registry->GetExtensionById(it->first, ExtensionRegistry::ENABLED);
573 if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
574 continue;
575
576 // If OnScriptsExecuted is fired because of tabs.executeScript, the list
577 // of content scripts will be empty. We don't want to log it because
578 // the call to tabs.executeScript will have already been logged anyway.
579 if (!it->second.empty()) {
580 scoped_refptr<Action> action;
581 action = new Action(extension->id(),
582 base::Time::Now(),
583 Action::ACTION_CONTENT_SCRIPT,
584 ""); // no API call here
585 action->set_page_url(on_url);
586 action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
587 action->set_page_incognito(
588 web_contents->GetBrowserContext()->IsOffTheRecord());
589
590 const prerender::PrerenderManager* prerender_manager =
591 prerender::PrerenderManagerFactory::GetForProfile(profile);
592 if (prerender_manager &&
593 prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
594 action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
595 for (std::set<std::string>::const_iterator it2 = it->second.begin();
596 it2 != it->second.end();
597 ++it2) {
598 action->mutable_args()->AppendString(*it2);
599 }
600 LogAction(action);
601 }
602 }
603 }
604
OnApiEventDispatched(const std::string & extension_id,const std::string & event_name,scoped_ptr<base::ListValue> event_args)605 void ActivityLog::OnApiEventDispatched(const std::string& extension_id,
606 const std::string& event_name,
607 scoped_ptr<base::ListValue> event_args) {
608 DCHECK_CURRENTLY_ON(BrowserThread::UI);
609 scoped_refptr<Action> action = new Action(extension_id,
610 base::Time::Now(),
611 Action::ACTION_API_EVENT,
612 event_name);
613 action->set_args(event_args.Pass());
614 LogAction(action);
615 }
616
OnApiFunctionCalled(const std::string & extension_id,const std::string & api_name,scoped_ptr<base::ListValue> args)617 void ActivityLog::OnApiFunctionCalled(const std::string& extension_id,
618 const std::string& api_name,
619 scoped_ptr<base::ListValue> args) {
620 DCHECK_CURRENTLY_ON(BrowserThread::UI);
621 scoped_refptr<Action> action = new Action(extension_id,
622 base::Time::Now(),
623 Action::ACTION_API_CALL,
624 api_name);
625 action->set_args(args.Pass());
626 LogAction(action);
627 }
628
629 // LOOKUP ACTIONS. -------------------------------------------------------------
630
GetFilteredActions(const std::string & extension_id,const Action::ActionType type,const std::string & api_name,const std::string & page_url,const std::string & arg_url,const int daysAgo,const base::Callback<void (scoped_ptr<std::vector<scoped_refptr<Action>>>)> & callback)631 void ActivityLog::GetFilteredActions(
632 const std::string& extension_id,
633 const Action::ActionType type,
634 const std::string& api_name,
635 const std::string& page_url,
636 const std::string& arg_url,
637 const int daysAgo,
638 const base::Callback
639 <void(scoped_ptr<std::vector<scoped_refptr<Action> > >)>& callback) {
640 if (database_policy_) {
641 database_policy_->ReadFilteredData(
642 extension_id, type, api_name, page_url, arg_url, daysAgo, callback);
643 }
644 }
645
646 // DELETE ACTIONS. -------------------------------------------------------------
647
RemoveActions(const std::vector<int64> & action_ids)648 void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) {
649 if (!database_policy_)
650 return;
651 database_policy_->RemoveActions(action_ids);
652 }
653
RemoveURLs(const std::vector<GURL> & restrict_urls)654 void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) {
655 if (!database_policy_)
656 return;
657 database_policy_->RemoveURLs(restrict_urls);
658 }
659
RemoveURLs(const std::set<GURL> & restrict_urls)660 void ActivityLog::RemoveURLs(const std::set<GURL>& restrict_urls) {
661 if (!database_policy_)
662 return;
663
664 std::vector<GURL> urls;
665 for (std::set<GURL>::const_iterator it = restrict_urls.begin();
666 it != restrict_urls.end(); ++it) {
667 urls.push_back(*it);
668 }
669 database_policy_->RemoveURLs(urls);
670 }
671
RemoveURL(const GURL & url)672 void ActivityLog::RemoveURL(const GURL& url) {
673 if (url.is_empty())
674 return;
675 std::vector<GURL> urls;
676 urls.push_back(url);
677 RemoveURLs(urls);
678 }
679
DeleteDatabase()680 void ActivityLog::DeleteDatabase() {
681 if (!database_policy_)
682 return;
683 database_policy_->DeleteDatabase();
684 }
685
686 template <>
DeclareFactoryDependencies()687 void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() {
688 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
689 DependsOn(ExtensionRegistryFactory::GetInstance());
690 }
691
692 } // namespace extensions
693