| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/extensions/permissions_updater.h" |
| |
| #include "base/json/json_writer.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/values.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h" |
| #include "chrome/browser/extensions/extension_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/extensions/api/permissions.h" |
| #include "content/public/browser/notification_observer.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_prefs.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/feature_switch.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #include "extensions/common/url_pattern.h" |
| #include "extensions/common/url_pattern_set.h" |
| |
| using content::RenderProcessHost; |
| using extensions::permissions_api_helpers::PackPermissionSet; |
| |
| namespace extensions { |
| |
| namespace permissions = api::permissions; |
| |
| namespace { |
| |
| // Returns a PermissionSet that has the active permissions of the extension, |
| // bounded to its current manifest. |
| scoped_refptr<const PermissionSet> GetBoundedActivePermissions( |
| const Extension* extension, ExtensionPrefs* extension_prefs) { |
| // If the extension has used the optional permissions API, it will have a |
| // custom set of active permissions defined in the extension prefs. Here, |
| // we update the extension's active permissions based on the prefs. |
| scoped_refptr<const PermissionSet> active_permissions = |
| extension_prefs->GetActivePermissions(extension->id()); |
| if (!active_permissions) |
| return extension->permissions_data()->active_permissions(); |
| |
| scoped_refptr<const PermissionSet> required_permissions = |
| PermissionsParser::GetRequiredPermissions(extension); |
| |
| // We restrict the active permissions to be within the bounds defined in the |
| // extension's manifest. |
| // a) active permissions must be a subset of optional + default permissions |
| // b) active permissions must contains all default permissions |
| scoped_refptr<PermissionSet> total_permissions = PermissionSet::CreateUnion( |
| required_permissions, |
| PermissionsParser::GetOptionalPermissions(extension)); |
| |
| // Make sure the active permissions contain no more than optional + default. |
| scoped_refptr<PermissionSet> adjusted_active = |
| PermissionSet::CreateIntersection(total_permissions, active_permissions); |
| |
| // Make sure the active permissions contain the default permissions. |
| adjusted_active = |
| PermissionSet::CreateUnion(required_permissions, adjusted_active); |
| |
| return adjusted_active; |
| } |
| |
| // Divvy up the |url patterns| between those we grant and those we do not. If |
| // |withhold_permissions| is false (because the requisite feature is not |
| // enabled), no permissions are withheld. |
| void SegregateUrlPermissions(const URLPatternSet& url_patterns, |
| bool withhold_permissions, |
| URLPatternSet* granted, |
| URLPatternSet* withheld) { |
| for (URLPatternSet::const_iterator iter = url_patterns.begin(); |
| iter != url_patterns.end(); |
| ++iter) { |
| if (withhold_permissions && iter->ImpliesAllHosts()) |
| withheld->AddPattern(*iter); |
| else |
| granted->AddPattern(*iter); |
| } |
| } |
| |
| } // namespace |
| |
| PermissionsUpdater::PermissionsUpdater(content::BrowserContext* browser_context) |
| : browser_context_(browser_context) { |
| } |
| |
| PermissionsUpdater::~PermissionsUpdater() {} |
| |
| void PermissionsUpdater::AddPermissions( |
| const Extension* extension, const PermissionSet* permissions) { |
| scoped_refptr<const PermissionSet> existing( |
| extension->permissions_data()->active_permissions()); |
| scoped_refptr<PermissionSet> total( |
| PermissionSet::CreateUnion(existing.get(), permissions)); |
| scoped_refptr<PermissionSet> added( |
| PermissionSet::CreateDifference(total.get(), existing.get())); |
| |
| SetPermissions(extension, total, NULL); |
| |
| // Update the granted permissions so we don't auto-disable the extension. |
| GrantActivePermissions(extension); |
| |
| NotifyPermissionsUpdated(ADDED, extension, added.get()); |
| } |
| |
| void PermissionsUpdater::RemovePermissions( |
| const Extension* extension, const PermissionSet* permissions) { |
| scoped_refptr<const PermissionSet> existing( |
| extension->permissions_data()->active_permissions()); |
| scoped_refptr<PermissionSet> total( |
| PermissionSet::CreateDifference(existing.get(), permissions)); |
| scoped_refptr<PermissionSet> removed( |
| PermissionSet::CreateDifference(existing.get(), total.get())); |
| |
| // We update the active permissions, and not the granted permissions, because |
| // the extension, not the user, removed the permissions. This allows the |
| // extension to add them again without prompting the user. |
| SetPermissions(extension, total, NULL); |
| |
| NotifyPermissionsUpdated(REMOVED, extension, removed.get()); |
| } |
| |
| void PermissionsUpdater::GrantActivePermissions(const Extension* extension) { |
| CHECK(extension); |
| |
| // We only maintain the granted permissions prefs for INTERNAL and LOAD |
| // extensions. |
| if (!Manifest::IsUnpackedLocation(extension->location()) && |
| extension->location() != Manifest::INTERNAL) |
| return; |
| |
| ExtensionPrefs::Get(browser_context_)->AddGrantedPermissions( |
| extension->id(), |
| extension->permissions_data()->active_permissions().get()); |
| } |
| |
| void PermissionsUpdater::InitializePermissions(const Extension* extension) { |
| scoped_refptr<const PermissionSet> bounded_active = |
| GetBoundedActivePermissions(extension, |
| ExtensionPrefs::Get(browser_context_)); |
| |
| // We withhold permissions iff the switch to do so is enabled, the extension |
| // shows up in chrome:extensions (so the user can grant withheld permissions), |
| // the extension is not part of chrome or corporate policy, and also not on |
| // the scripting whitelist. Additionally, we don't withhold if the extension |
| // has the preference to allow scripting on all urls. |
| bool should_withhold_permissions = |
| FeatureSwitch::scripts_require_action()->IsEnabled() && |
| extension->ShouldDisplayInExtensionSettings() && |
| !Manifest::IsPolicyLocation(extension->location()) && |
| !Manifest::IsComponentLocation(extension->location()) && |
| !PermissionsData::CanExecuteScriptEverywhere(extension) && |
| !util::AllowedScriptingOnAllUrls(extension->id(), browser_context_); |
| |
| URLPatternSet granted_explicit_hosts; |
| URLPatternSet withheld_explicit_hosts; |
| SegregateUrlPermissions(bounded_active->explicit_hosts(), |
| should_withhold_permissions, |
| &granted_explicit_hosts, |
| &withheld_explicit_hosts); |
| |
| URLPatternSet granted_scriptable_hosts; |
| URLPatternSet withheld_scriptable_hosts; |
| SegregateUrlPermissions(bounded_active->scriptable_hosts(), |
| should_withhold_permissions, |
| &granted_scriptable_hosts, |
| &withheld_scriptable_hosts); |
| |
| bounded_active = new PermissionSet(bounded_active->apis(), |
| bounded_active->manifest_permissions(), |
| granted_explicit_hosts, |
| granted_scriptable_hosts); |
| |
| scoped_refptr<const PermissionSet> withheld = |
| new PermissionSet(APIPermissionSet(), |
| ManifestPermissionSet(), |
| withheld_explicit_hosts, |
| withheld_scriptable_hosts); |
| SetPermissions(extension, bounded_active, withheld); |
| } |
| |
| void PermissionsUpdater::WithholdImpliedAllHosts(const Extension* extension) { |
| scoped_refptr<const PermissionSet> active = |
| extension->permissions_data()->active_permissions(); |
| scoped_refptr<const PermissionSet> withheld = |
| extension->permissions_data()->withheld_permissions(); |
| |
| URLPatternSet withheld_scriptable = withheld->scriptable_hosts(); |
| URLPatternSet active_scriptable; |
| SegregateUrlPermissions(active->scriptable_hosts(), |
| true, // withhold permissions |
| &active_scriptable, |
| &withheld_scriptable); |
| |
| URLPatternSet withheld_explicit = withheld->explicit_hosts(); |
| URLPatternSet active_explicit; |
| SegregateUrlPermissions(active->explicit_hosts(), |
| true, // withhold permissions |
| &active_explicit, |
| &withheld_explicit); |
| |
| SetPermissions(extension, |
| new PermissionSet(active->apis(), |
| active->manifest_permissions(), |
| active_explicit, |
| active_scriptable), |
| new PermissionSet(withheld->apis(), |
| withheld->manifest_permissions(), |
| withheld_explicit, |
| withheld_scriptable)); |
| // TODO(rdevlin.cronin) We should notify the observers/renderer. |
| } |
| |
| void PermissionsUpdater::GrantWithheldImpliedAllHosts( |
| const Extension* extension) { |
| scoped_refptr<const PermissionSet> active = |
| extension->permissions_data()->active_permissions(); |
| scoped_refptr<const PermissionSet> withheld = |
| extension->permissions_data()->withheld_permissions(); |
| |
| // Move the all-hosts permission from withheld to active. |
| // We can cheat a bit here since we know that the only host permission we |
| // withhold is allhosts (or something similar enough to it), so we can just |
| // grant all withheld host permissions. |
| URLPatternSet explicit_hosts; |
| URLPatternSet::CreateUnion( |
| active->explicit_hosts(), withheld->explicit_hosts(), &explicit_hosts); |
| URLPatternSet scriptable_hosts; |
| URLPatternSet::CreateUnion(active->scriptable_hosts(), |
| withheld->scriptable_hosts(), |
| &scriptable_hosts); |
| |
| // Since we only withhold host permissions (so far), we know that withheld |
| // permissions will be empty. |
| SetPermissions(extension, |
| new PermissionSet(active->apis(), |
| active->manifest_permissions(), |
| explicit_hosts, |
| scriptable_hosts), |
| new PermissionSet()); |
| // TODO(rdevlin.cronin) We should notify the observers/renderer. |
| } |
| |
| void PermissionsUpdater::SetPermissions( |
| const Extension* extension, |
| const scoped_refptr<const PermissionSet>& active, |
| scoped_refptr<const PermissionSet> withheld) { |
| withheld = withheld.get() ? withheld |
| : extension->permissions_data()->withheld_permissions(); |
| extension->permissions_data()->SetPermissions(active, withheld); |
| ExtensionPrefs::Get(browser_context_)->SetActivePermissions( |
| extension->id(), active.get()); |
| } |
| |
| void PermissionsUpdater::DispatchEvent( |
| const std::string& extension_id, |
| const char* event_name, |
| const PermissionSet* changed_permissions) { |
| EventRouter* event_router = EventRouter::Get(browser_context_); |
| if (!event_router) |
| return; |
| |
| scoped_ptr<base::ListValue> value(new base::ListValue()); |
| scoped_ptr<api::permissions::Permissions> permissions = |
| PackPermissionSet(changed_permissions); |
| value->Append(permissions->ToValue().release()); |
| scoped_ptr<Event> event(new Event(event_name, value.Pass())); |
| event->restrict_to_browser_context = browser_context_; |
| event_router->DispatchEventToExtension(extension_id, event.Pass()); |
| } |
| |
| void PermissionsUpdater::NotifyPermissionsUpdated( |
| EventType event_type, |
| const Extension* extension, |
| const PermissionSet* changed) { |
| if (!changed || changed->IsEmpty()) |
| return; |
| |
| UpdatedExtensionPermissionsInfo::Reason reason; |
| const char* event_name = NULL; |
| |
| if (event_type == REMOVED) { |
| reason = UpdatedExtensionPermissionsInfo::REMOVED; |
| event_name = permissions::OnRemoved::kEventName; |
| } else { |
| CHECK_EQ(ADDED, event_type); |
| reason = UpdatedExtensionPermissionsInfo::ADDED; |
| event_name = permissions::OnAdded::kEventName; |
| } |
| |
| // Notify other APIs or interested parties. |
| UpdatedExtensionPermissionsInfo info = UpdatedExtensionPermissionsInfo( |
| extension, changed, reason); |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| content::NotificationService::current()->Notify( |
| chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, |
| content::Source<Profile>(profile), |
| content::Details<UpdatedExtensionPermissionsInfo>(&info)); |
| |
| ExtensionMsg_UpdatePermissions_Params params; |
| params.extension_id = extension->id(); |
| params.active_permissions = ExtensionMsg_PermissionSetStruct( |
| extension->permissions_data()->active_permissions()); |
| params.withheld_permissions = ExtensionMsg_PermissionSetStruct( |
| extension->permissions_data()->withheld_permissions()); |
| |
| // Send the new permissions to the renderers. |
| for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator()); |
| !i.IsAtEnd(); i.Advance()) { |
| RenderProcessHost* host = i.GetCurrentValue(); |
| if (profile->IsSameProfile( |
| Profile::FromBrowserContext(host->GetBrowserContext()))) { |
| host->Send(new ExtensionMsg_UpdatePermissions(params)); |
| } |
| } |
| |
| // Trigger the onAdded and onRemoved events in the extension. |
| DispatchEvent(extension->id(), event_name, changed); |
| } |
| |
| } // namespace extensions |