blob: 875fc77cbdc8958cbe9d2d43ccc9ae082d432d7a [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/standard_management_policy_provider.h"
#include <string>
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/managed_installation_mode.h"
#include "chrome/browser/extensions/manifest_v2_experiment_manager.h"
#include "chrome/grit/generated_resources.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/manifest.h"
#include "extensions/strings/grit/extensions_strings.h"
#include "ui/base/l10n/l10n_util.h"
static_assert(BUILDFLAG(ENABLE_EXTENSIONS_CORE));
namespace extensions {
namespace {
bool AdminPolicyIsModifiable(ExtensionManagement* settings,
const Extension* source_extension,
const Extension* extension,
std::u16string* error) {
// Component and force installed extensions can enable/disable all other
// extensions including force installed ones (but component are off limits).
const bool component_or_force_installed =
source_extension &&
(Manifest::IsComponentLocation(source_extension->location()) ||
Manifest::IsPolicyLocation(source_extension->location()));
// We also specifically disallow the Webstore to modify force installed
// extensions even though it is a component extension, because it doesn't
// need this capability and it can open up interesting attacks if it's
// leveraged via bookmarklets or devtools.
// TODO(crbug.com/40239460): This protection should be expanded by also
// blocking bookmarklets on the Webstore Origin through checks on the Blink
// side.
const bool is_webstore_hosted_app =
source_extension && source_extension->id() == extensions::kWebStoreAppId;
bool is_modifiable = true;
// Component extensions are not modifiable.
if (Manifest::IsComponentLocation(extension->location()))
is_modifiable = false;
if (Manifest::IsPolicyLocation(extension->location())) {
// A policy-installed extension *generally* can't be modified.
is_modifiable = false;
// Exceptions:
// A policy-installed extension can be modified by other
// policy-installed or component extensions (other than the Webstore).
if (component_or_force_installed && !is_webstore_hosted_app) {
is_modifiable = true;
} else if (!source_extension &&
settings->IsGreylistedForceInstalledInLowTrustEnvironment(
extension->id())) {
// A policy-installed extension that is greylisted in a low-trust
// environment is modifiable by the user.
is_modifiable = true;
}
}
if (is_modifiable) {
return true;
}
if (error) {
*error = l10n_util::GetStringFUTF16(
IDS_EXTENSION_CANT_MODIFY_POLICY_REQUIRED,
base::UTF8ToUTF16(extension->name()));
}
return false;
}
} // namespace
StandardManagementPolicyProvider::StandardManagementPolicyProvider(
ExtensionManagement* settings,
Profile* profile)
: profile_(profile), settings_(settings) {}
StandardManagementPolicyProvider::~StandardManagementPolicyProvider() = default;
std::string
StandardManagementPolicyProvider::GetDebugPolicyProviderName() const {
#if DCHECK_IS_ON()
return "extension management policy controlled settings";
#else
base::ImmediateCrash();
#endif
}
bool StandardManagementPolicyProvider::UserMayLoad(
const Extension* extension,
std::u16string* error) const {
if (Manifest::IsComponentLocation(extension->location())) {
return true;
}
// Shared modules are always allowed too: they only contain resources that
// are used by other extensions. The extension that depends on the shared
// module may be filtered by policy.
if (extension->is_shared_module())
return true;
// Check whether the extension type is allowed.
//
// If you get a compile error here saying that the type you added is not
// handled by the switch statement below, please consider whether enterprise
// policy should be able to disallow extensions of the new type. If so, add
// a branch to the second block and add a line to the definition of
// kAllowedTypesMap in extension_management_constants.h.
switch (extension->GetType()) {
case Manifest::TYPE_UNKNOWN:
break;
case Manifest::TYPE_EXTENSION:
case Manifest::TYPE_THEME:
case Manifest::TYPE_USER_SCRIPT:
case Manifest::TYPE_HOSTED_APP:
case Manifest::TYPE_LEGACY_PACKAGED_APP:
case Manifest::TYPE_PLATFORM_APP:
case Manifest::TYPE_SHARED_MODULE:
case Manifest::TYPE_LOGIN_SCREEN_EXTENSION:
case Manifest::TYPE_CHROMEOS_SYSTEM_EXTENSION: {
if (!settings_->IsAllowedManifestType(extension->GetType(),
extension->id()))
return ReturnLoadError(extension, error);
break;
}
case Manifest::NUM_LOAD_TYPES:
NOTREACHED();
}
ManagedInstallationMode installation_mode =
settings_->GetInstallationMode(extension);
if (installation_mode == ManagedInstallationMode::kBlocked ||
installation_mode == ManagedInstallationMode::kRemoved) {
return ReturnLoadError(extension, error);
}
if (!settings_->IsAllowedManifestVersion(extension)) {
if (error) {
*error = l10n_util::GetStringFUTF16(
IDS_EXTENSION_MANIFEST_VERSION_NOT_SUPPORTED,
base::UTF8ToUTF16(extension->name()));
}
return false;
}
return true;
}
bool StandardManagementPolicyProvider::UserMayInstall(
const Extension* extension,
std::u16string* error) const {
ManagedInstallationMode installation_mode =
settings_->GetInstallationMode(extension);
// Force-installed extensions cannot be overwritten manually.
if (!Manifest::IsPolicyLocation(extension->location()) &&
installation_mode == ManagedInstallationMode::kForced) {
return ReturnLoadError(extension, error);
}
// Check if the extension would be force-disabled once it's installed. If it
// would, block the new installation.
auto* mv2_experiment_manager = ManifestV2ExperimentManager::Get(profile_);
if (mv2_experiment_manager &&
mv2_experiment_manager->ShouldBlockExtensionEnable(*extension)) {
*error =
l10n_util::GetStringUTF16(IDS_EXTENSIONS_CANT_INSTALL_MV2_EXTENSION);
return false;
}
return UserMayLoad(extension, error);
}
bool StandardManagementPolicyProvider::UserMayModifySettings(
const Extension* extension,
std::u16string* error) const {
return AdminPolicyIsModifiable(settings_, nullptr, extension, error);
}
bool StandardManagementPolicyProvider::ExtensionMayModifySettings(
const Extension* source_extension,
const Extension* extension,
std::u16string* error) const {
return AdminPolicyIsModifiable(settings_, source_extension, extension, error);
}
bool StandardManagementPolicyProvider::MustRemainEnabled(
const Extension* extension,
std::u16string* error) const {
return !AdminPolicyIsModifiable(settings_, nullptr, extension, error);
}
bool StandardManagementPolicyProvider::MustRemainDisabled(
const Extension* extension,
disable_reason::DisableReason* reason) const {
std::string required_version;
if (!settings_->CheckMinimumVersion(extension, &required_version)) {
if (reason) {
*reason = disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY;
}
return true;
}
if (!settings_->IsAllowedByUnpublishedAvailabilityPolicy(extension)) {
if (reason) {
*reason = disable_reason::DISABLE_PUBLISHED_IN_STORE_REQUIRED_BY_POLICY;
}
return true;
}
if (!settings_->IsAllowedByUnpackedDeveloperModePolicy(*extension)) {
if (reason) {
*reason = disable_reason::DISABLE_UNSUPPORTED_DEVELOPER_EXTENSION;
}
return true;
}
if (settings_->ShouldBlockForceInstalledOffstoreExtension(*extension)) {
if (reason) {
*reason = disable_reason::DISABLE_NOT_VERIFIED;
}
return true;
}
// Note: `mv2_experiment_manager` may be null for certain types of profiles
// (such as the sign-in profile). We can ignore this check in this case, since
// users can't install extensions in these profiles.
auto* mv2_experiment_manager = ManifestV2ExperimentManager::Get(profile_);
if (mv2_experiment_manager &&
mv2_experiment_manager->ShouldBlockExtensionEnable(*extension)) {
if (reason) {
*reason = disable_reason::DISABLE_UNSUPPORTED_MANIFEST_VERSION;
}
return true;
}
return false;
}
bool StandardManagementPolicyProvider::MustRemainInstalled(
const Extension* extension,
std::u16string* error) const {
ManagedInstallationMode mode = settings_->GetInstallationMode(extension);
// Disallow removing of recommended extension, to avoid re-install it
// again while policy is reload. But disabling of recommended extension is
// allowed.
if (mode == ManagedInstallationMode::kForced ||
mode == ManagedInstallationMode::kRecommended) {
if (error) {
*error = l10n_util::GetStringFUTF16(
IDS_EXTENSION_CANT_UNINSTALL_POLICY_REQUIRED,
base::UTF8ToUTF16(extension->name()));
}
return true;
}
return false;
}
bool StandardManagementPolicyProvider::ShouldForceUninstall(
const Extension* extension,
std::u16string* error) const {
if (UserMayLoad(extension, error))
return false;
if (settings_->GetInstallationMode(extension) ==
ManagedInstallationMode::kRemoved) {
return true;
}
return false;
}
bool StandardManagementPolicyProvider::ReturnLoadError(
const extensions::Extension* extension,
std::u16string* error) const {
if (error) {
*error = l10n_util::GetStringFUTF16(
IDS_EXTENSION_CANT_INSTALL_POLICY_BLOCKED,
base::UTF8ToUTF16(extension->name()),
base::UTF8ToUTF16(extension->id()),
base::UTF8ToUTF16(settings_->BlockedInstallMessage(extension->id())));
}
return false;
}
} // namespace extensions