| // 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 <utility> |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/values.h" |
| #include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h" |
| #include "chrome/browser/extensions/scripting_permissions_modifier.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/browser/notification_types.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/extension_messages.h" |
| #include "extensions/common/manifest_handlers/permissions_parser.h" |
| #include "extensions/common/permissions/permission_set.h" |
| #include "extensions/common/permissions/permissions_data.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. |
| std::unique_ptr<const PermissionSet> GetBoundedActivePermissions( |
| const Extension* extension, |
| const PermissionSet* active_permissions) { |
| // 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. |
| if (!active_permissions) |
| return extension->permissions_data()->active_permissions().Clone(); |
| |
| 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 |
| std::unique_ptr<const PermissionSet> total_permissions = |
| PermissionSet::CreateUnion( |
| required_permissions, |
| PermissionsParser::GetOptionalPermissions(extension)); |
| |
| // Make sure the active permissions contain no more than optional + default. |
| std::unique_ptr<const 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; |
| } |
| |
| PermissionsUpdater::Delegate* g_delegate = nullptr; |
| |
| } // namespace |
| |
| PermissionsUpdater::PermissionsUpdater(content::BrowserContext* browser_context) |
| : browser_context_(browser_context), init_flag_(INIT_FLAG_NONE) { |
| } |
| |
| PermissionsUpdater::PermissionsUpdater(content::BrowserContext* browser_context, |
| InitFlag init_flag) |
| : browser_context_(browser_context), init_flag_(init_flag) { |
| } |
| |
| PermissionsUpdater::~PermissionsUpdater() {} |
| |
| // static |
| void PermissionsUpdater::SetPlatformDelegate(Delegate* delegate) { |
| // Make sure we're setting it only once (allow setting to nullptr, but then |
| // take special care of actually freeing it). |
| CHECK(!g_delegate || !delegate); |
| g_delegate = delegate; |
| } |
| |
| void PermissionsUpdater::AddPermissions(const Extension* extension, |
| const PermissionSet& permissions) { |
| const PermissionSet& active = |
| extension->permissions_data()->active_permissions(); |
| std::unique_ptr<const PermissionSet> total = |
| PermissionSet::CreateUnion(active, permissions); |
| std::unique_ptr<const PermissionSet> added = |
| PermissionSet::CreateDifference(*total, active); |
| |
| std::unique_ptr<const PermissionSet> new_withheld = |
| PermissionSet::CreateDifference( |
| extension->permissions_data()->withheld_permissions(), permissions); |
| SetPermissions(extension, std::move(total), std::move(new_withheld)); |
| |
| // Update the granted permissions so we don't auto-disable the extension. |
| GrantActivePermissions(extension); |
| |
| NotifyPermissionsUpdated(ADDED, extension, *added); |
| } |
| |
| void PermissionsUpdater::RemovePermissions(const Extension* extension, |
| const PermissionSet& to_remove, |
| RemoveType remove_type) { |
| // We should only be revoking revokable permissions. |
| CHECK(GetRevokablePermissions(extension)->Contains(to_remove)); |
| |
| const PermissionSet& active = |
| extension->permissions_data()->active_permissions(); |
| std::unique_ptr<const PermissionSet> remaining = |
| PermissionSet::CreateDifference(active, to_remove); |
| |
| // Move any granted permissions that were in the withheld set back to the |
| // withheld set so they can be added back later. |
| // Any revoked permission that isn't from the optional permissions can only |
| // be a withheld permission. |
| std::unique_ptr<const PermissionSet> removed_withheld = |
| PermissionSet::CreateDifference( |
| to_remove, PermissionsParser::GetOptionalPermissions(extension)); |
| std::unique_ptr<const PermissionSet> withheld = PermissionSet::CreateUnion( |
| *removed_withheld, extension->permissions_data()->withheld_permissions()); |
| |
| SetPermissions(extension, std::move(remaining), std::move(withheld)); |
| |
| // We might not want to revoke the granted permissions because the extension, |
| // not the user, removed the permissions. This allows the extension to add |
| // them again without prompting the user. |
| if (remove_type == REMOVE_HARD) { |
| ExtensionPrefs::Get(browser_context_) |
| ->RemoveGrantedPermissions(extension->id(), to_remove); |
| } |
| |
| NotifyPermissionsUpdated(REMOVED, extension, to_remove); |
| } |
| |
| void PermissionsUpdater::SetPolicyHostRestrictions( |
| const Extension* extension, |
| const URLPatternSet& runtime_blocked_hosts, |
| const URLPatternSet& runtime_allowed_hosts) { |
| extension->permissions_data()->SetPolicyHostRestrictions( |
| runtime_blocked_hosts, runtime_allowed_hosts); |
| |
| // Send notification to the currently running renderers of the runtime block |
| // hosts settings. |
| const PermissionSet perms; |
| NotifyPermissionsUpdated(POLICY, extension, perms); |
| } |
| |
| void PermissionsUpdater::SetUsesDefaultHostRestrictions( |
| const Extension* extension) { |
| extension->permissions_data()->SetUsesDefaultHostRestrictions(); |
| const PermissionSet perms; |
| NotifyPermissionsUpdated(POLICY, extension, perms); |
| } |
| |
| void PermissionsUpdater::SetDefaultPolicyHostRestrictions( |
| const URLPatternSet& default_runtime_blocked_hosts, |
| const URLPatternSet& default_runtime_allowed_hosts) { |
| PermissionsData::SetDefaultPolicyHostRestrictions( |
| default_runtime_blocked_hosts, default_runtime_allowed_hosts); |
| |
| // Send notification to the currently running renderers of the runtime block |
| // hosts settings. |
| NotifyDefaultPolicyHostRestrictionsUpdated(default_runtime_blocked_hosts, |
| default_runtime_allowed_hosts); |
| } |
| |
| void PermissionsUpdater::RemovePermissionsUnsafe( |
| const Extension* extension, |
| const PermissionSet& to_remove) { |
| const PermissionSet& active = |
| extension->permissions_data()->active_permissions(); |
| std::unique_ptr<const PermissionSet> total = |
| PermissionSet::CreateDifference(active, to_remove); |
| // |successfully_removed| might not equal |to_remove| if |to_remove| contains |
| // permissions the extension didn't have. |
| std::unique_ptr<const PermissionSet> successfully_removed = |
| PermissionSet::CreateDifference(active, *total); |
| |
| SetPermissions(extension, std::move(total), nullptr); |
| NotifyPermissionsUpdated(REMOVED, extension, *successfully_removed); |
| } |
| |
| std::unique_ptr<const PermissionSet> |
| PermissionsUpdater::GetRevokablePermissions(const Extension* extension) const { |
| // The user can revoke any permissions they granted. In other words, any |
| // permissions the extension didn't start with can be revoked. |
| const PermissionSet& required = |
| PermissionsParser::GetRequiredPermissions(extension); |
| std::unique_ptr<const PermissionSet> granted; |
| std::unique_ptr<const PermissionSet> withheld; |
| ScriptingPermissionsModifier(browser_context_, make_scoped_refptr(extension)) |
| .WithholdPermissions(required, &granted, &withheld, true); |
| return PermissionSet::CreateDifference( |
| extension->permissions_data()->active_permissions(), *granted); |
| } |
| |
| void PermissionsUpdater::GrantActivePermissions(const Extension* extension) { |
| CHECK(extension); |
| |
| ExtensionPrefs::Get(browser_context_) |
| ->AddGrantedPermissions( |
| extension->id(), extension->permissions_data()->active_permissions()); |
| } |
| |
| void PermissionsUpdater::InitializePermissions(const Extension* extension) { |
| std::unique_ptr<const PermissionSet> bounded_wrapper; |
| const PermissionSet* bounded_active = nullptr; |
| // If |extension| is a transient dummy extension, we do not want to look for |
| // it in preferences. |
| if (init_flag_ & INIT_FLAG_TRANSIENT) { |
| bounded_active = &extension->permissions_data()->active_permissions(); |
| } else { |
| std::unique_ptr<const PermissionSet> active_permissions = |
| ExtensionPrefs::Get(browser_context_) |
| ->GetActivePermissions(extension->id()); |
| bounded_wrapper = |
| GetBoundedActivePermissions(extension, active_permissions.get()); |
| bounded_active = bounded_wrapper.get(); |
| } |
| |
| std::unique_ptr<const PermissionSet> granted_permissions; |
| std::unique_ptr<const PermissionSet> withheld_permissions; |
| ScriptingPermissionsModifier(browser_context_, make_scoped_refptr(extension)) |
| .WithholdPermissions(*bounded_active, &granted_permissions, |
| &withheld_permissions, |
| (init_flag_ & INIT_FLAG_TRANSIENT) != 0); |
| |
| if (g_delegate) |
| g_delegate->InitializePermissions(extension, &granted_permissions); |
| |
| SetPermissions(extension, std::move(granted_permissions), |
| std::move(withheld_permissions)); |
| } |
| |
| void PermissionsUpdater::SetPermissions( |
| const Extension* extension, |
| std::unique_ptr<const PermissionSet> active, |
| std::unique_ptr<const PermissionSet> withheld) { |
| DCHECK(active); |
| const PermissionSet& active_weak = *active; |
| if (withheld) { |
| extension->permissions_data()->SetPermissions(std::move(active), |
| std::move(withheld)); |
| } else { |
| extension->permissions_data()->SetActivePermissions(std::move(active)); |
| } |
| |
| if ((init_flag_ & INIT_FLAG_TRANSIENT) == 0) { |
| ExtensionPrefs::Get(browser_context_) |
| ->SetActivePermissions(extension->id(), active_weak); |
| } |
| } |
| |
| void PermissionsUpdater::DispatchEvent( |
| const std::string& extension_id, |
| events::HistogramValue histogram_value, |
| const char* event_name, |
| const PermissionSet& changed_permissions) { |
| EventRouter* event_router = EventRouter::Get(browser_context_); |
| if (!event_router) |
| return; |
| |
| std::unique_ptr<base::ListValue> value(new base::ListValue()); |
| std::unique_ptr<api::permissions::Permissions> permissions = |
| PackPermissionSet(changed_permissions); |
| value->Append(permissions->ToValue()); |
| auto event = base::MakeUnique<Event>(histogram_value, event_name, |
| std::move(value), browser_context_); |
| event_router->DispatchEventToExtension(extension_id, std::move(event)); |
| } |
| |
| void PermissionsUpdater::NotifyPermissionsUpdated( |
| EventType event_type, |
| const Extension* extension, |
| const PermissionSet& changed) { |
| DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT); |
| |
| if (changed.IsEmpty() && event_type != POLICY) |
| return; |
| |
| UpdatedExtensionPermissionsInfo::Reason reason; |
| events::HistogramValue histogram_value = events::UNKNOWN; |
| const char* event_name = NULL; |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| |
| if (event_type == REMOVED) { |
| reason = UpdatedExtensionPermissionsInfo::REMOVED; |
| histogram_value = events::PERMISSIONS_ON_REMOVED; |
| event_name = permissions::OnRemoved::kEventName; |
| } else if (event_type == ADDED) { |
| reason = UpdatedExtensionPermissionsInfo::ADDED; |
| histogram_value = events::PERMISSIONS_ON_ADDED; |
| event_name = permissions::OnAdded::kEventName; |
| } else { |
| DCHECK_EQ(POLICY, event_type); |
| reason = UpdatedExtensionPermissionsInfo::POLICY; |
| } |
| |
| // Notify other APIs or interested parties. |
| UpdatedExtensionPermissionsInfo info = |
| UpdatedExtensionPermissionsInfo(extension, changed, reason); |
| content::NotificationService::current()->Notify( |
| extensions::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()); |
| params.uses_default_policy_host_restrictions = |
| extension->permissions_data()->UsesDefaultPolicyHostRestrictions(); |
| if (!params.uses_default_policy_host_restrictions) { |
| params.policy_blocked_hosts = |
| extension->permissions_data()->policy_blocked_hosts(); |
| params.policy_allowed_hosts = |
| extension->permissions_data()->policy_allowed_hosts(); |
| } |
| |
| // 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. We explicitly |
| // don't do this for policy-related events. |
| if (event_name) |
| DispatchEvent(extension->id(), histogram_value, event_name, changed); |
| } |
| |
| // Notify the renderers that extension policy (policy_blocked_hosts) is updated |
| // and provide new set of hosts. |
| void PermissionsUpdater::NotifyDefaultPolicyHostRestrictionsUpdated( |
| const URLPatternSet& default_runtime_blocked_hosts, |
| const URLPatternSet& default_runtime_allowed_hosts) { |
| DCHECK_EQ(0, init_flag_ & INIT_FLAG_TRANSIENT); |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context_); |
| |
| ExtensionMsg_UpdateDefaultPolicyHostRestrictions_Params params; |
| params.default_policy_blocked_hosts = default_runtime_blocked_hosts; |
| params.default_policy_allowed_hosts = default_runtime_allowed_hosts; |
| |
| // Send the new policy to the renderers. |
| for (RenderProcessHost::iterator host_iterator( |
| RenderProcessHost::AllHostsIterator()); |
| !host_iterator.IsAtEnd(); host_iterator.Advance()) { |
| RenderProcessHost* host = host_iterator.GetCurrentValue(); |
| if (profile->IsSameProfile( |
| Profile::FromBrowserContext(host->GetBrowserContext()))) { |
| host->Send(new ExtensionMsg_UpdateDefaultPolicyHostRestrictions(params)); |
| } |
| } |
| } |
| |
| } // namespace extensions |