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