activity_log.cc revision ba5b9a6411cb1792fd21f0a078d7a25cd1ceec16
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/logging.h"
13#include "base/strings/string_util.h"
14#include "base/strings/utf_string_conversions.h"
15#include "base/threading/thread_checker.h"
16#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
17#include "chrome/browser/extensions/activity_log/counting_policy.h"
18#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
19#include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
20#include "chrome/browser/extensions/extension_service.h"
21#include "chrome/browser/extensions/extension_system.h"
22#include "chrome/browser/extensions/extension_system_factory.h"
23#include "chrome/browser/extensions/extension_tab_util.h"
24#include "chrome/browser/extensions/install_tracker_factory.h"
25#include "chrome/browser/prerender/prerender_manager.h"
26#include "chrome/browser/prerender/prerender_manager_factory.h"
27#include "chrome/browser/profiles/incognito_helpers.h"
28#include "chrome/browser/ui/browser.h"
29#include "chrome/common/chrome_constants.h"
30#include "chrome/common/chrome_switches.h"
31#include "chrome/common/extensions/extension.h"
32#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
33#include "content/public/browser/web_contents.h"
34#include "third_party/re2/re2/re2.h"
35#include "url/gurl.h"
36
37namespace constants = activity_log_constants;
38
39namespace {
40
41// Concatenate arguments.
42std::string MakeArgList(const base::ListValue* args) {
43  std::string call_signature;
44  base::ListValue::const_iterator it = args->begin();
45  for (; it != args->end(); ++it) {
46    std::string arg;
47    JSONStringValueSerializer serializer(&arg);
48    if (serializer.SerializeAndOmitBinaryValues(**it)) {
49      if (it != args->begin())
50        call_signature += ", ";
51      call_signature += arg;
52    }
53  }
54  return call_signature;
55}
56
57// This is a hack for AL callers who don't have access to a profile object
58// when deciding whether or not to do the work required for logging. The state
59// is accessed through the static ActivityLog::IsLogEnabledOnAnyProfile()
60// method. It returns true if --enable-extension-activity-logging is set on the
61// command line OR *ANY* profile has the activity log whitelisted extension
62// installed.
63class LogIsEnabled {
64 public:
65  LogIsEnabled() : any_profile_enabled_(false) {
66    ComputeIsFlagEnabled();
67  }
68
69  void ComputeIsFlagEnabled() {
70    base::AutoLock auto_lock(lock_);
71    cmd_line_enabled_ = CommandLine::ForCurrentProcess()->
72        HasSwitch(switches::kEnableExtensionActivityLogging);
73  }
74
75  static LogIsEnabled* GetInstance() {
76    return Singleton<LogIsEnabled>::get();
77  }
78
79  bool IsEnabled() {
80    base::AutoLock auto_lock(lock_);
81    return cmd_line_enabled_ || any_profile_enabled_;
82  }
83
84  void SetProfileEnabled(bool any_profile_enabled) {
85    base::AutoLock auto_lock(lock_);
86    any_profile_enabled_ = any_profile_enabled;
87  }
88
89 private:
90  base::Lock lock_;
91  bool any_profile_enabled_;
92  bool cmd_line_enabled_;
93};
94
95// Gets the URL for a given tab ID.  Helper method for LookupTabId.  Returns
96// true if able to perform the lookup.  The URL is stored to *url, and
97// *is_incognito is set to indicate whether the URL is for an incognito tab.
98bool GetUrlForTabId(int tab_id,
99                    Profile* profile,
100                    GURL* url,
101                    bool* is_incognito) {
102  content::WebContents* contents = NULL;
103  Browser* browser = NULL;
104  bool found = ExtensionTabUtil::GetTabById(tab_id,
105                                            profile,
106                                            true,  // search incognito tabs too
107                                            &browser,
108                                            NULL,
109                                            &contents,
110                                            NULL);
111  if (found) {
112    *url = contents->GetURL();
113    *is_incognito = browser->profile()->IsOffTheRecord();
114    return true;
115  } else {
116    return false;
117  }
118}
119
120// Translate tab IDs to URLs in tabs API calls.  Mutates the Action object in
121// place.  There is a small chance that the URL translation could be wrong, if
122// the tab has already been navigated by the time of invocation.
123//
124// If a single tab ID is translated to a URL, the URL is stored into arg_url
125// where it can more easily be searched for in the database.  For APIs that
126// take a list of tab IDs, replace each tab ID with the URL in the argument
127// list; we can only extract a single URL into arg_url so arbitrarily pull out
128// the first one.
129void LookupTabIds(scoped_refptr<extensions::Action> action, Profile* profile) {
130  const std::string& api_call = action->api_name();
131  if (api_call == "tabs.get" ||                 // api calls, ID as int
132      api_call == "tabs.connect" ||
133      api_call == "tabs.sendMessage" ||
134      api_call == "tabs.duplicate" ||
135      api_call == "tabs.update" ||
136      api_call == "tabs.reload" ||
137      api_call == "tabs.detectLanguage" ||
138      api_call == "tabs.executeScript" ||
139      api_call == "tabs.insertCSS" ||
140      api_call == "tabs.move" ||                // api calls, IDs in array
141      api_call == "tabs.remove" ||
142      api_call == "tabs.onUpdated" ||           // events, ID as int
143      api_call == "tabs.onMoved" ||
144      api_call == "tabs.onDetached" ||
145      api_call == "tabs.onAttached" ||
146      api_call == "tabs.onRemoved" ||
147      api_call == "tabs.onReplaced") {
148    int tab_id;
149    base::ListValue* id_list;
150    base::ListValue* args = action->mutable_args();
151    if (args->GetInteger(0, &tab_id)) {
152      // Single tab ID to translate.
153      GURL url;
154      bool is_incognito;
155      if (GetUrlForTabId(tab_id, profile, &url, &is_incognito)) {
156        action->set_arg_url(url);
157        action->set_arg_incognito(is_incognito);
158      }
159    } else if ((api_call == "tabs.move" || api_call == "tabs.remove") &&
160               args->GetList(0, &id_list)) {
161      // Array of tab IDs to translate.
162      for (int i = 0; i < static_cast<int>(id_list->GetSize()); ++i) {
163        if (id_list->GetInteger(i, &tab_id)) {
164          GURL url;
165          bool is_incognito;
166          if (GetUrlForTabId(tab_id, profile, &url, &is_incognito) &&
167              !is_incognito) {
168            id_list->Set(i, new base::StringValue(url.spec()));
169            if (i == 0) {
170              action->set_arg_url(url);
171              action->set_arg_incognito(is_incognito);
172            }
173          }
174        } else {
175          LOG(ERROR) << "The tab ID array is malformed at index " << i;
176        }
177      }
178    }
179  }
180}
181
182}  // namespace
183
184namespace extensions {
185
186// static
187bool ActivityLog::IsLogEnabledOnAnyProfile() {
188  return LogIsEnabled::GetInstance()->IsEnabled();
189}
190
191// static
192void ActivityLog::RecomputeLoggingIsEnabled(bool profile_enabled) {
193  LogIsEnabled::GetInstance()->ComputeIsFlagEnabled();
194  LogIsEnabled::GetInstance()->SetProfileEnabled(profile_enabled);
195}
196
197// ActivityLogFactory
198
199ActivityLogFactory* ActivityLogFactory::GetInstance() {
200  return Singleton<ActivityLogFactory>::get();
201}
202
203BrowserContextKeyedService* ActivityLogFactory::BuildServiceInstanceFor(
204    content::BrowserContext* profile) const {
205  return new ActivityLog(static_cast<Profile*>(profile));
206}
207
208content::BrowserContext* ActivityLogFactory::GetBrowserContextToUse(
209    content::BrowserContext* context) const {
210  return chrome::GetBrowserContextRedirectedInIncognito(context);
211}
212
213ActivityLogFactory::ActivityLogFactory()
214    : BrowserContextKeyedServiceFactory(
215        "ActivityLog",
216        BrowserContextDependencyManager::GetInstance()) {
217  DependsOn(ExtensionSystemFactory::GetInstance());
218  DependsOn(InstallTrackerFactory::GetInstance());
219}
220
221ActivityLogFactory::~ActivityLogFactory() {
222}
223
224// ActivityLog
225
226void ActivityLog::SetDefaultPolicy(ActivityLogPolicy::PolicyType policy_type) {
227  // Can't use IsLogEnabled() here because this is called from inside Init.
228  if (policy_type != policy_type_ && enabled_) {
229    // Deleting the old policy takes place asynchronously, on the database
230    // thread.  Initializing a new policy below similarly happens
231    // asynchronously.  Since the two operations are both queued for the
232    // database, the queue ordering should ensure that the deletion completes
233    // before database initialization occurs.
234    //
235    // However, changing policies at runtime is still not recommended, and
236    // likely only should be done for unit tests.
237    if (policy_)
238      policy_->Close();
239
240    switch (policy_type) {
241      case ActivityLogPolicy::POLICY_FULLSTREAM:
242        policy_ = new FullStreamUIPolicy(profile_);
243        break;
244      case ActivityLogPolicy::POLICY_COUNTS:
245        policy_ = new CountingPolicy(profile_);
246        break;
247      default:
248        NOTREACHED();
249    }
250    policy_type_ = policy_type;
251  }
252}
253
254// Use GetInstance instead of directly creating an ActivityLog.
255ActivityLog::ActivityLog(Profile* profile)
256    : policy_(NULL),
257      policy_type_(ActivityLogPolicy::POLICY_INVALID),
258      profile_(profile),
259      enabled_(false),
260      initialized_(false),
261      policy_chosen_(false),
262      testing_mode_(false),
263      has_threads_(true),
264      tracker_(NULL) {
265  // This controls whether logging statements are printed, which policy is set,
266  // etc.
267  testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
268    switches::kEnableExtensionActivityLogTesting);
269
270  // Check that the right threads exist. If not, we shouldn't try to do things
271  // that require them.
272  if (!BrowserThread::IsMessageLoopValid(BrowserThread::DB) ||
273      !BrowserThread::IsMessageLoopValid(BrowserThread::FILE) ||
274      !BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
275    LOG(ERROR) << "Missing threads, disabling Activity Logging!";
276    has_threads_ = false;
277  } else {
278    enabled_ = IsLogEnabledOnAnyProfile();
279    ExtensionSystem::Get(profile_)->ready().Post(
280      FROM_HERE, base::Bind(&ActivityLog::Init, base::Unretained(this)));
281  }
282
283  observers_ = new ObserverListThreadSafe<Observer>;
284}
285
286void ActivityLog::Init() {
287  DCHECK(has_threads_);
288  DCHECK(!initialized_);
289  const Extension* whitelisted_extension = ExtensionSystem::Get(profile_)->
290      extension_service()->GetExtensionById(kActivityLogExtensionId, false);
291  if (whitelisted_extension) {
292    enabled_ = true;
293    LogIsEnabled::GetInstance()->SetProfileEnabled(true);
294  }
295  tracker_ = InstallTrackerFactory::GetForProfile(profile_);
296  tracker_->AddObserver(this);
297  ChooseDefaultPolicy();
298  initialized_ = true;
299}
300
301void ActivityLog::ChooseDefaultPolicy() {
302  if (policy_chosen_ || !enabled_) return;
303  if (testing_mode_)
304    SetDefaultPolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
305  else
306    SetDefaultPolicy(ActivityLogPolicy::POLICY_COUNTS);
307}
308
309void ActivityLog::Shutdown() {
310  if (tracker_) tracker_->RemoveObserver(this);
311}
312
313ActivityLog::~ActivityLog() {
314  if (policy_)
315    policy_->Close();
316}
317
318bool ActivityLog::IsLogEnabled() {
319  if (!has_threads_ || !initialized_) return false;
320  return enabled_;
321}
322
323void ActivityLog::OnExtensionLoaded(const Extension* extension) {
324  if (extension->id() != kActivityLogExtensionId) return;
325  enabled_ = true;
326  LogIsEnabled::GetInstance()->SetProfileEnabled(true);
327  ChooseDefaultPolicy();
328}
329
330void ActivityLog::OnExtensionUnloaded(const Extension* extension) {
331  if (extension->id() != kActivityLogExtensionId) return;
332  if (!CommandLine::ForCurrentProcess()->HasSwitch(
333      switches::kEnableExtensionActivityLogging))
334    enabled_ = false;
335}
336
337// static
338ActivityLog* ActivityLog::GetInstance(Profile* profile) {
339  return ActivityLogFactory::GetForProfile(profile);
340}
341
342void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
343  observers_->AddObserver(observer);
344}
345
346void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
347  observers_->RemoveObserver(observer);
348}
349
350void ActivityLog::LogAction(scoped_refptr<Action> action) {
351  if (!IsLogEnabled() ||
352      ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
353    return;
354
355  // Perform some preprocessing of the Action data: convert tab IDs to URLs and
356  // mask out incognito URLs if appropriate.
357  if ((action->action_type() == Action::ACTION_API_CALL ||
358       action->action_type() == Action::ACTION_API_EVENT) &&
359      StartsWithASCII(action->api_name(), "tabs.", true)) {
360    LookupTabIds(action, profile_);
361  }
362
363  // TODO(mvrable): Add any necessary processing of incognito URLs here, for
364  // crbug.com/253368
365
366  if (policy_)
367    policy_->ProcessAction(action);
368  observers_->Notify(&Observer::OnExtensionActivity, action);
369  if (testing_mode_)
370    LOG(INFO) << action->PrintForDebug();
371}
372
373void ActivityLog::GetActions(
374    const std::string& extension_id,
375    const int day,
376    const base::Callback
377        <void(scoped_ptr<std::vector<scoped_refptr<Action> > >)>& callback) {
378  if (policy_) {
379    policy_->ReadData(extension_id, day, callback);
380  }
381}
382
383void ActivityLog::OnScriptsExecuted(
384    const content::WebContents* web_contents,
385    const ExecutingScriptsMap& extension_ids,
386    int32 on_page_id,
387    const GURL& on_url) {
388  if (!IsLogEnabled()) return;
389  Profile* profile =
390      Profile::FromBrowserContext(web_contents->GetBrowserContext());
391  const ExtensionService* extension_service =
392      ExtensionSystem::Get(profile)->extension_service();
393  const ExtensionSet* extensions = extension_service->extensions();
394  const prerender::PrerenderManager* prerender_manager =
395      prerender::PrerenderManagerFactory::GetForProfile(
396          Profile::FromBrowserContext(web_contents->GetBrowserContext()));
397
398  for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
399       it != extension_ids.end(); ++it) {
400    const Extension* extension = extensions->GetByID(it->first);
401    if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
402      continue;
403
404    // If OnScriptsExecuted is fired because of tabs.executeScript, the list
405    // of content scripts will be empty.  We don't want to log it because
406    // the call to tabs.executeScript will have already been logged anyway.
407    if (!it->second.empty()) {
408      scoped_refptr<Action> action;
409      action = new Action(extension->id(),
410                          base::Time::Now(),
411                          Action::ACTION_CONTENT_SCRIPT,
412                          "");  // no API call here
413      action->set_page_url(on_url);
414      action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
415      action->set_page_incognito(
416          web_contents->GetBrowserContext()->IsOffTheRecord());
417      if (prerender_manager &&
418          prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
419        action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
420      for (std::set<std::string>::const_iterator it2 = it->second.begin();
421           it2 != it->second.end();
422           ++it2) {
423        action->mutable_args()->AppendString(*it2);
424      }
425      LogAction(action);
426    }
427  }
428}
429
430}  // namespace extensions
431