Chromium Code Reviews| Index: chrome/browser/extensions/extension_permissions_api.cc |
| diff --git a/chrome/browser/extensions/extension_permissions_api.cc b/chrome/browser/extensions/extension_permissions_api.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c138a172ac379b24307482745633017973c47592 |
| --- /dev/null |
| +++ b/chrome/browser/extensions/extension_permissions_api.cc |
| @@ -0,0 +1,366 @@ |
| +// Copyright (c) 2011 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/extension_permissions_api.h" |
| + |
| +#include "base/json/json_writer.h" |
| +#include "base/stringprintf.h" |
| +#include "base/values.h" |
| +#include "chrome/browser/extensions/extension_event_router.h" |
| +#include "chrome/browser/extensions/extension_permissions_api_constants.h" |
| +#include "chrome/browser/extensions/extension_prefs.h" |
| +#include "chrome/browser/extensions/extension_service.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "chrome/common/chrome_notification_types.h" |
| +#include "chrome/common/extensions/extension.h" |
| +#include "chrome/common/extensions/extension_messages.h" |
| +#include "chrome/common/extensions/extension_permission_set.h" |
| +#include "chrome/common/extensions/url_pattern_set.h" |
| +#include "content/common/notification_service.h" |
| +#include "googleurl/src/gurl.h" |
| + |
| + |
| +namespace keys = extension_permissions_module_constants; |
| + |
| +namespace { |
| + |
| +enum AutoConfirmForTest { |
| + DO_NOT_SKIP = 0, |
| + PROCEED, |
| + ABORT |
| +}; |
| +AutoConfirmForTest auto_confirm_for_tests = DO_NOT_SKIP; |
| + |
| +DictionaryValue* CreatePermissionsValue(const ExtensionPermissionSet* set) { |
| + DictionaryValue* value = new DictionaryValue(); |
| + |
| + // Generate the list of API permissions. |
| + ListValue* apis = new ListValue(); |
| + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); |
| + for (ExtensionAPIPermissionSet::const_iterator i = set->apis().begin(); |
| + i != set->apis().end(); ++i) |
| + apis->Append(Value::CreateStringValue(info->GetByID(*i)->name())); |
| + |
| + // TODO(jstritar): Include hosts once the API supports them. |
| + |
| + value->Set(keys::kApisKey, apis); |
| + return value; |
| +} |
| + |
| +// Creates a new ExtensionPermissionSet from its |value| and passes ownership to |
| +// the caller through |ptr|. Sets |bad_message| to true if the message is badly |
| +// formed. Returns false if the method fails to unpack a permission set. |
| +bool UnpackPermissionSet(DictionaryValue* value, |
|
Mihai Parparita -not on Chrome
2011/07/20 22:03:43
Seems like CreatePermissionsValue and UnpackPermis
jstritar
2011/07/22 19:21:55
Done.
|
| + scoped_ptr<ExtensionPermissionSet>* ptr, |
| + bool* bad_message, |
| + std::string* error) { |
| + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); |
| + ExtensionAPIPermissionSet apis; |
| + if (value->HasKey(keys::kApisKey)) { |
| + ListValue* api_list = NULL; |
| + if (!value->GetList(keys::kApisKey, &api_list)) { |
| + *bad_message = true; |
| + return false; |
| + } |
| + for (size_t i = 0; i < api_list->GetSize(); ++i) { |
| + std::string api_name; |
| + if (!api_list->GetString(i, &api_name)) { |
| + *bad_message = true; |
| + return false; |
| + } |
| + |
| + ExtensionAPIPermission* permission = info->GetByName(api_name); |
| + if (!permission) { |
| + *error = base::StringPrintf( |
| + keys::kUnknownPermissionError, api_name.c_str()); |
| + return false; |
| + } |
| + apis.insert(permission->id()); |
| + } |
| + } |
| + |
| + // Ignore host permissions for now. |
| + URLPatternSet empty_set; |
| + ptr->reset(new ExtensionPermissionSet(apis, empty_set, empty_set)); |
| + return true; |
| +} |
| + |
| +} // namespace |
| + |
| +ExtensionPermissionsManager::ExtensionPermissionsManager( |
| + ExtensionService* extension_service) |
| + : extension_service_(extension_service) { |
| + RegisterWhiteList(); |
| +} |
| + |
| +ExtensionPermissionsManager::~ExtensionPermissionsManager() { |
| +} |
| + |
| +void ExtensionPermissionsManager::AddPermissions( |
| + const Extension* extension, const ExtensionPermissionSet* permissions) { |
| + const ExtensionPermissionSet* existing = extension->GetActivePermissions(); |
|
Mihai Parparita -not on Chrome
2011/07/21 21:18:13
I the type of existing should be scoped_refptr<Ext
jstritar
2011/07/22 19:21:55
Woops, nice catch. Done.
|
| + scoped_refptr<ExtensionPermissionSet> total( |
| + ExtensionPermissionSet::CreateUnion(existing, permissions)); |
| + scoped_ptr<ExtensionPermissionSet> added( |
| + ExtensionPermissionSet::CreateDifference(total.get(), existing)); |
| + |
| + extension_service_->UpdateActivePermissions(extension, total.get()); |
| + |
| + // Update the granted permissions so we don't auto-disable the extension. |
| + if (extension->location() == Extension::INTERNAL) |
|
Mihai Parparita -not on Chrome
2011/07/20 22:03:43
Not sure I understand the location check. Don't we
jstritar
2011/07/22 19:21:55
We only prompt the user to confirm permissions for
|
| + extension_service_->GrantPermissions(extension); |
| + |
| + NotifyPermissionsUpdated(extension, total.get(), added.get(), ADDED); |
| +} |
| + |
| +void ExtensionPermissionsManager::RemovePermissions( |
| + const Extension* extension, const ExtensionPermissionSet* permissions) { |
| + const ExtensionPermissionSet* existing = extension->GetActivePermissions(); |
| + scoped_refptr<ExtensionPermissionSet> total( |
| + ExtensionPermissionSet::CreateDifference(existing, permissions)); |
| + scoped_ptr<ExtensionPermissionSet> removed( |
| + ExtensionPermissionSet::CreateDifference(existing, 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. |
| + extension_service_->UpdateActivePermissions(extension, total.get()); |
| + |
| + NotifyPermissionsUpdated(extension, total.get(), removed.get(), REMOVED); |
| +} |
| + |
| +void ExtensionPermissionsManager::DispatchEvent( |
| + const std::string& extension_id, |
| + const char* event_name, |
| + const ExtensionPermissionSet* permissions) { |
| + if (!permissions || permissions->IsEmpty()) |
|
Mihai Parparita -not on Chrome
2011/07/20 22:03:43
Any reason why this check is here, and not earlier
jstritar
2011/07/22 19:21:55
Just a relic from some last minute shuffling. I've
|
| + return; |
| + |
| + Profile* profile = extension_service_->profile(); |
| + if (profile && profile->GetExtensionEventRouter()) { |
| + ListValue value; |
| + value.Append(CreatePermissionsValue(permissions)); |
| + std::string json_value; |
| + base::JSONWriter::Write(&value, false, &json_value); |
| + profile->GetExtensionEventRouter()->DispatchEventToExtension( |
| + extension_id, event_name, json_value, profile, GURL()); |
| + } |
| +} |
| + |
| +void ExtensionPermissionsManager::NotifyPermissionsUpdated( |
| + const Extension* extension, |
| + const ExtensionPermissionSet* active, |
| + const ExtensionPermissionSet* changed, |
| + EventType event_type) { |
| + UpdatedExtensionPermissionsInfo::Reason reason; |
| + const char* event_name = NULL; |
| + |
| + if (event_type == REMOVED) { |
| + reason = UpdatedExtensionPermissionsInfo::REMOVED; |
| + event_name = keys::kOnRemoved; |
| + } else { |
| + CHECK_EQ(ADDED, event_type); |
| + reason = UpdatedExtensionPermissionsInfo::ADDED; |
| + event_name = keys::kOnAdded; |
| + } |
| + |
| + // Notify other APIs or interested parties. |
| + UpdatedExtensionPermissionsInfo info = UpdatedExtensionPermissionsInfo( |
| + extension, changed, reason); |
| + NotificationService::current()->Notify( |
| + chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, |
| + Source<Profile>(extension_service_->profile()), |
| + Details<UpdatedExtensionPermissionsInfo>(&info)); |
| + |
| + // Trigger the onAdded and onRemoved events in the extension. |
| + DispatchEvent(extension->id(), event_name, changed); |
| + |
| + // Send the new permissions to the renderers. |
| + for (RenderProcessHost::iterator i(RenderProcessHost::AllHostsIterator()); |
| + !i.IsAtEnd(); i.Advance()) { |
| + RenderProcessHost* host = i.GetCurrentValue(); |
| + if (extension_service_->profile()->IsSameProfile(host->profile())) |
| + host->Send(new ExtensionMsg_UpdatePermissions( |
| + extension->id(), |
| + active->apis(), |
| + active->explicit_hosts(), |
| + active->scriptable_hosts())); |
| + } |
| +} |
| + |
| +void ExtensionPermissionsManager::RegisterWhiteList() { |
| + // TODO(jstritar): This could be a field on ExtensionAPIPermission. |
| + ExtensionAPIPermissionSet api_white_list; |
| + api_white_list.insert(ExtensionAPIPermission::kClipboardRead); |
| + api_white_list.insert(ExtensionAPIPermission::kClipboardWrite); |
| + api_white_list.insert(ExtensionAPIPermission::kNotification); |
| + api_white_list.insert(ExtensionAPIPermission::kBookmark); |
| + api_white_list.insert(ExtensionAPIPermission::kContextMenus); |
| + api_white_list.insert(ExtensionAPIPermission::kCookie); |
| + api_white_list.insert(ExtensionAPIPermission::kDebugger); |
| + api_white_list.insert(ExtensionAPIPermission::kHistory); |
| + api_white_list.insert(ExtensionAPIPermission::kIdle); |
| + api_white_list.insert(ExtensionAPIPermission::kTab); |
| + api_white_list.insert(ExtensionAPIPermission::kManagement); |
| + api_white_list.insert(ExtensionAPIPermission::kBackground); |
| + white_list_.reset(new ExtensionPermissionSet( |
| + api_white_list, URLPatternSet(), URLPatternSet())); |
| +} |
| + |
| +bool ContainsPermissionsFunction::RunImpl() { |
| + DictionaryValue* args = NULL; |
| + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| + std::string error; |
| + if (!args) |
| + return false; |
| + |
| + scoped_ptr<ExtensionPermissionSet> permissions; |
| + if (!UnpackPermissionSet(args, &permissions, &bad_message_, &error_)) |
| + return false; |
| + CHECK(permissions.get()); |
| + |
| + result_.reset(Value::CreateBooleanValue( |
| + GetExtension()->GetActivePermissions()->Contains(*permissions))); |
| + return true; |
| +} |
| + |
| +bool GetAllPermissionsFunction::RunImpl() { |
| + result_.reset(CreatePermissionsValue( |
| + GetExtension()->GetActivePermissions())); |
| + return true; |
| +} |
| + |
| +bool RemovePermissionsFunction::RunImpl() { |
| + DictionaryValue* args = NULL; |
| + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| + if (!args) |
| + return false; |
| + |
| + scoped_ptr<ExtensionPermissionSet> permissions; |
| + if (!UnpackPermissionSet(args, &permissions, &bad_message_, &error_)) |
| + return false; |
| + CHECK(permissions.get()); |
| + |
| + const Extension* extension = GetExtension(); |
| + ExtensionPermissionsManager* perms_manager = |
| + profile()->GetExtensionService()->permissions_manager(); |
| + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); |
| + |
| + // Make sure they're only trying to remove permissions supported by this API. |
| + scoped_ptr<ExtensionPermissionSet> unsupported( |
| + ExtensionPermissionSet::CreateDifference( |
| + permissions.get(), &perms_manager->white_list())); |
| + if (unsupported->apis().size()) { |
| + std::string api_name = info->GetByID(*unsupported->apis().begin())->name(); |
| + error_ = base::StringPrintf(keys::kNotWhiteListedError, api_name.c_str()); |
| + return false; |
| + } |
| + |
| + // Make sure we don't remove any required pemissions. |
| + const ExtensionPermissionSet* required = extension->required_permission_set(); |
| + scoped_ptr<ExtensionPermissionSet> intersection( |
| + ExtensionPermissionSet::CreateIntersection(permissions.get(), required)); |
| + if (!intersection->IsEmpty()) { |
| + error_ = keys::kCantRemoveRequiredPermissionsError; |
| + result_.reset(Value::CreateBooleanValue(false)); |
| + return false; |
| + } |
| + |
| + perms_manager->RemovePermissions(extension, permissions.get()); |
| + result_.reset(Value::CreateBooleanValue(true)); |
| + return true; |
| +} |
| + |
| +void RequestPermissionsFunction::SetAutoConfirmForTests(bool should_proceed) { |
| + auto_confirm_for_tests = should_proceed ? PROCEED : ABORT; |
| +} |
| + |
| +bool RequestPermissionsFunction::RunImpl() { |
| + DictionaryValue* args = NULL; |
| + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); |
| + if (!args) |
| + return false; |
| + |
| + if (!UnpackPermissionSet( |
| + args, &requested_permissions_, &bad_message_, &error_)) |
| + return false; |
| + CHECK(requested_permissions_.get()); |
| + |
| + extension_ = GetExtension(); |
| + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); |
| + ExtensionPermissionsManager* perms_manager = |
| + profile()->GetExtensionService()->permissions_manager(); |
| + ExtensionPrefs* prefs = profile()->GetExtensionService()->extension_prefs(); |
| + |
| + // Make sure only white listed permissions have been requested. |
| + scoped_ptr<ExtensionPermissionSet> unsupported( |
| + ExtensionPermissionSet::CreateDifference( |
| + requested_permissions_.get(), &perms_manager->white_list())); |
| + if (unsupported->apis().size()) { |
| + std::string api_name = info->GetByID(*unsupported->apis().begin())->name(); |
| + error_ = base::StringPrintf(keys::kNotWhiteListedError, api_name.c_str()); |
| + return false; |
| + } |
| + |
| + // The requested permissions must be defined as optional in the manifest. |
| + if (!extension_->optional_permission_set()->Contains( |
| + *requested_permissions_)) { |
| + error_ = keys::kNotInOptionalPermissionsError; |
| + result_.reset(Value::CreateBooleanValue(false)); |
| + return false; |
| + } |
| + |
| + // We don't need to prompt the user if the requested permissions are a subset |
| + // of the granted permissions set. |
|
Mihai Parparita -not on Chrome
2011/07/20 22:03:43
Would it make sense to filter out the requested pe
jstritar
2011/07/22 19:21:55
Done.
|
| + const ExtensionPermissionSet* granted = |
| + prefs->GetGrantedPermissions(extension_->id()); |
| + if (granted && granted->Contains(*requested_permissions_)) { |
| + perms_manager->AddPermissions(extension_, requested_permissions_.get()); |
| + result_.reset(Value::CreateBooleanValue(true)); |
| + SendResponse(true); |
| + return true; |
| + } |
| + |
| + // Balanced with Release() in InstallUIProceed() and InstallUIAbort(). |
| + AddRef(); |
| + |
| + // We don't need to show the prompt if there are no new warnings, or if |
| + // we're skipping the confirmation UI. |
| + if (auto_confirm_for_tests == PROCEED || |
| + requested_permissions_->GetWarningMessages().size() == 0) { |
| + InstallUIProceed(); |
| + } else if (auto_confirm_for_tests == ABORT) { |
| + // Pretend the user clicked cancel. |
| + InstallUIAbort(true); |
| + } else { |
| + CHECK_EQ(DO_NOT_SKIP, auto_confirm_for_tests); |
| + install_ui_.reset(new ExtensionInstallUI(profile())); |
| + install_ui_->ConfirmPermissions( |
| + this, extension_, requested_permissions_.get()); |
| + } |
| + |
| + return true; |
| +} |
| + |
| +void RequestPermissionsFunction::InstallUIProceed() { |
| + ExtensionPermissionsManager* perms_manager = |
| + profile()->GetExtensionService()->permissions_manager(); |
| + |
| + install_ui_.reset(); |
| + result_.reset(Value::CreateBooleanValue(true)); |
| + perms_manager->AddPermissions(extension_, requested_permissions_.get()); |
| + |
| + SendResponse(true); |
| + |
| + Release(); |
| +} |
| + |
| +void RequestPermissionsFunction::InstallUIAbort(bool user_initiated) { |
| + install_ui_.reset(); |
| + result_.reset(Value::CreateBooleanValue(false)); |
| + requested_permissions_.reset(); |
| + |
| + SendResponse(true); |
| + Release(); |
| +} |