diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-27 14:36:49 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-07-27 14:36:49 +0000 |
commit | 38383b194bc471ebba4de124cbf391b00114923a (patch) | |
tree | f7a027d2ea9f88f28216a29d0b9a0368e03ae502 /chrome | |
parent | 4d48dccadf757b39157537331a3becd13a5f7d9f (diff) | |
download | chromium_src-38383b194bc471ebba4de124cbf391b00114923a.zip chromium_src-38383b194bc471ebba4de124cbf391b00114923a.tar.gz chromium_src-38383b194bc471ebba4de124cbf391b00114923a.tar.bz2 |
Add an experimental permissions API for extensions.
The permissions API lets extensions specify optional permissions in their manifest that they can request at run-time. It currently supports API permissions through a white-list. Host permissions will come later.
BUG=48119, 70466, 84507
TEST=*Extension*
Review URL: http://codereview.chromium.org/7432006
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@94288 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
52 files changed, 4107 insertions, 426 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 404e58b..7382d99 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -3672,6 +3672,9 @@ are declared in build/common.gypi. <message name="IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE" desc="Titlebar of the extension or app prompt window when re-enabling an extension that requires additional permissions"> Confirm Re-enable </message> + <message name="IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE" desc="Titlebar of the extension or app permissions prompt window"> + Confirm Permissions + </message> <message name="IDS_EXTENSION_INSTALL_PROMPT_HEADING" desc="First bold line in the content area of the extension or app installation prompt. Asks the user if they want to install a particular extension or app."> Install <ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>? </message> @@ -3681,6 +3684,9 @@ are declared in build/common.gypi. <message name="IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING" desc="First bold line in the content area of the extension or app installation prompt. Asks the user if they want to re-enable a particular extension or app."> The newest version of "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" has been disabled because it requires more permissions. </message> + <message name="IDS_EXTENSION_PERMISSIONS_PROMPT_HEADING" desc="First bold line in the content area of the extension or app permissions prompt. Asks the user if they want to grant the permissions to the particular extension or app."> + The extension "<ph name="EXTENSION_NAME">$1<ex>Gmail Checker</ex></ph>" has requested additional permissions. + </message> <!-- Extension/App install dialog strings --> <message name="IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO" desc="Second line in the content area of the extension or app installation prompt. Note that the exact wording is important. This should mean that the extension or app _can_ access the listed privileges, but not necessarily that it will or needs to."> @@ -3689,6 +3695,10 @@ are declared in build/common.gypi. <message name="IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO" desc="Second line in the content area of the extension or app re-enable prompt. Note that the exact wording is important. This should mean that the extension _can now_ access the listed privileges, but not necessarily that it will or needs to. This message appeared because the user must approve new permissions of the extension or app."> It can now access: </message> + <message name="IDS_EXTENSION_PROMPT_WANTS_ACCESS_TO" desc="Second line in the content area of the extension or app permissions prompt. Note that the exact wording is important. This should mean that the extension _wants to_ access the listed privileges, but not necessarily that it will or needs to. This message appeared because the user must approve new permissions of the extension or app."> + It could access: + </message> + <message name="IDS_EXTENSION_PROMPT_WARNING_FULL_ACCESS" desc="Permission string for full access to the computer and all websites."> All data on your computer and the websites you visit </message> @@ -4033,6 +4043,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON" desc="Text for the enable button on the extension re-enable prompt"> Re-enable </message> + <message name="IDS_EXTENSION_PROMPT_PERMISSIONS_BUTTON" desc="Text for the allow button on the extension permissions prompt"> + Allow + </message> + <message name="IDS_EXTENSION_PROMPT_PERMISSIONS_ABORT_BUTTON" desc="Text for the deny button on the extension permissions prompt"> + Deny + </message> <message name="IDS_EXTENSION_WEB_STORE_TITLE" desc="Text for the Chrome Web Store"> Web Store </message> @@ -7736,7 +7752,7 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_AUTOFILL_HELP_LABEL" desc="The label of the Help link on the Autofill dialog."> About Autofill </message> - + <message name="IDS_AUTOFILL_SHOW_PREDICTIONS_TITLE" desc="The title for form elements when annotated with Autofill predictions."> heuristic type: <ph name="HEURISTIC_TYPE">$1<ex>NAME_FIRST</ex></ph>\nserver type: <ph name="SERVER_TYPE">$2<ex>NAME_FIRST</ex></ph>\nfield signature: <ph name="FIELD_SIGNATURE">$3<ex>12345678</ex></ph>\nform signature: <ph name="FORM_SIGNATURE">$4<ex>1234567812345678</ex></ph>\nexperiment id: "<ph name="EXPERIMENT_ID">$5<ex>ar1</ex></ph>" </message> @@ -8287,7 +8303,7 @@ Keep your key file in a safe place. You will need it to create new versions of y </message> <message name="IDS_APP_DEFAULT_PAGE_NAME" desc="Default name for the first 'Apps' page on the New Tab Page."> - Apps + Apps </message> <message name="IDS_APP_CONTEXT_MENU_OPEN_PINNED" desc="Text for the button that opens the app in a pinned tab."> diff --git a/chrome/browser/automation/testing_automation_provider.cc b/chrome/browser/automation/testing_automation_provider.cc index 44a3b5b..d1a523f 100644 --- a/chrome/browser/automation/testing_automation_provider.cc +++ b/chrome/browser/automation/testing_automation_provider.cc @@ -4111,7 +4111,7 @@ ListValue* GetHostPermissions(const Extension* ext, bool effective_perm) { if (effective_perm) pattern_set = ext->GetEffectiveHostPermissions(); else - pattern_set = ext->permission_set()->explicit_hosts(); + pattern_set = ext->GetActivePermissions()->explicit_hosts(); ListValue* permissions = new ListValue; for (URLPatternSet::const_iterator perm = pattern_set.begin(); @@ -4124,7 +4124,8 @@ ListValue* GetHostPermissions(const Extension* ext, bool effective_perm) { ListValue* GetAPIPermissions(const Extension* ext) { ListValue* permissions = new ListValue; - std::set<std::string> perm_list = ext->permission_set()->GetAPIsAsStrings(); + std::set<std::string> perm_list = + ext->GetActivePermissions()->GetAPIsAsStrings(); for (std::set<std::string>::const_iterator perm = perm_list.begin(); perm != perm_list.end(); ++perm) { permissions->Append(new StringValue(perm->c_str())); diff --git a/chrome/browser/background/background_mode_manager.cc b/chrome/browser/background/background_mode_manager.cc index c44362c..33eecf1 100644 --- a/chrome/browser/background/background_mode_manager.cc +++ b/chrome/browser/background/background_mode_manager.cc @@ -196,13 +196,15 @@ void BackgroundModeManager::RegisterProfile(Profile* profile) { profile, this)); background_mode_data_[profile] = bmd; - // Listen for when extensions are loaded/unloaded so we can track the - // number of background apps and modify our keep-alive and launch-on-startup - // state appropriately. + // Listen for when extensions are loaded/unloaded or add/remove the + // background permission so we can track the number of background apps and + // modify our keep-alive and launch-on-startup state appropriately. registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED, Source<Profile>(profile)); registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED, Source<Profile>(profile)); + registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, + Source<Profile>(profile)); // Check for the presence of background apps after all extensions have been // loaded, to handle the case where an extension has been manually removed @@ -274,6 +276,22 @@ void BackgroundModeManager::Observe(int type, OnBackgroundAppUninstalled(); } break; + case chrome::NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED: { + UpdatedExtensionPermissionsInfo* info = + Details<UpdatedExtensionPermissionsInfo>(details).ptr(); + if (!info->permissions->HasAPIPermission( + ExtensionAPIPermission::kBackground)) + break; + + if (info->reason == UpdatedExtensionPermissionsInfo::ADDED) { + OnBackgroundAppInstalled(info->extension); + OnBackgroundAppLoaded(); + } else { + OnBackgroundAppUnloaded(); + OnBackgroundAppUninstalled(); + } + } + break; case content::NOTIFICATION_APP_TERMINATING: // Make sure we aren't still keeping the app alive (only happens if we // don't receive an EXTENSIONS_READY notification for some reason). diff --git a/chrome/browser/extensions/convert_web_app_browsertest.cc b/chrome/browser/extensions/convert_web_app_browsertest.cc index a8405b1..7fe6f94 100644 --- a/chrome/browser/extensions/convert_web_app_browsertest.cc +++ b/chrome/browser/extensions/convert_web_app_browsertest.cc @@ -70,7 +70,7 @@ IN_PROC_BROWSER_TEST_F(ExtensionFromWebAppTest, Basic) { EXPECT_EQ(extension_misc::LAUNCH_PANEL, installed_extension_->launch_container()); - ASSERT_EQ(2u, installed_extension_->permission_set()->apis().size()); + ASSERT_EQ(2u, installed_extension_->GetActivePermissions()->apis().size()); EXPECT_TRUE(installed_extension_->HasAPIPermission( ExtensionAPIPermission::kGeolocation)); EXPECT_TRUE(installed_extension_->HasAPIPermission( diff --git a/chrome/browser/extensions/convert_web_app_unittest.cc b/chrome/browser/extensions/convert_web_app_unittest.cc index 0f3b537..640b31b 100644 --- a/chrome/browser/extensions/convert_web_app_unittest.cc +++ b/chrome/browser/extensions/convert_web_app_unittest.cc @@ -123,7 +123,7 @@ TEST(ExtensionFromWebApp, Basic) { EXPECT_EQ(UTF16ToUTF8(web_app.title), extension->name()); EXPECT_EQ(UTF16ToUTF8(web_app.description), extension->description()); EXPECT_EQ(web_app.app_url, extension->GetFullLaunchURL()); - EXPECT_EQ(2u, extension->permission_set()->apis().size()); + EXPECT_EQ(2u, extension->GetActivePermissions()->apis().size()); EXPECT_TRUE(extension->HasAPIPermission("geolocation")); EXPECT_TRUE(extension->HasAPIPermission("notifications")); ASSERT_EQ(1u, extension->web_extent().patterns().size()); @@ -167,7 +167,7 @@ TEST(ExtensionFromWebApp, Minimal) { EXPECT_EQ("", extension->description()); EXPECT_EQ(web_app.app_url, extension->GetFullLaunchURL()); EXPECT_EQ(0u, extension->icons().map().size()); - EXPECT_EQ(0u, extension->permission_set()->apis().size()); + EXPECT_EQ(0u, extension->GetActivePermissions()->apis().size()); ASSERT_EQ(1u, extension->web_extent().patterns().size()); EXPECT_EQ("*://aaronboodman.com/*", extension->web_extent().patterns().begin()->GetAsString()); diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index c8cbf9e..15529b2 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -32,6 +32,7 @@ #include "chrome/browser/extensions/extension_module.h" #include "chrome/browser/extensions/extension_omnibox_api.h" #include "chrome/browser/extensions/extension_page_actions_module.h" +#include "chrome/browser/extensions/extension_permissions_api.h" #include "chrome/browser/extensions/extension_preference_api.h" #include "chrome/browser/extensions/extension_processes_api.h" #include "chrome/browser/extensions/extension_proxy_api.h" @@ -393,6 +394,12 @@ void FactoryRegistry::ResetFunctions() { // Experimental App API. RegisterFunction<AppNotifyFunction>(); RegisterFunction<AppClearAllNotificationsFunction>(); + + // Permissions + RegisterFunction<ContainsPermissionsFunction>(); + RegisterFunction<GetAllPermissionsFunction>(); + RegisterFunction<RemovePermissionsFunction>(); + RegisterFunction<RequestPermissionsFunction>(); } void FactoryRegistry::GetAllNames(std::vector<std::string>* names) { diff --git a/chrome/browser/extensions/extension_install_ui.cc b/chrome/browser/extensions/extension_install_ui.cc index 62d904a..bdbd839 100644 --- a/chrome/browser/extensions/extension_install_ui.cc +++ b/chrome/browser/extensions/extension_install_ui.cc @@ -39,22 +39,32 @@ // static const int ExtensionInstallUI::kTitleIds[NUM_PROMPT_TYPES] = { IDS_EXTENSION_INSTALL_PROMPT_TITLE, - IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE + IDS_EXTENSION_RE_ENABLE_PROMPT_TITLE, + IDS_EXTENSION_PERMISSIONS_PROMPT_TITLE }; // static const int ExtensionInstallUI::kHeadingIds[NUM_PROMPT_TYPES] = { IDS_EXTENSION_INSTALL_PROMPT_HEADING, - IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING + IDS_EXTENSION_RE_ENABLE_PROMPT_HEADING, + IDS_EXTENSION_PERMISSIONS_PROMPT_HEADING }; // static const int ExtensionInstallUI::kButtonIds[NUM_PROMPT_TYPES] = { IDS_EXTENSION_PROMPT_INSTALL_BUTTON, - IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON + IDS_EXTENSION_PROMPT_RE_ENABLE_BUTTON, + IDS_EXTENSION_PROMPT_PERMISSIONS_BUTTON +}; +// static +const int ExtensionInstallUI::kAbortButtonIds[NUM_PROMPT_TYPES] = { + 0, + 0, + IDS_EXTENSION_PROMPT_PERMISSIONS_ABORT_BUTTON }; // static const int ExtensionInstallUI::kWarningIds[NUM_PROMPT_TYPES] = { IDS_EXTENSION_PROMPT_WILL_HAVE_ACCESS_TO, - IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO + IDS_EXTENSION_PROMPT_WILL_NOW_HAVE_ACCESS_TO, + IDS_EXTENSION_PROMPT_WANTS_ACCESS_TO, }; namespace { @@ -112,6 +122,7 @@ void ExtensionInstallUI::ConfirmInstall(Delegate* delegate, const Extension* extension) { DCHECK(ui_loop_ == MessageLoop::current()); extension_ = extension; + permissions_ = extension->GetActivePermissions(); delegate_ = delegate; // We special-case themes to not show any confirm UI. Instead they are @@ -129,11 +140,24 @@ void ExtensionInstallUI::ConfirmReEnable(Delegate* delegate, const Extension* extension) { DCHECK(ui_loop_ == MessageLoop::current()); extension_ = extension; + permissions_ = extension->GetActivePermissions(); delegate_ = delegate; ShowConfirmation(RE_ENABLE_PROMPT); } +void ExtensionInstallUI::ConfirmPermissions( + Delegate* delegate, + const Extension* extension, + const ExtensionPermissionSet* permissions) { + DCHECK(ui_loop_ == MessageLoop::current()); + extension_ = extension; + permissions_ = permissions; + delegate_ = delegate; + + ShowConfirmation(PERMISSIONS_PROMPT); +} + void ExtensionInstallUI::OnInstallSuccess(const Extension* extension, SkBitmap* icon) { extension_ = extension; @@ -193,6 +217,7 @@ void ExtensionInstallUI::OnImageLoaded( SetIcon(image); switch (prompt_type_) { + case PERMISSIONS_PROMPT: case RE_ENABLE_PROMPT: case INSTALL_PROMPT: { // TODO(jcivelli): http://crbug.com/44771 We should not show an install @@ -203,7 +228,7 @@ void ExtensionInstallUI::OnImageLoaded( NotificationService::NoDetails()); std::vector<string16> warnings = - extension_->GetPermissionMessageStrings(); + permissions_->GetWarningMessages(); ShowExtensionInstallDialog( profile_, delegate_, extension_, &icon_, warnings, prompt_type_); break; diff --git a/chrome/browser/extensions/extension_install_ui.h b/chrome/browser/extensions/extension_install_ui.h index 222f6a3..19965e5 100644 --- a/chrome/browser/extensions/extension_install_ui.h +++ b/chrome/browser/extensions/extension_install_ui.h @@ -16,6 +16,7 @@ #include "ui/gfx/native_widget_types.h" class Extension; +class ExtensionPermissionSet; class MessageLoop; class Profile; class InfoBarDelegate; @@ -28,6 +29,7 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer { UNSET_PROMPT_TYPE = -1, INSTALL_PROMPT = 0, RE_ENABLE_PROMPT, + PERMISSIONS_PROMPT, NUM_PROMPT_TYPES }; @@ -36,6 +38,7 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer { static const int kHeadingIds[NUM_PROMPT_TYPES]; static const int kButtonIds[NUM_PROMPT_TYPES]; static const int kWarningIds[NUM_PROMPT_TYPES]; + static const int kAbortButtonIds[NUM_PROMPT_TYPES]; class Delegate { public: @@ -65,6 +68,14 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer { // We *MUST* eventually call either Proceed() or Abort() on |delegate|. virtual void ConfirmReEnable(Delegate* delegate, const Extension* extension); + // This is called by the extension permissions API to verify whether an + // extension may be granted additional permissions. + // + // We *MUST* eventually call either Proceed() or Abort() on |delegate|. + virtual void ConfirmPermissions(Delegate* delegate, + const Extension* extension, + const ExtensionPermissionSet* permissions); + // Installation was successful. This is declared virtual for testing. virtual void OnInstallSuccess(const Extension* extension, SkBitmap* icon); @@ -122,6 +133,9 @@ class ExtensionInstallUI : public ImageLoadingTracker::Observer { // The extension we are showing the UI for. const Extension* extension_; + // The permissions being prompted for. + scoped_refptr<const ExtensionPermissionSet> permissions_; + // The delegate we will call Proceed/Abort on after confirmation UI. Delegate* delegate_; diff --git a/chrome/browser/extensions/extension_management_api.cc b/chrome/browser/extensions/extension_management_api.cc index 3955361..8037f56 100644 --- a/chrome/browser/extensions/extension_management_api.cc +++ b/chrome/browser/extensions/extension_management_api.cc @@ -92,7 +92,7 @@ static DictionaryValue* CreateExtensionInfo(const Extension& extension, } const std::set<std::string> perms = - extension.permission_set()->GetAPIsAsStrings(); + extension.GetActivePermissions()->GetAPIsAsStrings(); ListValue* permission_list = new ListValue(); if (!perms.empty()) { std::set<std::string>::const_iterator perms_iter; @@ -107,7 +107,7 @@ static DictionaryValue* CreateExtensionInfo(const Extension& extension, if (!extension.is_hosted_app()) { // Skip host permissions for hosted apps. const URLPatternSet host_perms = - extension.permission_set()->explicit_hosts(); + extension.GetActivePermissions()->explicit_hosts(); if (!host_perms.is_empty()) { URLPatternSet::const_iterator host_perms_iter; for (host_perms_iter = host_perms.begin(); diff --git a/chrome/browser/extensions/extension_permissions_api.cc b/chrome/browser/extensions/extension_permissions_api.cc new file mode 100644 index 0000000..a9a6d23 --- /dev/null +++ b/chrome/browser/extensions/extension_permissions_api.cc @@ -0,0 +1,374 @@ +// 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* PackPermissionsToValue(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. At that point, + // we could also shared this code with ExtensionPermissionSet methods in + // ExtensionPrefs. + + 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 UnpackPermissionsFromValue(DictionaryValue* value, + scoped_refptr<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 = 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) { + scoped_refptr<const ExtensionPermissionSet> existing( + extension->GetActivePermissions()); + scoped_refptr<ExtensionPermissionSet> total( + ExtensionPermissionSet::CreateUnion(existing, permissions)); + scoped_refptr<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. + extension_service_->GrantPermissions(extension); + + NotifyPermissionsUpdated(extension, total.get(), added.get(), ADDED); +} + +void ExtensionPermissionsManager::RemovePermissions( + const Extension* extension, const ExtensionPermissionSet* permissions) { + scoped_refptr<const ExtensionPermissionSet> existing( + extension->GetActivePermissions()); + scoped_refptr<ExtensionPermissionSet> total( + ExtensionPermissionSet::CreateDifference(existing, permissions)); + scoped_refptr<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* changed_permissions) { + Profile* profile = extension_service_->profile(); + if (profile && profile->GetExtensionEventRouter()) { + ListValue value; + value.Append(PackPermissionsToValue(changed_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) { + if (!changed || changed->IsEmpty()) + return; + + 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_whitelist; + api_whitelist.insert(ExtensionAPIPermission::kClipboardRead); + api_whitelist.insert(ExtensionAPIPermission::kClipboardWrite); + api_whitelist.insert(ExtensionAPIPermission::kNotification); + api_whitelist.insert(ExtensionAPIPermission::kBookmark); + api_whitelist.insert(ExtensionAPIPermission::kContextMenus); + api_whitelist.insert(ExtensionAPIPermission::kCookie); + api_whitelist.insert(ExtensionAPIPermission::kDebugger); + api_whitelist.insert(ExtensionAPIPermission::kHistory); + api_whitelist.insert(ExtensionAPIPermission::kIdle); + api_whitelist.insert(ExtensionAPIPermission::kTab); + api_whitelist.insert(ExtensionAPIPermission::kManagement); + api_whitelist.insert(ExtensionAPIPermission::kBackground); + whitelist_ = new ExtensionPermissionSet( + api_whitelist, URLPatternSet(), URLPatternSet()); +} + +bool ContainsPermissionsFunction::RunImpl() { + DictionaryValue* args = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); + std::string error; + if (!args) + return false; + + scoped_refptr<ExtensionPermissionSet> permissions; + if (!UnpackPermissionsFromValue(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(PackPermissionsToValue( + GetExtension()->GetActivePermissions())); + return true; +} + +bool RemovePermissionsFunction::RunImpl() { + DictionaryValue* args = NULL; + EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args)); + if (!args) + return false; + + scoped_refptr<ExtensionPermissionSet> permissions; + if (!UnpackPermissionsFromValue(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_refptr<ExtensionPermissionSet> unsupported( + ExtensionPermissionSet::CreateDifference( + permissions.get(), &perms_manager->whitelist())); + 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_refptr<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 (!UnpackPermissionsFromValue( + 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_refptr<ExtensionPermissionSet> unsupported( + ExtensionPermissionSet::CreateDifference( + requested_permissions_.get(), &perms_manager->whitelist())); + 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. + 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; + } + + // Filter out the granted permissions so we only prompt for new ones. + requested_permissions_ = ExtensionPermissionSet::CreateDifference( + requested_permissions_.get(), granted); + + // 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. All extension types but INTERNAL + // are allowed to silently increase their permission level. + 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_ = NULL; + + SendResponse(true); + Release(); +} diff --git a/chrome/browser/extensions/extension_permissions_api.h b/chrome/browser/extensions/extension_permissions_api.h new file mode 100644 index 0000000..15b0769 --- /dev/null +++ b/chrome/browser/extensions/extension_permissions_api.h @@ -0,0 +1,117 @@ +// 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_H__ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_H__ +#pragma once + +#include "chrome/browser/extensions/extension_function.h" +#include "chrome/browser/extensions/extension_install_ui.h" +#include "chrome/common/extensions/extension_permission_set.h" +#include "chrome/common/chrome_notification_types.h" +#include "content/browser/renderer_host/render_process_host.h" +#include "content/common/notification_service.h" + +namespace base { +class DictionaryValue; +} +class Extension; +class ExtensionPermissionSet; +class ExtensionService; +class Profile; + +class ExtensionPermissionsManager { + public: + explicit ExtensionPermissionsManager(ExtensionService* extension_service); + ~ExtensionPermissionsManager(); + + // Adds the set of |permissions| to the |extension|'s active permission set + // and sends the relevant messages and notifications. This method assumes the + // user has already been prompted, if necessary, for the extra permissions. + void AddPermissions(const Extension* extension, + const ExtensionPermissionSet* permissions); + + // Removes the set of |permissions| from the |extension|'s active permission + // set and sends the relevant messages and notifications. + void RemovePermissions(const Extension* extension, + const ExtensionPermissionSet* permissions); + + // Returns the list of API permissions that are supported by the optional + // permissions API. + const ExtensionPermissionSet& whitelist() const { return *whitelist_; } + + private: + enum EventType { + ADDED, + REMOVED, + }; + + // Dispatches specified event to the extension. + void DispatchEvent(const std::string& extension_id, + const char* event_name, + const ExtensionPermissionSet* changed_permissions); + + // Issues the relevant events, messages and notifications when the permissions + // have changed for the |extension| (|changed| is the permission delta, while + // |active| is the new permission set). Specifically, this sends the + // EXTENSION_PERMISSIONS_UPDATED notification, the + // ExtensionMsg_UpdatePermissions IPC message, and fires the onAdded/onRemoved + // events in the extension. + void NotifyPermissionsUpdated(const Extension* extension, + const ExtensionPermissionSet* active, + const ExtensionPermissionSet* changed, + EventType event_type); + + // Registers the list of APIs supported by the optional permissions API. + void RegisterWhitelist(); + + ExtensionService* extension_service_; + scoped_refptr<ExtensionPermissionSet> whitelist_; +}; + + +// chrome.permissions.contains +class ContainsPermissionsFunction : public SyncExtensionFunction { + virtual ~ContainsPermissionsFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.permissions.contains") +}; + +// chrome.permissions.getAll +class GetAllPermissionsFunction : public SyncExtensionFunction { + virtual ~GetAllPermissionsFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.permissions.getAll") +}; + +// chrome.permissions.remove +class RemovePermissionsFunction : public SyncExtensionFunction { + virtual ~RemovePermissionsFunction() {} + virtual bool RunImpl() OVERRIDE; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.permissions.remove") +}; + +// chrome.permissions.request +class RequestPermissionsFunction : public AsyncExtensionFunction, + public ExtensionInstallUI::Delegate { + public: + // FOR TESTS ONLY to bypass the confirmation UI. + static void SetAutoConfirmForTests(bool should_proceed); + + // Implementing ExtensionInstallUI::Delegate interface. + virtual void InstallUIProceed() OVERRIDE; + virtual void InstallUIAbort(bool user_initiated) OVERRIDE; + + protected: + virtual ~RequestPermissionsFunction() {} + virtual bool RunImpl() OVERRIDE; + + private: + scoped_ptr<ExtensionInstallUI> install_ui_; + scoped_refptr<ExtensionPermissionSet> requested_permissions_; + const Extension* extension_; + DECLARE_EXTENSION_FUNCTION_NAME("experimental.permissions.request") +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_H__ diff --git a/chrome/browser/extensions/extension_permissions_api_constants.cc b/chrome/browser/extensions/extension_permissions_api_constants.cc new file mode 100644 index 0000000..5d75ccd --- /dev/null +++ b/chrome/browser/extensions/extension_permissions_api_constants.cc @@ -0,0 +1,23 @@ +// 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_constants.h" + +namespace extension_permissions_module_constants { + +const char kApisKey[] = "permissions"; + +const char kCantRemoveRequiredPermissionsError[] = + "You cannot remove required permissions."; +const char kNotInOptionalPermissionsError[] = + "Optional permissions must be listed in extension manifest."; +const char kNotWhitelistedError[] = + "The optional permissions API does not support '%s'."; +const char kUnknownPermissionError[] = + "'%s' is not a recognized permission."; + +const char kOnAdded[] = "experimental.permissions.onAdded"; +const char kOnRemoved[] = "experimental.permissions.onRemoved"; + +}; // namespace extension_permissions_module_constants diff --git a/chrome/browser/extensions/extension_permissions_api_constants.h b/chrome/browser/extensions/extension_permissions_api_constants.h new file mode 100644 index 0000000..64b5116 --- /dev/null +++ b/chrome/browser/extensions/extension_permissions_api_constants.h @@ -0,0 +1,28 @@ +// 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. + +// Constants used for the Tabs API and the Windows API. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_CONSTANTS_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_CONSTANTS_H_ +#pragma once + +namespace extension_permissions_module_constants { + +// Keys used in serializing permissions data and events. +extern const char kApisKey[]; + +// Error messages. +extern const char kCantRemoveRequiredPermissionsError[]; +extern const char kNotInOptionalPermissionsError[]; +extern const char kNotWhitelistedError[]; +extern const char kUnknownPermissionError[]; + +// Event names. +extern const char kOnAdded[]; +extern const char kOnRemoved[]; + +}; // namespace extension_permissions_module_constants + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_PERMISSIONS_API_CONSTANTS_H_ diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index 334b64f..e0a8d6e 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -102,11 +102,15 @@ const char kBrowserActionVisible[] = "browser_action_visible"; // Preferences that hold which permissions the user has granted the extension. // We explicitly keep track of these so that extensions can contain unknown // permissions, for backwards compatibility reasons, and we can still prompt -// the user to accept them once recognized. -const char kPrefGrantedAPIs[] = "granted_permissions.api"; -const char kPrefGrantedExplicitHosts[] = "granted_permissions.explicit_host"; -const char kPrefGrantedScriptableHosts[] = - "granted_permissions.scriptable_host"; +// the user to accept them once recognized. We store the active permission +// permissions because they may differ from those defined in the manifest. +const char kPrefActivePermissions[] = "active_permissions"; +const char kPrefGrantedPermissions[] = "granted_permissions"; + +// The preference names for ExtensionPermissionSet values. +const char kPrefAPIs[] = "api"; +const char kPrefExplicitHosts[] = "explicit_host"; +const char kPrefScriptableHosts[] = "scriptable_host"; // The preference names for the old granted permissions scheme. const char kPrefOldGrantedFullAccess[] = "granted_permissions.full"; @@ -206,6 +210,10 @@ class ScopedExtensionControlledPrefUpdate : public DictionaryPrefUpdate { DISALLOW_COPY_AND_ASSIGN(ScopedExtensionControlledPrefUpdate); }; +std::string JoinPrefs(std::string parent, const char* child) { + return parent + "." + child; +} + } // namespace ExtensionPrefs::ExtensionPrefs( @@ -423,6 +431,75 @@ void ExtensionPrefs::SetExtensionPrefURLPatternSet( UpdateExtensionPref(extension_id, pref_key, value); } +ExtensionPermissionSet* ExtensionPrefs::ReadExtensionPrefPermissionSet( + const std::string& extension_id, + const std::string& pref_key) { + if (!GetExtensionPref(extension_id)) + return NULL; + + // Retrieve the API permissions. + ExtensionAPIPermissionSet apis; + const ListValue* api_values = NULL; + std::string api_pref = JoinPrefs(pref_key, kPrefAPIs); + if (ReadExtensionPrefList(extension_id, api_pref, &api_values)) { + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + for (size_t i = 0; i < api_values->GetSize(); ++i) { + std::string permission_name; + if (api_values->GetString(i, &permission_name)) { + ExtensionAPIPermission *permission = info->GetByName(permission_name); + if (permission) + apis.insert(permission->id()); + } + } + } + + // Retrieve the explicit host permissions. + URLPatternSet explicit_hosts; + ReadExtensionPrefURLPatternSet( + extension_id, JoinPrefs(pref_key, kPrefExplicitHosts), + &explicit_hosts, Extension::kValidHostPermissionSchemes); + + // Retrieve the scriptable host permissions. + URLPatternSet scriptable_hosts; + ReadExtensionPrefURLPatternSet( + extension_id, JoinPrefs(pref_key, kPrefScriptableHosts), + &scriptable_hosts, UserScript::kValidUserScriptSchemes); + + return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); +} + +void ExtensionPrefs::SetExtensionPrefPermissionSet( + const std::string& extension_id, + const std::string& pref_key, + const ExtensionPermissionSet* new_value) { + // Set the API permissions. + ListValue* api_values = new ListValue(); + ExtensionAPIPermissionSet apis = new_value->apis(); + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); + std::string api_pref = JoinPrefs(pref_key, kPrefAPIs); + for (ExtensionAPIPermissionSet::const_iterator i = apis.begin(); + i != apis.end(); ++i) { + ExtensionAPIPermission* perm = info->GetByID(*i); + if (perm) + api_values->Append(Value::CreateStringValue(perm->name())); + } + UpdateExtensionPref(extension_id, api_pref, api_values); + + // Set the explicit host permissions. + if (!new_value->explicit_hosts().is_empty()) { + SetExtensionPrefURLPatternSet(extension_id, + JoinPrefs(pref_key, kPrefExplicitHosts), + new_value->explicit_hosts()); + } + + // Set the scriptable host permissions. + if (!new_value->scriptable_hosts().is_empty()) { + SetExtensionPrefURLPatternSet(extension_id, + JoinPrefs(pref_key, kPrefScriptableHosts), + new_value->scriptable_hosts()); + } +} + void ExtensionPrefs::SavePrefs() { prefs_->ScheduleSavePersistentPrefs(); } @@ -638,7 +715,9 @@ void ExtensionPrefs::MigratePermissions(const ExtensionIdSet& extension_ids) { ListValue* apis = NULL; ListValue* new_apis = NULL; - if (ext->GetList(kPrefGrantedAPIs, &apis)) + std::string granted_apis = + JoinPrefs(kPrefGrantedPermissions, kPrefAPIs); + if (ext->GetList(kPrefOldGrantedAPIs, &apis)) new_apis = apis->DeepCopy(); else new_apis = new ListValue(); @@ -646,7 +725,7 @@ void ExtensionPrefs::MigratePermissions(const ExtensionIdSet& extension_ids) { std::string plugin_name = info->GetByID( ExtensionAPIPermission::kPlugin)->name(); new_apis->Append(Value::CreateStringValue(plugin_name)); - UpdateExtensionPref(*ext_id, kPrefGrantedAPIs, new_apis); + UpdateExtensionPref(*ext_id, granted_apis, new_apis); } // The granted permissions originally only held the effective hosts, @@ -656,9 +735,11 @@ void ExtensionPrefs::MigratePermissions(const ExtensionIdSet& extension_ids) { // new effective hosts will be the same, so we move them to explicit // host permissions. ListValue* hosts; + std::string explicit_hosts = + JoinPrefs(kPrefGrantedPermissions, kPrefExplicitHosts); if (ext->GetList(kPrefOldGrantedHosts, &hosts)) { UpdateExtensionPref( - *ext_id, kPrefGrantedExplicitHosts, hosts->DeepCopy()); + *ext_id, explicit_hosts, hosts->DeepCopy()); // We can get rid of the old one by setting it to an empty list. UpdateExtensionPref(*ext_id, kPrefOldGrantedHosts, new ListValue()); @@ -669,39 +750,7 @@ void ExtensionPrefs::MigratePermissions(const ExtensionIdSet& extension_ids) { ExtensionPermissionSet* ExtensionPrefs::GetGrantedPermissions( const std::string& extension_id) { CHECK(Extension::IdIsValid(extension_id)); - - const DictionaryValue* ext = GetExtensionPref(extension_id); - if (!ext) - return NULL; - - // Retrieve the API permissions. - ExtensionAPIPermissionSet apis; - const ListValue* api_values = NULL; - if (ReadExtensionPrefList(extension_id, kPrefGrantedAPIs, &api_values)) { - ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); - for (size_t i = 0; i < api_values->GetSize(); ++i) { - std::string permission_name; - if (api_values->GetString(i, &permission_name)) { - ExtensionAPIPermission *permission = info->GetByName(permission_name); - if (permission) - apis.insert(permission->id()); - } - } - } - - // Retrieve the explicit host permissions. - URLPatternSet explicit_hosts; - ReadExtensionPrefURLPatternSet( - extension_id, kPrefGrantedExplicitHosts, - &explicit_hosts, Extension::kValidHostPermissionSchemes); - - // Retrieve the scriptable host permissions. - URLPatternSet scriptable_hosts; - ReadExtensionPrefURLPatternSet( - extension_id, kPrefGrantedScriptableHosts, - &scriptable_hosts, UserScript::kValidUserScriptSchemes); - - return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); + return ReadExtensionPrefPermissionSet(extension_id, kPrefGrantedPermissions); } void ExtensionPrefs::AddGrantedPermissions( @@ -709,40 +758,30 @@ void ExtensionPrefs::AddGrantedPermissions( const ExtensionPermissionSet* permissions) { CHECK(Extension::IdIsValid(extension_id)); - scoped_ptr<ExtensionPermissionSet> granted_permissions( + scoped_refptr<ExtensionPermissionSet> granted_permissions( GetGrantedPermissions(extension_id)); // The new granted permissions are the union of the already granted // permissions and the newly granted permissions. - scoped_ptr<ExtensionPermissionSet> new_perms( + scoped_refptr<ExtensionPermissionSet> new_perms( ExtensionPermissionSet::CreateUnion( permissions, granted_permissions.get())); - // Set the API permissions. - ListValue* api_values = new ListValue(); - ExtensionAPIPermissionSet apis = new_perms->apis(); - ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); - for (ExtensionAPIPermissionSet::const_iterator i = apis.begin(); - i != apis.end(); ++i) { - ExtensionAPIPermission* perm = info->GetByID(*i); - if (perm) - api_values->Append(Value::CreateStringValue(perm->name())); - } - UpdateExtensionPref(extension_id, kPrefGrantedAPIs, api_values); + SetExtensionPrefPermissionSet( + extension_id, kPrefGrantedPermissions, new_perms.get()); +} - // Set the explicit host permissions. - if (!new_perms->explicit_hosts().is_empty()) { - SetExtensionPrefURLPatternSet(extension_id, - kPrefGrantedExplicitHosts, - new_perms->explicit_hosts()); - } +ExtensionPermissionSet* ExtensionPrefs::GetActivePermissions( + const std::string& extension_id) { + CHECK(Extension::IdIsValid(extension_id)); + return ReadExtensionPrefPermissionSet(extension_id, kPrefActivePermissions); +} - // Set the scriptable host permissions. - if (!new_perms->scriptable_hosts().is_empty()) { - SetExtensionPrefURLPatternSet(extension_id, - kPrefGrantedScriptableHosts, - new_perms->scriptable_hosts()); - } +void ExtensionPrefs::SetActivePermissions( + const std::string& extension_id, + const ExtensionPermissionSet* permissions) { + SetExtensionPrefPermissionSet( + extension_id, kPrefActivePermissions, permissions); } bool ExtensionPrefs::IsIncognitoEnabled(const std::string& extension_id) { diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h index 6b5ffd4..92f8672 100644 --- a/chrome/browser/extensions/extension_prefs.h +++ b/chrome/browser/extensions/extension_prefs.h @@ -187,6 +187,16 @@ class ExtensionPrefs : public ExtensionContentSettingsStore::Observer { void AddGrantedPermissions(const std::string& extension_id, const ExtensionPermissionSet* permissions); + // Gets the active permission set for the specified extension. This may + // differ from the permissions in the manifest due to the optional + // permissions API. This passes ownership of the set to the caller. + ExtensionPermissionSet* GetActivePermissions( + const std::string& extension_id); + + // Sets the active |permissions| for the extension with |extension_id|. + void SetActivePermissions(const std::string& extension_id, + const ExtensionPermissionSet* permissions); + // Returns true if the user enabled this extension to be loaded in incognito // mode. bool IsIncognitoEnabled(const std::string& extension_id); @@ -409,6 +419,18 @@ class ExtensionPrefs : public ExtensionContentSettingsStore::Observer { const std::string& pref_key, const URLPatternSet& new_value); + // Interprets |pref_key| in |extension_id|'s preferences as an + // ExtensionPermissionSet, and passes ownership of the set to the caller. + ExtensionPermissionSet* ReadExtensionPrefPermissionSet( + const std::string& extension_id, + const std::string& pref_key); + + // Converts the |new_value| to its value and sets the |pref_key| pref + // belonging to |extension_id|. + void SetExtensionPrefPermissionSet(const std::string& extension_id, + const std::string& pref_key, + const ExtensionPermissionSet* new_value); + // Returns a dictionary for extension |id|'s prefs or NULL if it doesn't // exist. const base::DictionaryValue* GetExtensionPref(const std::string& id) const; diff --git a/chrome/browser/extensions/extension_prefs_unittest.cc b/chrome/browser/extensions/extension_prefs_unittest.cc index f582077..2ed6588 100644 --- a/chrome/browser/extensions/extension_prefs_unittest.cc +++ b/chrome/browser/extensions/extension_prefs_unittest.cc @@ -219,32 +219,32 @@ class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { ExtensionAPIPermissionSet empty_set; URLPatternSet empty_extent; - scoped_ptr<ExtensionPermissionSet> permissions; - scoped_ptr<ExtensionPermissionSet> granted_permissions; + scoped_refptr<ExtensionPermissionSet> permissions; + scoped_refptr<ExtensionPermissionSet> granted_permissions; // Make sure both granted api and host permissions start empty. - granted_permissions.reset( - prefs()->GetGrantedPermissions(extension_id_)); + granted_permissions = + prefs()->GetGrantedPermissions(extension_id_); EXPECT_TRUE(granted_permissions->IsEmpty()); - permissions.reset(new ExtensionPermissionSet( - api_perm_set1_, empty_extent, empty_extent)); + permissions = new ExtensionPermissionSet( + api_perm_set1_, empty_extent, empty_extent); // Add part of the api permissions. prefs()->AddGrantedPermissions(extension_id_, permissions.get()); - granted_permissions.reset(prefs()->GetGrantedPermissions(extension_id_)); + granted_permissions = prefs()->GetGrantedPermissions(extension_id_); EXPECT_TRUE(granted_permissions.get()); EXPECT_FALSE(granted_permissions->IsEmpty()); EXPECT_EQ(expected_apis, granted_permissions->apis()); EXPECT_TRUE(granted_permissions->effective_hosts().is_empty()); EXPECT_FALSE(granted_permissions->HasEffectiveFullAccess()); - granted_permissions.reset(); + granted_permissions = NULL; // Add part of the explicit host permissions. - permissions.reset(new ExtensionPermissionSet( - empty_set, ehost_perm_set1_, empty_extent)); + permissions = new ExtensionPermissionSet( + empty_set, ehost_perm_set1_, empty_extent); prefs()->AddGrantedPermissions(extension_id_, permissions.get()); - granted_permissions.reset(prefs()->GetGrantedPermissions(extension_id_)); + granted_permissions = prefs()->GetGrantedPermissions(extension_id_); EXPECT_FALSE(granted_permissions->IsEmpty()); EXPECT_FALSE(granted_permissions->HasEffectiveFullAccess()); EXPECT_EQ(expected_apis, granted_permissions->apis()); @@ -254,10 +254,10 @@ class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { granted_permissions->effective_hosts()); // Add part of the scriptable host permissions. - permissions.reset(new ExtensionPermissionSet( - empty_set, empty_extent, shost_perm_set1_)); + permissions = new ExtensionPermissionSet( + empty_set, empty_extent, shost_perm_set1_); prefs()->AddGrantedPermissions(extension_id_, permissions.get()); - granted_permissions.reset(prefs()->GetGrantedPermissions(extension_id_)); + granted_permissions = prefs()->GetGrantedPermissions(extension_id_); EXPECT_FALSE(granted_permissions->IsEmpty()); EXPECT_FALSE(granted_permissions->HasEffectiveFullAccess()); EXPECT_EQ(expected_apis, granted_permissions->apis()); @@ -271,15 +271,15 @@ class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { EXPECT_EQ(effective_permissions_, granted_permissions->effective_hosts()); // Add the rest of both the permissions. - permissions.reset(new ExtensionPermissionSet( - api_perm_set2_, ehost_perm_set2_, shost_perm_set2_)); + permissions = new ExtensionPermissionSet( + api_perm_set2_, ehost_perm_set2_, shost_perm_set2_); std::set_union(expected_apis.begin(), expected_apis.end(), api_perm_set2_.begin(), api_perm_set2_.end(), std::inserter(api_permissions_, api_permissions_.begin())); prefs()->AddGrantedPermissions(extension_id_, permissions.get()); - granted_permissions.reset(prefs()->GetGrantedPermissions(extension_id_)); + granted_permissions = prefs()->GetGrantedPermissions(extension_id_); EXPECT_TRUE(granted_permissions.get()); EXPECT_FALSE(granted_permissions->IsEmpty()); EXPECT_EQ(api_permissions_, granted_permissions->apis()); @@ -294,7 +294,7 @@ class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { } virtual void Verify() { - scoped_ptr<ExtensionPermissionSet> permissions( + scoped_refptr<ExtensionPermissionSet> permissions( prefs()->GetGrantedPermissions(extension_id_)); EXPECT_TRUE(permissions.get()); EXPECT_FALSE(permissions->HasEffectiveFullAccess()); @@ -321,6 +321,54 @@ class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { }; TEST_F(ExtensionPrefsGrantedPermissions, GrantedPermissions) {} +// Tests the SetActivePermissions / GetActivePermissions functions. +class ExtensionPrefsActivePermissions : public ExtensionPrefsTest { + public: + virtual void Initialize() { + extension_id_ = prefs_.AddExtensionAndReturnId("test"); + + ExtensionAPIPermissionSet api_perms; + api_perms.insert(ExtensionAPIPermission::kTab); + api_perms.insert(ExtensionAPIPermission::kBookmark); + api_perms.insert(ExtensionAPIPermission::kHistory); + + URLPatternSet ehosts; + AddPattern(&ehosts, "http://*.google.com/*"); + AddPattern(&ehosts, "http://example.com/*"); + AddPattern(&ehosts, "chrome://favicon/*"); + + URLPatternSet shosts; + AddPattern(&shosts, "https://*.google.com/*"); + AddPattern(&shosts, "http://reddit.com/r/test/*"); + + active_perms_ = new ExtensionPermissionSet(api_perms, ehosts, shosts); + + // Make sure the active permissions start empty. + scoped_refptr<ExtensionPermissionSet> active( + prefs()->GetActivePermissions(extension_id_)); + EXPECT_TRUE(active->IsEmpty()); + + // Set the active permissions. + prefs()->SetActivePermissions(extension_id_, active_perms_.get()); + active = prefs()->GetActivePermissions(extension_id_); + EXPECT_EQ(active_perms_->apis(), active->apis()); + EXPECT_EQ(active_perms_->explicit_hosts(), active->explicit_hosts()); + EXPECT_EQ(active_perms_->scriptable_hosts(), active->scriptable_hosts()); + EXPECT_EQ(*active_perms_, *active); + } + + virtual void Verify() { + scoped_refptr<ExtensionPermissionSet> permissions( + prefs()->GetActivePermissions(extension_id_)); + EXPECT_EQ(*active_perms_, *permissions); + } + + private: + std::string extension_id_; + scoped_refptr<ExtensionPermissionSet> active_perms_; +}; +TEST_F(ExtensionPrefsActivePermissions, SetAndGetActivePermissions) {} + // Tests the GetVersionString function. class ExtensionPrefsVersionString : public ExtensionPrefsTest { public: diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 5ff972e..72667a1 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -552,6 +552,7 @@ ExtensionService::ExtensionService(Profile* profile, show_extensions_prompts_(true), ready_(false), toolbar_model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), + permissions_manager_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), apps_promo_(profile->GetPrefs()), event_routers_initialized_(false) { CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -974,11 +975,13 @@ void ExtensionService::DisableExtension(const std::string& extension_id) { void ExtensionService::GrantPermissions(const Extension* extension) { CHECK(extension); - // We only maintain the granted permissions prefs for INTERNAL extensions. - CHECK_EQ(Extension::INTERNAL, extension->location()); + // We only maintain the granted permissions prefs for extensions that can't + // silently increase their permissions. + if (extension->CanSilentlyIncreasePermissions()) + return; extension_prefs_->AddGrantedPermissions(extension->id(), - extension->permission_set()); + extension->GetActivePermissions()); } void ExtensionService::GrantPermissionsAndEnableExtension( @@ -991,6 +994,13 @@ void ExtensionService::GrantPermissionsAndEnableExtension( EnableExtension(extension->id()); } +void ExtensionService::UpdateActivePermissions( + const Extension* extension, + const ExtensionPermissionSet* permissions) { + extension_prefs()->SetActivePermissions(extension->id(), permissions); + extension->SetActivePermissions(permissions); +} + void ExtensionService::LoadExtension(const FilePath& extension_path) { BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, NewRunnableMethod(backend_.get(), @@ -1356,7 +1366,8 @@ void ExtensionService::NotifyExtensionLoaded(const Extension* extension) { if (host->profile()->GetOriginalProfile() == profile_->GetOriginalProfile()) { host->Send( - new ExtensionMsg_Loaded(ExtensionMsg_Loaded_Params(extension))); + new ExtensionMsg_Loaded(ExtensionMsg_Loaded_Params( + extension, extension->GetActivePermissions()))); } } @@ -1964,7 +1975,7 @@ void ExtensionService::AddExtension(const Extension* extension) { // Check if the extension's privileges have changed and disable the // extension if necessary. - DisableIfPrivilegeIncrease(extension); + InitializePermissions(extension); bool disabled = Extension::UserMayDisable(extension->location()) && extension_prefs_->GetExtensionState(extension->id()) == @@ -1985,7 +1996,35 @@ void ExtensionService::AddExtension(const Extension* extension) { NotifyExtensionLoaded(extension); } -void ExtensionService::DisableIfPrivilegeIncrease(const Extension* extension) { +void ExtensionService::InitializePermissions(const Extension* extension) { + // 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<ExtensionPermissionSet> active_permissions = + extension_prefs()->GetActivePermissions(extension->id()); + + if (active_permissions.get()) { + // 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<ExtensionPermissionSet> total_permissions = + ExtensionPermissionSet::CreateUnion( + extension->required_permission_set(), + extension->optional_permission_set()); + + // Make sure the active permissions contain no more than optional + default. + scoped_refptr<ExtensionPermissionSet> adjusted_active = + ExtensionPermissionSet::CreateIntersection( + total_permissions.get(), active_permissions.get()); + + // Make sure the active permissions contain the default permissions. + adjusted_active = ExtensionPermissionSet::CreateUnion( + extension->required_permission_set(), adjusted_active.get()); + + UpdateActivePermissions(extension, adjusted_active); + } + // We keep track of all permissions the user has granted each extension. // This allows extensions to gracefully support backwards compatibility // by including unknown permissions in their manifests. When the user @@ -2012,13 +2051,13 @@ void ExtensionService::DisableIfPrivilegeIncrease(const Extension* extension) { bool is_extension_upgrade = old != NULL; bool is_privilege_increase = false; - // We only record the granted permissions for INTERNAL extensions, since - // they can't silently increase privileges. - if (extension->location() == Extension::INTERNAL) { + // We only need to compare the granted permissions to the current permissions + // if the extension is not allowed to silently increase its permissions. + if (!extension->CanSilentlyIncreasePermissions()) { // Add all the recognized permissions if the granted permissions list // hasn't been initialized yet. - scoped_ptr<ExtensionPermissionSet> granted_permissions( - extension_prefs_->GetGrantedPermissions(extension->id())); + scoped_refptr<ExtensionPermissionSet> granted_permissions = + extension_prefs_->GetGrantedPermissions(extension->id()); CHECK(granted_permissions.get()); // Here, we check if an extension's privileges have increased in a manner @@ -2026,7 +2065,8 @@ void ExtensionService::DisableIfPrivilegeIncrease(const Extension* extension) { // upgraded and recognized additional privileges, or an extension upgrades // to a version that requires additional privileges. is_privilege_increase = - granted_permissions->HasLessPrivilegesThan(extension->permission_set()); + granted_permissions->HasLessPrivilegesThan( + extension->GetActivePermissions()); } if (is_extension_upgrade) { @@ -2378,7 +2418,8 @@ void ExtensionService::Observe(int type, // Loaded extensions. for (size_t i = 0; i < extensions_.size(); ++i) { process->Send(new ExtensionMsg_Loaded( - ExtensionMsg_Loaded_Params(extensions_[i]))); + ExtensionMsg_Loaded_Params( + extensions_[i], extensions_[i]->GetActivePermissions()))); } break; } diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h index 4c74f0a..901726f 100644 --- a/chrome/browser/extensions/extension_service.h +++ b/chrome/browser/extensions/extension_service.h @@ -26,6 +26,7 @@ #include "chrome/browser/extensions/extension_icon_manager.h" #include "chrome/browser/extensions/extension_menu_manager.h" #include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/extensions/extension_permissions_api.h" #include "chrome/browser/extensions/extension_process_manager.h" #include "chrome/browser/extensions/extension_toolbar_model.h" #include "chrome/browser/extensions/extensions_quota_service.h" @@ -320,6 +321,10 @@ class ExtensionService // extension. void GrantPermissionsAndEnableExtension(const Extension* extension); + // Sets the |extension|'s active permissions to |permissions|. + void UpdateActivePermissions(const Extension* extension, + const ExtensionPermissionSet* permissions); + // Loads the extension from the directory |extension_path|. void LoadExtension(const FilePath& extension_path); @@ -405,9 +410,9 @@ class ExtensionService void OnExtensionInstalled( const Extension* extension, bool from_webstore); - // Checks if the privileges requested by |extension| have increased, and if - // so, disables the extension and prompts the user to approve the change. - void DisableIfPrivilegeIncrease(const Extension* extension); + // Initializes the |extension|'s active permission set and disables the + // extension if the privilege level has increased (e.g., due to an upgrade). + void InitializePermissions(const Extension* extension); // Go through each extensions in pref, unload blacklisted extensions // and update the blacklist state in pref. @@ -468,6 +473,10 @@ class ExtensionService return &app_notification_manager_; } + ExtensionPermissionsManager* permissions_manager() { + return &permissions_manager_; + } + ExtensionBrowserEventRouter* browser_event_router() { return browser_event_router_.get(); } @@ -586,7 +595,6 @@ class ExtensionService bool include_disabled, bool include_terminated) const; - // Adds the given extension to the list of terminated extensions if // it is not already there and unloads it. void TrackTerminatedExtension(const Extension* extension); @@ -708,6 +716,9 @@ class ExtensionService // Keeps track of app notifications. AppNotificationManager app_notification_manager_; + // Keeps track of extension permissions. + ExtensionPermissionsManager permissions_manager_; + // Keeps track of favicon-sized omnibox icons for extensions. ExtensionIconManager omnibox_icon_manager_; ExtensionIconManager omnibox_popup_icon_manager_; diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc index bad22a1..c1a18d1 100644 --- a/chrome/browser/extensions/extension_service_unittest.cc +++ b/chrome/browser/extensions/extension_service_unittest.cc @@ -1023,7 +1023,8 @@ TEST_F(ExtensionServiceTest, LoadAllExtensionsFromDirectorySuccess) { expected_patterns.ClearPatterns(); AddPattern(&expected_patterns, "http://*.google.com/*"); AddPattern(&expected_patterns, "https://*.google.com/*"); - EXPECT_EQ(expected_patterns, extension->permission_set()->explicit_hosts()); + EXPECT_EQ(expected_patterns, + extension->GetActivePermissions()->explicit_hosts()); EXPECT_EQ(std::string(good1), loaded_[1]->id()); EXPECT_EQ(std::string("My extension 2"), loaded_[1]->name()); @@ -1364,7 +1365,7 @@ TEST_F(ExtensionServiceTest, GrantedPermissions) { // Make sure there aren't any granted permissions before the // extension is installed. - scoped_ptr<ExtensionPermissionSet> known_perms( + scoped_refptr<ExtensionPermissionSet> known_perms( prefs->GetGrantedPermissions(permissions_crx)); EXPECT_FALSE(known_perms.get()); @@ -1383,7 +1384,7 @@ TEST_F(ExtensionServiceTest, GrantedPermissions) { AddPattern(&expected_host_perms, "http://*.google.com.hk/*"); AddPattern(&expected_host_perms, "http://www.example.com/*"); - known_perms.reset(prefs->GetGrantedPermissions(extension_id)); + known_perms = prefs->GetGrantedPermissions(extension_id); EXPECT_TRUE(known_perms.get()); EXPECT_FALSE(known_perms->IsEmpty()); EXPECT_EQ(expected_api_perms, known_perms->apis()); @@ -1412,7 +1413,7 @@ TEST_F(ExtensionServiceTest, GrantedFullAccessPermissions) { std::string extension_id = extension->id(); ExtensionPrefs* prefs = service_->extension_prefs(); - scoped_ptr<ExtensionPermissionSet> permissions( + scoped_refptr<ExtensionPermissionSet> permissions( prefs->GetGrantedPermissions(extension_id)); EXPECT_FALSE(permissions->IsEmpty()); EXPECT_TRUE(permissions->HasEffectiveFullAccess()); @@ -1477,7 +1478,7 @@ TEST_F(ExtensionServiceTest, GrantedAPIAndHostPermissions) { ASSERT_TRUE(service_->IsExtensionEnabled(extension_id)); ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id)); - scoped_ptr<ExtensionPermissionSet> current_perms( + scoped_refptr<ExtensionPermissionSet> current_perms( prefs->GetGrantedPermissions(extension_id)); ASSERT_TRUE(current_perms.get()); ASSERT_FALSE(current_perms->IsEmpty()); @@ -1490,7 +1491,7 @@ TEST_F(ExtensionServiceTest, GrantedAPIAndHostPermissions) { // updating the browser to a version which recognizes additional host // permissions). host_permissions.clear(); - current_perms.reset(); + current_perms = NULL; host_permissions.insert("http://*.google.com/*"); host_permissions.insert("https://*.google.com/*"); @@ -1519,7 +1520,7 @@ TEST_F(ExtensionServiceTest, GrantedAPIAndHostPermissions) { ASSERT_TRUE(service_->IsExtensionEnabled(extension_id)); ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id)); - current_perms.reset(prefs->GetGrantedPermissions(extension_id)); + current_perms = prefs->GetGrantedPermissions(extension_id); ASSERT_TRUE(current_perms.get()); ASSERT_FALSE(current_perms->IsEmpty()); ASSERT_FALSE(current_perms->HasEffectiveFullAccess()); diff --git a/chrome/browser/extensions/permissions_apitest.cc b/chrome/browser/extensions/permissions_apitest.cc index e298a093..def0595 100644 --- a/chrome/browser/extensions/permissions_apitest.cc +++ b/chrome/browser/extensions/permissions_apitest.cc @@ -1,9 +1,14 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// 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_apitest.h" +#include "chrome/browser/extensions/extension_prefs.h" +#include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/ui/browser.h" #include "chrome/common/chrome_switches.h" +#include "chrome/common/extensions/extension_permission_set.h" class ExperimentalApiTest : public ExtensionApiTest { public: @@ -47,3 +52,35 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, FaviconPermission) { IN_PROC_BROWSER_TEST_F(ExtensionApiTest, AlwaysAllowed) { ASSERT_TRUE(RunExtensionTest("permissions/always_allowed")) << message_; } + +// Tests that the optional permissions API works correctly. +IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsGranted) { + // Mark all the tested APIs as granted to bypass the confirmation UI. + ExtensionAPIPermissionSet apis; + apis.insert(ExtensionAPIPermission::kTab); + apis.insert(ExtensionAPIPermission::kManagement); + apis.insert(ExtensionAPIPermission::kPermissions); + scoped_refptr<ExtensionPermissionSet> granted_permissions = + new ExtensionPermissionSet(apis, URLPatternSet(), URLPatternSet()); + + ExtensionPrefs* prefs = + browser()->profile()->GetExtensionService()->extension_prefs(); + prefs->AddGrantedPermissions("kjmkgkdkpedkejedfhmfcenooemhbpbo", + granted_permissions); + + EXPECT_TRUE(RunExtensionTest("permissions/optional")) << message_; +} + +// Tests that the optional permissions API works correctly. +IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsAutoConfirm) { + // Rather than setting the granted permissions, set the UI autoconfirm flag + // and run the same tests. + RequestPermissionsFunction::SetAutoConfirmForTests(true); + EXPECT_TRUE(RunExtensionTest("permissions/optional")) << message_; +} + +// Test that denying the optional permissions confirmation dialog works. +IN_PROC_BROWSER_TEST_F(ExperimentalApiTest, OptionalPermissionsDeny) { + RequestPermissionsFunction::SetAutoConfirmForTests(false); + EXPECT_TRUE(RunExtensionTest("permissions/optional_deny")) << message_; +} diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.h b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.h index 8c7818d..2617a53 100644 --- a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.h +++ b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.h @@ -37,6 +37,7 @@ class Profile; scoped_nsobject<NSString> title_; scoped_nsobject<NSString> warnings_; scoped_nsobject<NSString> button_; + scoped_nsobject<NSString> cancel_button_; scoped_nsobject<NSString> subtitle_; SkBitmap icon_; } diff --git a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm index 1c8306a..0e79076f 100644 --- a/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm +++ b/chrome/browser/ui/cocoa/extensions/extension_install_dialog_controller.mm @@ -92,6 +92,9 @@ void OffsetControlVertically(NSControl* control, CGFloat amount) { retain]); button_.reset([l10n_util::GetNSString(ExtensionInstallUI::kButtonIds[type]) retain]); + int cancel_id = ExtensionInstallUI::kAbortButtonIds[type]; + cancel_button_.reset([l10n_util::GetNSString( + cancel_id > 0 ? cancel_id : IDS_CANCEL) retain]); // We display the warnings as a simple text string, separated by newlines. if (!warnings.empty()) { @@ -132,6 +135,7 @@ void OffsetControlVertically(NSControl* control, CGFloat amount) { [titleField_ setStringValue:title_.get()]; [subtitleField_ setStringValue:subtitle_.get()]; [okButton_ setTitle:button_.get()]; + [cancelButton_ setTitle:cancel_button_.get()]; NSImage* image = gfx::SkBitmapToNSImage(icon_); [iconView_ setImage:image]; diff --git a/chrome/browser/ui/gtk/extensions/extension_install_dialog_gtk.cc b/chrome/browser/ui/gtk/extensions/extension_install_dialog_gtk.cc index cd2e99a..a49bc0a 100644 --- a/chrome/browser/ui/gtk/extensions/extension_install_dialog_gtk.cc +++ b/chrome/browser/ui/gtk/extensions/extension_install_dialog_gtk.cc @@ -62,8 +62,11 @@ void ShowInstallDialog(GtkWindow* parent, parent, GTK_DIALOG_MODAL, NULL); - GtkWidget* close_button = gtk_dialog_add_button(GTK_DIALOG(dialog), - GTK_STOCK_CANCEL, GTK_RESPONSE_CLOSE); + int cancel = ExtensionInstallUI::kAbortButtonIds[type]; + GtkWidget* close_button = gtk_dialog_add_button( + GTK_DIALOG(dialog), + cancel > 0 ? l10n_util::GetStringUTF8(cancel).c_str(): GTK_STOCK_CANCEL, + GTK_RESPONSE_CLOSE); gtk_dialog_add_button( GTK_DIALOG(dialog), l10n_util::GetStringUTF8(ExtensionInstallUI::kButtonIds[type]).c_str(), diff --git a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc index 1bf6d8f..b5c8a1ee 100644 --- a/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc +++ b/chrome/browser/ui/views/extensions/extension_install_dialog_view.cc @@ -275,8 +275,10 @@ std::wstring ExtensionInstallDialogView::GetDialogButtonLabel( case MessageBoxFlags::DIALOGBUTTON_OK: return UTF16ToWide( l10n_util::GetStringUTF16(ExtensionInstallUI::kButtonIds[type_])); - case MessageBoxFlags::DIALOGBUTTON_CANCEL: - return UTF16ToWide(l10n_util::GetStringUTF16(IDS_CANCEL)); + case MessageBoxFlags::DIALOGBUTTON_CANCEL: { + int id = ExtensionInstallUI::kAbortButtonIds[type_]; + return UTF16ToWide(l10n_util::GetStringUTF16(id > 0 ? id : IDS_CANCEL)); + } default: NOTREACHED(); return std::wstring(); diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 8b6d0a4..56ce107 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1002,6 +1002,10 @@ 'browser/extensions/extension_page_actions_module.h', 'browser/extensions/extension_page_actions_module_constants.cc', 'browser/extensions/extension_page_actions_module_constants.h', + 'browser/extensions/extension_permissions_api.cc', + 'browser/extensions/extension_permissions_api.h', + 'browser/extensions/extension_permissions_api_constants.cc', + 'browser/extensions/extension_permissions_api_constants.h', 'browser/extensions/extension_preference_api.cc', 'browser/extensions/extension_preference_api.h', 'browser/extensions/extension_preference_api_constants.cc', diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h index 2472eb5..515373b 100644 --- a/chrome/common/chrome_notification_types.h +++ b/chrome/common/chrome_notification_types.h @@ -428,6 +428,10 @@ enum { // details are an Extension*, and the source is a Profile*. NOTIFICATION_EXTENSION_UPDATE_DISABLED, + // Sent when an extension's permissions change. The details are an + // UpdatedExtensionPermissionsInfo, and the source is a Profile. + NOTIFICATION_EXTENSION_PERMISSIONS_UPDATED, + // Sent when an extension is about to be installed so we can (in the case of // themes) alert the user with a loading dialog. The source is the download // manager and the details are the download url. diff --git a/chrome/common/extensions/api/extension_api.json b/chrome/common/extensions/api/extension_api.json index f3f8559..3e032aa 100644 --- a/chrome/common/extensions/api/extension_api.json +++ b/chrome/common/extensions/api/extension_api.json @@ -1060,6 +1060,143 @@ ] }, { + "namespace": "experimental.permissions", + "types": [ + { + "id": "Permissions", + "type": "object", + "properties": { + "permissions": { + "type": "array", + "items": {"type": "string"}, + "optional": true, + "description": "List of named permissions (does not include hosts or origins)." + } + } + } + ], + "events": [ + { + "name": "onAdded", + "type": "function", + "unprivileged": true, + "description": "Fired when the extension acquires new permissions.", + "parameters": [ + { + "$ref": "Permissions", + "name": "permissions", + "description": "The newly acquired permissions." + } + ] + }, + { + "name": "onRemoved", + "type": "function", + "unprivileged": true, + "description": "Fired when access to permissions has been removed from the extension.", + "parameters": [ + { + "$ref": "Permissions", + "name": "permissions", + "description": "The permissions that have been removed." + } + ] + } + ], + "functions": [ + { + "name": "getAll", + "type": "function", + "unprivileged": true, + "description": "Gets the extension's current set of permissions.", + "parameters": [ + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "permissions", + "$ref": "Permissions", + "description": "The extension's active permissions." + } + ] + } + ] + }, + { + "name": "contains", + "type": "function", + "unprivileged": true, + "description": "Checks if the extension has the specified permissions.", + "parameters": [ + { + "name": "permissions", + "$ref": "Permissions" + }, + { + "name": "callback", + "type": "function", + "parameters": [ + { + "name": "result", + "type": "boolean", + "description": "True if the extension has the specified permissions." + } + ] + } + ] + }, + { + "name": "request", + "type": "function", + "unprivileged": true, + "description": "Requests access to the specified permissions. These permissions must be defined in the optional_permissions field of the manifest.", + "parameters": [ + { + "name": "permissions", + "$ref": "Permissions" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "granted", + "type": "boolean", + "description": "True if the user granted the specified permissions." + } + ] + } + ] + }, + { + "name": "remove", + "type": "function", + "unprivileged": true, + "description": "Removes access to the specified permissions.", + "parameters": [ + { + "name": "permissions", + "$ref": "Permissions" + }, + { + "name": "callback", + "type": "function", + "optional": true, + "parameters": [ + { + "name": "removed", + "type": "boolean", + "description": "True if the permissions were removed." + } + ] + } + ] + } + ] + }, + { "namespace": "tabs", "types": [ { diff --git a/chrome/common/extensions/docs/experimental.html b/chrome/common/extensions/docs/experimental.html index 49aa374..8c62aec 100644 --- a/chrome/common/extensions/docs/experimental.html +++ b/chrome/common/extensions/docs/experimental.html @@ -328,6 +328,7 @@ on the following experimental APIs: <a href="experimental.devtools.panels.html">experimental.devtools.panels</a></li><li> <a href="experimental.devtools.resources.html">experimental.devtools.resources</a></li><li> <a href="experimental.infobars.html">experimental.infobars</a></li><li> + <a href="experimental.permissions.html">experimental.permissions</a></li><li> <a href="experimental.processes.html">experimental.processes</a></li><li> <a href="experimental.sidebar.html">experimental.sidebar</a></li><li> <a href="experimental.webNavigation.html">experimental.webNavigation</a></li><li> diff --git a/chrome/common/extensions/docs/experimental.permissions.html b/chrome/common/extensions/docs/experimental.permissions.html new file mode 100644 index 0000000..b167ee9 --- /dev/null +++ b/chrome/common/extensions/docs/experimental.permissions.html @@ -0,0 +1,1784 @@ +<!DOCTYPE html><!-- This page is a placeholder for generated extensions api doc. Note: + 1) The <head> information in this page is significant, should be uniform + across api docs and should be edited only with knowledge of the + templating mechanism. + 3) All <body>.innerHTML is genereated as an rendering step. If viewed in a + browser, it will be re-generated from the template, json schema and + authored overview content. + 4) The <body>.innerHTML is also generated by an offline step so that this + page may easily be indexed by search engines. +--><html xmlns="http://www.w3.org/1999/xhtml"><head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link href="css/ApiRefStyles.css" rel="stylesheet" type="text/css"> + <link href="css/print.css" rel="stylesheet" type="text/css" media="print"> + <script type="text/javascript" src="../../../third_party/jstemplate/jstemplate_compiled.js"> + </script> + <script type="text/javascript" src="js/api_page_generator.js"></script> + <script type="text/javascript" src="js/bootstrap.js"></script> + <script type="text/javascript" src="js/sidebar.js"></script> + <title>chrome.experimental.permissions - Google Chrome Extensions - Google Code</title></head> + <body> <div id="gc-container" class="labs"> + <div id="devModeWarning"> + You are viewing extension docs in chrome via the 'file:' scheme: are you expecting to see local changes when you refresh? You'll need run chrome with --allow-file-access-from-files. + </div> + <!-- SUBTEMPLATES: DO NOT MOVE FROM THIS LOCATION --> + <!-- In particular, sub-templates that recurse, must be used by allowing + jstemplate to make a copy of the template in this section which + are not operated on by way of the jsskip="true" --> + <div style="display:none"> + + <!-- VALUE --> + <div id="valueTemplate"> + <dt> + <var>paramName</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span class="enum">enumerated</span> + <span id="typeTemplate"> + <span> + <a> Type</a> + </span> + <span> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd> + Description of this parameter from the json schema. + </dd> + <dd> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd> + <div></div> + </dd> + + </div> <!-- /VALUE --> + + <div id="functionParametersTemplate"> + <h5>Parameters</h5> + <dl> + <div> + <div> + </div> + </div> + </dl> + </div> + </div> <!-- /SUBTEMPLATES --> + + <a id="top"></a> + <div id="skipto"> + <a href="#gc-pagecontent">Skip to page content</a> + <a href="#gc-toc">Skip to main navigation</a> + </div> + <!-- API HEADER --> + <table id="header" width="100%" cellspacing="0" border="0"> + <tbody><tr> + <td valign="middle"><a href="http://code.google.com/"><img src="images/code_labs_logo.gif" height="43" width="161" alt="Google Code Labs" style="border:0; margin:0;"></a></td> + <td valign="middle" width="100%" style="padding-left:0.6em;"> + <form action="http://www.google.com/cse" id="cse" style="margin-top:0.5em"> + <div id="gsc-search-box"> + <input type="hidden" name="cx" value="002967670403910741006:61_cvzfqtno"> + <input type="hidden" name="ie" value="UTF-8"> + <input type="text" name="q" value="" size="55"> + <input class="gsc-search-button" type="submit" name="sa" value="Search"> + <br> + <span class="greytext">e.g. "page action" or "tabs"</span> + </div> + </form> + + <script type="text/javascript" src="http://www.google.com/jsapi"></script> + <script type="text/javascript">google.load("elements", "1", {packages: "transliteration"});</script> + <script type="text/javascript" src="http://www.google.com/coop/cse/t13n?form=cse&t13n_langs=en"></script> + <script type="text/javascript" src="http://www.google.com/coop/cse/brand?form=cse&lang=en"></script> + </td> + </tr> + </tbody></table> + + <div id="codesiteContent" class=""> + + <a id="gc-topnav-anchor"></a> + <div id="gc-topnav"> + <h1>Google Chrome Extensions (<a href="http://code.google.com/labs/">Labs</a>)</h1> + <ul id="home" class="gc-topnav-tabs"> + <li id="home_link"> + <a href="index.html" title="Google Chrome Extensions home page">Home</a> + </li> + <li id="docs_link"> + <a href="docs.html" title="Official Google Chrome Extensions documentation">Docs</a> + </li> + <li id="faq_link"> + <a href="faq.html" title="Answers to frequently asked questions about Google Chrome Extensions">FAQ</a> + </li> + <li id="samples_link"> + <a href="samples.html" title="Sample extensions (with source code)">Samples</a> + </li> + <li id="group_link"> + <a href="http://groups.google.com/a/chromium.org/group/chromium-extensions" title="Google Chrome Extensions developer forum">Group</a> + </li> + </ul> + </div> <!-- end gc-topnav --> + + <div class="g-section g-tpl-170"> + <!-- SIDENAV --> + <div class="g-unit g-first" id="gc-toc"> + <ul> + <li><a href="getstarted.html">Getting Started</a></li> + <li><a href="overview.html">Overview</a></li> + <li><a href="whats_new.html">What's New?</a></li> + <li><h2><a href="devguide.html">Developer's Guide</a></h2> + <ul> + <li>Browser UI + <ul> + <li><a href="browserAction.html">Browser Actions</a></li> + <li><a href="contextMenus.html">Context Menus</a></li> + <li><a href="notifications.html">Desktop Notifications</a></li> + <li><a href="omnibox.html">Omnibox</a></li> + <li><a href="options.html">Options Pages</a></li> + <li><a href="override.html">Override Pages</a></li> + <li><a href="pageAction.html">Page Actions</a></li> + </ul> + </li> + <li>Browser Interaction + <ul> + <li><a href="bookmarks.html">Bookmarks</a></li> + <li><a href="cookies.html">Cookies</a></li> + <li><a href="events.html">Events</a></li> + <li><a href="history.html">History</a></li> + <li><a href="management.html">Management</a></li> + <li><a href="tabs.html">Tabs</a></li> + <li><a href="windows.html">Windows</a></li> + </ul> + </li> + <li>Implementation + <ul> + <li><a href="a11y.html">Accessibility</a></li> + <li><a href="background_pages.html">Background Pages</a></li> + <li><a href="content_scripts.html">Content Scripts</a></li> + <li><a href="xhr.html">Cross-Origin XHR</a></li> + <li><a href="idle.html">Idle</a></li> + <li><a href="i18n.html">Internationalization</a></li> + <li><a href="messaging.html">Message Passing</a></li> + <li><a href="npapi.html">NPAPI Plugins</a></li> + </ul> + </li> + <li>Finishing + <ul> + <li><a href="hosting.html">Hosting</a></li> + <li><a href="external_extensions.html">Other Deployment Options</a></li> + </ul> + </li> + </ul> + </li> + <li><h2><a href="apps.html">Packaged Apps</a></h2></li> + <li><h2><a href="tutorials.html">Tutorials</a></h2> + <ul> + <li><a href="tut_debugging.html">Debugging</a></li> + <li><a href="tut_analytics.html">Google Analytics</a></li> + <li><a href="tut_oauth.html">OAuth</a></li> + </ul> + </li> + <li><h2>Reference</h2> + <ul> + <li>Formats + <ul> + <li><a href="manifest.html">Manifest Files</a></li> + <li><a href="match_patterns.html">Match Patterns</a></li> + </ul> + </li> + <li><a href="permission_warnings.html">Permission Warnings</a></li> + <li><a href="api_index.html">chrome.* APIs</a></li> + <li><a href="api_other.html">Other APIs</a></li> + </ul> + </li> + <li><h2><a href="samples.html">Samples</a></h2></li> + <div class="line"> </div> + <li><h2>More</h2> + <ul> + <li><a href="http://code.google.com/chrome/webstore/docs/index.html">Chrome Web Store</a></li> + <li><a href="http://code.google.com/chrome/apps/docs/developers_guide.html">Hosted Apps</a></li> + <li><a href="themes.html">Themes</a></li> + </ul> + </li> + </ul> + </div> + <script> + initToggles(); + </script> + + <div class="g-unit" id="gc-pagecontent"> + <div id="pageTitle"> + <h1 class="page_title">chrome.experimental.permissions</h1> + </div> + <!-- TABLE OF CONTENTS --> + <div id="toc"> + <h2>Contents</h2> + <ol> + <li style="display: none; "> + <a>h2Name</a> + <ol> + <li> + <a>h3Name</a> + </li> + </ol> + </li> + <li> + <a href="#apiReference">API reference: chrome.experimental.permissions</a> + <ol> + <li style="display: none; "> + <a href="#properties">Properties</a> + <ol> + <li> + <a href="#property-anchor">propertyName</a> + </li> + </ol> + </li> + <li> + <a href="#global-methods">Methods</a> + <ol> + <li> + <a href="#method-contains">contains</a> + </li><li> + <a href="#method-getAll">getAll</a> + </li><li> + <a href="#method-remove">remove</a> + </li><li> + <a href="#method-request">request</a> + </li> + </ol> + </li> + <li> + <a href="#global-events">Events</a> + <ol> + <li> + <a href="#event-onAdded">onAdded</a> + </li><li> + <a href="#event-onRemoved">onRemoved</a> + </li> + </ol> + </li> + <li> + <a href="#types">Types</a> + <ol> + <li> + <a href="#type-Permissions">Permissions</a> + </li> + </ol> + </li> + </ol> + </li> + </ol> + </div> + <!-- /TABLE OF CONTENTS --> + + <!-- Standard content lead-in for experimental API pages --> + <p id="classSummary"> + For information on how to use experimental APIs, see the <a href="experimental.html">chrome.experimental.* APIs</a> page. + </p> + + <!-- STATIC CONTENT PLACEHOLDER --> + <div id="static"></div> + + <!-- API PAGE --> + <div class="apiPage"> + <a name="apiReference"></a> + <h2>API reference: chrome.experimental.permissions</h2> + + <!-- PROPERTIES --> + <div class="apiGroup" style="display: none; "> + <a name="properties"></a> + <h3 id="properties">Properties</h3> + + <div> + <a></a> + <h4>getLastError</h4> + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.extension</span><span>lastError</span> + </div> + <div> + </div> + </div> + + </div> <!-- /apiGroup --> + + <!-- METHODS --> + <div id="methodsTemplate" class="apiGroup"> + <a name="global-methods"></a> + <h3>Methods</h3> + + <!-- iterates over all functions --> + <div class="apiItem"> + <a name="method-contains"></a> <!-- method-anchor --> + <h4>contains</h4> + + <div class="summary"><span style="display: none; ">void</span> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.permissions.contains</span>(<span class="null"><span style="display: none; ">, </span><span>Permissions</span> + <var><span>permissions</span></var></span><span class="null"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Checks if the extension has the specified permissions.</p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>function</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + + <!-- RETURNS --> + <h4 style="display: none; ">Returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <p style="display: none; "> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>boolean result</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>result</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>boolean</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>True if the extension has the specified permissions.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + </div> + + <!-- MIN_VERSION --> + <p style="display: none; "> + This function was added in version <b><span></span></b>. + If you require this function, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </p> + </div> <!-- /description --> + + </div><div class="apiItem"> + <a name="method-getAll"></a> <!-- method-anchor --> + <h4>getAll</h4> + + <div class="summary"><span style="display: none; ">void</span> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.permissions.getAll</span>(<span class="null"><span style="display: none; ">, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Gets the extension's current set of permissions.</p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>callback</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>function</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + + <!-- RETURNS --> + <h4 style="display: none; ">Returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <p style="display: none; "> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>Permissions permissions</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>The extension's active permissions.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + </div> + + <!-- MIN_VERSION --> + <p style="display: none; "> + This function was added in version <b><span></span></b>. + If you require this function, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </p> + </div> <!-- /description --> + + </div><div class="apiItem"> + <a name="method-remove"></a> <!-- method-anchor --> + <h4>remove</h4> + + <div class="summary"><span style="display: none; ">void</span> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.permissions.remove</span>(<span class="null"><span style="display: none; ">, </span><span>Permissions</span> + <var><span>permissions</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Removes access to the specified permissions.</p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>function</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + + <!-- RETURNS --> + <h4 style="display: none; ">Returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p style="display: none; "> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>boolean removed</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>removed</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>boolean</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>True if the permissions were removed.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + </div> + + <!-- MIN_VERSION --> + <p style="display: none; "> + This function was added in version <b><span></span></b>. + If you require this function, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </p> + </div> <!-- /description --> + + </div><div class="apiItem"> + <a name="method-request"></a> <!-- method-anchor --> + <h4>request</h4> + + <div class="summary"><span style="display: none; ">void</span> + <!-- Note: intentionally longer 80 columns --> + <span>chrome.experimental.permissions.request</span>(<span class="null"><span style="display: none; ">, </span><span>Permissions</span> + <var><span>permissions</span></var></span><span class="optional"><span>, </span><span>function</span> + <var><span>callback</span></var></span>)</div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Requests access to the specified permissions. These permissions must be defined in the optional_permissions field of the manifest.</p> + + <!-- PARAMETERS --> + <h4>Parameters</h4> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div><div> + <div> + <dt> + <var>callback</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>function</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + + <!-- RETURNS --> + <h4 style="display: none; ">Returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + <!-- CALLBACK --> + <div> + <div> + <h4>Callback function</h4> + <p style="display: none; "> + The callback <em>parameter</em> should specify a function + that looks like this: + </p> + <p> + If you specify the <em>callback</em> parameter, it should + specify a function that looks like this: + </p> + + <!-- Note: intentionally longer 80 columns --> + <pre>function(<span>boolean granted</span>) <span class="subdued">{...}</span>;</pre> + <dl> + <div> + <div> + <dt> + <var>granted</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>boolean</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>True if the user granted the specified permissions.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + </div> + + <!-- MIN_VERSION --> + <p style="display: none; "> + This function was added in version <b><span></span></b>. + If you require this function, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </p> + </div> <!-- /description --> + + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + <!-- EVENTS --> + <div id="eventsTemplate" class="apiGroup"> + <a name="global-events"></a> + <h3>Events</h3> + <!-- iterates over all events --> + <div class="apiItem"> + <a name="event-onAdded"></a> + <h4>onAdded</h4> + + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span class="subdued">chrome.experimental.permissions.</span><span>onAdded</span><span class="subdued">.addListener</span>(function(<span>Permissions permissions</span>) <span class="subdued">{...}</span><span></span>)); + </div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Fired when the extension acquires new permissions.</p> + + <!-- LISTENER PARAMETERS --> + <div> + <h4>Listener parameters</h4> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>The newly acquired permissions.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + + <!-- EXTRA PARAMETERS --> + <div style="display: none; "> + <h4>Extra parameters to addListener</h4> + <dl> + <div> + <div> + </div> + </div> + </dl> + </div> + + <!-- LISTENER RETURN VALUE --> + <h4 style="display: none; ">Listener returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + </div> <!-- /description --> + </div><div class="apiItem"> + <a name="event-onRemoved"></a> + <h4>onRemoved</h4> + + <div class="summary"> + <!-- Note: intentionally longer 80 columns --> + <span class="subdued">chrome.experimental.permissions.</span><span>onRemoved</span><span class="subdued">.addListener</span>(function(<span>Permissions permissions</span>) <span class="subdued">{...}</span><span></span>)); + </div> + + <div class="description"> + <p class="todo" style="display: none; ">Undocumented.</p> + <p>Fired when access to permissions has been removed from the extension.</p> + + <!-- LISTENER PARAMETERS --> + <div> + <h4>Listener parameters</h4> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span> + <a href="experimental.permissions.html#type-Permissions">Permissions</a> + </span> + <span style="display: none; "> + <span> + array of <span><span></span></span> + </span> + <span>paramType</span> + <span></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>The permissions that have been removed.</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </div> + + <!-- EXTRA PARAMETERS --> + <div style="display: none; "> + <h4>Extra parameters to addListener</h4> + <dl> + <div> + <div> + </div> + </div> + </dl> + </div> + + <!-- LISTENER RETURN VALUE --> + <h4 style="display: none; ">Listener returns</h4> + <dl> + <div style="display: none; "> + <div> + </div> + </div> + </dl> + + </div> <!-- /description --> + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + <!-- TYPES --> + <div class="apiGroup"> + <a name="types"></a> + <h3 id="types">Types</h3> + + <!-- iterates over all types --> + <div class="apiItem"> + <a name="type-Permissions"></a> + <h4>Permissions</h4> + + <div> + <dt> + <var style="display: none; ">paramName</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional" style="display: none; ">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>object</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo"> + Undocumented. + </dd> + <dd style="display: none; "> + Description of this parameter from the json schema. + </dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd> + <dl> + <div> + <div> + <dt> + <var>permissions</var> + <em> + + <!-- TYPE --> + <div style="display:inline"> + ( + <span class="optional">optional</span> + <span class="enum" style="display: none; ">enumerated</span> + <span id="typeTemplate"> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span> + array of <span><span> + <span style="display: none; "> + <a> Type</a> + </span> + <span> + <span style="display: none; "> + array of <span><span></span></span> + </span> + <span>string</span> + <span style="display: none; "></span> + </span> + </span></span> + </span> + <span style="display: none; ">paramType</span> + <span style="display: none; "></span> + </span> + </span> + ) + </div> + + </em> + </dt> + <dd class="todo" style="display: none; "> + Undocumented. + </dd> + <dd>List of named permissions (does not include hosts or origins).</dd> + <dd style="display: none; "> + This parameter was added in version + <b><span></span></b>. + You must omit this parameter in earlier versions, + and you may omit it in any version. If you require this + parameter, the manifest key + <a href="manifest.html#minimum_chrome_version">minimum_chrome_version</a> + can ensure that your extension won't be run in an earlier browser version. + </dd> + + <!-- OBJECT PROPERTIES --> + <dd style="display: none; "> + <dl> + <div> + <div> + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + </div> + </dl> + </dd> + + <!-- OBJECT METHODS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- OBJECT EVENT FIELDS --> + <dd style="display: none; "> + <div></div> + </dd> + + <!-- FUNCTION PARAMETERS --> + <dd style="display: none; "> + <div></div> + </dd> + + </div> + + </div> <!-- /apiItem --> + + </div> <!-- /apiGroup --> + + </div> <!-- /apiPage --> + </div> <!-- /gc-pagecontent --> + </div> <!-- /g-section --> + </div> <!-- /codesiteContent --> + <div id="gc-footer" --=""> + <div class="text"> + <p> + Except as otherwise <a href="http://code.google.com/policies.html#restrictions">noted</a>, + the content of this page is licensed under the <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons + Attribution 3.0 License</a>, and code samples are licensed under the + <a rel="license" href="http://code.google.com/google_bsd_license.html">BSD License</a>. + </p> + <p> + ©2011 Google + </p> + +<!-- begin analytics --> +<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script> +<script src="http://www.google-analytics.com/ga.js" type="text/javascript"></script> + +<script type="text/javascript"> + // chrome doc tracking + try { + var engdocs = _gat._getTracker("YT-10763712-2"); + engdocs._trackPageview(); + } catch(err) {} + + // code.google.com site-wide tracking + try { + _uacct="UA-18071-1"; + _uanchor=1; + _uff=0; + urchinTracker(); + } + catch(e) {/* urchinTracker not available. */} +</script> +<!-- end analytics --> + </div> + </div> <!-- /gc-footer --> + </div> <!-- /gc-container --> +</body></html> diff --git a/chrome/common/extensions/docs/samples.json b/chrome/common/extensions/docs/samples.json index 862059d..56dbf21 100644 --- a/chrome/common/extensions/docs/samples.json +++ b/chrome/common/extensions/docs/samples.json @@ -51,6 +51,12 @@ "chrome.experimental.devtools.resources.onFinished": "experimental.devtools.resources.html#event-onFinished", "chrome.experimental.devtools.resources.onNavigation": "experimental.devtools.resources.html#event-onNavigation", "chrome.experimental.infobars.show": "experimental.infobars.html#method-show", + "chrome.experimental.permissions.contains": "experimental.permissions.html#method-contains", + "chrome.experimental.permissions.getAll": "experimental.permissions.html#method-getAll", + "chrome.experimental.permissions.onAdded": "experimental.permissions.html#event-onAdded", + "chrome.experimental.permissions.onRemoved": "experimental.permissions.html#event-onRemoved", + "chrome.experimental.permissions.remove": "experimental.permissions.html#method-remove", + "chrome.experimental.permissions.request": "experimental.permissions.html#method-request", "chrome.experimental.processes.getProcessIdForTab": "experimental.processes.html#method-getProcessIdForTab", "chrome.experimental.processes.onUpdated": "experimental.processes.html#event-onUpdated", "chrome.experimental.sidebar.collapse": "experimental.sidebar.html#method-collapse", diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index 111ed76..5488349 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -286,20 +286,6 @@ Extension::Location Extension::GetHigherPriorityLocation( return (loc1_rank > loc2_rank ? loc1 : loc2 ); } -ExtensionPermissionMessages Extension::GetPermissionMessages() const { - if (IsTrustedId(id_)) - return ExtensionPermissionMessages(); - else - return permission_set_->GetPermissionMessages(); -} - -std::vector<string16> Extension::GetPermissionMessageStrings() const { - if (IsTrustedId(id_)) - return std::vector<string16>(); - else - return permission_set_->GetWarningMessages(); -} - void Extension::OverrideLaunchUrl(const GURL& override_url) { GURL new_url(override_url); if (!new_url.is_valid()) { @@ -1202,6 +1188,7 @@ bool Extension::EnsureNotHybridApp(const DictionaryValue* manifest, if (!IsBaseCrxKey(*key) && *key != keys::kApp && *key != keys::kPermissions && + *key != keys::kOptionalPermissions && *key != keys::kOptionsPage && *key != keys::kBackground) { *error = ExtensionErrorUtils::FormatErrorMessage( @@ -1385,13 +1372,16 @@ GURL Extension::GetBaseURLFromExtensionId(const std::string& extension_id) { bool Extension::InitFromValue(const DictionaryValue& source, int flags, std::string* error) { + base::AutoLock auto_lock(runtime_data_lock_); // When strict error checks are enabled, make URL pattern parsing strict. URLPattern::ParseOption parse_strictness = (flags & STRICT_ERROR_CHECKS ? URLPattern::ERROR_ON_PORTS : URLPattern::IGNORE_PORTS); // Initialize permissions with an empty, default permission set. - permission_set_.reset(new ExtensionPermissionSet()); + runtime_data_.SetActivePermissions(new ExtensionPermissionSet()); + optional_permission_set_ = new ExtensionPermissionSet(); + required_permission_set_ = new ExtensionPermissionSet(); if (source.HasKey(keys::kPublicKey)) { std::string public_key_bytes; @@ -1911,104 +1901,30 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, } } + // Initialize the permissions (optional). ExtensionAPIPermissionSet api_permissions; URLPatternSet host_permissions; + if (!ParsePermissions(&source, + keys::kPermissions, + flags, + error, + &api_permissions, + &host_permissions)) { + return false; + } - // Initialize the permissions (optional). - if (source.HasKey(keys::kPermissions)) { - ListValue* permissions = NULL; - if (!source.GetList(keys::kPermissions, &permissions)) { - *error = ExtensionErrorUtils::FormatErrorMessage( - errors::kInvalidPermissions, ""); - return false; - } - - for (size_t i = 0; i < permissions->GetSize(); ++i) { - std::string permission_str; - if (!permissions->GetString(i, &permission_str)) { - *error = ExtensionErrorUtils::FormatErrorMessage( - errors::kInvalidPermission, base::IntToString(i)); - return false; - } - - ExtensionAPIPermission* permission = - ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str); - - // Only COMPONENT extensions can use private APIs. - // TODO(asargent) - We want a more general purpose mechanism for this, - // and better error messages. (http://crbug.com/54013) - if (!IsComponentOnlyPermission(permission) -#ifndef NDEBUG - && !CommandLine::ForCurrentProcess()->HasSwitch( - switches::kExposePrivateExtensionApi) -#endif - ) { - continue; - } - - if (web_extent().is_empty() || location() == Extension::COMPONENT) { - // Check if it's a module permission. If so, enable that permission. - if (permission != NULL) { - // Only allow the experimental API permission if the command line - // flag is present, or if the extension is a component of Chrome. - if (IsDisallowedExperimentalPermission(permission->id()) && - location() != Extension::COMPONENT) { - *error = errors::kExperimentalFlagRequired; - return false; - } - api_permissions.insert(permission->id()); - continue; - } - } else { - // Hosted apps only get access to a subset of the valid permissions. - if (permission != NULL && permission->is_hosted_app()) { - if (IsDisallowedExperimentalPermission(permission->id())) { - *error = errors::kExperimentalFlagRequired; - return false; - } - api_permissions.insert(permission->id()); - continue; - } - } - - // Check if it's a host pattern permission. - URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ? - URLPattern::SCHEME_ALL : kValidHostPermissionSchemes); - - URLPattern::ParseResult parse_result = pattern.Parse(permission_str, - parse_strictness); - if (parse_result == URLPattern::PARSE_SUCCESS) { - if (!CanSpecifyHostPermission(pattern)) { - *error = ExtensionErrorUtils::FormatErrorMessage( - errors::kInvalidPermissionScheme, base::IntToString(i)); - return false; - } - - // The path component is not used for host permissions, so we force it - // to match all paths. - pattern.SetPath("/*"); - - if (pattern.MatchesScheme(chrome::kFileScheme) && - !CanExecuteScriptEverywhere()) { - wants_file_access_ = true; - if (!(flags & ALLOW_FILE_ACCESS)) - pattern.SetValidSchemes( - pattern.valid_schemes() & ~URLPattern::SCHEME_FILE); - } - - host_permissions.AddPattern(pattern); - } - - // If it's not a host permission, then it's probably an unknown API - // permission. Do not throw an error so extensions can retain - // backwards compatability (http://crbug.com/42742). - // TODO(jstritar): We can improve error messages by adding better - // validation of API permissions here. - // TODO(skerner): Consider showing the reason |permission_str| is not - // a valid URL pattern if it is almost valid. For example, if it has - // a valid scheme, and failed to parse because it has a port, show an - // error. - } + // Initialize the optional permissions (optional). + ExtensionAPIPermissionSet optional_api_permissions; + URLPatternSet optional_host_permissions; + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalExtensionApis) && + !ParsePermissions(&source, + keys::kOptionalPermissions, + flags, + error, + &optional_api_permissions, + &optional_host_permissions)) { + return false; } // Initialize background url (optional). @@ -2394,8 +2310,12 @@ bool Extension::InitFromValue(const DictionaryValue& source, int flags, return false; } - permission_set_.reset( - new ExtensionPermissionSet(this, api_permissions, host_permissions)); + runtime_data_.SetActivePermissions(new ExtensionPermissionSet( + this, api_permissions, host_permissions)); + required_permission_set_ = new ExtensionPermissionSet( + this, api_permissions, host_permissions); + optional_permission_set_ = new ExtensionPermissionSet( + optional_api_permissions, optional_host_permissions, URLPatternSet()); // Although |source| is passed in as a const, it's still possible to modify // it. This is dangerous since the utility process re-uses |source| after @@ -2573,6 +2493,118 @@ GURL Extension::GetIconURL(int size, return GetResourceURL(path); } +bool Extension::ParsePermissions(const DictionaryValue* source, + const char* key, + int flags, + std::string* error, + ExtensionAPIPermissionSet* api_permissions, + URLPatternSet* host_permissions) { + if (source->HasKey(key)) { + // When strict error checks are enabled, make URL pattern parsing strict. + URLPattern::ParseOption parse_strictness = + (flags & STRICT_ERROR_CHECKS ? URLPattern::ERROR_ON_PORTS + : URLPattern::IGNORE_PORTS); + ListValue* permissions = NULL; + if (!source->GetList(key, &permissions)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPermissions, ""); + return false; + } + + for (size_t i = 0; i < permissions->GetSize(); ++i) { + std::string permission_str; + if (!permissions->GetString(i, &permission_str)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPermission, base::IntToString(i)); + return false; + } + + ExtensionAPIPermission* permission = + ExtensionPermissionsInfo::GetInstance()->GetByName(permission_str); + + // Only COMPONENT extensions can use private APIs. + // TODO(asargent) - We want a more general purpose mechanism for this, + // and better error messages. (http://crbug.com/54013) + if (!IsComponentOnlyPermission(permission) +#ifndef NDEBUG + && !CommandLine::ForCurrentProcess()->HasSwitch( + switches::kExposePrivateExtensionApi) +#endif + ) { + continue; + } + + if (web_extent().is_empty() || location() == Extension::COMPONENT) { + // Check if it's a module permission. If so, enable that permission. + if (permission != NULL) { + // Only allow the experimental API permission if the command line + // flag is present, or if the extension is a component of Chrome. + if (IsDisallowedExperimentalPermission(permission->id()) && + location() != Extension::COMPONENT) { + *error = errors::kExperimentalFlagRequired; + return false; + } + api_permissions->insert(permission->id()); + continue; + } + } else { + // Hosted apps only get access to a subset of the valid permissions. + if (permission != NULL && permission->is_hosted_app()) { + if (IsDisallowedExperimentalPermission(permission->id())) { + *error = errors::kExperimentalFlagRequired; + return false; + } + api_permissions->insert(permission->id()); + continue; + } + } + + // Check if it's a host pattern permission. + URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ? + URLPattern::SCHEME_ALL : kValidHostPermissionSchemes); + + URLPattern::ParseResult parse_result = pattern.Parse(permission_str, + parse_strictness); + if (parse_result == URLPattern::PARSE_SUCCESS) { + if (!CanSpecifyHostPermission(pattern)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPermissionScheme, base::IntToString(i)); + return false; + } + + // The path component is not used for host permissions, so we force it + // to match all paths. + pattern.SetPath("/*"); + + if (pattern.MatchesScheme(chrome::kFileScheme) && + !CanExecuteScriptEverywhere()) { + wants_file_access_ = true; + if (!(flags & ALLOW_FILE_ACCESS)) + pattern.SetValidSchemes( + pattern.valid_schemes() & ~URLPattern::SCHEME_FILE); + } + + host_permissions->AddPattern(pattern); + } + + // If it's not a host permission, then it's probably an unknown API + // permission. Do not throw an error so extensions can retain + // backwards compatability (http://crbug.com/42742). + // TODO(jstritar): We can improve error messages by adding better + // validation of API permissions here. + // TODO(skerner): Consider showing the reason |permission_str| is not + // a valid URL pattern if it is almost valid. For example, if it has + // a valid scheme, and failed to parse because it has a port, show an + // error. + } + } + return true; +} + +bool Extension::CanSilentlyIncreasePermissions() const { + return location() != INTERNAL; +} + bool Extension::CanSpecifyHostPermission(const URLPattern& pattern) const { if (!pattern.match_all_urls() && pattern.MatchesScheme(chrome::kChromeUIScheme)) { @@ -2588,24 +2620,68 @@ bool Extension::CanSpecifyHostPermission(const URLPattern& pattern) const { bool Extension::HasAPIPermission( ExtensionAPIPermission::ID permission) const { - return permission_set()->HasAPIPermission(permission); + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions()->HasAPIPermission(permission); } bool Extension::HasAPIPermission( const std::string& function_name) const { - return permission_set()->HasAccessToFunction(function_name); + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions()-> + HasAccessToFunction(function_name); } const URLPatternSet& Extension::GetEffectiveHostPermissions() const { - return permission_set()->effective_hosts(); + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions()->effective_hosts(); } bool Extension::HasHostPermission(const GURL& url) const { + base::AutoLock auto_lock(runtime_data_lock_); if (url.SchemeIs(chrome::kChromeUIScheme) && url.host() != chrome::kChromeUIFaviconHost && location() != Extension::COMPONENT) return false; - return permission_set()->HasExplicitAccessToOrigin(url); + return runtime_data_.GetActivePermissions()-> + HasExplicitAccessToOrigin(url); +} + +bool Extension::HasEffectiveAccessToAllHosts() const { + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions()->HasEffectiveAccessToAllHosts(); +} + +bool Extension::HasFullPermissions() const { + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions()->HasEffectiveFullAccess(); +} + +ExtensionPermissionMessages Extension::GetPermissionMessages() const { + base::AutoLock auto_lock(runtime_data_lock_); + if (IsTrustedId(id_)) + return ExtensionPermissionMessages(); + else + return runtime_data_.GetActivePermissions()->GetPermissionMessages(); +} + +std::vector<string16> Extension::GetPermissionMessageStrings() const { + base::AutoLock auto_lock(runtime_data_lock_); + if (IsTrustedId(id_)) + return std::vector<string16>(); + else + return runtime_data_.GetActivePermissions()->GetWarningMessages(); +} + +void Extension::SetActivePermissions( + const ExtensionPermissionSet* permissions) const { + base::AutoLock auto_lock(runtime_data_lock_); + runtime_data_.SetActivePermissions(permissions); +} + +scoped_refptr<const ExtensionPermissionSet> + Extension::GetActivePermissions() const { + base::AutoLock auto_lock(runtime_data_lock_); + return runtime_data_.GetActivePermissions(); } bool Extension::IsComponentOnlyPermission( @@ -2637,6 +2713,7 @@ bool Extension::HasMultipleUISurfaces() const { bool Extension::CanExecuteScriptOnPage(const GURL& page_url, const UserScript* script, std::string* error) const { + base::AutoLock auto_lock(runtime_data_lock_); // The gallery is special-cased as a restricted URL for scripting to prevent // access to special JS bindings we expose to the gallery (and avoid things // like extensions removing the "report abuse" link). @@ -2661,7 +2738,8 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url, // Otherwise, see if this extension has permission to execute script // programmatically on pages. - if (permission_set()->HasExplicitAccessToOrigin(page_url)) + if (runtime_data_.GetActivePermissions()->HasExplicitAccessToOrigin( + page_url)) return true; if (error) { @@ -2672,14 +2750,6 @@ bool Extension::CanExecuteScriptOnPage(const GURL& page_url, return false; } -bool Extension::HasEffectiveAccessToAllHosts() const { - return permission_set_->HasEffectiveAccessToAllHosts(); -} - -bool Extension::HasFullPermissions() const { - return permission_set_->HasEffectiveFullAccess(); -} - bool Extension::ShowConfigureContextMenus() const { // Don't show context menu for component extensions. We might want to show // options for component extension button but now there is no component @@ -2767,11 +2837,26 @@ ExtensionInfo::ExtensionInfo(const DictionaryValue* manifest, ExtensionInfo::~ExtensionInfo() {} +Extension::RuntimeData::RuntimeData() {} +Extension::RuntimeData::RuntimeData(const ExtensionPermissionSet* active) + : active_permissions_(active) {} +Extension::RuntimeData::~RuntimeData() {} + +scoped_refptr<const ExtensionPermissionSet> + Extension::RuntimeData::GetActivePermissions() const { + return active_permissions_; +} + +void Extension::RuntimeData::SetActivePermissions( + const ExtensionPermissionSet* active) { + active_permissions_ = active; +} + UninstalledExtensionInfo::UninstalledExtensionInfo( const Extension& extension) : extension_id(extension.id()), extension_api_permissions( - extension.permission_set()->GetAPIsAsStrings()), + extension.GetActivePermissions()->GetAPIsAsStrings()), extension_type(extension.GetType()), update_url(extension.update_url()) {} @@ -2784,3 +2869,11 @@ UnloadedExtensionInfo::UnloadedExtensionInfo( : reason(reason), already_disabled(false), extension(extension) {} + +UpdatedExtensionPermissionsInfo::UpdatedExtensionPermissionsInfo( + const Extension* extension, + const ExtensionPermissionSet* permissions, + Reason reason) + : reason(reason), + extension(extension), + permissions(permissions) {} diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index a2ee0e7..e46cba1 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -16,6 +16,7 @@ #include "base/memory/linked_ptr.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "base/synchronization/lock.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/common/extensions/extension_icon_set.h" #include "chrome/common/extensions/extension_permission_set.h" @@ -209,15 +210,6 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // its install source should be set to GetHigherPriorityLocation(A, B). static Location GetHigherPriorityLocation(Location loc1, Location loc2); - // Returns the full list of permission messages that this extension - // should display at install time. - ExtensionPermissionMessages GetPermissionMessages() const; - - // Returns the full list of permission messages that this extension - // should display at install time. The messages are returned as strings - // for convenience. - std::vector<string16> GetPermissionMessageStrings() const; - // Icon sizes used by the extension system. static const int kIconSizes[]; @@ -375,11 +367,25 @@ class Extension : public base::RefCountedThreadSafe<Extension> { static void SetScriptingWhitelist(const ScriptingWhitelist& whitelist); static const ScriptingWhitelist* GetScriptingWhitelist(); + // Parses the host and api permissions from the specified permission |key| + // in the manifest |source|. + bool ParsePermissions(const base::DictionaryValue* source, + const char* key, + int flags, + std::string* error, + ExtensionAPIPermissionSet* api_permissions, + URLPatternSet* host_permissions); + bool HasAPIPermission(ExtensionAPIPermission::ID permission) const; bool HasAPIPermission(const std::string& function_name) const; const URLPatternSet& GetEffectiveHostPermissions() const; + // Returns true if the extension can silently increase its permission level. + // Extensions that can silently increase permissions are installed through + // mechanisms that are implicitly trusted. + bool CanSilentlyIncreasePermissions() const; + // Whether or not the extension is allowed permission for a URL pattern from // the manifest. http, https, and chrome://favicon/ is allowed for all // extensions, while component extensions are allowed access to @@ -400,6 +406,21 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // having an NPAPI plugin). bool HasFullPermissions() const; + // Returns the full list of permission messages that this extension + // should display at install time. + ExtensionPermissionMessages GetPermissionMessages() const; + + // Returns the full list of permission messages that this extension + // should display at install time. The messages are returned as strings + // for convenience. + std::vector<string16> GetPermissionMessageStrings() const; + + // Sets the active |permissions|. + void SetActivePermissions(const ExtensionPermissionSet* permissions) const; + + // Gets the extension's active permission set. + scoped_refptr<const ExtensionPermissionSet> GetActivePermissions() const; + // Whether context menu should be shown for page and browser actions. bool ShowConfigureContextMenus() const; @@ -494,8 +515,11 @@ class Extension : public base::RefCountedThreadSafe<Extension> { const GURL& options_url() const { return options_url_; } const GURL& devtools_url() const { return devtools_url_; } const std::vector<GURL>& toolstrips() const { return toolstrips_; } - const ExtensionPermissionSet* permission_set() const { - return permission_set_.get(); + const ExtensionPermissionSet* optional_permission_set() const { + return optional_permission_set_.get(); + } + const ExtensionPermissionSet* required_permission_set() const { + return required_permission_set_.get(); } const GURL& update_url() const { return update_url_; } const ExtensionIconSet& icons() const { return icons_; } @@ -551,6 +575,20 @@ class Extension : public base::RefCountedThreadSafe<Extension> { typedef std::pair<FilePath, std::string> ImageCacheKey; typedef std::map<ImageCacheKey, SkBitmap> ImageCache; + class RuntimeData { + public: + RuntimeData(); + explicit RuntimeData(const ExtensionPermissionSet* active); + ~RuntimeData(); + + void SetActivePermissions(const ExtensionPermissionSet* active); + scoped_refptr<const ExtensionPermissionSet> GetActivePermissions() const; + + private: + friend class base::RefCountedThreadSafe<RuntimeData>; + scoped_refptr<const ExtensionPermissionSet> active_permissions_; + }; + // Normalize the path for use by the extension. On Windows, this will make // sure the drive letter is uppercase. static FilePath MaybeNormalizePath(const FilePath& path); @@ -673,8 +711,15 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // Defines the set of URLs in the extension's web content. URLPatternSet extent_; - // The set of permissions that the extension effectively has access to. - scoped_ptr<ExtensionPermissionSet> permission_set_; + // The extension runtime data. + mutable base::Lock runtime_data_lock_; + mutable RuntimeData runtime_data_; + + // The set of permissions the extension can request at runtime. + scoped_refptr<const ExtensionPermissionSet> optional_permission_set_; + + // The extension's required / default set of permissions. + scoped_refptr<const ExtensionPermissionSet> required_permission_set_; // The icons for the extension. ExtensionIconSet icons_; @@ -870,4 +915,27 @@ struct UnloadedExtensionInfo { UnloadedExtensionInfo(const Extension* extension, Reason reason); }; +// The details sent for EXTENSION_PERMISSIONS_UPDATED notifications. +struct UpdatedExtensionPermissionsInfo { + enum Reason { + ADDED, // The permissions were added to the extension. + REMOVED, // The permissions were removed from the extension. + }; + + Reason reason; + + // The extension who's permissions have changed. + const Extension* extension; + + // The permissions that have changed. For Reason::ADDED, this would contain + // only the permissions that have added, and for Reason::REMOVED, this would + // only contain the removed permissions. + const ExtensionPermissionSet* permissions; + + UpdatedExtensionPermissionsInfo( + const Extension* extension, + const ExtensionPermissionSet* permissions, + Reason reason); +}; + #endif // CHROME_COMMON_EXTENSIONS_EXTENSION_H_ diff --git a/chrome/common/extensions/extension_constants.cc b/chrome/common/extensions/extension_constants.cc index df532ad..a0eb53f 100644 --- a/chrome/common/extensions/extension_constants.cc +++ b/chrome/common/extensions/extension_constants.cc @@ -50,6 +50,7 @@ const char* kNaClModulesMIMEType = "mime_type"; const char* kNaClModulesPath = "path"; const char* kOmnibox = "omnibox"; const char* kOmniboxKeyword = "omnibox.keyword"; +const char* kOptionalPermissions = "optional_permissions"; const char* kOptionsPage = "options_page"; const char* kPageAction = "page_action"; const char* kPageActionDefaultIcon = "default_icon"; diff --git a/chrome/common/extensions/extension_constants.h b/chrome/common/extensions/extension_constants.h index fe2c7bf..5c980e0f 100644 --- a/chrome/common/extensions/extension_constants.h +++ b/chrome/common/extensions/extension_constants.h @@ -55,6 +55,7 @@ namespace extension_manifest_keys { extern const char* kName; extern const char* kOmnibox; extern const char* kOmniboxKeyword; + extern const char* kOptionalPermissions; extern const char* kOptionsPage; extern const char* kPageAction; extern const char* kPageActionDefaultIcon; diff --git a/chrome/common/extensions/extension_messages.cc b/chrome/common/extensions/extension_messages.cc index b388234..fe88896 100644 --- a/chrome/common/extensions/extension_messages.cc +++ b/chrome/common/extensions/extension_messages.cc @@ -24,10 +24,14 @@ ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params( } ExtensionMsg_Loaded_Params::ExtensionMsg_Loaded_Params( - const Extension* extension) + const Extension* extension, + const ExtensionPermissionSet* active) : manifest(new DictionaryValue()), location(extension->location()), path(extension->path()), + apis(active->apis()), + explicit_hosts(active->explicit_hosts()), + scriptable_hosts(active->scriptable_hosts()), id(extension->id()), creation_flags(extension->creation_flags()) { // As we need more bits of extension data in the renderer, add more keys to @@ -65,6 +69,11 @@ scoped_refptr<Extension> return extension; } +const ExtensionPermissionSet* + ExtensionMsg_Loaded_Params::GetActivePermissions() const { + return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); +} + namespace IPC { template <> @@ -101,8 +110,15 @@ bool ParamTraits<URLPattern>::Read(const Message* m, void** iter, !ReadParam(m, iter, &spec)) return false; + // TODO(jstritar): We don't want the URLPattern to fail parsing when the + // scheme is invalid. Instead, the pattern should parse but it should not + // match the invalid patterns. We get around this by setting the valid + // schemes after parsing the pattern. Update these method calls once we can + // ignore scheme validation with URLPattern parse options. crbug.com/90544 + p->SetValidSchemes(URLPattern::SCHEME_ALL); + URLPattern::ParseResult result = p->Parse(spec, URLPattern::IGNORE_PORTS); p->SetValidSchemes(valid_schemes); - return URLPattern::PARSE_SUCCESS == p->Parse(spec, URLPattern::IGNORE_PORTS); + return URLPattern::PARSE_SUCCESS == result; } void ParamTraits<URLPattern>::Log(const param_type& p, std::string* l) { @@ -116,9 +132,7 @@ void ParamTraits<URLPatternSet>::Write(Message* m, const param_type& p) { bool ParamTraits<URLPatternSet>::Read(const Message* m, void** iter, param_type* p) { std::set<URLPattern> patterns; - bool success = - ReadParam(m, iter, &patterns); - if (!success) + if (!ReadParam(m, iter, &patterns)) return false; for (std::set<URLPattern>::iterator i = patterns.begin(); @@ -131,12 +145,35 @@ void ParamTraits<URLPatternSet>::Log(const param_type& p, std::string* l) { LogParam(p.patterns(), l); } +void ParamTraits<ExtensionAPIPermission::ID>::Write( + Message* m, const param_type& p) { + WriteParam(m, static_cast<int>(p)); +} + +bool ParamTraits<ExtensionAPIPermission::ID>::Read( + const Message* m, void** iter, param_type* p) { + int api_id = -2; + if (!ReadParam(m, iter, &api_id)) + return false; + + *p = static_cast<ExtensionAPIPermission::ID>(api_id); + return true; +} + +void ParamTraits<ExtensionAPIPermission::ID>::Log( + const param_type& p, std::string* l) { + LogParam(static_cast<int>(p), l); +} + void ParamTraits<ExtensionMsg_Loaded_Params>::Write(Message* m, const param_type& p) { WriteParam(m, p.location); WriteParam(m, p.path); WriteParam(m, *(p.manifest)); WriteParam(m, p.creation_flags); + WriteParam(m, p.apis); + WriteParam(m, p.explicit_hosts); + WriteParam(m, p.scriptable_hosts); } bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const Message* m, @@ -146,7 +183,10 @@ bool ParamTraits<ExtensionMsg_Loaded_Params>::Read(const Message* m, return ReadParam(m, iter, &p->location) && ReadParam(m, iter, &p->path) && ReadParam(m, iter, p->manifest.get()) && - ReadParam(m, iter, &p->creation_flags); + ReadParam(m, iter, &p->creation_flags) && + ReadParam(m, iter, &p->apis) && + ReadParam(m, iter, &p->explicit_hosts) && + ReadParam(m, iter, &p->scriptable_hosts); } void ParamTraits<ExtensionMsg_Loaded_Params>::Log(const param_type& p, diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h index 53abba6..6d41b9c 100644 --- a/chrome/common/extensions/extension_messages.h +++ b/chrome/common/extensions/extension_messages.h @@ -8,6 +8,7 @@ #include "base/shared_memory.h" #include "base/values.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/extension_permission_set.h" #include "chrome/common/extensions/url_pattern.h" #include "chrome/common/extensions/url_pattern_set.h" #include "chrome/common/web_apps.h" @@ -90,7 +91,9 @@ typedef std::map<std::string, std::string> SubstitutionMap; struct ExtensionMsg_Loaded_Params { ExtensionMsg_Loaded_Params(); ~ExtensionMsg_Loaded_Params(); - explicit ExtensionMsg_Loaded_Params(const Extension* extension); + explicit ExtensionMsg_Loaded_Params( + const Extension* extension, + const ExtensionPermissionSet* active_permissions); // A copy constructor is needed because this structure can end up getting // copied inside the IPC machinery on gcc <= 4.2. @@ -99,6 +102,9 @@ struct ExtensionMsg_Loaded_Params { // Creates a new extension from the data in this object. scoped_refptr<Extension> ConvertToExtension() const; + // Passes ownership to the caller. + const ExtensionPermissionSet* GetActivePermissions() const; + // The subset of the extension manifest data we send to renderers. scoped_ptr<DictionaryValue> manifest; @@ -109,6 +115,11 @@ struct ExtensionMsg_Loaded_Params { // to generate the extension ID for extensions that are loaded unpacked. FilePath path; + // The extension's current active permissions. + ExtensionAPIPermissionSet apis; + URLPatternSet explicit_hosts; + URLPatternSet scriptable_hosts; + // We keep this separate so that it can be used in logging. std::string id; @@ -135,6 +146,14 @@ struct ParamTraits<URLPatternSet> { }; template <> +struct ParamTraits<ExtensionAPIPermission::ID> { + typedef ExtensionAPIPermission::ID param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, void** iter, param_type* p); + static void Log(const param_type& p, std::string* l); +}; + +template <> struct ParamTraits<ExtensionMsg_Loaded_Params> { typedef ExtensionMsg_Loaded_Params param_type; static void Write(Message* m, const param_type& p); @@ -214,6 +233,13 @@ IPC_MESSAGE_ROUTED1(ExtensionMsg_GetApplicationInfo, IPC_MESSAGE_ROUTED1(ExtensionMsg_UpdateBrowserWindowId, int /* id of browser window */) +// Tell the renderer to update an extension's permission set. +IPC_MESSAGE_CONTROL4(ExtensionMsg_UpdatePermissions, + std::string /* extension_id*/, + ExtensionAPIPermissionSet, + URLPatternSet, + URLPatternSet) + // Tell the renderer which type this view is. IPC_MESSAGE_ROUTED1(ExtensionMsg_NotifyRenderViewType, ViewType::Type /* view_type */) diff --git a/chrome/common/extensions/extension_permission_set.cc b/chrome/common/extensions/extension_permission_set.cc index 0fbf2cd..462e022 100644 --- a/chrome/common/extensions/extension_permission_set.cc +++ b/chrome/common/extensions/extension_permission_set.cc @@ -247,6 +247,9 @@ ExtensionPermissionsInfo::ExtensionPermissionsInfo() RegisterHostedAppPermission( ExtensionAPIPermission::kUnlimitedStorage, "unlimitedStorage", 0, ExtensionPermissionMessage::kNone); + RegisterHostedAppPermission( + ExtensionAPIPermission::kPermissions, "permissions", 0, + ExtensionPermissionMessage::kNone); // Hosted app and private permissions. RegisterPermission( @@ -433,12 +436,62 @@ ExtensionPermissionSet::~ExtensionPermissionSet() { } // static +ExtensionPermissionSet* ExtensionPermissionSet::CreateDifference( + const ExtensionPermissionSet* set1, + const ExtensionPermissionSet* set2) { + scoped_refptr<ExtensionPermissionSet> empty = new ExtensionPermissionSet(); + const ExtensionPermissionSet* set1_safe = (set1 == NULL) ? empty : set1; + const ExtensionPermissionSet* set2_safe = (set2 == NULL) ? empty : set2; + + ExtensionAPIPermissionSet apis; + std::set_difference(set1_safe->apis().begin(), set1_safe->apis().end(), + set2_safe->apis().begin(), set2_safe->apis().end(), + std::insert_iterator<ExtensionAPIPermissionSet>( + apis, apis.begin())); + + URLPatternSet explicit_hosts; + URLPatternSet::CreateDifference(set1_safe->explicit_hosts(), + set2_safe->explicit_hosts(), + &explicit_hosts); + + URLPatternSet scriptable_hosts; + URLPatternSet::CreateDifference(set1_safe->scriptable_hosts(), + set2_safe->scriptable_hosts(), + &scriptable_hosts); + return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); +} + +// static +ExtensionPermissionSet* ExtensionPermissionSet::CreateIntersection( + const ExtensionPermissionSet* set1, + const ExtensionPermissionSet* set2) { + scoped_refptr<ExtensionPermissionSet> empty = new ExtensionPermissionSet(); + const ExtensionPermissionSet* set1_safe = (set1 == NULL) ? empty : set1; + const ExtensionPermissionSet* set2_safe = (set2 == NULL) ? empty : set2; + + ExtensionAPIPermissionSet apis; + std::set_intersection(set1_safe->apis().begin(), set1_safe->apis().end(), + set2_safe->apis().begin(), set2_safe->apis().end(), + std::insert_iterator<ExtensionAPIPermissionSet>( + apis, apis.begin())); + URLPatternSet explicit_hosts; + URLPatternSet::CreateIntersection(set1_safe->explicit_hosts(), + set2_safe->explicit_hosts(), + &explicit_hosts); + + URLPatternSet scriptable_hosts; + URLPatternSet::CreateIntersection(set1_safe->scriptable_hosts(), + set2_safe->scriptable_hosts(), + &scriptable_hosts); + return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); +} +// static ExtensionPermissionSet* ExtensionPermissionSet::CreateUnion( const ExtensionPermissionSet* set1, const ExtensionPermissionSet* set2) { - ExtensionPermissionSet empty; - const ExtensionPermissionSet* set1_safe = (set1 == NULL) ? &empty : set1; - const ExtensionPermissionSet* set2_safe = (set2 == NULL) ? &empty : set2; + scoped_refptr<ExtensionPermissionSet> empty = new ExtensionPermissionSet(); + const ExtensionPermissionSet* set1_safe = (set1 == NULL) ? empty : set1; + const ExtensionPermissionSet* set2_safe = (set2 == NULL) ? empty : set2; ExtensionAPIPermissionSet apis; std::set_union(set1_safe->apis().begin(), set1_safe->apis().end(), @@ -447,10 +500,11 @@ ExtensionPermissionSet* ExtensionPermissionSet::CreateUnion( apis, apis.begin())); URLPatternSet explicit_hosts; - URLPatternSet scriptable_hosts; URLPatternSet::CreateUnion(set1_safe->explicit_hosts(), set2_safe->explicit_hosts(), &explicit_hosts); + + URLPatternSet scriptable_hosts; URLPatternSet::CreateUnion(set1_safe->scriptable_hosts(), set2_safe->scriptable_hosts(), &scriptable_hosts); @@ -458,6 +512,31 @@ ExtensionPermissionSet* ExtensionPermissionSet::CreateUnion( return new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts); } +bool ExtensionPermissionSet::operator==( + const ExtensionPermissionSet& rhs) const { + return apis_ == rhs.apis_ && + scriptable_hosts_ == rhs.scriptable_hosts_ && + explicit_hosts_ == rhs.explicit_hosts_; +} + +bool ExtensionPermissionSet::Contains(const ExtensionPermissionSet& set) const { + // Every set includes the empty set. + if (set.IsEmpty()) + return true; + + if (!std::includes(apis_.begin(), apis_.end(), + set.apis().begin(), set.apis().end())) + return false; + + if (!explicit_hosts().Contains(set.explicit_hosts())) + return false; + + if (!scriptable_hosts().Contains(set.scriptable_hosts())) + return false; + + return true; +} + std::set<std::string> ExtensionPermissionSet::GetAPIsAsStrings() const { ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); std::set<std::string> apis_str; diff --git a/chrome/common/extensions/extension_permission_set.h b/chrome/common/extensions/extension_permission_set.h index c9a67e9..3d4d353 100644 --- a/chrome/common/extensions/extension_permission_set.h +++ b/chrome/common/extensions/extension_permission_set.h @@ -13,6 +13,7 @@ #include "base/gtest_prod_util.h" #include "base/memory/singleton.h" +#include "base/memory/ref_counted.h" #include "base/scoped_ptr.h" #include "base/string16.h" #include "chrome/common/extensions/url_pattern_set.h" @@ -121,6 +122,7 @@ class ExtensionAPIPermission { kWebstorePrivate, kDevtools, kPlugin, + kPermissions, kEnumBoundary }; @@ -266,7 +268,8 @@ class ExtensionPermissionsInfo { // The ExtensionPermissionSet is an immutable class that encapsulates an // extension's permissions. The class exposes set operations for combining and // manipulating the permissions. -class ExtensionPermissionSet { +class ExtensionPermissionSet + : public base::RefCountedThreadSafe<ExtensionPermissionSet> { public: // Creates an empty permission set (e.g. default permissions). ExtensionPermissionSet(); @@ -286,11 +289,26 @@ class ExtensionPermissionSet { ~ExtensionPermissionSet(); + // Creates a new permission set equal to |set1| - |set2|, passing ownership of + // the new set to the caller. + static ExtensionPermissionSet* CreateDifference( + const ExtensionPermissionSet* set1, const ExtensionPermissionSet* set2); + + // Creates a new permission set equal to the intersection of |set1| and + // |set2|, passing ownership of the new set to the caller. + static ExtensionPermissionSet* CreateIntersection( + const ExtensionPermissionSet* set1, const ExtensionPermissionSet* set2); + // Creates a new permission set equal to the union of |set1| and |set2|. // Passes ownership of the new set to the caller. static ExtensionPermissionSet* CreateUnion( const ExtensionPermissionSet* set1, const ExtensionPermissionSet* set2); + bool operator==(const ExtensionPermissionSet& rhs) const; + + // Returns true if |set| is a subset of this. + bool Contains(const ExtensionPermissionSet& set) const; + // Gets the API permissions in this set as a set of strings. std::set<std::string> GetAPIsAsStrings() const; @@ -353,6 +371,8 @@ class ExtensionPermissionSet { FRIEND_TEST_ALL_PREFIXES(ExtensionPermissionSetTest, HasLessHostPrivilegesThan); + friend class base::RefCountedThreadSafe<ExtensionPermissionSet>; + static std::set<std::string> GetDistinctHosts( const URLPatternSet& host_patterns, bool include_rcd); @@ -380,9 +400,11 @@ class ExtensionPermissionSet { ExtensionAPIPermissionSet apis_; // The list of hosts that can be accessed directly from the extension. + // TODO(jstritar): Rename to "hosts_"? URLPatternSet explicit_hosts_; // The list of hosts that can be scripted by content scripts. + // TODO(jstritar): Rename to "user_script_hosts_"? URLPatternSet scriptable_hosts_; // The list of hosts this effectively grants access to. diff --git a/chrome/common/extensions/extension_permission_set_unittest.cc b/chrome/common/extensions/extension_permission_set_unittest.cc index b1a08a3..7bba33a 100644 --- a/chrome/common/extensions/extension_permission_set_unittest.cc +++ b/chrome/common/extensions/extension_permission_set_unittest.cc @@ -152,6 +152,7 @@ TEST(ExtensionAPIPermissionTest, HostedAppPermissions) { hosted_perms.insert(ExtensionAPIPermission::kNotification); hosted_perms.insert(ExtensionAPIPermission::kUnlimitedStorage); hosted_perms.insert(ExtensionAPIPermission::kWebstorePrivate); + hosted_perms.insert(ExtensionAPIPermission::kPermissions); ExtensionAPIPermissionSet perms = info->GetAll(); size_t count = 0; @@ -161,8 +162,8 @@ TEST(ExtensionAPIPermissionTest, HostedAppPermissions) { EXPECT_EQ(hosted_perms.count(*i) > 0, info->GetByID(*i)->is_hosted_app()); } - EXPECT_EQ(10u, count); - EXPECT_EQ(10u, info->get_hosted_app_permission_count()); + EXPECT_EQ(hosted_perms.size(), count); + EXPECT_EQ(hosted_perms.size(), info->get_hosted_app_permission_count()); } TEST(ExtensionAPIPermissionTest, ComponentOnlyPermissions) { @@ -188,17 +189,17 @@ TEST(ExtensionAPIPermissionTest, ComponentOnlyPermissions) { TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { scoped_refptr<Extension> extension; - const ExtensionPermissionSet* permissions = NULL; + scoped_refptr<const ExtensionPermissionSet> permissions; extension = LoadManifest("effective_host_permissions", "empty.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_EQ(0u, extension->GetEffectiveHostPermissions().patterns().size()); EXPECT_FALSE(permissions->HasEffectiveAccessToURL( GURL("http://www.google.com"))); EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "one_host.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL( GURL("http://www.google.com"))); EXPECT_FALSE(permissions->HasEffectiveAccessToURL( @@ -207,14 +208,14 @@ TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { extension = LoadManifest("effective_host_permissions", "one_host_wildcard.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); EXPECT_TRUE(permissions->HasEffectiveAccessToURL( GURL("http://foo.google.com"))); EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "two_hosts.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL( GURL("http://www.google.com"))); EXPECT_TRUE(permissions->HasEffectiveAccessToURL( @@ -223,14 +224,14 @@ TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { extension = LoadManifest("effective_host_permissions", "https_not_considered.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://google.com"))); EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "two_content_scripts.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://google.com"))); EXPECT_TRUE(permissions->HasEffectiveAccessToURL( GURL("http://www.reddit.com"))); @@ -239,7 +240,7 @@ TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { EXPECT_FALSE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("https://test/"))); EXPECT_TRUE( @@ -247,14 +248,14 @@ TEST(ExtensionPermissionSetTest, EffectiveHostPermissions) { EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts2.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); EXPECT_TRUE( permissions->HasEffectiveAccessToURL(GURL("http://www.google.com"))); EXPECT_TRUE(permissions->HasEffectiveAccessToAllHosts()); extension = LoadManifest("effective_host_permissions", "all_hosts3.json"); - permissions = extension->permission_set(); + permissions = extension->GetActivePermissions(); EXPECT_FALSE(permissions->HasEffectiveAccessToURL(GURL("http://test/"))); EXPECT_TRUE(permissions->HasEffectiveAccessToURL(GURL("https://test/"))); EXPECT_TRUE( @@ -271,16 +272,17 @@ TEST(ExtensionPermissionSetTest, ExplicitAccessToOrigin) { // The explicit host paths should get set to /*. AddPattern(&explicit_hosts, "http://www.example.com/a/particular/path/*"); - ExtensionPermissionSet perm_set(apis, explicit_hosts, scriptable_hosts); - ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + scoped_refptr<ExtensionPermissionSet> perm_set = new ExtensionPermissionSet( + apis, explicit_hosts, scriptable_hosts); + ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin( GURL("http://www.google.com/"))); - ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin( GURL("http://test.google.com/"))); - ASSERT_TRUE(perm_set.HasExplicitAccessToOrigin( + ASSERT_TRUE(perm_set->HasExplicitAccessToOrigin( GURL("http://www.example.com"))); - ASSERT_TRUE(perm_set.HasEffectiveAccessToURL( + ASSERT_TRUE(perm_set->HasEffectiveAccessToURL( GURL("http://www.example.com"))); - ASSERT_FALSE(perm_set.HasExplicitAccessToOrigin( + ASSERT_FALSE(perm_set->HasExplicitAccessToOrigin( GURL("http://test.example.com"))); } @@ -299,9 +301,9 @@ TEST(ExtensionPermissionSetTest, CreateUnion) { URLPatternSet effective_hosts; - scoped_ptr<ExtensionPermissionSet> set1; - scoped_ptr<ExtensionPermissionSet> set2; - scoped_ptr<ExtensionPermissionSet> union_set; + scoped_refptr<ExtensionPermissionSet> set1; + scoped_refptr<ExtensionPermissionSet> set2; + scoped_refptr<ExtensionPermissionSet> union_set; // Union with an empty set. apis1.insert(ExtensionAPIPermission::kTab); @@ -313,11 +315,15 @@ TEST(ExtensionPermissionSetTest, CreateUnion) { AddPattern(&expected_explicit_hosts, "http://*.google.com/*"); AddPattern(&effective_hosts, "http://*.google.com/*"); - set1.reset(new ExtensionPermissionSet( - apis1, explicit_hosts1, scriptable_hosts1)); - set2.reset(new ExtensionPermissionSet( - apis2, explicit_hosts2, scriptable_hosts2)); - union_set.reset(ExtensionPermissionSet::CreateUnion(set1.get(), set2.get())); + set1 = new ExtensionPermissionSet(apis1, explicit_hosts1, scriptable_hosts1); + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + union_set = ExtensionPermissionSet::CreateUnion(set1.get(), set2.get()); + EXPECT_TRUE(set1->Contains(*set2)); + EXPECT_TRUE(set1->Contains(*union_set)); + EXPECT_FALSE(set2->Contains(*set1)); + EXPECT_FALSE(set2->Contains(*union_set)); + EXPECT_TRUE(union_set->Contains(*set1)); + EXPECT_TRUE(union_set->Contains(*set2)); EXPECT_FALSE(union_set->HasEffectiveFullAccess()); EXPECT_EQ(expected_apis, union_set->apis()); @@ -343,9 +349,16 @@ TEST(ExtensionPermissionSetTest, CreateUnion) { effective_hosts.ClearPatterns(); AddPattern(&effective_hosts, "<all_urls>"); - set2.reset(new ExtensionPermissionSet( - apis2, explicit_hosts2, scriptable_hosts2)); - union_set.reset(ExtensionPermissionSet::CreateUnion(set1.get(), set2.get())); + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + union_set = ExtensionPermissionSet::CreateUnion(set1.get(), set2.get()); + + EXPECT_FALSE(set1->Contains(*set2)); + EXPECT_FALSE(set1->Contains(*union_set)); + EXPECT_FALSE(set2->Contains(*set1)); + EXPECT_FALSE(set2->Contains(*union_set)); + EXPECT_TRUE(union_set->Contains(*set1)); + EXPECT_TRUE(union_set->Contains(*set2)); + EXPECT_TRUE(union_set->HasEffectiveFullAccess()); EXPECT_TRUE(union_set->HasEffectiveAccessToAllHosts()); EXPECT_EQ(expected_apis, union_set->apis()); @@ -354,6 +367,146 @@ TEST(ExtensionPermissionSetTest, CreateUnion) { EXPECT_EQ(effective_hosts, union_set->effective_hosts()); } +TEST(ExtensionPermissionSetTest, CreateIntersection) { + ExtensionAPIPermissionSet apis1; + ExtensionAPIPermissionSet apis2; + ExtensionAPIPermissionSet expected_apis; + + URLPatternSet explicit_hosts1; + URLPatternSet explicit_hosts2; + URLPatternSet expected_explicit_hosts; + + URLPatternSet scriptable_hosts1; + URLPatternSet scriptable_hosts2; + URLPatternSet expected_scriptable_hosts; + + URLPatternSet effective_hosts; + + scoped_refptr<ExtensionPermissionSet> set1; + scoped_refptr<ExtensionPermissionSet> set2; + scoped_refptr<ExtensionPermissionSet> new_set; + + // Intersection with an empty set. + apis1.insert(ExtensionAPIPermission::kTab); + apis1.insert(ExtensionAPIPermission::kBackground); + + AddPattern(&explicit_hosts1, "http://*.google.com/*"); + AddPattern(&scriptable_hosts1, "http://www.reddit.com/*"); + + set1 = new ExtensionPermissionSet(apis1, explicit_hosts1, scriptable_hosts1); + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + new_set = ExtensionPermissionSet::CreateIntersection(set1.get(), set2.get()); + EXPECT_TRUE(set1->Contains(*new_set)); + EXPECT_TRUE(set2->Contains(*new_set)); + EXPECT_TRUE(set1->Contains(*set2)); + EXPECT_FALSE(set2->Contains(*set1)); + EXPECT_FALSE(new_set->Contains(*set1)); + EXPECT_TRUE(new_set->Contains(*set2)); + + EXPECT_TRUE(new_set->IsEmpty()); + EXPECT_FALSE(new_set->HasEffectiveFullAccess()); + EXPECT_EQ(expected_apis, new_set->apis()); + EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts()); + EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts()); + EXPECT_EQ(expected_explicit_hosts, new_set->effective_hosts()); + + // Now use a real second set. + apis2.insert(ExtensionAPIPermission::kTab); + apis2.insert(ExtensionAPIPermission::kProxy); + apis2.insert(ExtensionAPIPermission::kClipboardWrite); + apis2.insert(ExtensionAPIPermission::kPlugin); + expected_apis.insert(ExtensionAPIPermission::kTab); + + AddPattern(&explicit_hosts2, "http://*.example.com/*"); + AddPattern(&explicit_hosts2, "http://*.google.com/*"); + AddPattern(&scriptable_hosts2, "http://*.google.com/*"); + AddPattern(&expected_explicit_hosts, "http://*.google.com/*"); + + effective_hosts.ClearPatterns(); + AddPattern(&effective_hosts, "http://*.google.com/*"); + + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + new_set = ExtensionPermissionSet::CreateIntersection(set1.get(), set2.get()); + + EXPECT_TRUE(set1->Contains(*new_set)); + EXPECT_TRUE(set2->Contains(*new_set)); + EXPECT_FALSE(set1->Contains(*set2)); + EXPECT_FALSE(set2->Contains(*set1)); + EXPECT_FALSE(new_set->Contains(*set1)); + EXPECT_FALSE(new_set->Contains(*set2)); + + EXPECT_FALSE(new_set->HasEffectiveFullAccess()); + EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts()); + EXPECT_EQ(expected_apis, new_set->apis()); + EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts()); + EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts()); + EXPECT_EQ(effective_hosts, new_set->effective_hosts()); +} + +TEST(ExtensionPermissionSetTest, CreateDifference) { + ExtensionAPIPermissionSet apis1; + ExtensionAPIPermissionSet apis2; + ExtensionAPIPermissionSet expected_apis; + + URLPatternSet explicit_hosts1; + URLPatternSet explicit_hosts2; + URLPatternSet expected_explicit_hosts; + + URLPatternSet scriptable_hosts1; + URLPatternSet scriptable_hosts2; + URLPatternSet expected_scriptable_hosts; + + URLPatternSet effective_hosts; + + scoped_refptr<ExtensionPermissionSet> set1; + scoped_refptr<ExtensionPermissionSet> set2; + scoped_refptr<ExtensionPermissionSet> new_set; + + // Difference with an empty set. + apis1.insert(ExtensionAPIPermission::kTab); + apis1.insert(ExtensionAPIPermission::kBackground); + + AddPattern(&explicit_hosts1, "http://*.google.com/*"); + AddPattern(&scriptable_hosts1, "http://www.reddit.com/*"); + + set1 = new ExtensionPermissionSet(apis1, explicit_hosts1, scriptable_hosts1); + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + new_set = ExtensionPermissionSet::CreateDifference(set1.get(), set2.get()); + EXPECT_EQ(*set1, *new_set); + + // Now use a real second set. + apis2.insert(ExtensionAPIPermission::kTab); + apis2.insert(ExtensionAPIPermission::kProxy); + apis2.insert(ExtensionAPIPermission::kClipboardWrite); + apis2.insert(ExtensionAPIPermission::kPlugin); + expected_apis.insert(ExtensionAPIPermission::kBackground); + + AddPattern(&explicit_hosts2, "http://*.example.com/*"); + AddPattern(&explicit_hosts2, "http://*.google.com/*"); + AddPattern(&scriptable_hosts2, "http://*.google.com/*"); + AddPattern(&expected_scriptable_hosts, "http://www.reddit.com/*"); + + effective_hosts.ClearPatterns(); + AddPattern(&effective_hosts, "http://www.reddit.com/*"); + + set2 = new ExtensionPermissionSet(apis2, explicit_hosts2, scriptable_hosts2); + new_set = ExtensionPermissionSet::CreateDifference(set1.get(), set2.get()); + + EXPECT_TRUE(set1->Contains(*new_set)); + EXPECT_FALSE(set2->Contains(*new_set)); + + EXPECT_FALSE(new_set->HasEffectiveFullAccess()); + EXPECT_FALSE(new_set->HasEffectiveAccessToAllHosts()); + EXPECT_EQ(expected_apis, new_set->apis()); + EXPECT_EQ(expected_explicit_hosts, new_set->explicit_hosts()); + EXPECT_EQ(expected_scriptable_hosts, new_set->scriptable_hosts()); + EXPECT_EQ(effective_hosts, new_set->effective_hosts()); + + // |set3| = |set1| - |set2| --> |set3| intersect |set2| == empty_set + set1 = ExtensionPermissionSet::CreateIntersection(new_set.get(), set2.get()); + EXPECT_TRUE(set1->IsEmpty()); +} + TEST(ExtensionPermissionSetTest, HasLessPrivilegesThan) { const struct { const char* base_name; @@ -435,11 +588,13 @@ TEST(ExtensionPermissionSetTest, HasLessPrivilegesThan) { if (!new_extension.get()) continue; - const ExtensionPermissionSet* old_p = old_extension->permission_set(); - const ExtensionPermissionSet* new_p = new_extension->permission_set(); + scoped_refptr<const ExtensionPermissionSet> old_p( + old_extension->GetActivePermissions()); + scoped_refptr<const ExtensionPermissionSet> new_p( + new_extension->GetActivePermissions()); - EXPECT_EQ(kTests[i].expect_increase, old_p->HasLessPrivilegesThan(new_p)) - << kTests[i].base_name; + EXPECT_EQ(kTests[i].expect_increase, + old_p->HasLessPrivilegesThan(new_p)) << kTests[i].base_name; } } @@ -491,6 +646,10 @@ TEST(ExtensionPermissionSetTest, PermissionMessages) { // Warned as part of host permissions. skip.insert(ExtensionAPIPermission::kDevtools); + + // This will warn users later, when they request new permissions. + skip.insert(ExtensionAPIPermission::kPermissions); + ExtensionPermissionsInfo* info = ExtensionPermissionsInfo::GetInstance(); ExtensionAPIPermissionSet permissions = info->GetAll(); for (ExtensionAPIPermissionSet::const_iterator i = permissions.begin(); @@ -538,10 +697,10 @@ TEST(ExtensionPermissionSetTest, DefaultFunctionAccess) { { "tabs.getSelected", false}, }; - ExtensionPermissionSet permissions; + scoped_refptr<ExtensionPermissionSet> empty = new ExtensionPermissionSet(); for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { EXPECT_EQ(kTests[i].expect_success, - permissions.HasAccessToFunction(kTests[i].permission_name)); + empty->HasAccessToFunction(kTests[i].permission_name)); } } @@ -550,7 +709,7 @@ TEST(ExtensionPermissionSetTest, GetWarningMessages_ManyHosts) { extension = LoadManifest("permissions", "many-hosts.json"); std::vector<string16> warnings = - extension->permission_set()->GetWarningMessages(); + extension->GetActivePermissions()->GetWarningMessages(); ASSERT_EQ(1u, warnings.size()); EXPECT_EQ("Your data on encrypted.google.com and www.google.com", UTF16ToUTF8(warnings[0])); @@ -558,11 +717,11 @@ TEST(ExtensionPermissionSetTest, GetWarningMessages_ManyHosts) { TEST(ExtensionPermissionSetTest, GetWarningMessages_Plugins) { scoped_refptr<Extension> extension; - scoped_ptr<ExtensionPermissionSet> permissions; + scoped_refptr<ExtensionPermissionSet> permissions; extension = LoadManifest("permissions", "plugins.json"); std::vector<string16> warnings = - extension->permission_set()->GetWarningMessages(); + extension->GetActivePermissions()->GetWarningMessages(); // We don't parse the plugins key on Chrome OS, so it should not ask for any // permissions. #if defined(OS_CHROMEOS) @@ -575,7 +734,7 @@ TEST(ExtensionPermissionSetTest, GetWarningMessages_Plugins) { } TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { - scoped_ptr<ExtensionPermissionSet> perm_set; + scoped_refptr<ExtensionPermissionSet> perm_set; ExtensionAPIPermissionSet empty_perms; std::set<std::string> expected; expected.insert("www.foo.com"); @@ -594,8 +753,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/path")); explicit_hosts.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -607,8 +766,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { URLPattern(URLPattern::SCHEME_HTTP, "http://www.foo.com/path")); explicit_hosts.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.baz.com/path")); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -618,8 +777,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { // Add a pattern that differs only by scheme. This should be filtered out. explicit_hosts.AddPattern( URLPattern(URLPattern::SCHEME_HTTPS, "https://www.bar.com/path")); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -629,8 +788,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { // Add some dupes by path. explicit_hosts.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.bar.com/pathypath")); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -646,8 +805,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { expected.insert("monkey.www.bar.com"); expected.insert("bar.com"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -677,8 +836,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { expected.insert("www.foo.xyzzy"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -690,8 +849,8 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { expected.insert("*.google.com"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -709,14 +868,14 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay) { expected.insert("*.google.com"); expected.insert("*.example.com"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } } TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_ComIsBestRcd) { - scoped_ptr<ExtensionPermissionSet> perm_set; + scoped_refptr<ExtensionPermissionSet> perm_set; ExtensionAPIPermissionSet empty_perms; URLPatternSet explicit_hosts; URLPatternSet scriptable_hosts; @@ -735,13 +894,13 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_ComIsBestRcd) { std::set<std::string> expected; expected.insert("www.foo.com"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) { - scoped_ptr<ExtensionPermissionSet> perm_set; + scoped_refptr<ExtensionPermissionSet> perm_set; ExtensionAPIPermissionSet empty_perms; URLPatternSet explicit_hosts; URLPatternSet scriptable_hosts; @@ -759,14 +918,14 @@ TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_NetIs2ndBestRcd) { std::set<std::string> expected; expected.insert("www.foo.net"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_OrgIs3rdBestRcd) { - scoped_ptr<ExtensionPermissionSet> perm_set; + scoped_refptr<ExtensionPermissionSet> perm_set; ExtensionAPIPermissionSet empty_perms; URLPatternSet explicit_hosts; URLPatternSet scriptable_hosts; @@ -783,14 +942,14 @@ TEST(ExtensionPermissionSetTest, std::set<std::string> expected; expected.insert("www.foo.org"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } TEST(ExtensionPermissionSetTest, GetDistinctHostsForDisplay_FirstInListIs4thBestRcd) { - scoped_ptr<ExtensionPermissionSet> perm_set; + scoped_refptr<ExtensionPermissionSet> perm_set; ExtensionAPIPermissionSet empty_perms; URLPatternSet explicit_hosts; URLPatternSet scriptable_hosts; @@ -806,8 +965,8 @@ TEST(ExtensionPermissionSetTest, std::set<std::string> expected; expected.insert("www.foo.ca"); - perm_set.reset(new ExtensionPermissionSet( - empty_perms, explicit_hosts, scriptable_hosts)); + perm_set = new ExtensionPermissionSet( + empty_perms, explicit_hosts, scriptable_hosts); EXPECT_EQ(expected, perm_set->GetDistinctHostsForDisplay()); } @@ -816,8 +975,8 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { URLPatternSet elist2; URLPatternSet slist1; URLPatternSet slist2; - scoped_ptr<ExtensionPermissionSet> set1; - scoped_ptr<ExtensionPermissionSet> set2; + scoped_refptr<ExtensionPermissionSet> set1; + scoped_refptr<ExtensionPermissionSet> set2; ExtensionAPIPermissionSet empty_perms; elist1.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); @@ -830,8 +989,8 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/path")); - set1.reset(new ExtensionPermissionSet(empty_perms, elist1, slist1)); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set1 = new ExtensionPermissionSet(empty_perms, elist1, slist1); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); @@ -840,7 +999,7 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { elist2.ClearPatterns(); elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/*")); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); @@ -848,7 +1007,7 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { elist2.ClearPatterns(); elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com.hk/*")); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_FALSE(set1->HasLessHostPrivilegesThan(set2.get())); EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); @@ -856,7 +1015,7 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { elist2.ClearPatterns(); elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://*.google.com.hk/*")); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); //TODO(jstritar): Does not match subdomains properly. http://crbug.com/65337 //EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); @@ -867,7 +1026,7 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { URLPattern(URLPattern::SCHEME_HTTP, "http://www.google.com/path")); elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://www.example.org/path")); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); EXPECT_FALSE(set2->HasLessHostPrivilegesThan(set1.get())); @@ -875,7 +1034,7 @@ TEST(ExtensionPermissionSetTest, HasLessHostPrivilegesThan) { elist2.ClearPatterns(); elist2.AddPattern( URLPattern(URLPattern::SCHEME_HTTP, "http://mail.google.com/*")); - set2.reset(new ExtensionPermissionSet(empty_perms, elist2, slist2)); + set2 = new ExtensionPermissionSet(empty_perms, elist2, slist2); EXPECT_TRUE(set1->HasLessHostPrivilegesThan(set2.get())); EXPECT_TRUE(set2->HasLessHostPrivilegesThan(set1.get())); } @@ -889,8 +1048,9 @@ TEST(ExtensionPermissionSetTest, GetAPIsAsStrings) { apis.insert(ExtensionAPIPermission::kNotification); apis.insert(ExtensionAPIPermission::kTab); - ExtensionPermissionSet perm_set(apis, empty_set, empty_set); - std::set<std::string> api_names = perm_set.GetAPIsAsStrings(); + scoped_refptr<ExtensionPermissionSet> perm_set = new ExtensionPermissionSet( + apis, empty_set, empty_set); + std::set<std::string> api_names = perm_set->GetAPIsAsStrings(); // The result is correct if it has the same number of elements // and we can convert it back to the id set. @@ -903,27 +1063,28 @@ TEST(ExtensionPermissionSetTest, IsEmpty) { ExtensionAPIPermissionSet empty_apis; URLPatternSet empty_extent; - ExtensionPermissionSet perm_set; - EXPECT_TRUE(perm_set.IsEmpty()); + scoped_refptr<ExtensionPermissionSet> empty = new ExtensionPermissionSet(); + EXPECT_TRUE(empty->IsEmpty()); + scoped_refptr<ExtensionPermissionSet> perm_set; - perm_set = ExtensionPermissionSet(empty_apis, empty_extent, empty_extent); - EXPECT_TRUE(perm_set.IsEmpty()); + perm_set = new ExtensionPermissionSet(empty_apis, empty_extent, empty_extent); + EXPECT_TRUE(perm_set->IsEmpty()); ExtensionAPIPermissionSet non_empty_apis; non_empty_apis.insert(ExtensionAPIPermission::kBackground); - perm_set = ExtensionPermissionSet( + perm_set = new ExtensionPermissionSet( non_empty_apis, empty_extent, empty_extent); - EXPECT_FALSE(perm_set.IsEmpty()); + EXPECT_FALSE(perm_set->IsEmpty()); // Try non standard host URLPatternSet non_empty_extent; AddPattern(&non_empty_extent, "http://www.google.com/*"); - perm_set = ExtensionPermissionSet( + perm_set = new ExtensionPermissionSet( empty_apis, non_empty_extent, empty_extent); - EXPECT_FALSE(perm_set.IsEmpty()); + EXPECT_FALSE(perm_set->IsEmpty()); - perm_set = ExtensionPermissionSet( + perm_set = new ExtensionPermissionSet( empty_apis, empty_extent, non_empty_extent); - EXPECT_FALSE(perm_set.IsEmpty()); + EXPECT_FALSE(perm_set->IsEmpty()); } diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index 79cad1a..5115573 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -389,10 +389,10 @@ TEST(ExtensionTest, EffectiveHostPermissions) { hosts = extension->GetEffectiveHostPermissions(); EXPECT_TRUE(hosts.MatchesURL(GURL("http://google.com"))); EXPECT_TRUE(hosts.MatchesURL(GURL("http://www.reddit.com"))); - EXPECT_TRUE(extension->permission_set()->HasEffectiveAccessToURL( + EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL( GURL("http://www.reddit.com"))); EXPECT_TRUE(hosts.MatchesURL(GURL("http://news.ycombinator.com"))); - EXPECT_TRUE(extension->permission_set()->HasEffectiveAccessToURL( + EXPECT_TRUE(extension->GetActivePermissions()->HasEffectiveAccessToURL( GURL("http://news.ycombinator.com"))); EXPECT_FALSE(extension->HasEffectiveAccessToAllHosts()); diff --git a/chrome/common/extensions/url_pattern_set.cc b/chrome/common/extensions/url_pattern_set.cc index 3c8f52c..cf2b9a6 100644 --- a/chrome/common/extensions/url_pattern_set.cc +++ b/chrome/common/extensions/url_pattern_set.cc @@ -10,6 +10,29 @@ #include "chrome/common/extensions/url_pattern.h" #include "googleurl/src/gurl.h" + +// static +void URLPatternSet::CreateDifference(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + out->ClearPatterns(); + std::set_difference(set1.patterns_.begin(), set1.patterns_.end(), + set2.patterns_.begin(), set2.patterns_.end(), + std::inserter<std::set<URLPattern> >( + out->patterns_, out->patterns_.begin())); +} + +// static +void URLPatternSet::CreateIntersection(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out) { + out->ClearPatterns(); + std::set_intersection(set1.patterns_.begin(), set1.patterns_.end(), + set2.patterns_.begin(), set2.patterns_.end(), + std::inserter<std::set<URLPattern> >( + out->patterns_, out->patterns_.begin())); +} + // static void URLPatternSet::CreateUnion(const URLPatternSet& set1, const URLPatternSet& set2, @@ -52,6 +75,11 @@ void URLPatternSet::ClearPatterns() { patterns_.clear(); } +bool URLPatternSet::Contains(const URLPatternSet& set) const { + return std::includes(patterns_.begin(), patterns_.end(), + set.patterns_.begin(), set.patterns_.end()); +} + bool URLPatternSet::MatchesURL(const GURL& url) const { for (URLPatternSet::const_iterator pattern = patterns_.begin(); pattern != patterns_.end(); ++pattern) { diff --git a/chrome/common/extensions/url_pattern_set.h b/chrome/common/extensions/url_pattern_set.h index 1848e74..d26321c 100644 --- a/chrome/common/extensions/url_pattern_set.h +++ b/chrome/common/extensions/url_pattern_set.h @@ -18,6 +18,17 @@ class URLPatternSet { typedef std::set<URLPattern>::const_iterator const_iterator; typedef std::set<URLPattern>::iterator iterator; + // Clears |out| and populates the set with |set1| - |set2|. + static void CreateDifference(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + + // Clears |out| and populates the set with the intersection of |set1| + // and |set2|. + static void CreateIntersection(const URLPatternSet& set1, + const URLPatternSet& set2, + URLPatternSet* out); + // Clears |out| and populates the set with the union of |set1| and |set2|. static void CreateUnion(const URLPatternSet& set1, const URLPatternSet& set2, @@ -39,6 +50,9 @@ class URLPatternSet { void AddPattern(const URLPattern& pattern); void ClearPatterns(); + // Returns true if the permission |set| is a subset of this. + bool Contains(const URLPatternSet& set) const; + // Test if the extent contains a URL. bool MatchesURL(const GURL& url) const; diff --git a/chrome/common/extensions/url_pattern_set_unittest.cc b/chrome/common/extensions/url_pattern_set_unittest.cc index f9717ef..ff78d90 100644 --- a/chrome/common/extensions/url_pattern_set_unittest.cc +++ b/chrome/common/extensions/url_pattern_set_unittest.cc @@ -7,88 +7,195 @@ #include "googleurl/src/gurl.h" #include "testing/gtest/include/gtest/gtest.h" -static const int kAllSchemes = - URLPattern::SCHEME_HTTP | - URLPattern::SCHEME_HTTPS | - URLPattern::SCHEME_FILE | - URLPattern::SCHEME_FTP | - URLPattern::SCHEME_CHROMEUI; +namespace { +static void AddPattern(URLPatternSet* set, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + set->AddPattern(URLPattern(schemes, pattern)); +} + +} TEST(URLPatternSetTest, Empty) { - URLPatternSet extent; - EXPECT_FALSE(extent.MatchesURL(GURL("http://www.foo.com/bar"))); - EXPECT_FALSE(extent.MatchesURL(GURL())); - EXPECT_FALSE(extent.MatchesURL(GURL("invalid"))); + URLPatternSet set; + EXPECT_FALSE(set.MatchesURL(GURL("http://www.foo.com/bar"))); + EXPECT_FALSE(set.MatchesURL(GURL())); + EXPECT_FALSE(set.MatchesURL(GURL("invalid"))); } TEST(URLPatternSetTest, One) { - URLPatternSet extent; - extent.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/*")); + URLPatternSet set; + AddPattern(&set, "http://www.google.com/*"); - EXPECT_TRUE(extent.MatchesURL(GURL("http://www.google.com/"))); - EXPECT_TRUE(extent.MatchesURL(GURL("http://www.google.com/monkey"))); - EXPECT_FALSE(extent.MatchesURL(GURL("https://www.google.com/"))); - EXPECT_FALSE(extent.MatchesURL(GURL("https://www.microsoft.com/"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.google.com/"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.microsoft.com/"))); } TEST(URLPatternSetTest, Two) { - URLPatternSet extent; - extent.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/*")); - extent.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/*")); + URLPatternSet set; + AddPattern(&set, "http://www.google.com/*"); + AddPattern(&set, "http://www.yahoo.com/*"); - EXPECT_TRUE(extent.MatchesURL(GURL("http://www.google.com/monkey"))); - EXPECT_TRUE(extent.MatchesURL(GURL("http://www.yahoo.com/monkey"))); - EXPECT_FALSE(extent.MatchesURL(GURL("https://www.apple.com/monkey"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.google.com/monkey"))); + EXPECT_TRUE(set.MatchesURL(GURL("http://www.yahoo.com/monkey"))); + EXPECT_FALSE(set.MatchesURL(GURL("https://www.apple.com/monkey"))); } TEST(URLPatternSetTest, OverlapsWith) { - URLPatternSet extent1; - extent1.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); - extent1.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b*")); + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + + URLPatternSet set3; + AddPattern(&set3, "http://www.google.com/q/*"); + AddPattern(&set3, "http://www.yahoo.com/b/*"); + + EXPECT_FALSE(set1.OverlapsWith(set2)); + EXPECT_FALSE(set2.OverlapsWith(set1)); + + EXPECT_TRUE(set1.OverlapsWith(set3)); + EXPECT_TRUE(set3.OverlapsWith(set1)); +} + +TEST(URLPatternSetTest, CreateDifference) { + URLPatternSet expected; + URLPatternSet set1; + URLPatternSet set2; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); - URLPatternSet extent2; - extent2.AddPattern(URLPattern(kAllSchemes, "http://www.reddit.com/f*")); - extent2.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/z*")); + // Subtract an empty set. + URLPatternSet result; + URLPatternSet::CreateDifference(set1, set2, &result); + EXPECT_EQ(set1, result); - URLPatternSet extent3; - extent3.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/q/*")); - extent3.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b/*")); + // Subtract a real set. + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); - EXPECT_FALSE(extent1.OverlapsWith(extent2)); - EXPECT_FALSE(extent2.OverlapsWith(extent1)); + AddPattern(&expected, "http://www.yahoo.com/b*"); - EXPECT_TRUE(extent1.OverlapsWith(extent3)); - EXPECT_TRUE(extent3.OverlapsWith(extent1)); + result.ClearPatterns(); + URLPatternSet::CreateDifference(set1, set2, &result); + EXPECT_EQ(expected, result); + EXPECT_FALSE(result.is_empty()); + EXPECT_TRUE(set1.Contains(result)); + EXPECT_FALSE(result.Contains(set2)); + EXPECT_FALSE(set2.Contains(result)); + + URLPatternSet intersection; + URLPatternSet::CreateIntersection(result, set2, &intersection); + EXPECT_TRUE(intersection.is_empty()); +} + +TEST(URLPatternSetTest, CreateIntersection) { + URLPatternSet empty_set; + URLPatternSet expected; + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); + + // Intersection with an empty set. + URLPatternSet result; + URLPatternSet::CreateIntersection(set1, empty_set, &result); + EXPECT_EQ(expected, result); + EXPECT_TRUE(result.is_empty()); + EXPECT_TRUE(empty_set.Contains(result)); + EXPECT_TRUE(result.Contains(empty_set)); + EXPECT_TRUE(set1.Contains(result)); + + // Intersection with a real set. + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); + + AddPattern(&expected, "http://www.google.com/f*"); + + result.ClearPatterns(); + URLPatternSet::CreateIntersection(set1, set2, &result); + EXPECT_EQ(expected, result); + EXPECT_FALSE(result.is_empty()); + EXPECT_TRUE(set1.Contains(result)); + EXPECT_TRUE(set2.Contains(result)); } TEST(URLPatternSetTest, CreateUnion) { - URLPatternSet empty_extent; + URLPatternSet empty_set; - URLPatternSet extent1; - extent1.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); - extent1.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b*")); + URLPatternSet set1; + AddPattern(&set1, "http://www.google.com/f*"); + AddPattern(&set1, "http://www.yahoo.com/b*"); URLPatternSet expected; - expected.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); - expected.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/b*")); + AddPattern(&expected, "http://www.google.com/f*"); + AddPattern(&expected, "http://www.yahoo.com/b*"); // Union with an empty set. URLPatternSet result; - URLPatternSet::CreateUnion(extent1, empty_extent, &result); + URLPatternSet::CreateUnion(set1, empty_set, &result); EXPECT_EQ(expected, result); - // Union with a real set (including a duplicate). - URLPatternSet extent2; - extent2.AddPattern(URLPattern(kAllSchemes, "http://www.reddit.com/f*")); - extent2.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/z*")); - extent2.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + // Union with a real set. + URLPatternSet set2; + AddPattern(&set2, "http://www.reddit.com/f*"); + AddPattern(&set2, "http://www.yahoo.com/z*"); + AddPattern(&set2, "http://www.google.com/f*"); - expected.AddPattern(URLPattern(kAllSchemes, "http://www.reddit.com/f*")); - expected.AddPattern(URLPattern(kAllSchemes, "http://www.yahoo.com/z*")); - // CreateUnion does not filter out duplicates right now. - expected.AddPattern(URLPattern(kAllSchemes, "http://www.google.com/f*")); + AddPattern(&expected, "http://www.reddit.com/f*"); + AddPattern(&expected, "http://www.yahoo.com/z*"); result.ClearPatterns(); - URLPatternSet::CreateUnion(extent1, extent2, &result); + URLPatternSet::CreateUnion(set1, set2, &result); EXPECT_EQ(expected, result); } + +TEST(URLPatternSetTest, Contains) { + URLPatternSet set1; + URLPatternSet set2; + URLPatternSet empty_set; + + AddPattern(&set1, "http://www.google.com/*"); + AddPattern(&set1, "http://www.yahoo.com/*"); + + AddPattern(&set2, "http://www.reddit.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_TRUE(set1.Contains(empty_set)); + EXPECT_FALSE(empty_set.Contains(set1)); + + AddPattern(&set2, "http://www.yahoo.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_FALSE(set2.Contains(set1)); + + AddPattern(&set2, "http://www.google.com/*"); + + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_TRUE(set2.Contains(set1)); + + // Note that this just checks pattern equality, and not if individual patterns + // contain other patterns. For example: + AddPattern(&set1, "http://*.reddit.com/*"); + EXPECT_FALSE(set1.Contains(set2)); + EXPECT_FALSE(set2.Contains(set1)); +} + +TEST(URLPatternSetTest, Duplicates) { + URLPatternSet set1; + URLPatternSet set2; + + AddPattern(&set1, "http://www.google.com/*"); + AddPattern(&set2, "http://www.google.com/*"); + + AddPattern(&set1, "http://www.google.com/*"); + + // The sets should still be equal after adding a duplicate. + EXPECT_EQ(set2, set1); +} diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc index 098e3e5..d994d92 100644 --- a/chrome/renderer/extensions/extension_dispatcher.cc +++ b/chrome/renderer/extensions/extension_dispatcher.cc @@ -9,6 +9,7 @@ #include "chrome/common/chrome_switches.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/url_constants.h" #include "chrome/renderer/extensions/chrome_app_bindings.h" #include "chrome/renderer/extensions/event_bindings.h" @@ -65,6 +66,7 @@ bool ExtensionDispatcher::OnControlMessageReceived( OnSetScriptingWhitelist) IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateExtension, OnActivateExtension) IPC_MESSAGE_HANDLER(ExtensionMsg_ActivateApplication, OnActivateApplication) + IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions) IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -149,6 +151,7 @@ void ExtensionDispatcher::OnLoaded(const ExtensionMsg_Loaded_Params& params) { } extensions_.Insert(extension); + extension->SetActivePermissions(params.GetActivePermissions()); } void ExtensionDispatcher::OnUnloaded(const std::string& id) { @@ -244,7 +247,7 @@ void ExtensionDispatcher::InitHostPermissions(const Extension* extension) { } const URLPatternSet& permissions = - extension->permission_set()->explicit_hosts(); + extension->GetActivePermissions()->explicit_hosts(); for (URLPatternSet::const_iterator i = permissions.begin(); i != permissions.end(); ++i) { const char* schemes[] = { @@ -265,6 +268,19 @@ void ExtensionDispatcher::InitHostPermissions(const Extension* extension) { } } +void ExtensionDispatcher::OnUpdatePermissions( + const std::string& extension_id, + const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts, + const URLPatternSet& scriptable_hosts) { + const Extension* extension = extensions_.GetByID(extension_id); + if (!extension) + return; + + extension->SetActivePermissions( + new ExtensionPermissionSet(apis, explicit_hosts, scriptable_hosts)); +} + void ExtensionDispatcher::OnUpdateUserScripts( base::SharedMemoryHandle scripts) { DCHECK(base::SharedMemory::IsHandleValid(scripts)) << "Bad scripts handle"; diff --git a/chrome/renderer/extensions/extension_dispatcher.h b/chrome/renderer/extensions/extension_dispatcher.h index f2ae6fe9..f1fb8e4 100644 --- a/chrome/renderer/extensions/extension_dispatcher.h +++ b/chrome/renderer/extensions/extension_dispatcher.h @@ -20,6 +20,7 @@ class RenderThread; class URLPattern; class UserScriptSlave; struct ExtensionMsg_Loaded_Params; +struct ExtensionMsg_UpdatePermissions_Params; namespace base { class ListValue; @@ -77,6 +78,10 @@ class ExtensionDispatcher : public RenderProcessObserver { const std::vector<std::string>& page_actions); void OnActivateApplication(const std::string& extension_id); void OnActivateExtension(const std::string& extension_id); + void OnUpdatePermissions(const std::string& extension_id, + const ExtensionAPIPermissionSet& apis, + const URLPatternSet& explicit_hosts, + const URLPatternSet& scriptable_hosts); void OnUpdateUserScripts(base::SharedMemoryHandle table); // Update the list of active extensions that will be reported when we crash. diff --git a/chrome/renderer/resources/extension_apitest.js b/chrome/renderer/resources/extension_apitest.js index c09ed57..7e7e62f 100644 --- a/chrome/renderer/resources/extension_apitest.js +++ b/chrome/renderer/resources/extension_apitest.js @@ -71,7 +71,7 @@ var chrome = chrome || {}; if (pendingCallbacks == 0) { chrome.test.succeed(); } - } + }; }; chrome.test.runNextTest = function() { diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js index 6935c1d..b78b4c1 100644 --- a/chrome/renderer/resources/renderer_extension_bindings.js +++ b/chrome/renderer/resources/renderer_extension_bindings.js @@ -310,6 +310,7 @@ var chrome = chrome || {}; "experimental.input", "experimental.inputUI", "experimental.metrics", + "experimental.permissions", "experimental.popup", "experimental.processes", "experimental.rlz", diff --git a/chrome/test/data/extensions/api_test/permissions/experimental_disabled/background.html b/chrome/test/data/extensions/api_test/permissions/experimental_disabled/background.html index c46b2e8..4f27694 100644 --- a/chrome/test/data/extensions/api_test/permissions/experimental_disabled/background.html +++ b/chrome/test/data/extensions/api_test/permissions/experimental_disabled/background.html @@ -6,7 +6,7 @@ chrome.test.runTests([ function experimental() {
chrome.tabs.getSelected(null, function(tab) {
try {
- chrome.experimental.processes.getProcessForTab(tab.id,
+ chrome.experimental.processes.getProcessForTab(tab.id,
function(process) {
chrome.test.fail();
});
diff --git a/chrome/test/data/extensions/api_test/permissions/optional/background.html b/chrome/test/data/extensions/api_test/permissions/optional/background.html new file mode 100644 index 0000000..ba9a1c8 --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional/background.html @@ -0,0 +1,188 @@ +<script>
+
+var assertFalse = chrome.test.assertFalse;
+var assertTrue = chrome.test.assertTrue;
+var fail = chrome.test.callbackFail;
+var pass = chrome.test.callbackPass;
+var listenOnce = chrome.test.listenOnce;
+
+var NOT_OPTIONAL_ERROR =
+ "Optional permissions must be listed in extension manifest.";
+
+var NO_TABS_PERMISSION =
+ "You do not have permission to use 'windows.getAll'.";
+
+var REQUIRED_ERROR =
+ "You cannot remove required permissions.";
+
+var NOT_WHITE_LISTED_ERROR =
+ "The optional permissions API does not support '*'.";
+
+var UNKNOWN_PERMISSIONS_ERROR =
+ "'*' is not a recognized permission.";
+
+var emptyPermissions = {permissions: []};
+
+var initialPermissions = {
+ permissions: ['experimental', 'management', 'permissions']
+};
+
+var permissionsWithTabs = {
+ permissions: ['experimental', 'management', 'permissions', 'tabs']
+}
+
+function checkEqualSets(set1, set2) {
+ if (set1.length != set2.length)
+ return false;
+
+ for (var x = 0; x < set1.length; x++) {
+ if (!set2.some(function(v) { return v == set1[x]; }))
+ return false;
+ }
+
+ return true;
+}
+
+function checkPermSetsEq(set1, set2) {
+ return checkEqualSets(set1.permissions, set2.permissions);
+}
+
+chrome.test.runTests([
+ function contains() {
+ chrome.experimental.permissions.contains(
+ {permissions: ['management']},
+ pass(function(result) {
+ assertTrue(result);
+ }));
+ chrome.experimental.permissions.contains(
+ {permissions: ['devtools']},
+ pass(function(result) {
+ assertFalse(result);
+ }));
+ chrome.experimental.permissions.contains(
+ {permissions: ['permissions', 'management']},
+ pass(function(result) {
+ assertTrue(result);
+ }));
+ chrome.experimental.permissions.contains(
+ {permissions: ['management', 'permissions']},
+ pass(function(result) {
+ assertTrue(result);
+ }));
+ },
+
+ function getAll() {
+ chrome.experimental.permissions.getAll(pass(function(permissions) {
+ assertTrue(checkPermSetsEq(initialPermissions, permissions));
+ }));
+ },
+
+ // Nothing should happen if we request permission we already have
+ function requestNoOp() {
+ chrome.experimental.permissions.request(
+ {permissions:['management']},
+ pass(function(granted) {
+ assertTrue(granted);
+ }));
+ },
+
+ // We should get an error when requesting permissions that haven't been
+ // defined in "optional_permissions".
+ function requestNonOptional() {
+ chrome.experimental.permissions.request(
+ {permissions:['debugger']}, fail(NOT_OPTIONAL_ERROR));
+ },
+
+ // We should be able to request the tabs API since it's in the granted
+ // permissions list (see permissions_apitest.cc).
+ function requestTabs() {
+ try {
+ chrome.windows.getAll({populate: true}, function() {
+ chrome.test.fail("Should not have tabs API permission.");
+ });
+ } catch (e) {
+ assertTrue(e.message.indexOf(NO_TABS_PERMISSION) == 0);
+ }
+ listenOnce(chrome.experimental.permissions.onAdded, function(permissions) {
+ assertTrue(permissions.permissions.length == 1);
+ assertTrue(permissions.permissions[0] == 'tabs');
+ });
+ chrome.experimental.permissions.request(
+ {permissions:['tabs']},
+ pass(function(granted) {
+ assertTrue(granted);
+ chrome.windows.getAll({populate: true}, pass(function(windows) {
+ assertTrue(true);
+ }));
+ chrome.experimental.permissions.getAll(pass(function(permissions) {
+ assertTrue(checkPermSetsEq(permissionsWithTabs, permissions));
+ }));
+ }));
+ },
+
+ // You can't remove required permissions.
+ function removeRequired() {
+ chrome.experimental.permissions.remove(
+ {permissions:['management']}, fail(REQUIRED_ERROR));
+ },
+
+ // You can remove permissions you don't have (nothing happens).
+ function removeNoOp() {
+ chrome.experimental.permissions.remove(
+ {permissions:['background']},
+ pass(function(removed) {
+ assertTrue(removed);
+ }));
+ },
+
+ function removeTabs() {
+ chrome.windows.getAll({populate: true}, pass(function(windows) {
+ assertTrue(true);
+ }));
+ listenOnce(chrome.experimental.permissions.onRemoved,
+ function(permissions) {
+ assertTrue(permissions.permissions.length == 1);
+ assertTrue(permissions.permissions[0] == 'tabs');
+ });
+ chrome.experimental.permissions.remove(
+ {permissions:['tabs']},
+ pass(function() {
+ chrome.experimental.permissions.getAll(pass(function(permissions) {
+ assertTrue(checkPermSetsEq(initialPermissions, permissions));
+ }));
+ try {
+ chrome.windows.getAll({populate: true}, function() {
+ chrome.test.fail("Should not have tabs API permission.");
+ });
+ } catch (e) {
+ assertTrue(e.message.indexOf(NO_TABS_PERMISSION) == 0);
+ }
+ }));
+ },
+
+ // The user shouldn't have to approve permissions that have no warnings.
+ // TODO(jstritar): move to its own test and set a low timeout
+ function noPromptForNoWarnings() {
+ chrome.experimental.permissions.request(
+ {permissions: ['notifications']},
+ pass(function(granted) {
+ assertTrue(granted);
+ }));
+ },
+
+ // Make sure you can only access the white listed permissions.
+ function whitelist() {
+ var error_msg = NOT_WHITE_LISTED_ERROR.replace('*', 'chromeAuthPrivate');
+ chrome.experimental.permissions.request(
+ {permissions: ['chromeAuthPrivate']}, fail(error_msg));
+ chrome.experimental.permissions.remove(
+ {permissions: ['chromeAuthPrivate']}, fail(error_msg));
+ },
+
+ function unknownPermission() {
+ var error_msg = UNKNOWN_PERMISSIONS_ERROR.replace('*', 'asdf');
+ chrome.experimental.permissions.request(
+ {permissions: ['asdf']}, fail(error_msg));
+ }
+]);
+</script>
diff --git a/chrome/test/data/extensions/api_test/permissions/optional/manifest.json b/chrome/test/data/extensions/api_test/permissions/optional/manifest.json new file mode 100644 index 0000000..55ea80b --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "permissions/optional", + "description": "permissions/optional", + "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkprt3BRSqoikAhSygI6VUzDLt18cKODYmkaa/dwPp4dboyz93RSB+v76grbqsNWrJjkrEwRD3QFeBYBq7h27XAMV4X5XvWjmWQBkRTBQyQI8cZd7M9dgfKrI3EqX9OJvd/wTJkC0dgF47nwWRa/Tvwl7Y66GwEEUjpn2MTv4klwIDAQAB", + "version": "0.1", + "background_page": "background.html", + "permissions": [ + "experimental", + "management", + "permissions", + "http://example.org/" + ], + "optional_permissions": [ + "tabs", + "management", + "notifications", + "background" + ] +} diff --git a/chrome/test/data/extensions/api_test/permissions/optional_deny/background.html b/chrome/test/data/extensions/api_test/permissions/optional_deny/background.html new file mode 100644 index 0000000..ca02382 --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional_deny/background.html @@ -0,0 +1,36 @@ +<script>
+
+var assertFalse = chrome.test.assertFalse;
+var assertTrue = chrome.test.assertTrue;
+var pass = chrome.test.callbackPass;
+
+var NO_TABS_PERMISSION =
+ "You do not have permission to use 'windows.getAll'.";
+
+chrome.test.runTests([
+ function denyRequest() {
+ chrome.experimental.permissions.request( + {permissions: ['tabs']}, + pass(function(granted) {
+ // They were not granted, and there should be no error.
+ assertFalse(granted);
+ assertTrue(chrome.extension.lastError === undefined);
+
+ // Make sure they weren't granted...
+ chrome.experimental.permissions.contains( + {permissions: ['tabs']},
+ pass(function(result) {
+ assertFalse(result);
+ }));
+
+ try {
+ chrome.windows.getAll({populate: true}, function() {
+ chrome.test.fail("Should not have tabs API permission.");
+ });
+ } catch (e) {
+ assertTrue(e.message.indexOf(NO_TABS_PERMISSION) == 0);
+ }
+ }));
+ }
+]);
+</script>
diff --git a/chrome/test/data/extensions/api_test/permissions/optional_deny/manifest.json b/chrome/test/data/extensions/api_test/permissions/optional_deny/manifest.json new file mode 100644 index 0000000..e3b668c --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional_deny/manifest.json @@ -0,0 +1,8 @@ +{ + "name": "permissions/optional_deny", + "description": "permissions/optional_deny", + "version": "0.1", + "background_page": "background.html", + "permissions": ["experimental", "permissions", "http://example.org/"], + "optional_permissions": ["tabs", "management"] +} |