Commit ae9baf56 authored by Devlin Cronin's avatar Devlin Cronin Committed by Commit Bot

[Extensions] (More) Gracefully handle previously-installed policy extensions

Consider the following circumstance:
- User installs extension Alpha normally (e.g., through the webstore).
  Alpha has Manifest::Location INTERNAL.
- Corp policy pushes out an update that lists Alpha as a required
  extension.

Currently, this behaves fantastically poorly. We validate that the user
is allowed to load an extension on each run of Chrome when loading
installed extensions, and UserMayLoad() will return false if the
extension is required by corporate policy (in order to prevent users
from installing "over" a policy-required extension). This means that
the policy-required extension gets disabled with reason
DISABLE_BLOCKED_BY_POLICY.

Make this slightly less bad by introducing a new policy provider method,
UserMayInstall(). This checks whether the user is allowed to install a
given extension, rather than whether the user is allowed to load it. For
default implementations, UserMayInstall() forwards to UserMayLoad()
(since they should be treated equivalently). However, we can now move
the check for if a Manifest::INTERNAL extension is required policy to
the UserMayInstall() check rather than UserMayLoad(). The effect of this
is to allow the user to load an already-installed extension that's
required by policy, but not policy-installed itself, while still
preventing the user from installing a new copy of that extension.

This is not a perfect solution, since the installed version of the
extension will still be the Manifest::INTERNAL one. This has a number of
implications, including that the extension won't show most policy-
installed indications and won't have access to policy-only APIs. In
extreme cases, the extensions can also be different versions. In the
future, we will need to think about how to handle this scenario more
gracefully.

Bug: 894184

Change-Id: I83c9c305b56f90ea211e4a9b0120b22d601b4fb4
Reviewed-on: https://chromium-review.googlesource.com/c/1327616
Commit-Queue: Devlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarIstiaque Ahmed <lazyboy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608538}
parent 01e30939
...@@ -281,8 +281,9 @@ void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseSuccess( ...@@ -281,8 +281,9 @@ void WebstorePrivateBeginInstallWithManifest3Function::OnWebstoreParseSuccess(
// Check the management policy before the installation process begins. // Check the management policy before the installation process begins.
Profile* profile = chrome_details_.GetProfile(); Profile* profile = chrome_details_.GetProfile();
base::string16 policy_error; base::string16 policy_error;
bool allow = ExtensionSystem::Get(profile)-> bool allow =
management_policy()->UserMayLoad(dummy_extension_.get(), &policy_error); ExtensionSystem::Get(profile)->management_policy()->UserMayInstall(
dummy_extension_.get(), &policy_error);
if (!allow) { if (!allow) {
bool blocked_for_child = false; bool blocked_for_child = false;
#if BUILDFLAG(ENABLE_SUPERVISED_USERS) #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
......
...@@ -7322,4 +7322,62 @@ TEST_F(ExtensionServiceTest, UninstallDisabledMigratedExtension) { ...@@ -7322,4 +7322,62 @@ TEST_F(ExtensionServiceTest, UninstallDisabledMigratedExtension) {
EXPECT_FALSE(service()->GetInstalledExtension(cast_stable)); EXPECT_FALSE(service()->GetInstalledExtension(cast_stable));
} }
// Tests the case of a user installing a non-policy extension (e.g. through the
// webstore), and that extension later becoming required by policy.
// Regression test for https://crbug.com/894184.
TEST_F(ExtensionServiceTest, UserInstalledExtensionThenRequiredByPolicy) {
InitializeEmptyExtensionServiceWithTestingPrefs();
// Install an extension as if the user did it.
base::FilePath path = data_dir().AppendASCII("good.crx");
const Extension* extension = InstallCRX(path, INSTALL_NEW);
ASSERT_TRUE(extension);
EXPECT_EQ(good_crx, extension->id());
EXPECT_EQ(Manifest::INTERNAL, extension->location());
std::string kVersionStr = "1.0.0.0";
EXPECT_EQ(kVersionStr, extension->VersionString());
{
ManagementPrefUpdater pref(profile_->GetTestingPrefService());
// Mark good.crx for force-installation.
pref.SetIndividualExtensionAutoInstalled(
good_crx, "http://example.com/update_url", true);
}
// Require good.crx by policy.
MockExternalProvider* provider =
AddMockExternalProvider(Manifest::EXTERNAL_POLICY_DOWNLOAD);
// TODO(devlin): Do we also need to check installing extensions with different
// versions?
provider->UpdateOrAddExtension(good_crx, kVersionStr,
data_dir().AppendASCII("good.crx"));
service()->CheckForExternalUpdates();
ExtensionManagement* management =
ExtensionManagementFactory::GetForBrowserContext(profile());
ExtensionManagement::InstallationMode installation_mode =
management->GetInstallationMode(extension);
EXPECT_EQ(ExtensionManagement::INSTALLATION_FORCED, installation_mode);
// Reload all extensions.
service()->ReloadExtensionsForTest();
extension = registry()->GetInstalledExtension(good_crx);
ASSERT_TRUE(extension);
ManagementPolicy* policy =
ExtensionSystem::Get(browser_context())->management_policy();
// The extension should still be installed, and should be required to
// remain installed.
EXPECT_TRUE(policy->MustRemainInstalled(extension, nullptr));
// TODO(devlin): This currently doesn't work, because the extension is still
// installed with Manifest::Location INTERNAL.
// EXPECT_FALSE(policy->UserMayModifySettings(extension, nullptr));
EXPECT_TRUE(registry()->enabled_extensions().GetByID(good_crx));
ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
EXPECT_EQ(disable_reason::DISABLE_NONE, prefs->GetDisableReasons(good_crx));
EXPECT_FALSE(prefs->IsExtensionDisabled(good_crx));
}
} // namespace extensions } // namespace extensions
...@@ -92,15 +92,6 @@ bool StandardManagementPolicyProvider::UserMayLoad( ...@@ -92,15 +92,6 @@ bool StandardManagementPolicyProvider::UserMayLoad(
if (extension->from_bookmark()) if (extension->from_bookmark())
return true; return true;
ExtensionManagement::InstallationMode installation_mode =
settings_->GetInstallationMode(extension);
// Force-installed extensions cannot be overwritten manually.
if (!Manifest::IsPolicyLocation(extension->location()) &&
installation_mode == ExtensionManagement::INSTALLATION_FORCED) {
return ReturnLoadError(extension, error);
}
// Check whether the extension type is allowed. // Check whether the extension type is allowed.
// //
// If you get a compile error here saying that the type you added is not // If you get a compile error here saying that the type you added is not
...@@ -127,12 +118,29 @@ bool StandardManagementPolicyProvider::UserMayLoad( ...@@ -127,12 +118,29 @@ bool StandardManagementPolicyProvider::UserMayLoad(
NOTREACHED(); NOTREACHED();
} }
ExtensionManagement::InstallationMode installation_mode =
settings_->GetInstallationMode(extension);
if (installation_mode == ExtensionManagement::INSTALLATION_BLOCKED) if (installation_mode == ExtensionManagement::INSTALLATION_BLOCKED)
return ReturnLoadError(extension, error); return ReturnLoadError(extension, error);
return true; return true;
} }
bool StandardManagementPolicyProvider::UserMayInstall(
const Extension* extension,
base::string16* error) const {
ExtensionManagement::InstallationMode installation_mode =
settings_->GetInstallationMode(extension);
// Force-installed extensions cannot be overwritten manually.
if (!Manifest::IsPolicyLocation(extension->location()) &&
installation_mode == ExtensionManagement::INSTALLATION_FORCED) {
return ReturnLoadError(extension, error);
}
return UserMayLoad(extension, error);
}
bool StandardManagementPolicyProvider::UserMayModifySettings( bool StandardManagementPolicyProvider::UserMayModifySettings(
const Extension* extension, const Extension* extension,
base::string16* error) const { base::string16* error) const {
......
...@@ -28,6 +28,8 @@ class StandardManagementPolicyProvider : public ManagementPolicy::Provider { ...@@ -28,6 +28,8 @@ class StandardManagementPolicyProvider : public ManagementPolicy::Provider {
std::string GetDebugPolicyProviderName() const override; std::string GetDebugPolicyProviderName() const override;
bool UserMayLoad(const Extension* extension, bool UserMayLoad(const Extension* extension,
base::string16* error) const override; base::string16* error) const override;
bool UserMayInstall(const Extension* extension,
base::string16* error) const override;
bool UserMayModifySettings(const Extension* extension, bool UserMayModifySettings(const Extension* extension,
base::string16* error) const override; base::string16* error) const override;
bool ExtensionMayModifySettings(const Extension* source_extension, bool ExtensionMayModifySettings(const Extension* source_extension,
......
...@@ -29,6 +29,11 @@ bool ManagementPolicy::Provider::UserMayLoad(const Extension* extension, ...@@ -29,6 +29,11 @@ bool ManagementPolicy::Provider::UserMayLoad(const Extension* extension,
return true; return true;
} }
bool ManagementPolicy::Provider::UserMayInstall(const Extension* extension,
base::string16* error) const {
return UserMayLoad(extension, error);
}
bool ManagementPolicy::Provider::UserMayModifySettings( bool ManagementPolicy::Provider::UserMayModifySettings(
const Extension* extension, base::string16* error) const { const Extension* extension, base::string16* error) const {
return true; return true;
...@@ -80,6 +85,12 @@ bool ManagementPolicy::UserMayLoad(const Extension* extension, ...@@ -80,6 +85,12 @@ bool ManagementPolicy::UserMayLoad(const Extension* extension,
&Provider::UserMayLoad, "Installation", true, extension, error); &Provider::UserMayLoad, "Installation", true, extension, error);
} }
bool ManagementPolicy::UserMayInstall(const Extension* extension,
base::string16* error) const {
return ApplyToProviderList(&Provider::UserMayInstall, "Installation", true,
extension, error);
}
bool ManagementPolicy::UserMayModifySettings(const Extension* extension, bool ManagementPolicy::UserMayModifySettings(const Extension* extension,
base::string16* error) const { base::string16* error) const {
return ApplyToProviderList( return ApplyToProviderList(
......
...@@ -61,6 +61,12 @@ class ManagementPolicy { ...@@ -61,6 +61,12 @@ class ManagementPolicy {
virtual bool UserMayLoad(const Extension* extension, virtual bool UserMayLoad(const Extension* extension,
base::string16* error) const; base::string16* error) const;
// Returns false if the user should not be allowed to install the given
// |extension|. By default, this forwards to UserMayLoad() (since a user
// should not be able to install an extension they cannot load).
virtual bool UserMayInstall(const Extension* extension,
base::string16* error) const;
// Providers should return false if a user may not enable, disable, or // Providers should return false if a user may not enable, disable, or
// uninstall the |extension|, or change its usage options (incognito // uninstall the |extension|, or change its usage options (incognito
// permission, file access, etc.). // permission, file access, etc.).
...@@ -119,6 +125,11 @@ class ManagementPolicy { ...@@ -119,6 +125,11 @@ class ManagementPolicy {
// TODO(treib,pam): Misleading name; see comment in Provider. crbug.com/461747 // TODO(treib,pam): Misleading name; see comment in Provider. crbug.com/461747
bool UserMayLoad(const Extension* extension, base::string16* error) const; bool UserMayLoad(const Extension* extension, base::string16* error) const;
// Returns false if the user should not be allowed to install the given
// |extension|. By default, this forwards to UserMayLoad() (since a user
// should not be able to install an extension they cannot load).
bool UserMayInstall(const Extension* extension, base::string16* error) const;
// Returns true if the user is permitted to enable, disable, or uninstall the // Returns true if the user is permitted to enable, disable, or uninstall the
// given extension, or change the extension's usage options (incognito mode, // given extension, or change the extension's usage options (incognito mode,
// file access, etc.). If not, |error| may be set to an appropriate message. // file access, etc.). If not, |error| may be set to an appropriate message.
......
...@@ -18,7 +18,7 @@ PolicyCheck::~PolicyCheck() {} ...@@ -18,7 +18,7 @@ PolicyCheck::~PolicyCheck() {}
void PolicyCheck::Start(ResultCallback callback) { void PolicyCheck::Start(ResultCallback callback) {
Errors errors; Errors errors;
if (!ExtensionSystem::Get(context_)->management_policy()->UserMayLoad( if (!ExtensionSystem::Get(context_)->management_policy()->UserMayInstall(
extension(), &error_)) { extension(), &error_)) {
DCHECK(!error_.empty()); DCHECK(!error_.empty());
errors.insert(DISALLOWED_BY_POLICY); errors.insert(DISALLOWED_BY_POLICY);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment