diff options
30 files changed, 1034 insertions, 46 deletions
diff --git a/chrome/browser/extensions/api/permissions/permissions_api.cc b/chrome/browser/extensions/api/permissions/permissions_api.cc index 0f23d4c..2b0b6a0 100644 --- a/chrome/browser/extensions/api/permissions/permissions_api.cc +++ b/chrome/browser/extensions/api/permissions/permissions_api.cc @@ -7,6 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/extensions/api/permissions/permissions_api_helpers.h" +#include "chrome/browser/extensions/extension_management.h" #include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/extensions/api/permissions.h" @@ -30,6 +31,8 @@ namespace helpers = permissions_api_helpers; namespace { +const char kBlockedByEnterprisePolicy[] = + "Permissions are blocked by enterprise policy."; const char kCantRemoveRequiredPermissionsError[] = "You cannot remove required permissions."; const char kNotInOptionalPermissionsError[] = @@ -180,6 +183,15 @@ bool PermissionsRequestFunction::RunAsync() { return false; } + // Automatically declines api permissions requests, which are blocked by + // enterprise policy. + if (!ExtensionManagementFactory::GetForBrowserContext(GetProfile()) + ->IsPermissionSetAllowed(extension()->id(), + requested_permissions_)) { + error_ = kBlockedByEnterprisePolicy; + return false; + } + // We don't need to prompt the user if the requested permissions are a subset // of the granted permissions set. scoped_refptr<const PermissionSet> granted = diff --git a/chrome/browser/extensions/api/permissions/permissions_apitest.cc b/chrome/browser/extensions/api/permissions/permissions_apitest.cc index 798b2b5..7b6a35b 100644 --- a/chrome/browser/extensions/api/permissions/permissions_apitest.cc +++ b/chrome/browser/extensions/api/permissions/permissions_apitest.cc @@ -4,8 +4,11 @@ #include "chrome/browser/extensions/api/permissions/permissions_api.h" #include "chrome/browser/extensions/extension_apitest.h" +#include "chrome/browser/extensions/extension_management_test_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/browser.h" +#include "components/policy/core/browser/browser_policy_connector.h" +#include "components/policy/core/common/mock_configuration_policy_provider.h" #include "extensions/browser/extension_prefs.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/switches.h" @@ -23,13 +26,28 @@ static void AddPattern(URLPatternSet* extent, const std::string& pattern) { } // namespace class ExperimentalApiTest : public ExtensionApiTest { -public: - void SetUpCommandLine(CommandLine* command_line) override { + public: + void SetUpCommandLine(CommandLine* command_line) override { ExtensionApiTest::SetUpCommandLine(command_line); command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis); } }; +class ExtensionApiTestWithManagementPolicy : public ExtensionApiTest { + public: + void SetUpInProcessBrowserTestFixture() override { + ExtensionApiTest::SetUpInProcessBrowserTestFixture(); + EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_)) + .WillRepeatedly(testing::Return(true)); + policy_provider_.SetAutoRefresh(); + policy::BrowserPolicyConnector::SetPolicyProviderForTesting( + &policy_provider_); + } + + protected: + policy::MockConfigurationPolicyProvider policy_provider_; +}; + IN_PROC_BROWSER_TEST_F(ExtensionApiTest, PermissionsFail) { ASSERT_TRUE(RunExtensionTest("permissions/disabled")) << message_; @@ -128,6 +146,22 @@ IN_PROC_BROWSER_TEST_F(ExtensionApiTest, OptionalPermissionsRetainGesture) { << message_; } +// Test that optional permissions blocked by enterprise policy will be denied +// automatically. +IN_PROC_BROWSER_TEST_F(ExtensionApiTestWithManagementPolicy, + OptionalPermissionsPolicyBlocked) { + // Set enterprise policy to block some API permissions. + { + ExtensionManagementPolicyUpdater pref(&policy_provider_); + pref.AddBlockedPermission("*", "management"); + } + // Set auto confirm UI flag. + PermissionsRequestFunction::SetAutoConfirmForTests(true); + PermissionsRequestFunction::SetIgnoreUserGestureForTests(true); + EXPECT_TRUE(RunExtensionTest("permissions/optional_policy_blocked")) + << message_; +} + // Tests that an extension can't gain access to file: URLs without the checkbox // entry in prefs. There shouldn't be a warning either. IN_PROC_BROWSER_TEST_F(ExtensionApiTest, OptionalPermissionsFileAccess) { diff --git a/chrome/browser/extensions/extension_management.cc b/chrome/browser/extensions/extension_management.cc index dff3946..6904f5e 100644 --- a/chrome/browser/extensions/extension_management.cc +++ b/chrome/browser/extensions/extension_management.cc @@ -6,17 +6,18 @@ #include <algorithm> #include <string> -#include <vector> #include "base/bind.h" #include "base/bind_helpers.h" #include "base/logging.h" #include "base/prefs/pref_service.h" +#include "base/strings/string16.h" #include "base/strings/string_util.h" #include "chrome/browser/extensions/extension_management_constants.h" #include "chrome/browser/extensions/extension_management_internal.h" #include "chrome/browser/extensions/external_policy_loader.h" #include "chrome/browser/extensions/external_provider_impl.h" +#include "chrome/browser/extensions/permissions_based_management_policy_provider.h" #include "chrome/browser/extensions/standard_management_policy_provider.h" #include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/profile.h" @@ -24,6 +25,8 @@ #include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/pref_registry/pref_registry_syncable.h" #include "extensions/browser/pref_names.h" +#include "extensions/common/permissions/api_permission_set.h" +#include "extensions/common/permissions/permission_set.h" #include "extensions/common/url_pattern.h" #include "url/gurl.h" @@ -49,12 +52,18 @@ ExtensionManagement::ExtensionManagement(PrefService* pref_service) // before first call to Refresh(), so in order to resolve this, Refresh() must // be called in the initialization of ExtensionManagement. Refresh(); - provider_.reset(new StandardManagementPolicyProvider(this)); + providers_.push_back(new StandardManagementPolicyProvider(this)); + providers_.push_back(new PermissionsBasedManagementPolicyProvider(this)); } ExtensionManagement::~ExtensionManagement() { } +void ExtensionManagement::Shutdown() { + pref_change_registrar_.RemoveAll(); + pref_service_ = nullptr; +} + void ExtensionManagement::AddObserver(Observer* observer) { observer_list_.AddObserver(observer); } @@ -63,8 +72,9 @@ void ExtensionManagement::RemoveObserver(Observer* observer) { observer_list_.RemoveObserver(observer); } -ManagementPolicy::Provider* ExtensionManagement::GetProvider() const { - return provider_.get(); +std::vector<ManagementPolicy::Provider*> ExtensionManagement::GetProviders() + const { + return providers_.get(); } bool ExtensionManagement::BlacklistedByDefault() const { @@ -144,6 +154,31 @@ bool ExtensionManagement::IsAllowedManifestType( allowed_types.end(); } +const APIPermissionSet& ExtensionManagement::GetBlockedAPIPermissions( + const ExtensionId& id) const { + return ReadById(id)->blocked_permissions; +} + +scoped_refptr<const PermissionSet> ExtensionManagement::GetBlockedPermissions( + const ExtensionId& id) const { + // Only api permissions are supported currently. + return scoped_refptr<const PermissionSet>( + new PermissionSet(GetBlockedAPIPermissions(id), + ManifestPermissionSet(), + URLPatternSet(), + URLPatternSet())); +} + +bool ExtensionManagement::IsPermissionSetAllowed( + const ExtensionId& id, + scoped_refptr<const PermissionSet> perms) const { + for (const auto& blocked_api : GetBlockedAPIPermissions(id)) { + if (perms->HasAPIPermission(blocked_api->id())) + return false; + } + return true; +} + void ExtensionManagement::Refresh() { // Load all extension management settings preferences. const base::ListValue* allowed_list_pref = @@ -301,6 +336,8 @@ const base::Value* ExtensionManagement::LoadPreference( const char* pref_name, bool force_managed, base::Value::Type expected_type) { + if (!pref_service_) + return nullptr; const PrefService::Preference* pref = pref_service_->FindPreference(pref_name); if (pref && !pref->IsDefaultValue() && @@ -309,7 +346,7 @@ const base::Value* ExtensionManagement::LoadPreference( if (value && value->IsType(expected_type)) return value; } - return NULL; + return nullptr; } void ExtensionManagement::OnExtensionPrefChanged() { diff --git a/chrome/browser/extensions/extension_management.h b/chrome/browser/extensions/extension_management.h index 879cffb..5de7462 100644 --- a/chrome/browser/extensions/extension_management.h +++ b/chrome/browser/extensions/extension_management.h @@ -5,9 +5,13 @@ #ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_MANAGEMENT_H_ #define CHROME_BROWSER_EXTENSIONS_EXTENSION_MANAGEMENT_H_ +#include <vector> + #include "base/containers/scoped_ptr_hash_map.h" #include "base/macros.h" +#include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" #include "base/memory/singleton.h" #include "base/observer_list.h" #include "base/prefs/pref_change_registrar.h" @@ -34,6 +38,9 @@ struct GlobalSettings; } // namespace internal +class APIPermissionSet; +class PermissionSet; + // Tracks the management policies that affect extensions and provides interfaces // for observing and obtaining the global settings for all extensions, as well // as per-extension settings. @@ -65,12 +72,15 @@ class ExtensionManagement : public KeyedService { explicit ExtensionManagement(PrefService* pref_service); ~ExtensionManagement() override; + // KeyedService implementations: + void Shutdown() override; + void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); - // Get the ManagementPolicy::Provider controlled by extension management - // policy settings. - ManagementPolicy::Provider* GetProvider() const; + // Get the list of ManagementPolicy::Provider controlled by extension + // management policy settings. + std::vector<ManagementPolicy::Provider*> GetProviders() const; // Checks if extensions are blacklisted by default, by policy. When true, // this means that even extensions without an ID should be blacklisted (e.g. @@ -99,6 +109,17 @@ class ExtensionManagement : public KeyedService { // allowed to be installed. bool IsAllowedManifestType(Manifest::Type manifest_type) const; + // Returns the list of blocked API permissions for the extension |id|. + const APIPermissionSet& GetBlockedAPIPermissions(const ExtensionId& id) const; + + // Returns blocked permission set for extension |id|. + scoped_refptr<const PermissionSet> GetBlockedPermissions( + const ExtensionId& id) const; + + // Returns true if every permission in |perms| is allowed for extension |id|. + bool IsPermissionSetAllowed(const ExtensionId& id, + scoped_refptr<const PermissionSet> perms) const; + private: typedef base::ScopedPtrHashMap<ExtensionId, internal::IndividualSettings> SettingsIdMap; @@ -153,7 +174,7 @@ class ExtensionManagement : public KeyedService { ObserverList<Observer, true> observer_list_; PrefChangeRegistrar pref_change_registrar_; - scoped_ptr<ManagementPolicy::Provider> provider_; + ScopedVector<ManagementPolicy::Provider> providers_; DISALLOW_COPY_AND_ASSIGN(ExtensionManagement); }; diff --git a/chrome/browser/extensions/extension_management_constants.cc b/chrome/browser/extensions/extension_management_constants.cc index 399bb7c..707b3c0 100644 --- a/chrome/browser/extensions/extension_management_constants.cc +++ b/chrome/browser/extensions/extension_management_constants.cc @@ -17,6 +17,9 @@ const char kBlocked[] = "blocked"; const char kForceInstalled[] = "force_installed"; const char kNormalInstalled[] = "normal_installed"; +const char kBlockedPermissions[] = "blocked_permissions"; +const char kAllowedPermissions[] = "allowed_permissions"; + const char kUpdateUrl[] = "update_url"; const char kInstallSources[] = "install_sources"; const char kAllowedTypes[] = "allowed_types"; diff --git a/chrome/browser/extensions/extension_management_constants.h b/chrome/browser/extensions/extension_management_constants.h index 63a50aa..7df7e7d 100644 --- a/chrome/browser/extensions/extension_management_constants.h +++ b/chrome/browser/extensions/extension_management_constants.h @@ -20,6 +20,9 @@ extern const char kBlocked[]; extern const char kForceInstalled[]; extern const char kNormalInstalled[]; +extern const char kBlockedPermissions[]; +extern const char kAllowedPermissions[]; + extern const char kUpdateUrl[]; extern const char kInstallSources[]; extern const char kAllowedTypes[]; diff --git a/chrome/browser/extensions/extension_management_internal.cc b/chrome/browser/extensions/extension_management_internal.cc index 73bd132..ca7c5fb 100644 --- a/chrome/browser/extensions/extension_management_internal.cc +++ b/chrome/browser/extensions/extension_management_internal.cc @@ -67,12 +67,64 @@ bool IndividualSettings::Parse(const base::DictionaryValue* dict, } } + // Parses the blocked permission settings. + const base::ListValue* list_value = nullptr; + base::string16 error; + + // If applicable, inherit from global block list and remove all explicitly + // allowed permissions. + if (scope != SCOPE_DEFAULT && + dict->GetListWithoutPathExpansion(schema_constants::kAllowedPermissions, + &list_value)) { + // It is assumed that Parse() is already called for SCOPE_DEFAULT and + // settings specified for |this| is initialized by copying from default + // settings, including the |blocked_permissions| setting here. + // That is, |blocked_permissions| should be the default block permissions + // list settings here. + APIPermissionSet globally_blocked_permissions = blocked_permissions; + APIPermissionSet explicitly_allowed_permissions; + // Reuses code for parsing API permissions from manifest. But note that we + // only support list of strings type. + if (!APIPermissionSet::ParseFromJSON( + list_value, + APIPermissionSet::kDisallowInternalPermissions, + &explicitly_allowed_permissions, + &error, + nullptr)) { + // There might be unknown permissions, warn and just ignore them; + LOG(WARNING) << error; + } + APIPermissionSet::Difference(globally_blocked_permissions, + explicitly_allowed_permissions, + &blocked_permissions); + } + + // Then add all newly blocked permissions to the list. + if (dict->GetListWithoutPathExpansion(schema_constants::kBlockedPermissions, + &list_value)) { + // The |blocked_permissions| might be the result of the routines above, + // or remains the same as default block permissions settings. + APIPermissionSet permissions_to_merge_from = blocked_permissions; + APIPermissionSet permissions_parsed; + if (!APIPermissionSet::ParseFromJSON( + list_value, + APIPermissionSet::kDisallowInternalPermissions, + &permissions_parsed, + &error, + nullptr)) { + LOG(WARNING) << error; + } + APIPermissionSet::Union( + permissions_to_merge_from, permissions_parsed, &blocked_permissions); + } + return true; } void IndividualSettings::Reset() { installation_mode = ExtensionManagement::INSTALLATION_ALLOWED; update_url.clear(); + blocked_permissions.clear(); } GlobalSettings::GlobalSettings() { diff --git a/chrome/browser/extensions/extension_management_internal.h b/chrome/browser/extensions/extension_management_internal.h index d01bb33..0b3eaff 100644 --- a/chrome/browser/extensions/extension_management_internal.h +++ b/chrome/browser/extensions/extension_management_internal.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "chrome/browser/extensions/extension_management.h" #include "extensions/common/manifest.h" +#include "extensions/common/permissions/api_permission_set.h" namespace base { class DictionaryValue; @@ -42,6 +43,8 @@ struct IndividualSettings { // management preference and |scope| represents the applicable range of the // settings, a single extension, a group of extensions or default settings. // Note that in case of parsing errors, |this| will NOT be left untouched. + // This method is required to be called in order of ParsingScope, i.e. first + // SCOPE_DEFAULT, then SCOPE_INDIVIDUAL. bool Parse(const base::DictionaryValue* dict, ParsingScope scope); // Extension installation mode. Setting this to INSTALLATION_FORCED or @@ -55,6 +58,17 @@ struct IndividualSettings { ExtensionManagement::InstallationMode installation_mode; std::string update_url; + // Permissions settings for extensions. These settings won't grant permissions + // to extensions automatically. Instead, these settings will provide a list of + // blocked permissions for each extension. That is, if an extension requires a + // permission which has been blacklisted, this extension will not be allowed + // to load. And if it contains a blocked permission as optional requirement, + // it will be allowed to load (of course, with permission granted from user if + // necessary), but conflicting permissions will be dropped. These settings + // will merge from the default settings, and unspecified settings will take + // value from default settings. + APIPermissionSet blocked_permissions; + private: DISALLOW_ASSIGN(IndividualSettings); }; diff --git a/chrome/browser/extensions/extension_management_test_util.cc b/chrome/browser/extensions/extension_management_test_util.cc index e60f9fc..930208d 100644 --- a/chrome/browser/extensions/extension_management_test_util.cc +++ b/chrome/browser/extensions/extension_management_test_util.cc @@ -4,7 +4,16 @@ #include "chrome/browser/extensions/extension_management_test_util.h" +#include <string> + #include "components/crx_file/id_util.h" +#include "components/policy/core/common/configuration_policy_provider.h" +#include "components/policy/core/common/mock_configuration_policy_provider.h" +#include "components/policy/core/common/policy_bundle.h" +#include "components/policy/core/common/policy_map.h" +#include "components/policy/core/common/policy_namespace.h" +#include "components/policy/core/common/policy_types.h" +#include "policy/policy/policy_constants.h" namespace extensions { @@ -12,13 +21,13 @@ namespace schema = schema_constants; namespace { +const char kInstallSourcesPath[] = "*.install_sources"; +const char kAllowedTypesPath[] = "*.allowed_types"; + std::string make_path(std::string a, std::string b) { return a + "." + b; } -const char kInstallSourcesPath[] = "*.install_sources"; -const char kAllowedTypesPath[] = "*.allowed_types"; - } // namespace ExtensionManagementPrefUpdaterBase::ExtensionManagementPrefUpdaterBase() { @@ -27,6 +36,8 @@ ExtensionManagementPrefUpdaterBase::ExtensionManagementPrefUpdaterBase() { ExtensionManagementPrefUpdaterBase::~ExtensionManagementPrefUpdaterBase() { } +// Helper functions for per extension settings --------------------------------- + void ExtensionManagementPrefUpdaterBase::UnsetPerExtensionSettings( const ExtensionId& id) { DCHECK(crx_file::id_util::IdIsValid(id)); @@ -39,6 +50,8 @@ void ExtensionManagementPrefUpdaterBase::ClearPerExtensionSettings( pref_->SetWithoutPathExpansion(id, new base::DictionaryValue()); } +// Helper functions for 'installation_mode' manipulation ----------------------- + void ExtensionManagementPrefUpdaterBase::SetBlacklistedByDefault(bool value) { pref_->SetString(make_path(schema::kWildcard, schema::kInstallationMode), value ? schema::kBlocked : schema::kAllowed); @@ -77,6 +90,8 @@ void ExtensionManagementPrefUpdaterBase::SetIndividualExtensionAutoInstalled( pref_->SetString(make_path(id, schema::kUpdateUrl), update_url); } +// Helper functions for 'install_sources' manipulation ------------------------- + void ExtensionManagementPrefUpdaterBase::UnsetInstallSources() { pref_->Remove(kInstallSourcesPath, NULL); } @@ -95,6 +110,8 @@ void ExtensionManagementPrefUpdaterBase::RemoveInstallSource( RemoveStringFromList(kInstallSourcesPath, install_source); } +// Helper functions for 'allowed_types' manipulation --------------------------- + void ExtensionManagementPrefUpdaterBase::UnsetAllowedTypes() { pref_->Remove(kAllowedTypesPath, NULL); } @@ -108,10 +125,76 @@ void ExtensionManagementPrefUpdaterBase::AddAllowedType( AddStringToList(kAllowedTypesPath, allowed_type); } +void ExtensionManagementPrefUpdaterBase::RemoveAllowedType( + const std::string& allowed_type) { + RemoveStringFromList(kAllowedTypesPath, allowed_type); +} + +// Helper functions for 'blocked_permissions' manipulation --------------------- + +void ExtensionManagementPrefUpdaterBase::UnsetBlockedPermissions( + const std::string& prefix) { + DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix)); + pref_->Remove(make_path(prefix, schema::kBlockedPermissions), NULL); +} + +void ExtensionManagementPrefUpdaterBase::ClearBlockedPermissions( + const std::string& prefix) { + DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix)); + ClearList(make_path(prefix, schema::kBlockedPermissions)); +} + +void ExtensionManagementPrefUpdaterBase::AddBlockedPermission( + const std::string& prefix, + const std::string& permission) { + DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix)); + AddStringToList(make_path(prefix, schema::kBlockedPermissions), permission); +} + +void ExtensionManagementPrefUpdaterBase::RemoveBlockedPermission( + const std::string& prefix, + const std::string& permission) { + DCHECK(prefix == schema::kWildcard || crx_file::id_util::IdIsValid(prefix)); + RemoveStringFromList(make_path(prefix, schema::kBlockedPermissions), + permission); +} + +// Helper functions for 'allowed_permissions' manipulation --------------------- + +void ExtensionManagementPrefUpdaterBase::UnsetAllowedPermissions( + const std::string& id) { + DCHECK(crx_file::id_util::IdIsValid(id)); + pref_->Remove(make_path(id, schema::kAllowedPermissions), NULL); +} + +void ExtensionManagementPrefUpdaterBase::ClearAllowedPermissions( + const std::string& id) { + DCHECK(crx_file::id_util::IdIsValid(id)); + ClearList(make_path(id, schema::kAllowedPermissions)); +} + +void ExtensionManagementPrefUpdaterBase::AddAllowedPermission( + const std::string& id, + const std::string& permission) { + DCHECK(crx_file::id_util::IdIsValid(id)); + AddStringToList(make_path(id, schema::kAllowedPermissions), permission); +} + +void ExtensionManagementPrefUpdaterBase::RemoveAllowedPermission( + const std::string& id, + const std::string& permission) { + DCHECK(crx_file::id_util::IdIsValid(id)); + RemoveStringFromList(make_path(id, schema::kAllowedPermissions), permission); +} + +// Expose a read-only preference to user --------------------------------------- + const base::DictionaryValue* ExtensionManagementPrefUpdaterBase::GetPref() { return pref_.get(); } +// Private section functions --------------------------------------------------- + void ExtensionManagementPrefUpdaterBase::SetPref(base::DictionaryValue* pref) { pref_.reset(pref); } @@ -121,11 +204,6 @@ ExtensionManagementPrefUpdaterBase::TakePref() { return pref_.Pass(); } -void ExtensionManagementPrefUpdaterBase::RemoveAllowedType( - const std::string& allowd_type) { - RemoveStringFromList(kAllowedTypesPath, allowd_type); -} - void ExtensionManagementPrefUpdaterBase::ClearList(const std::string& path) { pref_->Set(path, new base::ListValue()); } @@ -149,4 +227,29 @@ void ExtensionManagementPrefUpdaterBase::RemoveStringFromList( CHECK(list_value->Remove(base::StringValue(str), NULL)); } +// ExtensionManagementPolicyUpdater -------------------------------------------- + +ExtensionManagementPolicyUpdater::ExtensionManagementPolicyUpdater( + policy::MockConfigurationPolicyProvider* policy_provider) + : provider_(policy_provider), policies_(new policy::PolicyBundle) { + policies_->CopyFrom(provider_->policies()); + const base::Value* policy_value = + policies_->Get(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, + std::string())) + .GetValue(policy::key::kExtensionSettings); + const base::DictionaryValue* dict_value = nullptr; + if (policy_value && policy_value->GetAsDictionary(&dict_value)) + SetPref(dict_value->DeepCopy()); + else + SetPref(new base::DictionaryValue); +} + +ExtensionManagementPolicyUpdater::~ExtensionManagementPolicyUpdater() { + policies_->Get(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME, + std::string())) + .Set(policy::key::kExtensionSettings, policy::POLICY_LEVEL_MANDATORY, + policy::POLICY_SCOPE_USER, TakePref().release(), nullptr); + provider_->UpdatePolicy(policies_.Pass()); +} + } // namespace extensions diff --git a/chrome/browser/extensions/extension_management_test_util.h b/chrome/browser/extensions/extension_management_test_util.h index 5347f42..ded3ea4 100644 --- a/chrome/browser/extensions/extension_management_test_util.h +++ b/chrome/browser/extensions/extension_management_test_util.h @@ -14,6 +14,11 @@ #include "extensions/browser/pref_names.h" #include "extensions/common/extension.h" +namespace policy { +class MockConfigurationPolicyProvider; +class PolicyBundle; +} // namespace policy + namespace extensions { // Base class for essential routines on preference manipulation. @@ -45,7 +50,25 @@ class ExtensionManagementPrefUpdaterBase { void UnsetAllowedTypes(); void ClearAllowedTypes(); void AddAllowedType(const std::string& allowed_type); - void RemoveAllowedType(const std::string& allowd_type); + void RemoveAllowedType(const std::string& allowed_type); + + // Helper functions for 'blocked_permissions' manipulation. |prefix| can be + // kWildCard or a valid extension ID. + void UnsetBlockedPermissions(const std::string& prefix); + void ClearBlockedPermissions(const std::string& prefix); + void AddBlockedPermission(const std::string& prefix, + const std::string& permission); + void RemoveBlockedPermission(const std::string& prefix, + const std::string& permission); + + // Helper functions for 'allowed_permissions' manipulation. |id| must be a + // valid extension id. + void UnsetAllowedPermissions(const std::string& id); + void ClearAllowedPermissions(const std::string& id); + void AddAllowedPermission(const std::string& id, + const std::string& permission); + void RemoveAllowedPermission(const std::string& id, + const std::string& permission); // Expose a read-only preference to user. const base::DictionaryValue* GetPref(); @@ -82,13 +105,11 @@ class ExtensionManagementPrefUpdater : service_(service) { const base::Value* pref_value = service_->GetManagedPref(pref_names::kExtensionManagement); - if (pref_value) { - const base::DictionaryValue* dict_value = NULL; - pref_value->GetAsDictionary(&dict_value); + const base::DictionaryValue* dict_value = nullptr; + if (pref_value && pref_value->GetAsDictionary(&dict_value)) SetPref(dict_value->DeepCopy()); - } else { + else SetPref(new base::DictionaryValue); - } } virtual ~ExtensionManagementPrefUpdater() { @@ -102,6 +123,22 @@ class ExtensionManagementPrefUpdater DISALLOW_COPY_AND_ASSIGN(ExtensionManagementPrefUpdater); }; +// A helper class to manipulate the extension management policy in browser +// tests. +class ExtensionManagementPolicyUpdater + : public ExtensionManagementPrefUpdaterBase { + public: + explicit ExtensionManagementPolicyUpdater( + policy::MockConfigurationPolicyProvider* provider); + ~ExtensionManagementPolicyUpdater() override; + + private: + policy::MockConfigurationPolicyProvider* provider_; + scoped_ptr<policy::PolicyBundle> policies_; + + DISALLOW_COPY_AND_ASSIGN(ExtensionManagementPolicyUpdater); +}; + } // namespace extensions #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_MANAGEMENT_TEST_UTIL_H_ diff --git a/chrome/browser/extensions/extension_management_unittest.cc b/chrome/browser/extensions/extension_management_unittest.cc index b3a59e4..451b1a8 100644 --- a/chrome/browser/extensions/extension_management_unittest.cc +++ b/chrome/browser/extensions/extension_management_unittest.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include <algorithm> +#include <string> #include <vector> #include "base/json/json_parser.h" @@ -14,9 +15,12 @@ #include "chrome/browser/extensions/extension_management_internal.h" #include "chrome/browser/extensions/extension_management_test_util.h" #include "chrome/browser/extensions/external_policy_loader.h" +#include "chrome/browser/extensions/standard_management_policy_provider.h" #include "extensions/browser/pref_names.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" +#include "extensions/common/permissions/api_permission.h" +#include "extensions/common/permissions/permissions_info.h" #include "extensions/common/url_pattern.h" #include "testing/gtest/include/gtest/gtest.h" #include "url/gurl.h" @@ -36,14 +40,18 @@ const char kExampleDictPreference[] = "{" " \"abcdefghijklmnopabcdefghijklmnop\": {" // kTargetExtension " \"installation_mode\": \"allowed\"," + " \"blocked_permissions\": [\"fileSystem\", \"bookmarks\"]," " }," " \"bcdefghijklmnopabcdefghijklmnopa\": {" // kTargetExtension2 " \"installation_mode\": \"force_installed\"," " \"update_url\": \"http://example.com/update_url\"," + " \"allowed_permissions\": [\"fileSystem\", \"bookmarks\"]," " }," " \"cdefghijklmnopabcdefghijklmnopab\": {" // kTargetExtension3 " \"installation_mode\": \"normal_installed\"," " \"update_url\": \"http://example.com/update_url\"," + " \"allowed_permissions\": [\"fileSystem\", \"downloads\"]," + " \"blocked_permissions\": [\"fileSystem\", \"history\"]," " }," " \"defghijklmnopabcdefghijklmnopabc\": {" // kTargetExtension4 " \"installation_mode\": \"blocked\"," @@ -52,6 +60,7 @@ const char kExampleDictPreference[] = " \"installation_mode\": \"blocked\"," " \"install_sources\": [\"*://foo.com/*\"]," " \"allowed_types\": [\"theme\", \"user_script\"]," + " \"blocked_permissions\": [\"fileSystem\", \"downloads\"]," " }," "}"; @@ -126,6 +135,11 @@ class ExtensionAdminPolicyTest : public ExtensionManagementServiceTest { ExtensionAdminPolicyTest() {} ~ExtensionAdminPolicyTest() override {} + void SetUpPolicyProvider() { + provider_.reset( + new StandardManagementPolicyProvider(extension_management_.get())); + } + void CreateExtension(Manifest::Location location) { base::DictionaryValue values; CreateExtensionFromValues(location, &values); @@ -161,12 +175,13 @@ class ExtensionAdminPolicyTest : public ExtensionManagementServiceTest { bool MustRemainEnabled(const Extension* extension, base::string16* error); protected: + scoped_ptr<StandardManagementPolicyProvider> provider_; scoped_refptr<Extension> extension_; }; bool ExtensionAdminPolicyTest::BlacklistedByDefault( const base::ListValue* blacklist) { - InitPrefService(); + SetUpPolicyProvider(); if (blacklist) SetPref(true, pref_names::kInstallDenyList, blacklist->DeepCopy()); return extension_management_->BlacklistedByDefault(); @@ -179,7 +194,7 @@ bool ExtensionAdminPolicyTest::UserMayLoad( const base::ListValue* allowed_types, const Extension* extension, base::string16* error) { - InitPrefService(); + SetUpPolicyProvider(); if (blacklist) SetPref(true, pref_names::kInstallDenyList, blacklist->DeepCopy()); if (whitelist) @@ -188,21 +203,19 @@ bool ExtensionAdminPolicyTest::UserMayLoad( SetPref(true, pref_names::kInstallForceList, forcelist->DeepCopy()); if (allowed_types) SetPref(true, pref_names::kAllowedTypes, allowed_types->DeepCopy()); - return extension_management_->GetProvider()->UserMayLoad(extension, error); + return provider_->UserMayLoad(extension, error); } bool ExtensionAdminPolicyTest::UserMayModifySettings(const Extension* extension, base::string16* error) { - InitPrefService(); - return extension_management_->GetProvider()->UserMayModifySettings(extension, - error); + SetUpPolicyProvider(); + return provider_->UserMayModifySettings(extension, error); } bool ExtensionAdminPolicyTest::MustRemainEnabled(const Extension* extension, base::string16* error) { - InitPrefService(); - return extension_management_->GetProvider()->MustRemainEnabled(extension, - error); + SetUpPolicyProvider(); + return provider_->MustRemainEnabled(extension, error); } // Verify that preference controlled by legacy ExtensionInstallSources policy is @@ -235,7 +248,7 @@ TEST_F(ExtensionManagementServiceTest, LegacyAllowedTypes) { const std::vector<Manifest::Type>& allowed_types = ReadGlobalSettings()->allowed_types; ASSERT_TRUE(ReadGlobalSettings()->has_restricted_allowed_types); - EXPECT_TRUE(allowed_types.size() == 2); + EXPECT_EQ(allowed_types.size(), 2u); EXPECT_FALSE(std::find(allowed_types.begin(), allowed_types.end(), Manifest::TYPE_EXTENSION) != allowed_types.end()); @@ -337,6 +350,32 @@ TEST_F(ExtensionManagementServiceTest, PreferenceParsing) { EXPECT_TRUE(std::find(allowed_types.begin(), allowed_types.end(), Manifest::TYPE_USER_SCRIPT) != allowed_types.end()); + + // Verifies blocked permission list settings. + APIPermissionSet api_permission_set; + api_permission_set.clear(); + api_permission_set.insert(APIPermission::kFileSystem); + api_permission_set.insert(APIPermission::kDownloads); + EXPECT_EQ(api_permission_set, + extension_management_->GetBlockedAPIPermissions(kOtherExtension)); + + api_permission_set.clear(); + api_permission_set.insert(APIPermission::kFileSystem); + api_permission_set.insert(APIPermission::kDownloads); + api_permission_set.insert(APIPermission::kBookmark); + EXPECT_EQ(api_permission_set, + extension_management_->GetBlockedAPIPermissions(kTargetExtension)); + + api_permission_set.clear(); + api_permission_set.insert(APIPermission::kDownloads); + EXPECT_EQ(api_permission_set, + extension_management_->GetBlockedAPIPermissions(kTargetExtension2)); + + api_permission_set.clear(); + api_permission_set.insert(APIPermission::kFileSystem); + api_permission_set.insert(APIPermission::kHistory); + EXPECT_EQ(api_permission_set, + extension_management_->GetBlockedAPIPermissions(kTargetExtension3)); } // Tests functionality of new preference as to deprecate legacy diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc index 7636dfe..76ac9a2 100644 --- a/chrome/browser/extensions/extension_service.cc +++ b/chrome/browser/extensions/extension_service.cc @@ -1709,6 +1709,23 @@ void ExtensionService::OnExtensionInstalled( void ExtensionService::OnExtensionManagementSettingsChanged() { error_controller_->ShowErrorIfNeeded(); + + // Revokes blocked permissions from active_permissions for all extensions. + extensions::ExtensionManagement* settings = + extensions::ExtensionManagementFactory::GetForBrowserContext(profile()); + CHECK(settings); + scoped_ptr<ExtensionSet> all_extensions( + registry_->GenerateInstalledExtensionsSet()); + for (const auto& extension : *all_extensions.get()) { + if (!settings->IsPermissionSetAllowed( + extension->id(), + extension->permissions_data()->active_permissions())) { + extensions::PermissionsUpdater(profile()).RemovePermissions( + extension.get(), + settings->GetBlockedPermissions(extension->id()).get()); + } + } + CheckManagementPolicy(); } diff --git a/chrome/browser/extensions/extension_service_unittest.cc b/chrome/browser/extensions/extension_service_unittest.cc index d23b98e..0fde625 100644 --- a/chrome/browser/extensions/extension_service_unittest.cc +++ b/chrome/browser/extensions/extension_service_unittest.cc @@ -55,6 +55,7 @@ #include "chrome/browser/extensions/pack_extension_job.h" #include "chrome/browser/extensions/pending_extension_info.h" #include "chrome/browser/extensions/pending_extension_manager.h" +#include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/extensions/test_blacklist.h" #include "chrome/browser/extensions/test_extension_system.h" #include "chrome/browser/extensions/unpacked_installer.h" @@ -99,6 +100,7 @@ #include "extensions/common/feature_switch.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_handlers/background_info.h" +#include "extensions/common/manifest_handlers/permissions_parser.h" #include "extensions/common/manifest_url_handlers.h" #include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permissions_data.h" @@ -189,6 +191,7 @@ const char theme2_crx[] = "pjpgmfcmabopnnfonnhmdjglfpjjfkbf"; const char permissions_crx[] = "eagpmdpfmaekmmcejjbmjoecnejeiiin"; const char unpacked[] = "cbcdidchbppangcjoddlpdjlenngjldk"; const char updates_from_webstore[] = "akjooamlhcgeopfifcmlggaebeocgokj"; +const char permissions_blocklist[] = "noffkehfcaggllbcojjbopcmlhcnhcdn"; struct ExtensionsOrder { bool operator()(const scoped_refptr<const Extension>& a, @@ -767,6 +770,16 @@ class ExtensionServiceTest : public extensions::ExtensionServiceTestBase, json_blacklist, gpu_info); } + // Grants all optional permissions stated in manifest to active permission + // set for extension |id|. + void GrantAllOptionalPermissions(const std::string& id) { + const Extension* extension = service()->GetInstalledExtension(id); + scoped_refptr<const PermissionSet> all_optional_permissions = + extensions::PermissionsParser::GetOptionalPermissions(extension); + extensions::PermissionsUpdater perms_updater(profile()); + perms_updater.AddPermissions(extension, all_optional_permissions.get()); + } + // Helper method to set up a WindowedNotificationObserver to wait for a // specific CrxInstaller to finish if we don't know the value of the // |installer| yet. @@ -3843,6 +3856,219 @@ TEST_F(ExtensionServiceTest, ManagementPolicyRequiresEnable) { EXPECT_EQ(0u, registry()->disabled_extensions().size()); } +// Tests that extensions with conflicting required permissions by enterprise +// policy cannot be installed. +TEST_F(ExtensionServiceTest, PolicyBlockedPermissionNewExtensionInstall) { + InitializeEmptyExtensionServiceWithTestingPrefs(); + base::FilePath path = data_dir().AppendASCII("permissions_blocklist"); + + { + // Update policy to block one of the required permissions of target. + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.AddBlockedPermission("*", "tabs"); + } + + // The extension should be failed to install. + PackAndInstallCRX(path, INSTALL_FAILED); + + { + // Update policy to block one of the optional permissions instead. + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.ClearBlockedPermissions("*"); + pref.AddBlockedPermission("*", "history"); + } + + // The extension should succeed to install this time. + std::string id = PackAndInstallCRX(path, INSTALL_NEW)->id(); + + // Uninstall the extension and update policy to block some arbitrary + // unknown permission. + UninstallExtension(id, false); + { + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.ClearBlockedPermissions("*"); + pref.AddBlockedPermission("*", "unknown.permission.for.testing"); + } + + // The extension should succeed to install as well. + PackAndInstallCRX(path, INSTALL_NEW); +} + +// Tests that extension supposed to be force installed but with conflicting +// required permissions cannot be installed. +TEST_F(ExtensionServiceTest, PolicyBlockedPermissionConflictsWithForceInstall) { + InitializeEmptyExtensionServiceWithTestingPrefs(); + + // Pack the crx file. + base::FilePath path = data_dir().AppendASCII("permissions_blocklist"); + base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem"); + base::ScopedTempDir temp_dir; + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); + + PackCRX(path, pem_path, crx_path); + + { + // Block one of the required permissions. + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.AddBlockedPermission("*", "tabs"); + } + + // Use MockExtensionProvider to simulate force installing extension. + MockExtensionProvider* provider = + new MockExtensionProvider(service(), Manifest::EXTERNAL_POLICY_DOWNLOAD); + AddMockExternalProvider(provider); + provider->UpdateOrAddExtension(permissions_blocklist, "1.0", crx_path); + + { + // Attempts to force install this extension. + content::WindowedNotificationObserver observer( + extensions::NOTIFICATION_CRX_INSTALLER_DONE, + content::NotificationService::AllSources()); + service()->CheckForExternalUpdates(); + observer.Wait(); + } + + // The extension should not be installed. + ASSERT_FALSE(service()->GetInstalledExtension(permissions_blocklist)); + + // Remove this extension from pending extension manager as we would like to + // give another attempt later. + service()->pending_extension_manager()->Remove(permissions_blocklist); + + { + // Clears the permission block list. + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.ClearBlockedPermissions("*"); + } + + { + // Attempts to force install this extension again. + content::WindowedNotificationObserver observer( + extensions::NOTIFICATION_CRX_INSTALLER_DONE, + content::NotificationService::AllSources()); + service()->CheckForExternalUpdates(); + observer.Wait(); + } + + const Extension* installed = + service()->GetInstalledExtension(permissions_blocklist); + ASSERT_TRUE(installed); + EXPECT_EQ(installed->location(), Manifest::EXTERNAL_POLICY_DOWNLOAD); +} + +// Tests that newer versions of an extension with conflicting required +// permissions by enterprise policy cannot be updated to. +TEST_F(ExtensionServiceTest, PolicyBlockedPermissionExtensionUpdate) { + InitializeEmptyExtensionServiceWithTestingPrefs(); + + base::FilePath path = data_dir().AppendASCII("permissions_blocklist"); + base::FilePath path2 = data_dir().AppendASCII("permissions_blocklist2"); + base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem"); + + // Install 'permissions_blocklist'. + const Extension* installed = PackAndInstallCRX(path, pem_path, INSTALL_NEW); + EXPECT_EQ(installed->id(), permissions_blocklist); + + { + // Block one of the required permissions of 'permissions_blocklist2'. + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.AddBlockedPermission("*", "downloads"); + } + + // Install 'permissions_blocklist' again, should be updated. + const Extension* updated = PackAndInstallCRX(path, pem_path, INSTALL_UPDATED); + EXPECT_EQ(updated->id(), permissions_blocklist); + + std::string old_version = updated->VersionString(); + + // Attempts to update to 'permissions_blocklist2' should fail. + PackAndInstallCRX(path2, pem_path, INSTALL_FAILED); + + // Verify that the old version is still enabled. + updated = service()->GetExtensionById(permissions_blocklist, false); + ASSERT_TRUE(updated); + EXPECT_EQ(old_version, updated->VersionString()); +} + +// Tests that policy update with additional permissions blocked revoke +// conflicting granted optional permissions and unload extensions with +// conflicting required permissions, including the force installed ones. +TEST_F(ExtensionServiceTest, PolicyBlockedPermissionPolicyUpdate) { + InitializeEmptyExtensionServiceWithTestingPrefs(); + + base::FilePath path = data_dir().AppendASCII("permissions_blocklist"); + base::FilePath path2 = data_dir().AppendASCII("permissions_blocklist2"); + base::FilePath pem_path = data_dir().AppendASCII("permissions_blocklist.pem"); + + // Pack the crx file. + base::ScopedTempDir temp_dir; + EXPECT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath crx_path = temp_dir.path().AppendASCII("temp.crx"); + + PackCRX(path2, pem_path, crx_path); + + // Install two arbitary extensions with specified manifest. + std::string ext1 = PackAndInstallCRX(path, INSTALL_NEW)->id(); + std::string ext2 = PackAndInstallCRX(path2, INSTALL_NEW)->id(); + ASSERT_NE(ext1, permissions_blocklist); + ASSERT_NE(ext2, permissions_blocklist); + ASSERT_NE(ext1, ext2); + + // Force install another extension with known id and same manifest as 'ext2'. + std::string ext2_forced = permissions_blocklist; + MockExtensionProvider* provider = + new MockExtensionProvider(service(), Manifest::EXTERNAL_POLICY_DOWNLOAD); + AddMockExternalProvider(provider); + provider->UpdateOrAddExtension(ext2_forced, "2.0", crx_path); + + content::WindowedNotificationObserver observer( + extensions::NOTIFICATION_CRX_INSTALLER_DONE, + content::NotificationService::AllSources()); + service()->CheckForExternalUpdates(); + observer.Wait(); + + extensions::ExtensionRegistry* registry = + extensions::ExtensionRegistry::Get(profile()); + + // Verify all three extensions are installed and enabled. + ASSERT_TRUE(registry->enabled_extensions().GetByID(ext1)); + ASSERT_TRUE(registry->enabled_extensions().GetByID(ext2)); + ASSERT_TRUE(registry->enabled_extensions().GetByID(ext2_forced)); + + // Grant all optional permissions to each extension. + GrantAllOptionalPermissions(ext1); + GrantAllOptionalPermissions(ext2); + GrantAllOptionalPermissions(ext2_forced); + + scoped_refptr<const PermissionSet> active_permissions( + ExtensionPrefs::Get(profile())->GetActivePermissions(ext1)); + EXPECT_TRUE(active_permissions->HasAPIPermission( + extensions::APIPermission::kDownloads)); + + // Set policy to block 'downloads' permission. + { + ManagementPrefUpdater pref(profile_->GetTestingPrefService()); + pref.AddBlockedPermission("*", "downloads"); + } + + base::RunLoop().RunUntilIdle(); + + // 'ext1' should still be enabled, but with 'downloads' permission revoked. + EXPECT_TRUE(registry->enabled_extensions().GetByID(ext1)); + active_permissions = + ExtensionPrefs::Get(profile())->GetActivePermissions(ext1); + EXPECT_FALSE(active_permissions->HasAPIPermission( + extensions::APIPermission::kDownloads)); + + // 'ext2' should be disabled because one of its required permissions is + // blocked. + EXPECT_FALSE(registry->enabled_extensions().GetByID(ext2)); + + // 'ext2_forced' should be handled the same as 'ext2' + EXPECT_FALSE(registry->enabled_extensions().GetByID(ext2_forced)); +} + // Flaky on windows; http://crbug.com/309833 #if defined(OS_WIN) #define MAYBE_ExternalExtensionAutoAcknowledgement DISABLED_ExternalExtensionAutoAcknowledgement diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc index 11d69fe..02de369 100644 --- a/chrome/browser/extensions/extension_system_impl.cc +++ b/chrome/browser/extensions/extension_system_impl.cc @@ -137,9 +137,9 @@ void ExtensionSystemImpl::Shared::InitPrefs() { } void ExtensionSystemImpl::Shared::RegisterManagementPolicyProviders() { - management_policy_->RegisterProvider( + management_policy_->RegisterProviders( ExtensionManagementFactory::GetForBrowserContext(profile_) - ->GetProvider()); + ->GetProviders()); #if defined(OS_CHROMEOS) if (device_local_account_management_policy_provider_) { diff --git a/chrome/browser/extensions/permissions_based_management_policy_provider.cc b/chrome/browser/extensions/permissions_based_management_policy_provider.cc new file mode 100644 index 0000000..4b29945 --- /dev/null +++ b/chrome/browser/extensions/permissions_based_management_policy_provider.cc @@ -0,0 +1,61 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/extensions/permissions_based_management_policy_provider.h" + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/extensions/extension_management.h" +#include "extensions/common/extension.h" +#include "extensions/common/manifest_handlers/permissions_parser.h" +#include "extensions/common/permissions/permission_set.h" +#include "grit/extensions_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace extensions { + +PermissionsBasedManagementPolicyProvider:: + PermissionsBasedManagementPolicyProvider(ExtensionManagement* settings) + : settings_(settings) { +} + +PermissionsBasedManagementPolicyProvider:: + ~PermissionsBasedManagementPolicyProvider() { +} + +std::string +PermissionsBasedManagementPolicyProvider::GetDebugPolicyProviderName() const { +#ifdef NDEBUG + NOTREACHED(); + return std::string(); +#else + return "Controlled by enterprise policy, restricting extension permissions."; +#endif +} + +bool PermissionsBasedManagementPolicyProvider::UserMayLoad( + const Extension* extension, + base::string16* error) const { + // Component extensions are always allowed. + if (Manifest::IsComponentLocation(extension->location())) + return true; + + scoped_refptr<const PermissionSet> required_permissions = + PermissionsParser::GetRequiredPermissions(extension); + + if (!settings_->IsPermissionSetAllowed(extension->id(), + required_permissions)) { + if (error) { + *error = + l10n_util::GetStringFUTF16(IDS_EXTENSION_CANT_INSTALL_POLICY_BLOCKED, + base::UTF8ToUTF16(extension->name()), + base::UTF8ToUTF16(extension->id())); + } + return false; + } + + return true; +} + +} // namespace extensions diff --git a/chrome/browser/extensions/permissions_based_management_policy_provider.h b/chrome/browser/extensions/permissions_based_management_policy_provider.h new file mode 100644 index 0000000..fc8ca72 --- /dev/null +++ b/chrome/browser/extensions/permissions_based_management_policy_provider.h @@ -0,0 +1,41 @@ +// Copyright 2014 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_PERMISSIONS_BASED_MANAGEMENT_POLICY_PROVIDER_H_ +#define CHROME_BROWSER_EXTENSIONS_PERMISSIONS_BASED_MANAGEMENT_POLICY_PROVIDER_H_ + +#include <string> + +#include "base/macros.h" +#include "extensions/browser/management_policy.h" + +namespace extensions { + +class Extension; +class ExtensionManagement; + +// A ManagementPolicyProvider controlled by enterprise policy, and prevent +// certain extensions from loading by checking if its permission data conflicts +// with policy or not. +class PermissionsBasedManagementPolicyProvider + : public ManagementPolicy::Provider { + public: + explicit PermissionsBasedManagementPolicyProvider( + ExtensionManagement* settings); + ~PermissionsBasedManagementPolicyProvider() override; + + // ManagementPolicy::Provider implementation. + std::string GetDebugPolicyProviderName() const override; + bool UserMayLoad(const Extension* extension, + base::string16* error) const override; + + private: + ExtensionManagement* settings_; + + DISALLOW_COPY_AND_ASSIGN(PermissionsBasedManagementPolicyProvider); +}; + +} // namespace extensions + +#endif // CHROME_BROWSER_EXTENSIONS_PERMISSIONS_BASED_MANAGEMENT_POLICY_PROVIDER_H_ diff --git a/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc b/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc new file mode 100644 index 0000000..e7eacfd --- /dev/null +++ b/chrome/browser/extensions/permissions_based_management_policy_provider_unittest.cc @@ -0,0 +1,169 @@ +// Copyright 2014 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 <string> +#include <vector> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/pref_registry_simple.h" +#include "base/prefs/testing_pref_service.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/values.h" +#include "chrome/browser/extensions/extension_management.h" +#include "chrome/browser/extensions/extension_management_test_util.h" +#include "chrome/browser/extensions/permissions_based_management_policy_provider.h" +#include "chrome/common/extensions/permissions/chrome_api_permissions.h" +#include "extensions/common/extension.h" +#include "extensions/common/manifest.h" +#include "extensions/common/manifest_constants.h" +#include "extensions/common/permissions/api_permission.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace extensions { + +class PermissionsBasedManagementPolicyProviderTest : public testing::Test { + public: + typedef ExtensionManagementPrefUpdater<TestingPrefServiceSimple> PrefUpdater; + + PermissionsBasedManagementPolicyProviderTest() + : pref_service_(new TestingPrefServiceSimple()), + settings_(new ExtensionManagement(pref_service_.get())), + provider_(settings_.get()) {} + + void SetUp() override { + ChromeAPIPermissions api_permissions; + perm_list_ = api_permissions.GetAllPermissions(); + pref_service_->registry()->RegisterDictionaryPref( + pref_names::kExtensionManagement); + } + + void TearDown() override { + STLDeleteElements(&perm_list_); + } + + // Get API permissions name for |id|, we cannot use arbitrary strings since + // they will be ignored by ExtensionManagementService. + std::string GetAPIPermissionName(APIPermission::ID id) { + for (const auto& perm : perm_list_) { + if (perm->id() == id) + return perm->name(); + } + ADD_FAILURE() << "Permission not found: " << id; + return std::string(); + } + + // Create an extension with specified |location|, |required_permissions| and + // |optional_permissions|. + scoped_refptr<const Extension> CreateExtensionWithPermission( + Manifest::Location location, + const base::ListValue* required_permissions, + const base::ListValue* optional_permissions) { + base::DictionaryValue manifest_dict; + manifest_dict.SetString(manifest_keys::kName, "test"); + manifest_dict.SetString(manifest_keys::kVersion, "0.1"); + if (required_permissions) { + manifest_dict.Set(manifest_keys::kPermissions, + required_permissions->DeepCopy()); + } + if (optional_permissions) { + manifest_dict.Set(manifest_keys::kOptionalPermissions, + optional_permissions->DeepCopy()); + } + std::string error; + scoped_refptr<const Extension> extension = Extension::Create( + base::FilePath(), location, manifest_dict, Extension::NO_FLAGS, &error); + CHECK(extension.get()) << error; + return extension; + } + + protected: + std::vector<APIPermissionInfo*> perm_list_; + + scoped_ptr<TestingPrefServiceSimple> pref_service_; + scoped_ptr<ExtensionManagement> settings_; + + PermissionsBasedManagementPolicyProvider provider_; +}; + +// Verifies that extensions with conflicting permissions cannot be loaded. +TEST_F(PermissionsBasedManagementPolicyProviderTest, APIPermissions) { + // Prepares the extension manifest. + base::ListValue required_permissions; + required_permissions.AppendString( + GetAPIPermissionName(APIPermission::kDownloads)); + required_permissions.AppendString( + GetAPIPermissionName(APIPermission::kCookie)); + base::ListValue optional_permissions; + optional_permissions.AppendString( + GetAPIPermissionName(APIPermission::kProxy)); + + scoped_refptr<const Extension> extension = + CreateExtensionWithPermission(Manifest::EXTERNAL_POLICY_DOWNLOAD, + &required_permissions, + &optional_permissions); + + base::string16 error16; + // The extension should be allowed to be loaded by default. + error16.clear(); + EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_TRUE(error16.empty()); + + // Blocks kProxy by default. The test extension should still be allowed. + { + PrefUpdater pref(pref_service_.get()); + pref.AddBlockedPermission("*", + GetAPIPermissionName(APIPermission::kProxy)); + } + error16.clear(); + EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_TRUE(error16.empty()); + + // Blocks kCookie this time. The test extension should not be allowed now. + { + PrefUpdater pref(pref_service_.get()); + pref.AddBlockedPermission("*", + GetAPIPermissionName(APIPermission::kCookie)); + } + error16.clear(); + EXPECT_FALSE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_FALSE(error16.empty()); + + // Explictly allows kCookie for test extension. It should be allowed again. + { + PrefUpdater pref(pref_service_.get()); + pref.AddAllowedPermission(extension->id(), + GetAPIPermissionName(APIPermission::kCookie)); + } + error16.clear(); + EXPECT_TRUE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_TRUE(error16.empty()); + + // Explictly blocks kCookie for test extension. It should be blocked again. + { + PrefUpdater pref(pref_service_.get()); + pref.AddBlockedPermission(extension->id(), + GetAPIPermissionName(APIPermission::kCookie)); + } + error16.clear(); + EXPECT_FALSE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_FALSE(error16.empty()); + + // Blocks kDownloads by default. It should be blocked. + { + PrefUpdater pref(pref_service_.get()); + pref.UnsetBlockedPermissions(extension->id()); + pref.UnsetAllowedPermissions(extension->id()); + pref.ClearBlockedPermissions("*"); + pref.AddBlockedPermission("*", + GetAPIPermissionName(APIPermission::kDownloads)); + } + error16.clear(); + EXPECT_FALSE(provider_.UserMayLoad(extension.get(), &error16)); + EXPECT_FALSE(error16.empty()); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/test_extension_system.cc b/chrome/browser/extensions/test_extension_system.cc index 0f513e4..d174257 100644 --- a/chrome/browser/extensions/test_extension_system.cc +++ b/chrome/browser/extensions/test_extension_system.cc @@ -89,9 +89,9 @@ ExtensionService* TestExtensionSystem::CreateExtensionService( state_store_.reset(new StateStore(profile_, value_store.Pass())); blacklist_.reset(new Blacklist(ExtensionPrefs::Get(profile_))); management_policy_.reset(new ManagementPolicy()); - management_policy_->RegisterProvider( + management_policy_->RegisterProviders( ExtensionManagementFactory::GetForBrowserContext(profile_) - ->GetProvider()); + ->GetProviders()); runtime_data_.reset(new RuntimeData(ExtensionRegistry::Get(profile_))); extension_service_.reset(new ExtensionService(profile_, command_line, diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index c4b73f5..fd1035e 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -734,6 +734,8 @@ 'browser/extensions/pending_extension_info.h', 'browser/extensions/pending_extension_manager.cc', 'browser/extensions/pending_extension_manager.h', + 'browser/extensions/permissions_based_management_policy_provider.cc', + 'browser/extensions/permissions_based_management_policy_provider.h', 'browser/extensions/permissions_updater.cc', 'browser/extensions/permissions_updater.h', 'browser/extensions/plugin_manager.cc', diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index a2144f9..df19165 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -377,6 +377,8 @@ 'browser/extensions/extension_install_ui_browsertest.cc', 'browser/extensions/extension_javascript_url_apitest.cc', 'browser/extensions/extension_loading_browsertest.cc', + 'browser/extensions/extension_management_test_util.cc', + 'browser/extensions/extension_management_test_util.h', 'browser/extensions/extension_messages_apitest.cc', 'browser/extensions/extension_override_apitest.cc', 'browser/extensions/extension_resource_request_policy_apitest.cc', diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 7098c86..642808f 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -423,6 +423,7 @@ 'browser/extensions/menu_manager_unittest.cc', 'browser/extensions/pack_extension_unittest.cc', 'browser/extensions/permission_messages_unittest.cc', + 'browser/extensions/permissions_based_management_policy_provider_unittest.cc', 'browser/extensions/permissions_updater_unittest.cc', 'browser/extensions/policy_handlers_unittest.cc', 'browser/extensions/sandboxed_unpacker_unittest.cc', diff --git a/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/background.js b/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/background.js new file mode 100644 index 0000000..8c02692 --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/background.js @@ -0,0 +1,27 @@ +// Copyright 2014 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. + +var assertTrue = chrome.test.assertTrue; +var fail = chrome.test.callbackFail; +var pass = chrome.test.callbackPass; + +var BLOCKED_BY_ENTERPRISE_ERROR = + "Permissions are blocked by enterprise policy."; + +chrome.test.getConfig(function(config) { + + chrome.test.runTests([ + function allowedPermission() { + chrome.permissions.request( + {permissions:['bookmarks']}, + pass(function(granted) { assertTrue(granted); })); + }, + + function allowedPermission() { + chrome.permissions.request( + {permissions:['management']}, + fail(BLOCKED_BY_ENTERPRISE_ERROR)); + } + ]); +}); diff --git a/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/manifest.json b/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/manifest.json new file mode 100644 index 0000000..a45a29f --- /dev/null +++ b/chrome/test/data/extensions/api_test/permissions/optional_policy_blocked/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "permissions/optional_policy_blocked", + "description": "permissions/optional_policy_blocked", + "version": "0.1", + "manifest_version": 2, + "background": { + "scripts": ["background.js"] + }, + "optional_permissions": [ + "bookmarks", + "management" + ] +} diff --git a/chrome/test/data/extensions/permissions_blocklist.pem b/chrome/test/data/extensions/permissions_blocklist.pem new file mode 100644 index 0000000..ea0e73b --- /dev/null +++ b/chrome/test/data/extensions/permissions_blocklist.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqPZEHZSW9LnsZ +QAfO1n0CTCEVeY2V5ydfGUkm3tuJC38NmRUCJkyWVVnD8hTknWarI1viFB2mf7// +T6seCnOPpRd/uzz4yYjgUTSMaRllIcXzWDv7iD4Yg2076zA/TOU6MsLluu+Ur2rq +UMU92yu8Nd3XekygWYmqJW4x7cNqc4VNLwFLc7TmemdAUeDpfOOmyPTPELkNRip7 +rLLRDAEQ/W0XPoPU9GCgyaY0DMw+/rcUicUTlo+fQKig8+vOZPNkuAenYAoeeZsT +QlMvC8EXvf5lGmUlKN6iQT6n60YPZPAOF4tnRL4rMHnlQfYqcWLE2fyY9xIKIZ+S +88l/qD9HAgMBAAECggEAYv+Aehtw0c3Y2fspTzfd5FsfFhzkoBqXA69xocARTGW6 +Ec9Gr0RUX0vs8oyxZ6y8yqK6SQNHps49eZv7t3F+eDcaLjZVI5wXE9NFSq3Hjc8R +DQzvfLti2OB8wk5ndTgjSS/BtNR7hCxqcS5dNTZh/YYZprfBHbJT4vT3WuOYMytd +VrKeil6seZiNlPjEGLWy6ioAUysenko21qnp5mu33GTtg8IQG8J9oJga9suuMWzM +s3Ep+pDAvNO196zbiv3O/n6MI7FduNB9wDsILZZGKCnWYg4B2haJVEszc2aHDYJ9 +0Vt7/ZnP+LajyzHnLr8rzDzls3iNelwUOg3K3zbOUQKBgQDc2HmzdqtYtz68ymcl +Z9b3SQESRBcMzx6xwLvS9VFIhQ4dnBlamf+PYj6LWCTQvBopWbauATTwt8MQnBbW +wjFHnDVqzln1ydji9RzyJ6Mk+gi+WvPqJgTPX8xn8sBOY6RbG/Nf+RxIoe2xHBas +GwlrEygzWwSJXr6A344hiCiV6QKBgQDFVurpnfZEQYYck3GIfh97DDiK+d3s6B8D +hbOu+ZAon2jRotHlMVi/Mc8v2dpkw0Ha4Bzehy6ijfXhZuw6Y8PiFU33L6AfZjty +U+RXE5qkM11HG10qfXgRr4buDX4jFYSZ/4v9Gd8mdthJPYbejayPZll2NvP8MV15 +OJzmJ599rwKBgQCV8EdYZ/ZXijoHFWhIRiL1y1P14dLAeJK/XjpnNPUVxnhZ9BRm +pe2TnMEX/CUqrrsYXfegVeNql2jRixgagMfSdaTyudzr+jnNhVs8sVqbjUKIctnt +nBhh7wmpfW/BCPeaf/SRsWDZk078kzR1bvnK/7uoemAvH+s5Ng6ah4X+mQKBgBzA +CQF4LgTLtwo6wPOtzFBU+3fj5104hCwzMnZvjX0dnFvOaN4HTnKsgw7polPDzrhg +plQVRcoQ/J2WLRxkpIkvqWg13BI3BrolVsMI7CK4CQLvDae/F9sbw4CzHoll/Sei +f3akakpkTiZ9fES4R0etFaOPZgR7/K5IfTNa2nWpAoGAI4AKRWL0TOGJQnByzFT1 +5QBjJ8Q9U3X40RYwjDXEJjWtWSoA7HTb7w3gvRyL3ljijqao0BxM0zIRoxLVLZSW +1sOx0YlYhDeaeydbDB/h/YYl4vOi8zOFeCK5p9/S7xxPi/LUtGYLuAluxmKF5/S2 +pTm3N5zLsXqZR9yiSkj8gBE= +-----END PRIVATE KEY----- diff --git a/chrome/test/data/extensions/permissions_blocklist/manifest.json b/chrome/test/data/extensions/permissions_blocklist/manifest.json new file mode 100644 index 0000000..9f7d895 --- /dev/null +++ b/chrome/test/data/extensions/permissions_blocklist/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Permissions Blocked by Policy Test", + "permissions": [ "tabs", "bookmarks" ], + "optional_permissions": [ "history", "downloads" ], + "manifest_version": 2, + "version": "1.0" +} diff --git a/chrome/test/data/extensions/permissions_blocklist2/manifest.json b/chrome/test/data/extensions/permissions_blocklist2/manifest.json new file mode 100644 index 0000000..01b8403 --- /dev/null +++ b/chrome/test/data/extensions/permissions_blocklist2/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Permissions Blocked by Policy Test 2", + "permissions": [ "tabs", "bookmarks", "downloads", "management" ], + "optional_permissions": [ "history" ], + "manifest_version": 2, + "version": "2.0" +} diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json index 3ad037d..433468d 100644 --- a/chrome/test/data/policy/policy_test_cases.json +++ b/chrome/test/data/policy/policy_test_cases.json @@ -704,14 +704,17 @@ "test_policy": { "ExtensionSettings": { "abcdefghijklmnopabcdefghijklmnop" : { - "installation_mode": "allowed" + "installation_mode": "allowed", + "blocked_permissions": ["history"] }, "bcdefghijklmnopabcdefghijklmnopa" : { "installation_mode": "force_installed", - "update_url": "http://example.com/update_url" + "update_url": "http://example.com/update_url", + "allowed_permissions": ["downloads"] }, "*": { "installation_mode": "blocked", + "blocked_permissions": ["downloads", "bookmarks"], "install_sources": ["http://company-intranet/chromeapps"], "allowed_types": ["hosted_app"] } diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json index 4beed68..5da8972 100644 --- a/components/policy/resources/policy_templates.json +++ b/components/policy/resources/policy_templates.json @@ -1750,8 +1750,19 @@ 'enum': ['blocked', 'allowed', 'force_installed', 'normal_installed'] }, 'update_url': { 'type': 'string' }, - } - } + 'blocked_permissions': { + 'type': 'array', + 'items': { + 'type': 'string', + 'pattern': '^[a-z][a-zA-Z.]*$', + }, + 'id': 'ListOfPermissions', + }, + 'allowed_permissions': { + '$ref': 'ListOfPermissions', + }, + }, + }, }, 'properties': { '*': { @@ -1761,15 +1772,18 @@ 'type': 'string', 'enum': ['blocked', 'allowed'] }, + 'blocked_permissions': { + '$ref': 'ListOfPermissions', + }, 'install_sources': { '$ref': 'ExtensionInstallSources', }, 'allowed_types': { '$ref': 'ExtensionAllowedTypes', }, - } - } - } + }, + }, + }, }, 'future': True, 'supported_on': ['chrome.*:40-', 'chrome_os:40-'], @@ -1780,13 +1794,16 @@ 'example_value': { 'abcdefghijklmnopabcdefghijklmnop' : { 'installation_mode': 'allowed', + 'blocked_permissions': ['history'], }, 'bcdefghijklmnopabcdefghijklmnopa' : { 'installation_mode': 'force_installed', 'update_url': 'http://example.com/update_url', + 'allowed_permissions': ['downloads'], }, '*': { 'installation_mode': 'blocked', + 'blocked_permissions': ['downloads', 'bookmarks'], 'install_sources': ['http://company-intranet/chromeapps'], 'allowed_types': ['hosted_app'], }, @@ -1809,6 +1826,10 @@ If the mode is set to "force_installed" or "normal_installed" then an "update_url" must be configured too. The update URL should point to an Update Manifest XML document as described at <ph name="LINK_TO_EXTENSION_DOC1">https://developer.chrome.com/extensions/autoupdate</ph>. Note that the update URL set in this policy is only used for the initial installation; subsequent updates of the extension will use the update URL indicated in the extension's manifest. + "blocked_permissions": maps to a list of strings indicating the blocked API permissions for the extension. The permissions names are same as the permission strings declared in manifest of extension as described at <ph name="LINK_TO_EXTENSION_DOC3">https://developer.chrome.com/extensions/declare_permissions</ph>. This setting also can be configured for "*" extension. If the extension requires a permission which is on the blocklist, it will not be allowed to load. If it contains a blocked permission as optional requirement, it will be handled in the normal way, but requesting conflicting permissions will be declined automatically at runtime. + + "allowed_permissions": similar to "blocked_permissions", but instead explicitly allow some permissions which might be blocked by global blocked permission list, thus can not be configured for "*" extension. Note that this setting doesn't give granted permissions to extensions automatically. + The following settings can be used only for the default "*" configuration: "install_sources": Each item in this list is an extension-style match pattern (see https://developer.chrome.com/extensions/match_patterns). Users will be able to easily install items from any URL that matches an item in this list. Both the location of the *.crx file and the page where the download is started from (i.e. the referrer) must be allowed by these patterns. diff --git a/extensions/browser/management_policy.cc b/extensions/browser/management_policy.cc index 156ba64..b09734a 100644 --- a/extensions/browser/management_policy.cc +++ b/extensions/browser/management_policy.cc @@ -61,6 +61,10 @@ void ManagementPolicy::UnregisterProvider(Provider* provider) { providers_.erase(provider); } +void ManagementPolicy::RegisterProviders(std::vector<Provider*> providers) { + providers_.insert(providers.begin(), providers.end()); +} + bool ManagementPolicy::UserMayLoad(const Extension* extension, base::string16* error) const { return ApplyToProviderList( diff --git a/extensions/browser/management_policy.h b/extensions/browser/management_policy.h index a857822..09b59b1 100644 --- a/extensions/browser/management_policy.h +++ b/extensions/browser/management_policy.h @@ -7,6 +7,7 @@ #include <set> #include <string> +#include <vector> #include "base/basictypes.h" #include "extensions/common/extension.h" @@ -92,6 +93,9 @@ class ManagementPolicy { void RegisterProvider(Provider* provider); void UnregisterProvider(Provider* provider); + // Like RegisterProvider(), but registers multiple providers instead. + void RegisterProviders(std::vector<Provider*> providers); + // Returns true if the user is permitted to install, load, and run the given // extension. If not, |error| may be set to an appropriate message. bool UserMayLoad(const Extension* extension, base::string16* error) const; |