| Index: chrome/browser/supervised_user/supervised_user_service.cc
|
| diff --git a/chrome/browser/supervised_user/supervised_user_service.cc b/chrome/browser/supervised_user/supervised_user_service.cc
|
| index 0f71827875aeaaebb3c71482ecd4956c8741e3f2..f3c44b6c13cd0de40324d3d35cfb7a2677631a80 100644
|
| --- a/chrome/browser/supervised_user/supervised_user_service.cc
|
| +++ b/chrome/browser/supervised_user/supervised_user_service.cc
|
| @@ -7,6 +7,7 @@
|
| #include <utility>
|
|
|
| #include "base/command_line.h"
|
| +#include "base/feature_list.h"
|
| #include "base/files/file_path.h"
|
| #include "base/files/file_util.h"
|
| #include "base/memory/ref_counted.h"
|
| @@ -27,6 +28,8 @@
|
| #include "chrome/browser/supervised_user/experimental/supervised_user_filtering_switches.h"
|
| #include "chrome/browser/supervised_user/permission_request_creator.h"
|
| #include "chrome/browser/supervised_user/supervised_user_constants.h"
|
| +#include "chrome/browser/supervised_user/supervised_user_features.h"
|
| +#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
|
| #include "chrome/browser/supervised_user/supervised_user_service_observer.h"
|
| #include "chrome/browser/supervised_user/supervised_user_settings_service.h"
|
| #include "chrome/browser/supervised_user/supervised_user_settings_service_factory.h"
|
| @@ -68,6 +71,9 @@
|
|
|
| #if defined(ENABLE_EXTENSIONS)
|
| #include "chrome/browser/extensions/extension_service.h"
|
| +#include "chrome/browser/extensions/extension_util.h"
|
| +#include "extensions/browser/extension_prefs.h"
|
| +#include "extensions/browser/extension_registry.h"
|
| #include "extensions/browser/extension_system.h"
|
| #endif
|
|
|
| @@ -80,6 +86,13 @@ using base::DictionaryValue;
|
| using base::UserMetricsAction;
|
| using content::BrowserThread;
|
|
|
| +#if defined(ENABLE_EXTENSIONS)
|
| +using extensions::Extension;
|
| +using extensions::ExtensionPrefs;
|
| +using extensions::ExtensionRegistry;
|
| +using extensions::ExtensionSystem;
|
| +#endif
|
| +
|
| namespace {
|
|
|
| // The URL from which to download a host blacklist if no local one exists yet.
|
| @@ -106,6 +119,13 @@ void CreateURLAccessRequest(
|
| creator->CreateURLAccessRequest(url, callback);
|
| }
|
|
|
| +void CreateExtensionInstallRequest(
|
| + const std::string& id,
|
| + PermissionRequestCreator* creator,
|
| + const SupervisedUserService::SuccessCallback& callback) {
|
| + creator->CreateExtensionInstallRequest(id, callback);
|
| +}
|
| +
|
| void CreateExtensionUpdateRequest(
|
| const std::string& id,
|
| PermissionRequestCreator* creator,
|
| @@ -113,6 +133,11 @@ void CreateExtensionUpdateRequest(
|
| creator->CreateExtensionUpdateRequest(id, callback);
|
| }
|
|
|
| +// Default callback for AddExtensionInstallRequest.
|
| +void ExtensionInstallRequestSent(const std::string& id, bool success) {
|
| + VLOG_IF(1, !success) << "Failed sending install request for " << id;
|
| +}
|
| +
|
| // Default callback for AddExtensionUpdateRequest.
|
| void ExtensionUpdateRequestSent(const std::string& id, bool success) {
|
| VLOG_IF(1, !success) << "Failed sending update request for " << id;
|
| @@ -123,46 +148,6 @@ base::FilePath GetBlacklistPath() {
|
| PathService::Get(chrome::DIR_USER_DATA, &blacklist_dir);
|
| return blacklist_dir.AppendASCII(kBlacklistFilename);
|
| }
|
| -
|
| -#if defined(ENABLE_EXTENSIONS)
|
| -enum ExtensionState {
|
| - EXTENSION_FORCED,
|
| - EXTENSION_BLOCKED,
|
| - EXTENSION_ALLOWED
|
| -};
|
| -
|
| -ExtensionState GetExtensionState(const extensions::Extension* extension) {
|
| - bool was_installed_by_default = extension->was_installed_by_default();
|
| -#if defined(OS_CHROMEOS)
|
| - // On Chrome OS all external sources are controlled by us so it means that
|
| - // they are "default". Method was_installed_by_default returns false because
|
| - // extensions creation flags are ignored in case of default extensions with
|
| - // update URL(the flags aren't passed to OnExternalExtensionUpdateUrlFound).
|
| - // TODO(dpolukhin): remove this Chrome OS specific code as soon as creation
|
| - // flags are not ignored.
|
| - was_installed_by_default =
|
| - extensions::Manifest::IsExternalLocation(extension->location());
|
| -#endif
|
| - // Note: Component extensions are protected from modification/uninstallation
|
| - // anyway, so there's no need to enforce them again for supervised users.
|
| - // Also, leave policy-installed extensions alone - they have their own
|
| - // management; in particular we don't want to override the force-install list.
|
| - if (extensions::Manifest::IsComponentLocation(extension->location()) ||
|
| - extensions::Manifest::IsPolicyLocation(extension->location()) ||
|
| - extension->is_theme() ||
|
| - extension->from_bookmark() ||
|
| - extension->is_shared_module() ||
|
| - was_installed_by_default) {
|
| - return EXTENSION_ALLOWED;
|
| - }
|
| -
|
| - if (extension->was_installed_by_custodian())
|
| - return EXTENSION_FORCED;
|
| -
|
| - return EXTENSION_BLOCKED;
|
| -}
|
| -#endif
|
| -
|
| } // namespace
|
|
|
| SupervisedUserService::~SupervisedUserService() {
|
| @@ -173,6 +158,7 @@ SupervisedUserService::~SupervisedUserService() {
|
| // static
|
| void SupervisedUserService::RegisterProfilePrefs(
|
| user_prefs::PrefRegistrySyncable* registry) {
|
| + registry->RegisterDictionaryPref(prefs::kSupervisedUserApprovedExtensions);
|
| registry->RegisterDictionaryPref(prefs::kSupervisedUserManualHosts);
|
| registry->RegisterDictionaryPref(prefs::kSupervisedUserManualURLs);
|
| registry->RegisterIntegerPref(prefs::kDefaultSupervisedUserFilteringBehavior,
|
| @@ -263,11 +249,28 @@ void SupervisedUserService::ReportURL(const GURL& url,
|
| callback.Run(false);
|
| }
|
|
|
| +void SupervisedUserService::AddExtensionInstallRequest(
|
| + const std::string& extension_id,
|
| + const base::Version& version,
|
| + const SuccessCallback& callback) {
|
| + std::string id = GetExtensionRequestId(extension_id, version);
|
| + AddPermissionRequestInternal(base::Bind(CreateExtensionInstallRequest, id),
|
| + callback, 0);
|
| +}
|
| +
|
| +void SupervisedUserService::AddExtensionInstallRequest(
|
| + const std::string& extension_id,
|
| + const base::Version& version) {
|
| + std::string id = GetExtensionRequestId(extension_id, version);
|
| + AddPermissionRequestInternal(base::Bind(CreateExtensionInstallRequest, id),
|
| + base::Bind(ExtensionInstallRequestSent, id), 0);
|
| +}
|
| +
|
| void SupervisedUserService::AddExtensionUpdateRequest(
|
| const std::string& extension_id,
|
| const base::Version& version,
|
| const SuccessCallback& callback) {
|
| - std::string id = GetExtensionUpdateRequestId(extension_id, version);
|
| + std::string id = GetExtensionRequestId(extension_id, version);
|
| AddPermissionRequestInternal(
|
| base::Bind(CreateExtensionUpdateRequest, id), callback, 0);
|
| }
|
| @@ -275,13 +278,52 @@ void SupervisedUserService::AddExtensionUpdateRequest(
|
| void SupervisedUserService::AddExtensionUpdateRequest(
|
| const std::string& extension_id,
|
| const base::Version& version) {
|
| - std::string id = GetExtensionUpdateRequestId(extension_id, version);
|
| + std::string id = GetExtensionRequestId(extension_id, version);
|
| AddExtensionUpdateRequest(extension_id, version,
|
| base::Bind(ExtensionUpdateRequestSent, id));
|
| }
|
|
|
| +void SupervisedUserService::ChangeExtensionStateIfNecessary(
|
| + const std::string& extension_id) {
|
| + ExtensionRegistry* registry = ExtensionRegistry::Get(profile_);
|
| + const Extension* extension = registry->GetInstalledExtension(extension_id);
|
| + // If the extension is not installed (yet), do nothing.
|
| + // When it gets installed, GetExtensionState() will return ALLOWED, hence
|
| + // it will be enabled.
|
| + if (!extension)
|
| + return;
|
| +
|
| + ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
|
| + ExtensionService* service =
|
| + ExtensionSystem::Get(profile_)->extension_service();
|
| +
|
| + ExtensionState state = GetExtensionState(*extension);
|
| + // BLOCKED/FORCED extensions should be already disabled/enabled
|
| + // and we don't need to change their state here.
|
| + if (state == ExtensionState::BLOCKED || state == ExtensionState::FORCED)
|
| + return;
|
| +
|
| + if (state == ExtensionState::REQUIRE_APPROVAL) {
|
| + service->DisableExtension(extension_id,
|
| + Extension::DISABLE_CUSTODIAN_APPROVAL_REQUIRED);
|
| + return;
|
| + }
|
| +
|
| + if (state == ExtensionState::ALLOWED) {
|
| + extension_prefs->RemoveDisableReason(
|
| + extension_id, Extension::DISABLE_CUSTODIAN_APPROVAL_REQUIRED);
|
| + extension_prefs->RemoveDisableReason(
|
| + extension_id, Extension::DISABLE_PERMISSIONS_INCREASE);
|
| + // If not disabled for other reasons, enable it.
|
| + if (extension_prefs->GetDisableReasons(extension_id) ==
|
| + Extension::DISABLE_NONE) {
|
| + service->EnableExtension(extension_id);
|
| + }
|
| + }
|
| +}
|
| +
|
| // static
|
| -std::string SupervisedUserService::GetExtensionUpdateRequestId(
|
| +std::string SupervisedUserService::GetExtensionRequestId(
|
| const std::string& extension_id,
|
| const base::Version& version) {
|
| return base::StringPrintf("%s:%s", extension_id.c_str(),
|
| @@ -521,8 +563,10 @@ SupervisedUserService::SupervisedUserService(Profile* profile)
|
| did_init_(false),
|
| did_shutdown_(false),
|
| blacklist_state_(BlacklistLoadState::NOT_LOADED),
|
| + registry_observer_(this),
|
| weak_ptr_factory_(this) {
|
| url_filter_context_.ui_url_filter()->AddObserver(this);
|
| + registry_observer_.Add(extensions::ExtensionRegistry::Get(profile));
|
| }
|
|
|
| void SupervisedUserService::SetActive(bool active) {
|
| @@ -587,6 +631,10 @@ void SupervisedUserService::SetActive(bool active) {
|
| prefs::kDefaultSupervisedUserFilteringBehavior,
|
| base::Bind(&SupervisedUserService::OnDefaultFilteringBehaviorChanged,
|
| base::Unretained(this)));
|
| + pref_change_registrar_.Add(
|
| + prefs::kSupervisedUserApprovedExtensions,
|
| + base::Bind(&SupervisedUserService::UpdateApprovedExtensions,
|
| + base::Unretained(this)));
|
| pref_change_registrar_.Add(prefs::kSupervisedUserSafeSites,
|
| base::Bind(&SupervisedUserService::OnSafeSitesSettingChanged,
|
| base::Unretained(this)));
|
| @@ -608,6 +656,7 @@ void SupervisedUserService::SetActive(bool active) {
|
| whitelist_service_->Init();
|
| UpdateManualHosts();
|
| UpdateManualURLs();
|
| + UpdateApprovedExtensions();
|
|
|
| #if !defined(OS_ANDROID)
|
| // TODO(bauerb): Get rid of the platform-specific #ifdef here.
|
| @@ -620,6 +669,7 @@ void SupervisedUserService::SetActive(bool active) {
|
|
|
| pref_change_registrar_.Remove(
|
| prefs::kDefaultSupervisedUserFilteringBehavior);
|
| + pref_change_registrar_.Remove(prefs::kSupervisedUserApprovedExtensions);
|
| pref_change_registrar_.Remove(prefs::kSupervisedUserManualHosts);
|
| pref_change_registrar_.Remove(prefs::kSupervisedUserManualURLs);
|
| for (const char* pref : kCustodianInfoPrefs) {
|
| @@ -915,6 +965,26 @@ void SupervisedUserService::UpdateManualURLs() {
|
| SupervisedUserServiceObserver, observer_list_, OnURLFilterChanged());
|
| }
|
|
|
| +void SupervisedUserService::UpdateApprovedExtensions() {
|
| + const base::DictionaryValue* dict = profile_->GetPrefs()->GetDictionary(
|
| + prefs::kSupervisedUserApprovedExtensions);
|
| + approved_extensions_map_.clear();
|
| + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
|
| + std::string version_str;
|
| + bool result = it.value().GetAsString(&version_str);
|
| + DCHECK(result);
|
| + base::Version version(version_str);
|
| + if (version.IsValid())
|
| + approved_extensions_map_[it.key()] = version;
|
| + else
|
| + LOG(WARNING) << "Invalid version number " << version_str;
|
| + }
|
| +
|
| + for (const auto& extensions_entry : approved_extensions_map_) {
|
| + ChangeExtensionStateIfNecessary(extensions_entry.first);
|
| + }
|
| +}
|
| +
|
| std::string SupervisedUserService::GetSupervisedUserName() const {
|
| #if defined(OS_CHROMEOS)
|
| // The active user can be NULL in unit tests.
|
| @@ -935,6 +1005,36 @@ void SupervisedUserService::OnForceSessionSyncChanged() {
|
| ->ReconfigureDatatypeManager();
|
| }
|
|
|
| +void SupervisedUserService::OnExtensionInstalled(
|
| + content::BrowserContext* browser_context,
|
| + const extensions::Extension* extension,
|
| + bool is_update) {
|
| + // This callback is responsible only for updating the approved version
|
| + // upon extension update if it doesn't require extra permission.
|
| + if (!is_update)
|
| + return;
|
| +
|
| + ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
|
| + const std::string& id = extension->id();
|
| + const base::Version& version = *extension->version();
|
| +
|
| + // If an already approved extension is updated without requiring
|
| + // new permissions, we update the approved_version and re-enable it.
|
| + if (!extension_prefs->HasDisableReason(
|
| + id, Extension::DISABLE_PERMISSIONS_INCREASE) &&
|
| + approved_extensions_map_.count(id) > 0 &&
|
| + approved_extensions_map_[id] < version) {
|
| + approved_extensions_map_[id] = version;
|
| +
|
| + std::string key = SupervisedUserSettingsService::MakeSplitSettingKey(
|
| + supervised_users::kApprovedExtensions, id);
|
| + std::unique_ptr<base::Value> version_value(
|
| + new base::StringValue(version.GetString()));
|
| + GetSettingsService()->UpdateSetting(key, std::move(version_value));
|
| + }
|
| + ChangeExtensionStateIfNecessary(id);
|
| +}
|
| +
|
| void SupervisedUserService::Shutdown() {
|
| if (!did_init_)
|
| return;
|
| @@ -954,6 +1054,50 @@ void SupervisedUserService::Shutdown() {
|
| }
|
|
|
| #if defined(ENABLE_EXTENSIONS)
|
| +SupervisedUserService::ExtensionState SupervisedUserService::GetExtensionState(
|
| + const Extension& extension) const {
|
| + bool was_installed_by_default = extension.was_installed_by_default();
|
| +#if defined(OS_CHROMEOS)
|
| + // On Chrome OS all external sources are controlled by us so it means that
|
| + // they are "default". Method was_installed_by_default returns false because
|
| + // extensions creation flags are ignored in case of default extensions with
|
| + // update URL(the flags aren't passed to OnExternalExtensionUpdateUrlFound).
|
| + // TODO(dpolukhin): remove this Chrome OS specific code as soon as creation
|
| + // flags are not ignored.
|
| + was_installed_by_default =
|
| + extensions::Manifest::IsExternalLocation(extension->location());
|
| +#endif
|
| + // Note: Component extensions are protected from modification/uninstallation
|
| + // anyway, so there's no need to enforce them again for supervised users.
|
| + // Also, leave policy-installed extensions alone - they have their own
|
| + // management; in particular we don't want to override the force-install list.
|
| + if (extensions::Manifest::IsComponentLocation(extension.location()) ||
|
| + extensions::Manifest::IsPolicyLocation(extension.location()) ||
|
| + extension.is_theme() || extension.from_bookmark() ||
|
| + extension.is_shared_module() || was_installed_by_default) {
|
| + return ExtensionState::ALLOWED;
|
| + }
|
| +
|
| + if (extension.was_installed_by_custodian())
|
| + return ExtensionState::FORCED;
|
| +
|
| + if (!base::FeatureList::IsEnabled(
|
| + supervised_users::kSupervisedUserInitiatedExtensionInstall)) {
|
| + return ExtensionState::BLOCKED;
|
| + }
|
| +
|
| + const std::string& id = extension.id();
|
| +
|
| + auto extension_it = approved_extensions_map_.find(id);
|
| + // If the installed version is approved, then the extension is allowed,
|
| + // otherwise, it requires approval.
|
| + if (extension_it != approved_extensions_map_.end() &&
|
| + extension_it->second == *extension.version()) {
|
| + return ExtensionState::ALLOWED;
|
| + }
|
| + return ExtensionState::REQUIRE_APPROVAL;
|
| +}
|
| +
|
| std::string SupervisedUserService::GetDebugPolicyProviderName() const {
|
| // Save the string space in official builds.
|
| #ifdef NDEBUG
|
| @@ -964,22 +1108,26 @@ std::string SupervisedUserService::GetDebugPolicyProviderName() const {
|
| #endif
|
| }
|
|
|
| -bool SupervisedUserService::UserMayLoad(const extensions::Extension* extension,
|
| +bool SupervisedUserService::UserMayLoad(const Extension* extension,
|
| base::string16* error) const {
|
| DCHECK(ProfileIsSupervised());
|
| - ExtensionState result = GetExtensionState(extension);
|
| - bool may_load = (result != EXTENSION_BLOCKED);
|
| + ExtensionState result = GetExtensionState(*extension);
|
| + bool may_load = result != ExtensionState::BLOCKED;
|
| if (!may_load && error)
|
| *error = GetExtensionsLockedMessage();
|
| return may_load;
|
| }
|
|
|
| -bool SupervisedUserService::UserMayModifySettings(
|
| - const extensions::Extension* extension,
|
| - base::string16* error) const {
|
| +bool SupervisedUserService::UserMayModifySettings(const Extension* extension,
|
| + base::string16* error) const {
|
| DCHECK(ProfileIsSupervised());
|
| - ExtensionState result = GetExtensionState(extension);
|
| - bool may_modify = (result == EXTENSION_ALLOWED);
|
| + ExtensionState result = GetExtensionState(*extension);
|
| + // While the following check allows the supervised user to modify the settings
|
| + // and enable or disable the extension, MustRemainDisabled properly takes care
|
| + // of keeping an extension disabled when required.
|
| + // For custodian-installed extensions, the state is always FORCED, even if
|
| + // it's waiting for an update approval.
|
| + bool may_modify = result != ExtensionState::FORCED;
|
| if (!may_modify && error)
|
| *error = GetExtensionsLockedMessage();
|
| return may_modify;
|
| @@ -988,17 +1136,58 @@ bool SupervisedUserService::UserMayModifySettings(
|
| // Note: Having MustRemainInstalled always say "true" for custodian-installed
|
| // extensions does NOT prevent remote uninstalls (which is a bit unexpected, but
|
| // exactly what we want).
|
| -bool SupervisedUserService::MustRemainInstalled(
|
| - const extensions::Extension* extension,
|
| - base::string16* error) const {
|
| +bool SupervisedUserService::MustRemainInstalled(const Extension* extension,
|
| + base::string16* error) const {
|
| DCHECK(ProfileIsSupervised());
|
| - ExtensionState result = GetExtensionState(extension);
|
| - bool may_not_uninstall = (result == EXTENSION_FORCED);
|
| + ExtensionState result = GetExtensionState(*extension);
|
| + bool may_not_uninstall = (result == ExtensionState::FORCED);
|
| if (may_not_uninstall && error)
|
| *error = GetExtensionsLockedMessage();
|
| return may_not_uninstall;
|
| }
|
|
|
| +bool SupervisedUserService::MustRemainDisabled(const Extension* extension,
|
| + Extension::DisableReason* reason,
|
| + base::string16* error) const {
|
| + DCHECK(ProfileIsSupervised());
|
| + ExtensionState state = GetExtensionState(*extension);
|
| + bool must_remain_disabled = state == ExtensionState::BLOCKED ||
|
| + state == ExtensionState::REQUIRE_APPROVAL;
|
| +
|
| + if (must_remain_disabled) {
|
| + if (error)
|
| + *error = l10n_util::GetStringUTF16(IDS_EXTENSIONS_LOCKED_SUPERVISED_USER);
|
| + // If the extension must remain disabled due to permission increase,
|
| + // then the update request has been already sent at update time.
|
| + // We do nothing and we don't add an extra disable reason.
|
| + ExtensionPrefs* extension_prefs = ExtensionPrefs::Get(profile_);
|
| + if (extension_prefs->HasDisableReason(
|
| + extension->id(), Extension::DISABLE_PERMISSIONS_INCREASE)) {
|
| + if (reason)
|
| + *reason = Extension::DISABLE_PERMISSIONS_INCREASE;
|
| + return true;
|
| + }
|
| + if (reason)
|
| + *reason = Extension::DISABLE_CUSTODIAN_APPROVAL_REQUIRED;
|
| + if (base::FeatureList::IsEnabled(
|
| + supervised_users::kSupervisedUserInitiatedExtensionInstall)) {
|
| + // If the Extension isn't pending a custodian approval already, send
|
| + // an approval request.
|
| + if (!extension_prefs->HasDisableReason(
|
| + extension->id(),
|
| + Extension::DISABLE_CUSTODIAN_APPROVAL_REQUIRED)) {
|
| + // MustRemainDisabled is a const method and hence cannot call
|
| + // AddExtensionInstallRequest directly.
|
| + SupervisedUserService* supervised_user_service =
|
| + SupervisedUserServiceFactory::GetForProfile(profile_);
|
| + supervised_user_service->AddExtensionInstallRequest(
|
| + extension->id(), *extension->version());
|
| + }
|
| + }
|
| + }
|
| + return must_remain_disabled;
|
| +}
|
| +
|
| void SupervisedUserService::SetExtensionsActive() {
|
| extensions::ExtensionSystem* extension_system =
|
| extensions::ExtensionSystem::Get(profile_);
|
|
|