diff options
author | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-28 23:58:54 +0000 |
---|---|---|
committer | jstritar@chromium.org <jstritar@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-28 23:58:54 +0000 |
commit | fdf6801902413c5662d071fba731e013e04f5181 (patch) | |
tree | d10a4ff3ed55e7ba76da3f173466b64b6b62c224 | |
parent | 93939461bcb7d8732d60f1cd70965c25b8617c4a (diff) | |
download | chromium_src-fdf6801902413c5662d071fba731e013e04f5181.zip chromium_src-fdf6801902413c5662d071fba731e013e04f5181.tar.gz chromium_src-fdf6801902413c5662d071fba731e013e04f5181.tar.bz2 |
Adds a section to the extension preferences to store the recognized permissions that the user has granted the extension.
Ignores unknown permissions when installing extensions.
Disables extension and prompts user to re-enable when unknown permissions become recognized (e.g., through browser upgrade).
Allows extensions to remove a permission in one version, then add them back in the next without prompting the user.
BUG=42742
TEST=ExtensionsServiceTest, ExtensionPrefsGrantedPermissions, ExtensionTest
Review URL: http://codereview.chromium.org/4687005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@67500 0039d316-1c4b-4281-b951-d872f2087c98
20 files changed, 964 insertions, 218 deletions
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index bbfb3c5c..231e875 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -799,7 +799,6 @@ void AutomationProvider::InstallExtension(const FilePath& crx_path, scoped_refptr<CrxInstaller> installer( new CrxInstaller(service, NULL)); // silent install, no UI - installer->set_allow_privilege_increase(true); installer->InstallCrx(crx_path); } else { AutomationMsg_InstallExtension::WriteReplyParams( @@ -868,7 +867,6 @@ void AutomationProvider::InstallExtensionAndGetHandle( ExtensionInstallUI* client = (with_ui ? new ExtensionInstallUI(profile_) : NULL); scoped_refptr<CrxInstaller> installer(new CrxInstaller(service, client)); - installer->set_allow_privilege_increase(true); installer->InstallCrx(crx_path); } else { AutomationMsg_InstallExtensionAndGetHandle::WriteReplyParams( diff --git a/chrome/browser/download/download_util.cc b/chrome/browser/download/download_util.cc index dd4a34c..082845c 100644 --- a/chrome/browser/download/download_util.cc +++ b/chrome/browser/download/download_util.cc @@ -252,7 +252,6 @@ void OpenChromeExtension(Profile* profile, download_item.url(), download_item.referrer_url()); installer->set_original_mime_type(download_item.original_mime_type()); installer->set_apps_require_extension_mime_type(true); - installer->set_allow_privilege_increase(true); installer->set_original_url(download_item.url()); installer->set_is_gallery_install(is_gallery_download); installer->InstallCrx(download_item.full_path()); diff --git a/chrome/browser/extensions/crx_installer.cc b/chrome/browser/extensions/crx_installer.cc index 3a9a3df..efe3ad1 100644 --- a/chrome/browser/extensions/crx_installer.cc +++ b/chrome/browser/extensions/crx_installer.cc @@ -83,7 +83,6 @@ CrxInstaller::CrxInstaller(ExtensionsService* frontend, install_source_(Extension::INTERNAL), extensions_enabled_(frontend->extensions_enabled()), delete_source_(false), - allow_privilege_increase_(false), is_gallery_install_(false), create_app_shortcut_(false), frontend_(frontend), @@ -433,7 +432,7 @@ void CrxInstaller::ReportSuccessFromUIThread() { // Tell the frontend about the installation and hand off ownership of // extension_ to it. - frontend_->OnExtensionInstalled(extension_, allow_privilege_increase_); + frontend_->OnExtensionInstalled(extension_); extension_ = NULL; // We're done. We don't post any more tasks to ourselves so we are deleted diff --git a/chrome/browser/extensions/crx_installer.h b/chrome/browser/extensions/crx_installer.h index 26bbbd8..c22a4a9 100644 --- a/chrome/browser/extensions/crx_installer.h +++ b/chrome/browser/extensions/crx_installer.h @@ -96,11 +96,6 @@ class CrxInstaller bool delete_source() const { return delete_source_; } void set_delete_source(bool val) { delete_source_ = val; } - bool allow_privilege_increase() const { return allow_privilege_increase_; } - void set_allow_privilege_increase(bool val) { - allow_privilege_increase_ = val; - } - bool allow_silent_install() const { return allow_silent_install_; } void set_allow_silent_install(bool val) { allow_silent_install_ = val; } @@ -178,14 +173,6 @@ class CrxInstaller // to false. bool delete_source_; - // Whether privileges should be allowed to silently increaes from any - // previously installed version of the extension. This is used for things - // like external extensions, where extensions come with third-party software - // or are distributed by the network administrator. There is no UI shown - // for these extensions, so there shouldn't be UI for privilege increase, - // either. Defaults to false. - bool allow_privilege_increase_; - // Whether the install originated from the gallery. bool is_gallery_install_; diff --git a/chrome/browser/extensions/extension_disabled_infobar_delegate.cc b/chrome/browser/extensions/extension_disabled_infobar_delegate.cc index d66a448..354fea9 100644 --- a/chrome/browser/extensions/extension_disabled_infobar_delegate.cc +++ b/chrome/browser/extensions/extension_disabled_infobar_delegate.cc @@ -36,9 +36,7 @@ class ExtensionDisabledDialogDelegate // Overridden from ExtensionInstallUI::Delegate: virtual void InstallUIProceed() { - ExtensionPrefs* prefs = service_->extension_prefs(); - prefs->SetDidExtensionEscalatePermissions(extension_, false); - service_->EnableExtension(extension_->id()); + service_->GrantPermissionsAndEnableExtension(extension_); Release(); } virtual void InstallUIAbort() { diff --git a/chrome/browser/extensions/extension_prefs.cc b/chrome/browser/extensions/extension_prefs.cc index 09b7dd6..ec6ada0 100644 --- a/chrome/browser/extensions/extension_prefs.cc +++ b/chrome/browser/extensions/extension_prefs.cc @@ -8,6 +8,7 @@ #include "base/string_number_conversions.h" #include "base/utf_string_conversions.h" #include "chrome/common/extensions/extension.h" +#include "chrome/common/extensions/url_pattern.h" #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" @@ -82,6 +83,14 @@ const char kUpdateUrlData[] = "update_url_data"; // Whether the browser action is visible in the toolbar. 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 kPrefGrantedPermissionsAPI[] = "granted_permissions.api"; +const char kPrefGrantedPermissionsHost[] = "granted_permissions.host"; +const char kPrefGrantedPermissionsAll[] = "granted_permissions.full"; + } // namespace //////////////////////////////////////////////////////////////////////////////// @@ -113,6 +122,15 @@ static void CleanupBadExtensionKeys(PrefService* prefs) { prefs->ScheduleSavePersistentPrefs(); } +static void ExtentToStringSet(const ExtensionExtent& host_extent, + std::set<std::string>* result) { + ExtensionExtent::PatternList patterns = host_extent.patterns(); + ExtensionExtent::PatternList::const_iterator i; + + for (i = patterns.begin(); i != patterns.end(); ++i) + result->insert(i->GetAsString()); +} + } // namespace ExtensionPrefs::ExtensionPrefs(PrefService* prefs, const FilePath& root_dir) @@ -218,24 +236,17 @@ DictionaryValue* ExtensionPrefs::CopyCurrentExtensions() { bool ExtensionPrefs::ReadBooleanFromPref( DictionaryValue* ext, const std::string& pref_key) { - if (!ext->HasKey(pref_key)) return false; bool bool_value = false; - if (!ext->GetBoolean(pref_key, &bool_value)) { - NOTREACHED() << "Failed to fetch " << pref_key << " flag."; - // In case we could not fetch the flag, we treat it as false. + if (!ext->GetBoolean(pref_key, &bool_value)) return false; - } + return bool_value; } bool ExtensionPrefs::ReadExtensionPrefBoolean( const std::string& extension_id, const std::string& pref_key) { - const DictionaryValue* extensions = prefs_->GetDictionary(kExtensionsPref); - if (!extensions) - return false; - - DictionaryValue* ext = NULL; - if (!extensions->GetDictionary(extension_id, &ext)) { + DictionaryValue* ext = GetExtensionPref(extension_id); + if (!ext) { // No such extension yet. return false; } @@ -244,29 +255,74 @@ bool ExtensionPrefs::ReadExtensionPrefBoolean( bool ExtensionPrefs::ReadIntegerFromPref( DictionaryValue* ext, const std::string& pref_key, int* out_value) { - if (!ext->HasKey(pref_key)) return false; - if (!ext->GetInteger(pref_key, out_value)) { - NOTREACHED() << "Failed to fetch " << pref_key << " flag."; - // In case we could not fetch the flag, we treat it as false. + if (!ext->GetInteger(pref_key, out_value)) return false; - } + return out_value != NULL; } bool ExtensionPrefs::ReadExtensionPrefInteger( const std::string& extension_id, const std::string& pref_key, int* out_value) { - const DictionaryValue* extensions = prefs_->GetDictionary(kExtensionsPref); - if (!extensions) - return false; - DictionaryValue* ext = NULL; - if (!extensions->GetDictionary(extension_id, &ext)) { + DictionaryValue* ext = GetExtensionPref(extension_id); + if (!ext) { // No such extension yet. return false; } return ReadIntegerFromPref(ext, pref_key, out_value); } +bool ExtensionPrefs::ReadExtensionPrefList( + const std::string& extension_id, const std::string& pref_key, + ListValue** out_value) { + DictionaryValue* ext = GetExtensionPref(extension_id); + if (!ext || !ext->GetList(pref_key, out_value)) + return false; + + return out_value != NULL; +} + +bool ExtensionPrefs::ReadExtensionPrefStringSet( + const std::string& extension_id, + const std::string& pref_key, + std::set<std::string>* result) { + ListValue* value = NULL; + if (!ReadExtensionPrefList(extension_id, pref_key, &value)) + return false; + + result->clear(); + + for (size_t i = 0; i < value->GetSize(); ++i) { + std::string item; + if (!value->GetString(i, &item)) + return false; + result->insert(item); + } + + return true; +} + +void ExtensionPrefs::AddToExtensionPrefStringSet( + const std::string& extension_id, + const std::string& pref_key, + const std::set<std::string>& added_value) { + std::set<std::string> old_value; + std::set<std::string> new_value; + ReadExtensionPrefStringSet(extension_id, pref_key, &old_value); + + std::set_union(old_value.begin(), old_value.end(), + added_value.begin(), added_value.end(), + std::inserter(new_value, new_value.end())); + + ListValue* value = new ListValue(); + for (std::set<std::string>::const_iterator iter = new_value.begin(); + iter != new_value.end(); ++iter) + value->Append(Value::CreateStringValue(*iter)); + + UpdateExtensionPref(extension_id, pref_key, value); + prefs_->ScheduleSavePersistentPrefs(); +} + void ExtensionPrefs::SavePrefsAndNotify() { prefs_->ScheduleSavePersistentPrefs(); prefs_->pref_notifier()->OnUserPreferenceSet(kExtensionsPref); @@ -411,6 +467,60 @@ void ExtensionPrefs::SetLastPingDayImpl(const Time& time, SavePrefsAndNotify(); } + +bool ExtensionPrefs::GetGrantedPermissions( + const std::string& extension_id, + bool* full_access, + std::set<std::string>* api_permissions, + ExtensionExtent* host_extent) { + CHECK(Extension::IdIsValid(extension_id)); + + DictionaryValue* ext = GetExtensionPref(extension_id); + if (!ext || !ext->GetBoolean(kPrefGrantedPermissionsAll, full_access)) + return false; + + ReadExtensionPrefStringSet( + extension_id, kPrefGrantedPermissionsAPI, api_permissions); + + std::set<std::string> host_permissions; + ReadExtensionPrefStringSet( + extension_id, kPrefGrantedPermissionsHost, &host_permissions); + + for (std::set<std::string>::iterator i = host_permissions.begin(); + i != host_permissions.end(); ++i) + host_extent->AddPattern(URLPattern( + Extension::kValidWebExtentSchemes | UserScript::kValidUserScriptSchemes, + *i)); + + return true; +} + +void ExtensionPrefs::AddGrantedPermissions( + const std::string& extension_id, + const bool full_access, + const std::set<std::string>& api_permissions, + const ExtensionExtent& host_extent) { + CHECK(Extension::IdIsValid(extension_id)); + + UpdateExtensionPref(extension_id, kPrefGrantedPermissionsAll, + Value::CreateBooleanValue(full_access)); + + if (!api_permissions.empty()) { + AddToExtensionPrefStringSet( + extension_id, kPrefGrantedPermissionsAPI, api_permissions); + } + + if (!host_extent.is_empty()) { + std::set<std::string> host_permissions; + ExtentToStringSet(host_extent, &host_permissions); + + AddToExtensionPrefStringSet( + extension_id, kPrefGrantedPermissionsHost, host_permissions); + } + + SavePrefsAndNotify(); +} + Time ExtensionPrefs::LastPingDay(const std::string& extension_id) const { DCHECK(Extension::IdIsValid(extension_id)); return LastPingDayImpl(GetExtensionPref(extension_id)); diff --git a/chrome/browser/extensions/extension_prefs.h b/chrome/browser/extensions/extension_prefs.h index 0bcd9f9..26e4327 100644 --- a/chrome/browser/extensions/extension_prefs.h +++ b/chrome/browser/extensions/extension_prefs.h @@ -123,6 +123,27 @@ class ExtensionPrefs { // the client's. void SetLastPingDay(const std::string& extension_id, const base::Time& time); + // Gets the permissions (|api_permissions|, |host_extent| and |full_access|) + // granted to the extension with |extension_id|. |full_access| will be true + // if the extension has all effective permissions (like from an NPAPI plugin). + // Returns false if the granted permissions haven't been initialized yet. + // TODO(jstritar): Refractor the permissions into a class that encapsulates + // all granted permissions, can be initialized from preferences or + // a manifest file, and can be compared to each other. + bool GetGrantedPermissions(const std::string& extension_id, + bool* full_access, + std::set<std::string>* api_permissions, + ExtensionExtent* host_extent); + + // Adds the specified |api_permissions|, |host_extent| and |full_access| + // to the granted permissions for extension with |extension_id|. + // |full_access| should be set to true if the extension effectively has all + // permissions (such as by having an NPAPI plugin). + void AddGrantedPermissions(const std::string& extension_id, + const bool full_access, + const std::set<std::string>& api_permissions, + const ExtensionExtent& host_extent); + // Similar to the 2 above, but for the extensions blacklist. base::Time BlacklistLastPingDay() const; void SetBlacklistLastPingDay(const base::Time& time); @@ -222,7 +243,7 @@ class ExtensionPrefs { void DeleteExtensionPrefs(const std::string& id); // Reads a boolean pref from |ext| with key |pref_key|. - // Return false if the value is false or kPrefBlacklist does not exist. + // Return false if the value is false or |pref_key| does not exist. bool ReadBooleanFromPref(DictionaryValue* ext, const std::string& pref_key); // Reads a boolean pref |pref_key| from extension with id |extension_id|. @@ -239,6 +260,24 @@ class ExtensionPrefs { const std::string& pref_key, int* out_value); + // Reads a list pref |pref_key| from extension with id | extension_id|. + bool ReadExtensionPrefList(const std::string& extension_id, + const std::string& pref_key, + ListValue** out_value); + + // Reads a list pref |pref_key| as a string set from the extension with + // id |extension_id|. + bool ReadExtensionPrefStringSet(const std::string& extension_id, + const std::string& pref_key, + std::set<std::string>* result); + + // Adds the |added_values| to the value of |pref_key| for the extension + // with id |extension_id| (the new value will be the union of the existing + // value and |added_values|). + void AddToExtensionPrefStringSet(const std::string& extension_id, + const std::string& pref_key, + const std::set<std::string>& added_values); + // Ensures and returns a mutable dictionary for extension |id|'s prefs. DictionaryValue* GetOrCreateExtensionPref(const std::string& id); diff --git a/chrome/browser/extensions/extension_prefs_unittest.cc b/chrome/browser/extensions/extension_prefs_unittest.cc index 4218790..ec3a33d 100644 --- a/chrome/browser/extensions/extension_prefs_unittest.cc +++ b/chrome/browser/extensions/extension_prefs_unittest.cc @@ -19,6 +19,28 @@ using base::Time; using base::TimeDelta; +static void AddPattern(ExtensionExtent* extent, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + extent->AddPattern(URLPattern(schemes, pattern)); +} + +static void AssertEqualExtents(ExtensionExtent* extent1, + ExtensionExtent* extent2) { + std::vector<URLPattern> patterns1 = extent1->patterns(); + std::vector<URLPattern> patterns2 = extent2->patterns(); + std::set<std::string> strings1; + EXPECT_EQ(patterns1.size(), patterns2.size()); + + for (size_t i = 0; i < patterns1.size(); ++i) + strings1.insert(patterns1.at(i).GetAsString()); + + std::set<std::string> strings2; + for (size_t i = 0; i < patterns2.size(); ++i) + strings2.insert(patterns2.at(i).GetAsString()); + + EXPECT_EQ(strings1, strings2); +} + // Base class for tests. class ExtensionPrefsTest : public testing::Test { public: @@ -145,6 +167,106 @@ class ExtensionPrefsEscalatePermissions : public ExtensionPrefsTest { }; TEST_F(ExtensionPrefsEscalatePermissions, EscalatePermissions) {} +// Tests the AddGrantedPermissions / GetGrantedPermissions functions. +class ExtensionPrefsGrantedPermissions : public ExtensionPrefsTest { + public: + virtual void Initialize() { + extension_id_ = prefs_.AddExtensionAndReturnId("test"); + + api_perm_set1_.insert("tabs"); + api_perm_set1_.insert("bookmarks"); + api_perm_set1_.insert("something_random"); + + api_perm_set2_.insert("history"); + api_perm_set2_.insert("unknown2"); + + AddPattern(&host_perm_set1_, "http://*.google.com/*"); + AddPattern(&host_perm_set1_, "http://example.com/*"); + + AddPattern(&host_perm_set2_, "https://*.google.com/*"); + // with duplicate: + AddPattern(&host_perm_set2_, "http://*.google.com/*"); + + std::set_union(api_perm_set1_.begin(), api_perm_set1_.end(), + api_perm_set2_.begin(), api_perm_set2_.end(), + std::inserter(api_permissions_, api_permissions_.end())); + + AddPattern(&host_permissions_, "http://*.google.com/*"); + AddPattern(&host_permissions_, "http://example.com/*"); + AddPattern(&host_permissions_, "https://*.google.com/*"); + + std::set<std::string> empty_set; + std::set<std::string> api_perms; + bool full_access = false; + ExtensionExtent host_perms; + ExtensionExtent empty_extent; + + // Make sure both granted api and host permissions start empty. + EXPECT_FALSE(prefs()->GetGrantedPermissions( + extension_id_, &full_access, &api_perms, &host_perms)); + + EXPECT_TRUE(api_perms.empty()); + EXPECT_TRUE(host_perms.is_empty()); + + // Add part of the api permissions. + prefs()->AddGrantedPermissions( + extension_id_, false, api_perm_set1_, empty_extent); + EXPECT_TRUE(prefs()->GetGrantedPermissions( + extension_id_, &full_access, &api_perms, &host_perms)); + EXPECT_EQ(api_perm_set1_, api_perms); + EXPECT_TRUE(host_perms.is_empty()); + EXPECT_FALSE(full_access); + host_perms.ClearPaths(); + api_perms.clear(); + + // Add part of the host permissions. + prefs()->AddGrantedPermissions( + extension_id_, false, empty_set, host_perm_set1_); + EXPECT_TRUE(prefs()->GetGrantedPermissions( + extension_id_, &full_access, &api_perms, &host_perms)); + EXPECT_FALSE(full_access); + EXPECT_EQ(api_perm_set1_, api_perms); + AssertEqualExtents(&host_perm_set1_, &host_perms); + host_perms.ClearPaths(); + api_perms.clear(); + + // Add the rest of both the api and host permissions. + prefs()->AddGrantedPermissions(extension_id_, + true, + api_perm_set2_, + host_perm_set2_); + + EXPECT_TRUE(prefs()->GetGrantedPermissions( + extension_id_, &full_access, &api_perms, &host_perms)); + EXPECT_TRUE(full_access); + EXPECT_EQ(api_permissions_, api_perms); + AssertEqualExtents(&host_permissions_, &host_perms); + } + + virtual void Verify() { + std::set<std::string> api_perms; + ExtensionExtent host_perms; + bool full_access; + + EXPECT_TRUE(prefs()->GetGrantedPermissions( + extension_id_, &full_access, &api_perms, &host_perms)); + EXPECT_EQ(api_permissions_, api_perms); + EXPECT_TRUE(full_access); + AssertEqualExtents(&host_permissions_, &host_perms); + } + + private: + std::string extension_id_; + std::set<std::string> api_perm_set1_; + std::set<std::string> api_perm_set2_; + ExtensionExtent host_perm_set1_; + ExtensionExtent host_perm_set2_; + + + std::set<std::string> api_permissions_; + ExtensionExtent host_permissions_; +}; +TEST_F(ExtensionPrefsGrantedPermissions, GrantedPermissions) {} // Tests the GetVersionString function. class ExtensionPrefsVersionString : public ExtensionPrefsTest { diff --git a/chrome/browser/extensions/extensions_service.cc b/chrome/browser/extensions/extensions_service.cc index a675f5b..8284def 100644 --- a/chrome/browser/extensions/extensions_service.cc +++ b/chrome/browser/extensions/extensions_service.cc @@ -347,8 +347,9 @@ void ExtensionsServiceBackend::LoadSingleExtension( // prefs. BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, - NewRunnableMethod(frontend_, &ExtensionsService::OnExtensionInstalled, - extension, true)); + NewRunnableMethod(frontend_, + &ExtensionsService::OnExtensionInstalled, + extension)); } void ExtensionsServiceBackend::ReportExtensionLoadError( @@ -644,7 +645,6 @@ void ExtensionsService::InstallExtension(const FilePath& extension_path) { scoped_refptr<CrxInstaller> installer( new CrxInstaller(this, // frontend NULL)); // no client (silent install) - installer->set_allow_privilege_increase(true); installer->InstallCrx(extension_path); } @@ -943,6 +943,27 @@ void ExtensionsService::DisableExtension(const std::string& extension_id) { UpdateActiveExtensionsInCrashReporter(); } +void ExtensionsService::GrantPermissions(const Extension* extension) { + CHECK(extension); + + // We only maintain the granted permissions prefs for INTERNAL extensions. + CHECK(extension->location() == Extension::INTERNAL); + + ExtensionExtent effective_hosts = extension->GetEffectiveHostPermissions(); + extension_prefs_->AddGrantedPermissions(extension->id(), + extension->HasFullPermissions(), + extension->api_permissions(), + effective_hosts); +} + +void ExtensionsService::GrantPermissionsAndEnableExtension( + const Extension* extension) { + CHECK(extension); + GrantPermissions(extension); + extension_prefs_->SetDidExtensionEscalatePermissions(extension, false); + EnableExtension(extension->id()); +} + void ExtensionsService::LoadExtension(const FilePath& extension_path) { BrowserThread::PostTask( BrowserThread::FILE, FROM_HERE, @@ -975,7 +996,7 @@ void ExtensionsService::LoadComponentExtensions() { return; } - OnExtensionLoaded(extension, false); // Don't allow privilege increase. + OnExtensionLoaded(extension); } } @@ -1120,7 +1141,6 @@ void ExtensionsService::LoadAllExtensions() { browser_action_count); } - void ExtensionsService::LoadInstalledExtension(const ExtensionInfo& info, bool write_to_prefs) { std::string error; @@ -1147,7 +1167,7 @@ void ExtensionsService::LoadInstalledExtension(const ExtensionInfo& info, if (write_to_prefs) extension_prefs_->UpdateManifest(extension); - OnExtensionLoaded(extension, true); + OnExtensionLoaded(extension); if (Extension::IsExternalLocation(info.extension_location)) { BrowserThread::PostTask( @@ -1523,8 +1543,7 @@ void ExtensionsService::OnLoadedInstalledExtensions() { NotificationService::NoDetails()); } -void ExtensionsService::OnExtensionLoaded(const Extension* extension, - bool allow_privilege_increase) { +void ExtensionsService::OnExtensionLoaded(const Extension* extension) { // Ensure extension is deleted unless we transfer ownership. scoped_refptr<const Extension> scoped_extension(extension); @@ -1535,63 +1554,29 @@ void ExtensionsService::OnExtensionLoaded(const Extension* extension, if (disabled_extension_paths_.erase(extension->id()) > 0) EnableExtension(extension->id()); - // TODO(aa): Need to re-evaluate this branch. Does this still make sense now - // that extensions are enabled by default? - if (extensions_enabled() || - extension->is_theme() || - extension->location() == Extension::LOAD || - extension->location() == Extension::COMPONENT || - Extension::IsExternalLocation(extension->location())) { - const Extension* old = GetExtensionByIdInternal(extension->id(), - true, true); - if (old) { - // CrxInstaller should have guaranteed that we aren't downgrading. - CHECK(extension->version()->CompareTo(*(old->version())) >= 0); - - bool allow_silent_upgrade = - allow_privilege_increase || !Extension::IsPrivilegeIncrease( - old, extension); - - // Extensions get upgraded if silent upgrades are allowed, otherwise - // they get disabled. - if (allow_silent_upgrade) { - SetBeingUpgraded(old, true); - SetBeingUpgraded(extension, true); - } - - // To upgrade an extension in place, unload the old one and - // then load the new one. - UnloadExtension(old->id()); - old = NULL; + // Check if the extension's privileges have changed and disable the extension + // if necessary. + DisableIfPrivilegeIncrease(extension); - if (!allow_silent_upgrade) { - // Extension has changed permissions significantly. Disable it. We - // send a notification below. - extension_prefs_->SetExtensionState(extension, Extension::DISABLED); - extension_prefs_->SetDidExtensionEscalatePermissions(extension, true); - } - } - - switch (extension_prefs_->GetExtensionState(extension->id())) { - case Extension::ENABLED: - extensions_.push_back(scoped_extension); + switch (extension_prefs_->GetExtensionState(extension->id())) { + case Extension::ENABLED: + extensions_.push_back(scoped_extension); - NotifyExtensionLoaded(extension); + NotifyExtensionLoaded(extension); - ExtensionDOMUI::RegisterChromeURLOverrides(profile_, - extension->GetChromeURLOverrides()); - break; - case Extension::DISABLED: - disabled_extensions_.push_back(scoped_extension); - NotificationService::current()->Notify( - NotificationType::EXTENSION_UPDATE_DISABLED, - Source<Profile>(profile_), - Details<const Extension>(extension)); - break; - default: - NOTREACHED(); - break; - } + ExtensionDOMUI::RegisterChromeURLOverrides( + profile_, extension->GetChromeURLOverrides()); + break; + case Extension::DISABLED: + disabled_extensions_.push_back(scoped_extension); + NotificationService::current()->Notify( + NotificationType::EXTENSION_UPDATE_DISABLED, + Source<Profile>(profile_), + Details<const Extension>(extension)); + break; + default: + NOTREACHED(); + break; } SetBeingUpgraded(extension, false); @@ -1609,6 +1594,86 @@ void ExtensionsService::OnExtensionLoaded(const Extension* extension, } } +void ExtensionsService::DisableIfPrivilegeIncrease(const Extension* extension) { + // 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 + // installs the extension, only the recognized permissions are recorded. + // When the unknown permissions become recognized (e.g., through browser + // upgrade), we can prompt the user to accept these new permissions. + // Extensions can also silently upgrade to less permissions, and then + // silently upgrade to a version that adds these permissions back. + // + // For example, pretend that Chrome 10 includes a permission "omnibox" + // for an API that adds suggestions to the omnibox. An extension can + // maintain backwards compatibility while still having "omnibox" in the + // manifest. If a user installs the extension on Chrome 9, the browser + // will record the permissions it recognized, not including "omnibox." + // When upgrading to Chrome 10, "omnibox" will be recognized and Chrome + // will disable the extension and prompt the user to approve the increase + // in privileges. The extension could then release a new version that + // removes the "omnibox" permission. When the user upgrades, Chrome will + // still remember that "omnibox" had been granted, so that if the + // extension once again includes "omnibox" in an upgrade, the extension + // can upgrade without requiring this user's approval. + const Extension* old = GetExtensionByIdInternal(extension->id(), + true, true); + bool granted_full_access; + std::set<std::string> granted_apis; + ExtensionExtent granted_extent; + + 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) { + // Add all the recognized permissions if the granted permissions list + // hasn't been initialized yet. + if (!extension_prefs_->GetGrantedPermissions(extension->id(), + &granted_full_access, + &granted_apis, + &granted_extent)) { + GrantPermissions(extension); + CHECK(extension_prefs_->GetGrantedPermissions(extension->id(), + &granted_full_access, + &granted_apis, + &granted_extent)); + } + + // Here, we check if an extension's privileges have increased in a manner + // that requires the user's approval. This could occur because the browser + // upgraded and recognized additional privileges, or an extension upgrades + // to a version that requires additional privileges. + is_privilege_increase = Extension::IsPrivilegeIncrease( + granted_full_access, granted_apis, granted_extent, extension); + } + + if (is_extension_upgrade) { + // CrxInstaller should have guaranteed that we aren't downgrading. + CHECK(extension->version()->CompareTo(*(old->version())) >= 0); + + // Extensions get upgraded if the privileges are allowed to increase or + // the privileges haven't increased. + if (!is_privilege_increase) { + SetBeingUpgraded(old, true); + SetBeingUpgraded(extension, true); + } + + // To upgrade an extension in place, unload the old one and + // then load the new one. + UnloadExtension(old->id()); + old = NULL; + } + + // Extension has changed permissions significantly. Disable it. A + // notification should be sent by the caller. + if (is_privilege_increase) { + extension_prefs_->SetExtensionState(extension, Extension::DISABLED); + extension_prefs_->SetDidExtensionEscalatePermissions(extension, true); + } +} + void ExtensionsService::UpdateActiveExtensionsInCrashReporter() { std::set<std::string> extension_ids; for (size_t i = 0; i < extensions_.size(); ++i) { @@ -1620,8 +1685,7 @@ void ExtensionsService::UpdateActiveExtensionsInCrashReporter() { child_process_logging::SetActiveExtensions(extension_ids); } -void ExtensionsService::OnExtensionInstalled(const Extension* extension, - bool allow_privilege_increase) { +void ExtensionsService::OnExtensionInstalled(const Extension* extension) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); // Ensure extension is deleted unless we transfer ownership. @@ -1763,7 +1827,7 @@ void ExtensionsService::OnExtensionInstalled(const Extension* extension, } // Transfer ownership of |extension| to OnExtensionLoaded. - OnExtensionLoaded(scoped_extension, allow_privilege_increase); + OnExtensionLoaded(scoped_extension); } const Extension* ExtensionsService::GetExtensionByIdInternal( @@ -1881,7 +1945,6 @@ void ExtensionsService::OnExternalExtensionFileFound( NULL)); // no client (silent install) installer->set_install_source(location); installer->set_expected_id(id); - installer->set_allow_privilege_increase(true); installer->InstallCrx(path); } diff --git a/chrome/browser/extensions/extensions_service.h b/chrome/browser/extensions/extensions_service.h index f19bb22..c831039 100644 --- a/chrome/browser/extensions/extensions_service.h +++ b/chrome/browser/extensions/extensions_service.h @@ -277,6 +277,15 @@ class ExtensionsService void EnableExtension(const std::string& extension_id); void DisableExtension(const std::string& extension_id); + // Updates the |extension|'s granted permissions lists to include all + // permissions in the |extension|'s manifest. + void GrantPermissions(const Extension* extension); + + // Updates the |extension|'s granted permissions lists to include all + // permissions in the |extension|'s manifest and re-enables the + // extension. + void GrantPermissionsAndEnableExtension(const Extension* extension); + // Load the extension from the directory |extension_path|. void LoadExtension(const FilePath& extension_path); @@ -351,12 +360,10 @@ class ExtensionsService virtual void OnLoadedInstalledExtensions(); // Called when an extension has been loaded. - void OnExtensionLoaded(const Extension* extension, - bool allow_privilege_increase); + void OnExtensionLoaded(const Extension* extension); // Called by the backend when an extension has been installed. - void OnExtensionInstalled(const Extension* extension, - bool allow_privilege_increase); + void OnExtensionInstalled(const Extension* extension); // Called by the backend when an external extension is found. void OnExternalExtensionFileFound(const std::string& id, @@ -364,6 +371,10 @@ class ExtensionsService const FilePath& path, Extension::Location location); + // 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); + // Go through each extensions in pref, unload blacklisted extensions // and update the blacklist state in pref. virtual void UpdateExtensionBlacklist( diff --git a/chrome/browser/extensions/extensions_service_unittest.cc b/chrome/browser/extensions/extensions_service_unittest.cc index e222c27..271309c 100644 --- a/chrome/browser/extensions/extensions_service_unittest.cc +++ b/chrome/browser/extensions/extensions_service_unittest.cc @@ -72,6 +72,7 @@ const char* const good_crx = "ldnnhddmnhbkjipkidpdiheffobcpfmf"; const char* const page_action = "obcimlgaoabeegjmmpldobjndiealpln"; const char* const theme_crx = "iamefpfkojoapidjnbafmgkgncegbkad"; const char* const theme2_crx = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf"; +const char* const permissions_crx = "eagpmdpfmaekmmcejjbmjoecnejeiiin"; struct ExtensionsOrder { bool operator()(const Extension* a, const Extension* b) { @@ -98,6 +99,28 @@ static std::vector<std::string> GetErrors() { return ret_val; } +static void AddPattern(ExtensionExtent* extent, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + extent->AddPattern(URLPattern(schemes, pattern)); +} + +static void AssertEqualExtents(ExtensionExtent* extent1, + ExtensionExtent* extent2) { + std::vector<URLPattern> patterns1 = extent1->patterns(); + std::vector<URLPattern> patterns2 = extent2->patterns(); + std::set<std::string> strings1; + EXPECT_EQ(patterns1.size(), patterns2.size()); + + for (size_t i = 0; i < patterns1.size(); ++i) + strings1.insert(patterns1.at(i).GetAsString()); + + std::set<std::string> strings2; + for (size_t i = 0; i < patterns2.size(); ++i) + strings2.insert(patterns2.at(i).GetAsString()); + + EXPECT_EQ(strings1, strings2); +} + } // namespace class MockExtensionProvider : public ExternalExtensionProvider { @@ -458,21 +481,39 @@ class ExtensionsServiceTest Extension::Location location); void PackAndInstallExtension(const FilePath& dir_path, + const FilePath& pem_path, bool should_succeed) { FilePath crx_path; ASSERT_TRUE(PathService::Get(base::DIR_TEMP, &crx_path)); crx_path = crx_path.AppendASCII("temp.crx"); - FilePath pem_path = crx_path.DirName().AppendASCII("temp.pem"); + + // Use the existing pem key, if provided. + FilePath pem_output_path; + if (pem_path.value().empty()) { + pem_output_path = crx_path.DirName().AppendASCII("temp.pem"); + ASSERT_TRUE(file_util::Delete(pem_output_path, false)); + } else { + ASSERT_TRUE(file_util::PathExists(pem_path)); + } ASSERT_TRUE(file_util::Delete(crx_path, false)); - ASSERT_TRUE(file_util::Delete(pem_path, false)); + scoped_ptr<ExtensionCreator> creator(new ExtensionCreator()); - ASSERT_TRUE(creator->Run(dir_path, crx_path, FilePath(), pem_path)); + ASSERT_TRUE(creator->Run(dir_path, + crx_path, + pem_path, + pem_output_path)); + ASSERT_TRUE(file_util::PathExists(crx_path)); InstallExtension(crx_path, should_succeed); } + void PackAndInstallExtension(const FilePath& dir_path, + bool should_succeed) { + PackAndInstallExtension(dir_path, FilePath(), should_succeed); + } + void InstallExtension(const FilePath& path, bool should_succeed) { ASSERT_TRUE(file_util::PathExists(path)); @@ -654,6 +695,19 @@ class ExtensionsServiceTest EXPECT_EQ(expected_val, val) << msg; } + void SetPref(const std::string& extension_id, + const std::string& pref_path, + Value* value, + const std::string& msg) { + const DictionaryValue* dict = + profile_->GetPrefs()->GetMutableDictionary("extensions.settings"); + ASSERT_TRUE(dict != NULL) << msg; + DictionaryValue* pref = NULL; + ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg; + EXPECT_TRUE(pref != NULL) << msg; + pref->Set(pref_path, value); + } + void SetPrefInteg(const std::string& extension_id, const std::string& pref_path, int value) { @@ -664,13 +718,46 @@ class ExtensionsServiceTest msg += " = "; msg += base::IntToString(value); + SetPref(extension_id, pref_path, Value::CreateIntegerValue(value), msg); + } + + void SetPrefBool(const std::string& extension_id, + const std::string& pref_path, + bool value) { + std::string msg = " while setting: "; + msg += extension_id + " " + pref_path; + msg += " = "; + msg += (value ? "true" : "false"); + + SetPref(extension_id, pref_path, Value::CreateBooleanValue(value), msg); + } + + void ClearPref(const std::string& extension_id, + const std::string& pref_path) { + std::string msg = " while clearing: "; + msg += extension_id + " " + pref_path; + const DictionaryValue* dict = profile_->GetPrefs()->GetMutableDictionary("extensions.settings"); ASSERT_TRUE(dict != NULL) << msg; DictionaryValue* pref = NULL; ASSERT_TRUE(dict->GetDictionary(extension_id, &pref)) << msg; EXPECT_TRUE(pref != NULL) << msg; - pref->SetInteger(pref_path, value); + pref->Remove(pref_path, NULL); + } + + void SetPrefStringSet(const std::string& extension_id, + const std::string& pref_path, + const std::set<std::string>& value) { + std::string msg = " while setting: "; + msg += extension_id + " " + pref_path; + + ListValue* list_value = new ListValue(); + for (std::set<std::string>::const_iterator iter = value.begin(); + iter != value.end(); ++iter) + list_value->Append(Value::CreateStringValue(*iter)); + + SetPref(extension_id, pref_path, list_value, msg); } protected: @@ -1007,6 +1094,225 @@ TEST_F(ExtensionsServiceTest, InstallUserScript) { ExtensionErrorReporter::GetInstance()->ClearErrors(); } +// This tests that the granted permissions preferences are correctly set when +// installing an extension. +TEST_F(ExtensionsServiceTest, GrantedPermissions) { + InitializeEmptyExtensionsService(); + FilePath path; + FilePath pem_path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); + path = path.AppendASCII("extensions") + .AppendASCII("permissions"); + + pem_path = path.AppendASCII("unknown.pem"); + path = path.AppendASCII("unknown"); + + ASSERT_TRUE(file_util::PathExists(pem_path)); + ASSERT_TRUE(file_util::PathExists(path)); + + ExtensionPrefs* prefs = service_->extension_prefs(); + + std::set<std::string> expected_api_perms; + std::set<std::string> known_api_perms; + bool full_access; + ExtensionExtent expected_host_perms; + ExtensionExtent known_host_perms; + + // Make sure there aren't any granted permissions before the + // extension is installed. + EXPECT_FALSE(prefs->GetGrantedPermissions( + permissions_crx, &full_access, &known_api_perms, &known_host_perms)); + EXPECT_TRUE(known_api_perms.empty()); + EXPECT_TRUE(known_host_perms.is_empty()); + + PackAndInstallExtension(path, pem_path, true); + + EXPECT_EQ(0u, GetErrors().size()); + ASSERT_EQ(1u, service_->extensions()->size()); + std::string extension_id = service_->extensions()->at(0)->id(); + EXPECT_EQ(permissions_crx, extension_id); + + + // Verify that the valid API permissions have been recognized. + expected_api_perms.insert("tabs"); + + AddPattern(&expected_host_perms, "http://*.google.com/*"); + AddPattern(&expected_host_perms, "https://*.google.com/*"); + AddPattern(&expected_host_perms, "http://www.example.com/*"); + + EXPECT_TRUE(prefs->GetGrantedPermissions(extension_id, + &full_access, + &known_api_perms, + &known_host_perms)); + + EXPECT_EQ(expected_api_perms, known_api_perms); + EXPECT_FALSE(full_access); + AssertEqualExtents(&expected_host_perms, &known_host_perms); +} + + +// Tests that the granted permissions full_access bit gets set correctly when +// an extension contains an NPAPI plugin. +TEST_F(ExtensionsServiceTest, GrantedFullAccessPermissions) { + InitializeEmptyExtensionsService(); + + FilePath path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); + path = path.AppendASCII("extensions") + .AppendASCII("good") + .AppendASCII("Extensions") + .AppendASCII("hpiknbiabeeppbpihjehijgoemciehgk") + .AppendASCII("2"); + + ASSERT_TRUE(file_util::PathExists(path)); + + PackAndInstallExtension(path, true); + + EXPECT_EQ(0u, GetErrors().size()); + EXPECT_EQ(1u, service_->extensions()->size()); + const Extension* extension = service_->extensions()->at(0); + std::string extension_id = extension->id(); + ExtensionPrefs* prefs = service_->extension_prefs(); + + bool full_access; + std::set<std::string> api_permissions; + ExtensionExtent host_permissions; + EXPECT_TRUE(prefs->GetGrantedPermissions( + extension_id, &full_access, &api_permissions, &host_permissions)); + + EXPECT_TRUE(full_access); + EXPECT_TRUE(api_permissions.empty()); + EXPECT_TRUE(host_permissions.is_empty()); +} + +// Tests that the extension is disabled when permissions are missing from +// the extension's granted permissions preferences. (This simulates updating +// the browser to a version which recognizes more permissions). +TEST_F(ExtensionsServiceTest, GrantedAPIAndHostPermissions) { + InitializeEmptyExtensionsService(); + + FilePath path; + ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &path)); + path = path.AppendASCII("extensions") + .AppendASCII("permissions") + .AppendASCII("unknown"); + + ASSERT_TRUE(file_util::PathExists(path)); + + PackAndInstallExtension(path, true); + + EXPECT_EQ(0u, GetErrors().size()); + EXPECT_EQ(1u, service_->extensions()->size()); + const Extension* extension = service_->extensions()->at(0); + std::string extension_id = extension->id(); + + ExtensionPrefs* prefs = service_->extension_prefs(); + + std::set<std::string> expected_api_permissions; + ExtensionExtent expected_host_permissions; + + expected_api_permissions.insert("tabs"); + AddPattern(&expected_host_permissions, "http://*.google.com/*"); + AddPattern(&expected_host_permissions, "https://*.google.com/*"); + AddPattern(&expected_host_permissions, "http://www.example.com/*"); + + std::set<std::string> api_permissions; + std::set<std::string> host_permissions; + + // Test that the extension is disabled when an API permission is missing from + // the extension's granted api permissions preference. (This simulates + // updating the browser to a version which recognizes a new API permission). + host_permissions.insert("http://*.google.com/*"); + host_permissions.insert("https://*.google.com/*"); + host_permissions.insert("http://www.example.com/*"); + + SetPrefBool(extension_id, "granted_permissions.initialized", true); + SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions); + SetPrefStringSet(extension_id, "granted_permissions.host", host_permissions); + + service_->ReloadExtensions(); + + ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED); + ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id)); + + // Now grant and re-enable the extension, making sure the prefs are updated. + service_->GrantPermissionsAndEnableExtension(extension); + + ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED); + ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id)); + + std::set<std::string> current_api_permissions; + ExtensionExtent current_host_permissions; + bool current_full_access; + + ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id, + ¤t_full_access, + ¤t_api_permissions, + ¤t_host_permissions)); + + ASSERT_FALSE(current_full_access); + ASSERT_EQ(expected_api_permissions, current_api_permissions); + AssertEqualExtents(&expected_host_permissions, ¤t_host_permissions); + + // Tests that the extension is disabled when a host permission is missing from + // the extension's granted host permissions preference. (This simulates + // updating the browser to a version which recognizes additional host + // permissions). + api_permissions.clear(); + host_permissions.clear(); + current_api_permissions.clear(); + current_host_permissions.ClearPaths(); + + api_permissions.insert("tabs"); + host_permissions.insert("http://*.google.com/*"); + host_permissions.insert("https://*.google.com/*"); + + SetPrefBool(extension_id, "granted_permissions.initialized", true); + SetPrefStringSet(extension_id, "granted_permissions.api", api_permissions); + SetPrefStringSet(extension_id, "granted_permissions.host", host_permissions); + + service_->ReloadExtensions(); + + ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::DISABLED); + ASSERT_TRUE(prefs->DidExtensionEscalatePermissions(extension_id)); + + // Now grant and re-enable the extension, making sure the prefs are updated. + service_->GrantPermissionsAndEnableExtension(extension); + + ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED); + ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id)); + + ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id, + ¤t_full_access, + ¤t_api_permissions, + ¤t_host_permissions)); + + ASSERT_FALSE(current_full_access); + ASSERT_EQ(expected_api_permissions, current_api_permissions); + AssertEqualExtents(&expected_host_permissions, ¤t_host_permissions); + + // Tests that the granted permissions preferences are initialized when + // migrating from the old pref schema. + current_api_permissions.clear(); + current_host_permissions.ClearPaths(); + + ClearPref(extension_id, "granted_permissions"); + + service_->ReloadExtensions(); + + ASSERT_TRUE(prefs->GetExtensionState(extension_id) == Extension::ENABLED); + ASSERT_FALSE(prefs->DidExtensionEscalatePermissions(extension_id)); + + ASSERT_TRUE(prefs->GetGrantedPermissions(extension_id, + ¤t_full_access, + ¤t_api_permissions, + ¤t_host_permissions)); + + ASSERT_FALSE(current_full_access); + ASSERT_EQ(expected_api_permissions, current_api_permissions); + AssertEqualExtents(&expected_host_permissions, ¤t_host_permissions); +} + // Test Packaging and installing an extension. TEST_F(ExtensionsServiceTest, PackExtension) { InitializeEmptyExtensionsService(); diff --git a/chrome/common/extensions/extension.cc b/chrome/common/extensions/extension.cc index ba316b8..4323e6e 100644 --- a/chrome/common/extensions/extension.cc +++ b/chrome/common/extensions/extension.cc @@ -71,9 +71,6 @@ static void ConvertHexadecimalToIDAlphabet(std::string* id) { } } -const int kValidWebExtentSchemes = - URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS; - // These keys are allowed by all crx files (apps, extensions, themes, etc). static const char* kBaseCrxKeys[] = { keys::kCurrentLocale, @@ -250,6 +247,9 @@ const size_t Extension::kNumHostedAppPermissions = // We purposefully don't put this into kPermissionNames. const char Extension::kOldUnlimitedStoragePermission[] = "unlimited_storage"; +const int Extension::kValidWebExtentSchemes = + URLPattern::SCHEME_HTTP | URLPattern::SCHEME_HTTPS; + // // Extension // @@ -1079,52 +1079,60 @@ bool Extension::FormatPEMForFileOutput(const std::string input, } // static -// TODO(aa): A problem with this code is that we silently allow upgrades to -// extensions that require less permissions than the current version, but then -// we don't silently allow them to go back. In order to fix this, we would need -// to remember the max set of permissions we ever granted a single extension. -bool Extension::IsPrivilegeIncrease(const Extension* old_extension, +bool Extension::IsPrivilegeIncrease(const bool granted_full_access, + const std::set<std::string>& granted_apis, + const ExtensionExtent& granted_extent, const Extension* new_extension) { - // If the old extension had native code access, we don't need to go any - // further. Things can't get any worse. - if (old_extension->plugins().size() > 0) + // If the extension had native code access, we don't need to go any further. + // Things can't get any worse. + if (granted_full_access) return false; // Otherwise, if the new extension has a plugin, it's a privilege increase. - if (new_extension->plugins().size() > 0) + if (new_extension->HasFullPermissions()) return true; - // If we are increasing the set of hosts we have access to (not - // counting scheme differences), it's a privilege increase. - if (!old_extension->HasEffectiveAccessToAllHosts()) { + // If the extension hadn't been granted access to all hosts in the past, then + // see if the extension requires more host permissions. + if (!HasEffectiveAccessToAllHosts(granted_extent, granted_apis)) { if (new_extension->HasEffectiveAccessToAllHosts()) return true; - // TODO(erikkay) This will trip when you add a new distinct hostname, - // but we should unique based on RCD as well. crbug.com/57042 - std::vector<std::string> old_hosts = old_extension->GetDistinctHosts(); - std::vector<std::string> new_hosts = new_extension->GetDistinctHosts(); + const ExtensionExtent new_extent = + new_extension->GetEffectiveHostPermissions(); + std::vector<std::string> new_hosts = + GetDistinctHosts(new_extent.patterns()); + std::vector<std::string> old_hosts = + GetDistinctHosts(granted_extent.patterns()); + std::set<std::string> old_hosts_set(old_hosts.begin(), old_hosts.end()); std::set<std::string> new_hosts_set(new_hosts.begin(), new_hosts.end()); - std::set<std::string> new_only; + std::set<std::string> new_hosts_only; + std::set_difference(new_hosts_set.begin(), new_hosts_set.end(), - old_hosts_set.begin(), old_hosts_set.end(), - std::inserter(new_only, new_only.end())); - if (new_only.size()) + old_hosts_set.begin(), old_hosts_set.end(), + std::inserter(new_hosts_only, new_hosts_only.end())); + + if (new_hosts_only.size()) return true; } - std::set<string16> old_messages = - old_extension->GetSimplePermissionMessages(); - std::set<string16> new_messages = - new_extension->GetSimplePermissionMessages(); - std::set<string16> new_only; - std::set_difference(new_messages.begin(), new_messages.end(), - old_messages.begin(), old_messages.end(), - std::inserter(new_only, new_only.end())); + std::set<std::string> new_apis = new_extension->api_permissions(); + std::set<std::string> new_apis_only; + std::set_difference(new_apis.begin(), new_apis.end(), + granted_apis.begin(), granted_apis.end(), + std::inserter(new_apis_only, new_apis_only.end())); - // If there are any new permission messages, then it's an increase. - if (!new_only.empty()) + // Ignore API permissions that don't require user approval when deciding if + // an extension has increased its privileges. + size_t new_api_count = 0; + for (std::set<std::string>::iterator i = new_apis_only.begin(); + i != new_apis_only.end(); ++i) { + if (GetPermissionMessageId(*i)) + new_api_count++; + } + + if (new_api_count) return true; return false; @@ -1706,29 +1714,31 @@ bool Extension::InitFromValue(const DictionaryValue& source, bool require_key, } } - // Otherwise, it's a host pattern permission. + // Check if it's a host pattern permission. URLPattern pattern = URLPattern(CanExecuteScriptEverywhere() ? URLPattern::SCHEME_ALL : (UserScript::kValidUserScriptSchemes | URLPattern::SCHEME_CHROMEUI) & ~URLPattern::SCHEME_FILE); - if (URLPattern::PARSE_SUCCESS != pattern.Parse(permission_str)) { - *error = ExtensionErrorUtils::FormatErrorMessage( - errors::kInvalidPermission, base::IntToString(i)); - return false; - } + if (URLPattern::PARSE_SUCCESS == pattern.Parse(permission_str)) { + if (!CanSpecifyHostPermission(pattern)) { + *error = ExtensionErrorUtils::FormatErrorMessage( + errors::kInvalidPermissionScheme, base::IntToString(i)); + return false; + } - 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.set_path("/*"); - // The path component is not used for host permissions, so we force it to - // match all paths. - pattern.set_path("/*"); + host_permissions_.push_back(pattern); + } - host_permissions_.push_back(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. } } @@ -2122,32 +2132,34 @@ bool Extension::CanExecuteScriptOnPage( return false; } -bool Extension::HasEffectiveAccessToAllHosts() const { +// static +bool Extension::HasEffectiveAccessToAllHosts( + const ExtensionExtent& effective_host_permissions, + const std::set<std::string>& api_permissions) { // Some APIs effectively grant access to every site. New ones should be // added here. (I'm looking at you, network API) - if (HasApiPermission(kProxyPermission)) + if (HasApiPermission(api_permissions, kProxyPermission)) return true; - for (URLPatternList::const_iterator host = host_permissions().begin(); - host != host_permissions().end(); ++host) { + const URLPatternList patterns = effective_host_permissions.patterns(); + for (URLPatternList::const_iterator host = patterns.begin(); + host != patterns.end(); ++host) { if (host->match_subdomains() && host->host().empty()) return true; } - for (UserScriptList::const_iterator content_script = - content_scripts().begin(); - content_script != content_scripts().end(); ++content_script) { - UserScript::PatternList::const_iterator pattern = - content_script->url_patterns().begin(); - for (; pattern != content_script->url_patterns().end(); ++pattern) { - if (pattern->match_subdomains() && pattern->host().empty()) - return true; - } - } - return false; } +bool Extension::HasEffectiveAccessToAllHosts() const { + return HasEffectiveAccessToAllHosts(GetEffectiveHostPermissions(), + api_permissions()); +} + +bool Extension::HasFullPermissions() const { + return plugins().size() > 0; +} + bool Extension::IsAPIPermission(const std::string& str) const { for (size_t i = 0; i < Extension::kNumPermissions; ++i) { if (str == Extension::kPermissions[i].name) { diff --git a/chrome/common/extensions/extension.h b/chrome/common/extensions/extension.h index 13b7cc1..29bc888 100644 --- a/chrome/common/extensions/extension.h +++ b/chrome/common/extensions/extension.h @@ -163,6 +163,9 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // still accepted as meaning the same thing as kUnlimitedStoragePermission. static const char kOldUnlimitedStoragePermission[]; + // Valid schemes for web extent URLPatterns. + static const int kValidWebExtentSchemes; + // Returns true if the string is one of the known hosted app permissions (see // kHostedAppPermissionNames). static bool IsHostedAppPermission(const std::string& permission); @@ -250,8 +253,11 @@ class Extension : public base::RefCountedThreadSafe<Extension> { std::string* output, bool is_public); // Determine whether |new_extension| has increased privileges compared to - // |old_extension|. - static bool IsPrivilegeIncrease(const Extension* old_extension, + // its previously granted permissions, specified by |granted_apis|, + // |granted_extent| and |granted_full_access|. + static bool IsPrivilegeIncrease(const bool granted_full_access, + const std::set<std::string>& granted_apis, + const ExtensionExtent& granted_extent, const Extension* new_extension); // Given an extension and icon size, read it if present and decode it into @@ -301,6 +307,13 @@ class Extension : public base::RefCountedThreadSafe<Extension> { static bool HasApiPermission(const std::set<std::string>& api_permissions, const std::string& function_name); + // Whether the |effective_host_permissions| and |api_permissions| include + // effective access to all hosts. See the non-static version of the method + // for more details. + static bool HasEffectiveAccessToAllHosts( + const ExtensionExtent& effective_host_permissions, + const std::set<std::string>& api_permissions); + bool HasApiPermission(const std::string& function_name) const { return HasApiPermission(this->api_permissions(), function_name); } @@ -325,6 +338,10 @@ class Extension : public base::RefCountedThreadSafe<Extension> { // network, etc.) bool HasEffectiveAccessToAllHosts() const; + // Whether the extension effectively has all permissions (for example, by + // having an NPAPI plugin). + bool HasFullPermissions() const; + // Returns the Homepage URL for this extension. If homepage_url was not // specified in the manifest, this returns the Google Gallery URL. For // third-party extensions, this returns a blank GURL. diff --git a/chrome/common/extensions/extension_manifests_unittest.cc b/chrome/common/extensions/extension_manifests_unittest.cc index 3a89166..446ff31 100644 --- a/chrome/common/extensions/extension_manifests_unittest.cc +++ b/chrome/common/extensions/extension_manifests_unittest.cc @@ -294,7 +294,7 @@ TEST_F(ExtensionManifestTest, OptionsPageInApps) { errors::kInvalidOptionsPageExpectUrlInPackage); } -TEST_F(ExtensionManifestTest, DisallowExtensionPermissions) { +TEST_F(ExtensionManifestTest, AllowUnrecognizedPermissions) { std::string error; scoped_ptr<DictionaryValue> manifest( LoadManifestFile("valid_app.json", &error)); @@ -308,13 +308,11 @@ TEST_F(ExtensionManifestTest, DisallowExtensionPermissions) { permissions->Clear(); permissions->Append(p); std::string message_name = base::StringPrintf("permission-%s", name); - if (Extension::IsHostedAppPermission(name)) { - scoped_refptr<Extension> extension; - extension = LoadAndExpectSuccess(manifest.get(), message_name); - } else { - LoadAndExpectError(manifest.get(), message_name, - errors::kInvalidPermission); - } + + // Extensions are allowed to contain unrecognized API permissions, + // so there shouldn't be any errors. + scoped_refptr<Extension> extension; + extension = LoadAndExpectSuccess(manifest.get(), message_name); } } diff --git a/chrome/common/extensions/extension_unittest.cc b/chrome/common/extensions/extension_unittest.cc index 013fce7..07bd6c6 100644 --- a/chrome/common/extensions/extension_unittest.cc +++ b/chrome/common/extensions/extension_unittest.cc @@ -48,6 +48,11 @@ void CompareLists(const std::vector<std::string>& expected, } } +static void AddPattern(ExtensionExtent* extent, const std::string& pattern) { + int schemes = URLPattern::SCHEME_ALL; + extent->AddPattern(URLPattern(schemes, pattern)); +} + } class ExtensionTest : public testing::Test { @@ -252,9 +257,10 @@ TEST(ExtensionTest, InitFromValueInvalid) { EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission)); + // We allow unknown API permissions, so this will be valid until we better + // distinguish between API and host permissions. permissions->Set(0, Value::CreateStringValue("www.google.com")); - EXPECT_FALSE(extension.InitFromValue(*input_value, true, &error)); - EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission)); + EXPECT_TRUE(extension.InitFromValue(*input_value, true, &error)); // Multiple page actions are not allowed. input_value.reset(static_cast<DictionaryValue*>(valid_value->DeepCopy())); @@ -327,10 +333,11 @@ TEST(ExtensionTest, InitFromValueValid) { ListValue* permissions = new ListValue; permissions->Set(0, Value::CreateStringValue("file:///C:/foo.txt")); input_value.Set(keys::kPermissions, permissions); - EXPECT_FALSE(extension.InitFromValue(input_value, false, &error)); - EXPECT_TRUE(MatchPattern(error, errors::kInvalidPermission)); + + // We allow unknown API permissions, so this will be valid until we better + // distinguish between API and host permissions. + EXPECT_TRUE(extension.InitFromValue(input_value, false, &error)); input_value.Remove(keys::kPermissions, NULL); - error.clear(); // Test with an options page. input_value.SetString(keys::kOptionsPage, "options.html"); @@ -816,29 +823,60 @@ TEST(ExtensionTest, EffectiveHostPermissions) { TEST(ExtensionTest, IsPrivilegeIncrease) { const struct { const char* base_name; + // Increase these sizes if you have more than 10. + const char* granted_apis[10]; + const char* granted_hosts[10]; + bool full_access; bool expect_success; } kTests[] = { - { "allhosts1", false }, // all -> all - { "allhosts2", false }, // all -> one - { "allhosts3", true }, // one -> all - { "hosts1", false }, // http://a,http://b -> http://a,http://b - { "hosts2", false }, // http://a,http://b -> https://a,http://*.b - { "hosts3", false }, // http://a,http://b -> http://a - { "hosts4", true }, // http://a -> http://a,http://b - { "hosts5", false }, // http://a,b,c -> http://a,b,c + https://a,b,c - { "hosts6", false }, // http://a.com -> http://a.com + http://a.co.uk - { "permissions1", false }, // tabs -> tabs - { "permissions2", true }, // tabs -> tabs,bookmarks - { "permissions3", true }, // http://a -> http://a,tabs - { "permissions5", true }, // bookmarks -> bookmarks,history + { "allhosts1", {NULL}, {"http://*/", NULL}, false, + false }, // all -> all + { "allhosts2", {NULL}, {"http://*/", NULL}, false, + false }, // all -> one + { "allhosts3", {NULL}, {NULL}, false, true }, // one -> all + { "hosts1", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + false }, // http://a,http://b -> http://a,http://b + { "hosts2", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + false }, // http://a,http://b -> https://a,http://*.b + { "hosts3", {NULL}, + {"http://www.google.com/", "http://www.reddit.com/", NULL}, false, + false }, // http://a,http://b -> http://a + { "hosts4", {NULL}, + {"http://www.google.com/", NULL}, false, + true }, // http://a -> http://a,http://b + { "hosts5", {"tabs", "notifications", NULL}, + {"http://*.example.com/", "http://*.example.com/*", + "http://*.example.co.uk/*", "http://*.example.com.au/*", + NULL}, false, + false }, // http://a,b,c -> http://a,b,c + https://a,b,c + { "hosts6", {"tabs", "notifications", NULL}, + {"http://*.example.com/", "http://*.example.com/*", NULL}, false, + false }, // http://a.com -> http://a.com + http://a.co.uk + { "permissions1", {"tabs", NULL}, + {NULL}, false, false }, // tabs -> tabs + { "permissions2", {"tabs", NULL}, + {NULL}, false, true }, // tabs -> tabs,bookmarks + { "permissions3", {NULL}, + {"http://*/*", NULL}, + false, true }, // http://a -> http://a,tabs + { "permissions5", {"bookmarks", NULL}, + {NULL}, false, true }, // bookmarks -> bookmarks,history #if !defined(OS_CHROMEOS) // plugins aren't allowed in ChromeOS - { "permissions4", false }, // plugin -> plugin,tabs - { "plugin1", false }, // plugin -> plugin - { "plugin2", false }, // plugin -> none - { "plugin3", true }, // none -> plugin + { "permissions4", {NULL}, + {NULL}, true, false }, // plugin -> plugin,tabs + { "plugin1", {NULL}, + {NULL}, true, false }, // plugin -> plugin + { "plugin2", {NULL}, + {NULL}, true, false }, // plugin -> none + { "plugin3", {NULL}, + {NULL}, false, true }, // none -> plugin #endif - { "storage", false }, // none -> storage - { "notifications", false } // none -> notifications + { "storage", {NULL}, + {NULL}, false, false }, // none -> storage + { "notifications", {NULL}, + {NULL}, false, false } // none -> notifications }; for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) { @@ -849,13 +887,22 @@ TEST(ExtensionTest, IsPrivilegeIncrease) { LoadManifest("allow_silent_upgrade", std::string(kTests[i].base_name) + "_new.json")); - EXPECT_TRUE(old_extension.get()) << kTests[i].base_name << "_old.json"; + std::set<std::string> granted_apis; + for (size_t j = 0; kTests[i].granted_apis[j] != NULL; ++j) + granted_apis.insert(kTests[i].granted_apis[j]); + + ExtensionExtent granted_hosts; + for (size_t j = 0; kTests[i].granted_hosts[j] != NULL; ++j) + AddPattern(&granted_hosts, kTests[i].granted_hosts[j]); + EXPECT_TRUE(new_extension.get()) << kTests[i].base_name << "_new.json"; - if (!old_extension.get() || !new_extension.get()) + if (!new_extension.get()) continue; EXPECT_EQ(kTests[i].expect_success, - Extension::IsPrivilegeIncrease(old_extension.get(), + Extension::IsPrivilegeIncrease(kTests[i].full_access, + granted_apis, + granted_hosts, new_extension.get())) << kTests[i].base_name; } diff --git a/chrome/test/data/extensions/good/Preferences b/chrome/test/data/extensions/good/Preferences index fc5524c..2d6f0bf 100644 --- a/chrome/test/data/extensions/good/Preferences +++ b/chrome/test/data/extensions/good/Preferences @@ -4,6 +4,11 @@ "behllobkkfkfnphdnhnkndlbkcpglgmj": { "location": 1, "path": "behllobkkfkfnphdnhnkndlbkcpglgmj/1.0.0.0", + "granted_permissions": { + "full": false, + "api": ["tabs"], + "host": ["file://*", "http://*.google.com/*", "https://*.google.com/*", "http://*.news.com/*"] + }, "state": 1, "allowFileAccess": true, "manifest": { @@ -34,6 +39,10 @@ "bjafgdebaacbbbecmhlhpofkepfkgcpa": { "location": 1, "path": "bjafgdebaacbbbecmhlhpofkepfkgcpa/1.0", + "granted_permissions": { + "full": false, + "api": [ "tabs" ] + }, "state": 1, "manifest": { "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRS2GUBOUAO5VZ2CMRId/eRR8/e9V42nUvY5XG+0sZ+JDHEjIQdq8qQy7HqdqEpCXKPMSPuMiC2t2HE9/hpL89SblNn3mwYPtSJGQdZvAzuv6SB0oA6jZ66V7+h/k0noGD3Tcu+Ko/vfkt5wCx2uHVK29k5JR/vGr0klaoVezGlwIDAQAB", @@ -46,6 +55,9 @@ "hpiknbiabeeppbpihjehijgoemciehgk": { "location": 1, "path": "hpiknbiabeeppbpihjehijgoemciehgk/2", + "granted_permissions": { + "full": true + }, "state": 1, "manifest": { "key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1nrgIE4OIQez0DVtc3JPR5O69s0XrH7TYC/xUC2e7Cla6eZldsA1PCWzLacimqtwfu7ljGXslk0HffkWNNou28Ip07KIC5oQHGEieAkNdPd3Pfi7QcAz+D0/xNPxuMtTKyuptWNtb2TTWD0MG7IdLHbMYFO6avkZtP+ldiKqxOwIDAQAB", diff --git a/chrome/test/data/extensions/permissions/unknown.pem b/chrome/test/data/extensions/permissions/unknown.pem new file mode 100644 index 0000000..e6f2e9f --- /dev/null +++ b/chrome/test/data/extensions/permissions/unknown.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALhtQMz6RmotoLKif +r1jhcO1jXcesy7d0cdRwxcl5KhgPkWH789iV5uLAHqHTW0SMPyxJGtjGvH8dn18S3 +1NjjviezsDRxGX5vKcCBfdfF6HCqGowD0qj4+qIixKNjdZ1nz62pyLAk1jls5zGoF +t1TnoNhkZN+d8Jn7bmS/FAgURAgMBAAECgYA84UPc5llMP5emoGF6H1pOyqXSRr4b +oHnZdDIUeBvNQUgNJ9yP9wS4geA8kmGKudCV+dvt3x9L9m0e62L4EBv9NLWIbsbwP +eWcG62w0YxM4SW5ZPHv9mWtjxaIbhyMPJawowbGCWUf8dIuoK7UyGOmN7Nqav6WqT +FD/LQFWenygQJBAO2KkiqlItIuU5uen1a1ilXpm6M82cCwi/6lZVMtnGH5hbxZhU+ +Mg7npOzm6PtWBkWyafWOf+naHQz0RlEmXtJkCQQDGwhIKOKDsauaBohlHK/NX71FL +7cgubN8eq8LEtQj02LcKWbJ7cMEt1Ho43JwoeVoDOJAPg5m3+SbE/NOeo6c5AkA7Z +/piG+Z3dgqG0sa6orA0CDZaPq+elwiL6MVMZg3EGuktT54dZODUr9WV/FQWhU7fQE +u3jJzQ48cXELxrIzuRAkEAw/06rLmKdZY0FkAxjOzZ2Cw0jhq6+oyAt03HkRCy9D1 ++wMYSKYbV9ss+ejOQCJkhGWv9Ik8ylScqA7ULN7NIgQJAZdPhs18Z+Nh6KTb4eUhF +UZIlZhPjU/2zTeSoUS5XXPcfTEICYwEv08++UDbJ89pzQZgl/nIZM9CT1B8u6uG2K +A== +-----END PRIVATE KEY----- diff --git a/chrome/test/data/extensions/permissions/unknown/manifest.json b/chrome/test/data/extensions/permissions/unknown/manifest.json new file mode 100644 index 0000000..227b8e3 --- /dev/null +++ b/chrome/test/data/extensions/permissions/unknown/manifest.json @@ -0,0 +1,9 @@ +{ + "content_scripts": [ { + "js": [ "test.js" ], + "matches": [ "http://*.google.com/*", "https://*.google.com/*", "http://www.example.com/*" ] + } ], + "name": "Unknown Permissions Test", + "permissions": [ "tabs", "blah" ], + "version": "1" +} diff --git a/chrome/test/data/extensions/permissions/unknown/test.js b/chrome/test/data/extensions/permissions/unknown/test.js new file mode 100644 index 0000000..0a77ac4 --- /dev/null +++ b/chrome/test/data/extensions/permissions/unknown/test.js @@ -0,0 +1,3 @@ +// place holder... +var x = 0; +x = x + 1;
\ No newline at end of file diff --git a/chrome/test/live_sync/live_extensions_sync_test_base.cc b/chrome/test/live_sync/live_extensions_sync_test_base.cc index 1eb040e..ce4396e 100644 --- a/chrome/test/live_sync/live_extensions_sync_test_base.cc +++ b/chrome/test/live_sync/live_extensions_sync_test_base.cc @@ -103,7 +103,7 @@ void LiveExtensionsSyncTestBase::InstallExtension( Profile* profile, scoped_refptr<Extension> extension) { CHECK(profile); CHECK(extension.get()); - profile->GetExtensionsService()->OnExtensionInstalled(extension, true); + profile->GetExtensionsService()->OnExtensionInstalled(extension); } void LiveExtensionsSyncTestBase::InstallAllPendingExtensions( |