Home | History | Annotate | Download | only in extensions
      1 // Copyright 2014 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/active_script_controller.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/memory/scoped_ptr.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/stl_util.h"
     12 #include "chrome/browser/extensions/active_tab_permission_granter.h"
     13 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
     14 #include "chrome/browser/extensions/extension_action.h"
     15 #include "chrome/browser/extensions/extension_action_manager.h"
     16 #include "chrome/browser/extensions/extension_util.h"
     17 #include "chrome/browser/extensions/permissions_updater.h"
     18 #include "chrome/browser/extensions/tab_helper.h"
     19 #include "chrome/browser/profiles/profile.h"
     20 #include "chrome/browser/sessions/session_tab_helper.h"
     21 #include "chrome/common/extensions/api/extension_action/action_info.h"
     22 #include "components/crx_file/id_util.h"
     23 #include "content/public/browser/navigation_controller.h"
     24 #include "content/public/browser/navigation_details.h"
     25 #include "content/public/browser/navigation_entry.h"
     26 #include "content/public/browser/render_view_host.h"
     27 #include "content/public/browser/web_contents.h"
     28 #include "extensions/browser/extension_registry.h"
     29 #include "extensions/common/extension.h"
     30 #include "extensions/common/extension_messages.h"
     31 #include "extensions/common/extension_set.h"
     32 #include "extensions/common/feature_switch.h"
     33 #include "extensions/common/manifest.h"
     34 #include "extensions/common/permissions/permission_set.h"
     35 #include "extensions/common/permissions/permissions_data.h"
     36 #include "ipc/ipc_message_macros.h"
     37 
     38 namespace extensions {
     39 
     40 namespace {
     41 
     42 // Returns true if the extension should be regarded as a "permitted" extension
     43 // for the case of metrics. We need this because we only actually withhold
     44 // permissions if the switch is enabled, but want to record metrics in all
     45 // cases.
     46 // "ExtensionWouldHaveHadHostPermissionsWithheldIfSwitchWasOn()" would be
     47 // more accurate, but too long.
     48 bool ShouldRecordExtension(const Extension* extension) {
     49   return extension->ShouldDisplayInExtensionSettings() &&
     50          !Manifest::IsPolicyLocation(extension->location()) &&
     51          !Manifest::IsComponentLocation(extension->location()) &&
     52          !PermissionsData::CanExecuteScriptEverywhere(extension) &&
     53          extension->permissions_data()
     54              ->active_permissions()
     55              ->ShouldWarnAllHosts();
     56 }
     57 
     58 }  // namespace
     59 
     60 ActiveScriptController::ActiveScriptController(
     61     content::WebContents* web_contents)
     62     : content::WebContentsObserver(web_contents),
     63       enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()),
     64       extension_registry_observer_(this) {
     65   CHECK(web_contents);
     66   extension_registry_observer_.Add(
     67       ExtensionRegistry::Get(web_contents->GetBrowserContext()));
     68 }
     69 
     70 ActiveScriptController::~ActiveScriptController() {
     71   LogUMA();
     72 }
     73 
     74 // static
     75 ActiveScriptController* ActiveScriptController::GetForWebContents(
     76     content::WebContents* web_contents) {
     77   if (!web_contents)
     78     return NULL;
     79   TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
     80   return tab_helper ? tab_helper->active_script_controller() : NULL;
     81 }
     82 
     83 void ActiveScriptController::OnActiveTabPermissionGranted(
     84     const Extension* extension) {
     85   RunPendingForExtension(extension);
     86 }
     87 
     88 void ActiveScriptController::OnAdInjectionDetected(
     89     const std::set<std::string>& ad_injectors) {
     90   // We're only interested in data if there are ad injectors detected.
     91   if (ad_injectors.empty())
     92     return;
     93 
     94   size_t num_preventable_ad_injectors =
     95       base::STLSetIntersection<std::set<std::string> >(
     96           ad_injectors, permitted_extensions_).size();
     97 
     98   UMA_HISTOGRAM_COUNTS_100(
     99       "Extensions.ActiveScriptController.PreventableAdInjectors",
    100       num_preventable_ad_injectors);
    101   UMA_HISTOGRAM_COUNTS_100(
    102       "Extensions.ActiveScriptController.UnpreventableAdInjectors",
    103       ad_injectors.size() - num_preventable_ad_injectors);
    104 }
    105 
    106 void ActiveScriptController::AlwaysRunOnVisibleOrigin(
    107     const Extension* extension) {
    108   const GURL& url = web_contents()->GetVisibleURL();
    109   URLPatternSet new_explicit_hosts;
    110   URLPatternSet new_scriptable_hosts;
    111 
    112   scoped_refptr<const PermissionSet> withheld_permissions =
    113       extension->permissions_data()->withheld_permissions();
    114   if (withheld_permissions->explicit_hosts().MatchesURL(url)) {
    115     new_explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
    116                                  url.GetOrigin());
    117   }
    118   if (withheld_permissions->scriptable_hosts().MatchesURL(url)) {
    119     new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
    120                                    url.GetOrigin());
    121   }
    122 
    123   scoped_refptr<PermissionSet> new_permissions =
    124       new PermissionSet(APIPermissionSet(),
    125                         ManifestPermissionSet(),
    126                         new_explicit_hosts,
    127                         new_scriptable_hosts);
    128 
    129   // Update permissions for the session. This adds |new_permissions| to active
    130   // permissions and granted permissions.
    131   // TODO(devlin): Make sure that the permission is removed from
    132   // withheld_permissions if appropriate.
    133   PermissionsUpdater(web_contents()->GetBrowserContext())
    134       .AddPermissions(extension, new_permissions.get());
    135 
    136   // Allow current tab to run injection.
    137   OnClicked(extension);
    138 }
    139 
    140 void ActiveScriptController::OnClicked(const Extension* extension) {
    141   DCHECK(ContainsKey(pending_requests_, extension->id()));
    142   RunPendingForExtension(extension);
    143 }
    144 
    145 bool ActiveScriptController::WantsToRun(const Extension* extension) {
    146   return enabled_ && pending_requests_.count(extension->id()) > 0;
    147 }
    148 
    149 PermissionsData::AccessType
    150 ActiveScriptController::RequiresUserConsentForScriptInjection(
    151     const Extension* extension,
    152     UserScript::InjectionType type) {
    153   CHECK(extension);
    154 
    155   // If the feature is not enabled, we automatically allow all extensions to
    156   // run scripts.
    157   if (!enabled_)
    158     permitted_extensions_.insert(extension->id());
    159 
    160   // Allow the extension if it's been explicitly granted permission.
    161   if (permitted_extensions_.count(extension->id()) > 0)
    162     return PermissionsData::ACCESS_ALLOWED;
    163 
    164   GURL url = web_contents()->GetVisibleURL();
    165   int tab_id = SessionTabHelper::IdForTab(web_contents());
    166   switch (type) {
    167     case UserScript::CONTENT_SCRIPT:
    168       return extension->permissions_data()->GetContentScriptAccess(
    169           extension, url, url, tab_id, -1, NULL);
    170     case UserScript::PROGRAMMATIC_SCRIPT:
    171       return extension->permissions_data()->GetPageAccess(
    172           extension, url, url, tab_id, -1, NULL);
    173   }
    174 
    175   NOTREACHED();
    176   return PermissionsData::ACCESS_DENIED;
    177 }
    178 
    179 void ActiveScriptController::RequestScriptInjection(
    180     const Extension* extension,
    181     const base::Closure& callback) {
    182   CHECK(extension);
    183   PendingRequestList& list = pending_requests_[extension->id()];
    184   list.push_back(callback);
    185 
    186   // If this was the first entry, notify the location bar that there's a new
    187   // icon.
    188   if (list.size() == 1u) {
    189     ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
    190         NotifyPageActionsChanged(web_contents());
    191   }
    192 }
    193 
    194 void ActiveScriptController::RunPendingForExtension(
    195     const Extension* extension) {
    196   DCHECK(extension);
    197 
    198   content::NavigationEntry* visible_entry =
    199       web_contents()->GetController().GetVisibleEntry();
    200   // Refuse to run if there's no visible entry, because we have no idea of
    201   // determining if it's the proper page. This should rarely, if ever, happen.
    202   if (!visible_entry)
    203     return;
    204 
    205   // We add this to the list of permitted extensions and erase pending entries
    206   // *before* running them to guard against the crazy case where running the
    207   // callbacks adds more entries.
    208   permitted_extensions_.insert(extension->id());
    209 
    210   PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
    211   if (iter == pending_requests_.end())
    212     return;
    213 
    214   PendingRequestList requests;
    215   iter->second.swap(requests);
    216   pending_requests_.erase(extension->id());
    217 
    218   // Clicking to run the extension counts as granting it permission to run on
    219   // the given tab.
    220   // The extension may already have active tab at this point, but granting
    221   // it twice is essentially a no-op.
    222   TabHelper::FromWebContents(web_contents())->
    223       active_tab_permission_granter()->GrantIfRequested(extension);
    224 
    225   // Run all pending injections for the given extension.
    226   for (PendingRequestList::iterator request = requests.begin();
    227        request != requests.end();
    228        ++request) {
    229     request->Run();
    230   }
    231 
    232   // Inform the location bar that the action is now gone.
    233   ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
    234       NotifyPageActionsChanged(web_contents());
    235 }
    236 
    237 void ActiveScriptController::OnRequestScriptInjectionPermission(
    238     const std::string& extension_id,
    239     UserScript::InjectionType script_type,
    240     int64 request_id) {
    241   if (!crx_file::id_util::IdIsValid(extension_id)) {
    242     NOTREACHED() << "'" << extension_id << "' is not a valid id.";
    243     return;
    244   }
    245 
    246   const Extension* extension =
    247       ExtensionRegistry::Get(web_contents()->GetBrowserContext())
    248           ->enabled_extensions().GetByID(extension_id);
    249   // We shouldn't allow extensions which are no longer enabled to run any
    250   // scripts. Ignore the request.
    251   if (!extension)
    252     return;
    253 
    254   // If the request id is -1, that signals that the content script has already
    255   // ran (because this feature is not enabled). Add the extension to the list of
    256   // permitted extensions (for metrics), and return immediately.
    257   if (request_id == -1) {
    258     if (ShouldRecordExtension(extension)) {
    259       DCHECK(!enabled_);
    260       permitted_extensions_.insert(extension->id());
    261     }
    262     return;
    263   }
    264 
    265   switch (RequiresUserConsentForScriptInjection(extension, script_type)) {
    266     case PermissionsData::ACCESS_ALLOWED:
    267       PermitScriptInjection(request_id);
    268       break;
    269     case PermissionsData::ACCESS_WITHHELD:
    270       // This base::Unretained() is safe, because the callback is only invoked
    271       // by this object.
    272       RequestScriptInjection(
    273           extension,
    274           base::Bind(&ActiveScriptController::PermitScriptInjection,
    275                      base::Unretained(this),
    276                      request_id));
    277       break;
    278     case PermissionsData::ACCESS_DENIED:
    279       // We should usually only get a "deny access" if the page changed (as the
    280       // renderer wouldn't have requested permission if the answer was always
    281       // "no"). Just let the request fizzle and die.
    282       break;
    283   }
    284 }
    285 
    286 void ActiveScriptController::PermitScriptInjection(int64 request_id) {
    287   // This only sends the response to the renderer - the process of adding the
    288   // extension to the list of |permitted_extensions_| is done elsewhere.
    289   content::RenderViewHost* render_view_host =
    290       web_contents()->GetRenderViewHost();
    291   if (render_view_host) {
    292     render_view_host->Send(new ExtensionMsg_PermitScriptInjection(
    293         render_view_host->GetRoutingID(), request_id));
    294   }
    295 }
    296 
    297 void ActiveScriptController::LogUMA() const {
    298   UMA_HISTOGRAM_COUNTS_100(
    299       "Extensions.ActiveScriptController.ShownActiveScriptsOnPage",
    300       pending_requests_.size());
    301 
    302   // We only log the permitted extensions metric if the feature is enabled,
    303   // because otherwise the data will be boring (100% allowed).
    304   if (enabled_) {
    305     UMA_HISTOGRAM_COUNTS_100(
    306         "Extensions.ActiveScriptController.PermittedExtensions",
    307         permitted_extensions_.size());
    308     UMA_HISTOGRAM_COUNTS_100(
    309         "Extensions.ActiveScriptController.DeniedExtensions",
    310         pending_requests_.size());
    311   }
    312 }
    313 
    314 bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) {
    315   bool handled = true;
    316   IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message)
    317     IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission,
    318                         OnRequestScriptInjectionPermission)
    319     IPC_MESSAGE_UNHANDLED(handled = false)
    320   IPC_END_MESSAGE_MAP()
    321   return handled;
    322 }
    323 
    324 void ActiveScriptController::DidNavigateMainFrame(
    325     const content::LoadCommittedDetails& details,
    326     const content::FrameNavigateParams& params) {
    327   if (details.is_in_page)
    328     return;
    329 
    330   LogUMA();
    331   permitted_extensions_.clear();
    332   pending_requests_.clear();
    333 }
    334 
    335 void ActiveScriptController::OnExtensionUnloaded(
    336     content::BrowserContext* browser_context,
    337     const Extension* extension,
    338     UnloadedExtensionInfo::Reason reason) {
    339   PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
    340   if (iter != pending_requests_.end()) {
    341     pending_requests_.erase(iter);
    342     ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
    343         NotifyPageActionsChanged(web_contents());
    344   }
    345 }
    346 
    347 }  // namespace extensions
    348