blob: 98c7d1ac64c4c01eab0b292119436bd07c262486 [file] [log] [blame]
// 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/component_updater/supervised_user_whitelist_installer.h"
#include <stddef.h>
#include <map>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/important_file_writer.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/user_metrics.h"
#include "base/metrics/user_metrics_action.h"
#include "base/path_service.h"
#include "base/scoped_observer.h"
#include "base/sequenced_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/task_scheduler/task_traits.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/value_conversions.h"
#include "base/values.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/supervised_user/supervised_user_whitelist_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/component_installer.h"
#include "components/component_updater/component_updater_paths.h"
#include "components/component_updater/component_updater_service.h"
#include "components/crx_file/id_util.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h"
#include "services/data_decoder/public/cpp/json_sanitizer.h"
namespace component_updater {
namespace {
const char kSanitizedWhitelistExtension[] = ".json";
const char kWhitelistedContent[] = "whitelisted_content";
const char kSites[] = "sites";
const char kClients[] = "clients";
const char kName[] = "name";
// These are copies of extensions::manifest_keys::kName and kShortName. They
// are duplicated here because we mustn't depend on code from extensions/
// (since it's not built on Android).
const char kExtensionName[] = "name";
const char kExtensionShortName[] = "short_name";
const char kExtensionIcons[] = "icons";
const char kExtensionLargeIcon[] = "128";
constexpr base::TaskTraits kTaskTraits = {
base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
base::string16 GetWhitelistTitle(const base::DictionaryValue& manifest) {
base::string16 title;
if (!manifest.GetString(kExtensionShortName, &title))
manifest.GetString(kExtensionName, &title);
return title;
}
base::FilePath GetSafeFilePath(const base::DictionaryValue& dictionary,
const std::string& key,
const base::FilePath& install_dir) {
const base::Value* path_value = nullptr;
if (!dictionary.Get(key, &path_value))
return base::FilePath();
base::FilePath path;
if (!base::GetValueAsFilePath(*path_value, &path))
return base::FilePath();
// Path components ("..") are not allowed.
if (path.ReferencesParent())
return base::FilePath();
return install_dir.Append(path);
}
base::FilePath GetLargeIconPath(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
const base::DictionaryValue* icons = nullptr;
if (!manifest.GetDictionary(kExtensionIcons, &icons))
return base::FilePath();
return GetSafeFilePath(*icons, kExtensionLargeIcon, install_dir);
}
base::FilePath GetRawWhitelistPath(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
const base::DictionaryValue* whitelist_dict = nullptr;
if (!manifest.GetDictionary(kWhitelistedContent, &whitelist_dict))
return base::FilePath();
return GetSafeFilePath(*whitelist_dict, kSites, install_dir);
}
base::FilePath GetSanitizedWhitelistPath(const std::string& crx_id) {
base::FilePath base_dir;
PathService::Get(chrome::DIR_SUPERVISED_USER_INSTALLED_WHITELISTS, &base_dir);
return base_dir.empty()
? base::FilePath()
: base_dir.AppendASCII(crx_id + kSanitizedWhitelistExtension);
}
void RecordUncleanUninstall() {
content::BrowserThread::GetTaskRunnerForThread(content::BrowserThread::UI)
->PostTask(
FROM_HERE,
base::BindOnce(&base::RecordAction,
base::UserMetricsAction(
"ManagedUsers_Whitelist_UncleanUninstall")));
}
void OnWhitelistSanitizationError(const base::FilePath& whitelist,
const std::string& error) {
LOG(WARNING) << "Invalid whitelist " << whitelist.value() << ": " << error;
}
void DeleteFileOnTaskRunner(const base::FilePath& path) {
if (!base::DeleteFile(path, true))
DPLOG(ERROR) << "Couldn't delete " << path.value();
}
void OnWhitelistSanitizationResult(
const std::string& crx_id,
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
base::OnceClosure callback,
const std::string& result) {
const base::FilePath sanitized_whitelist_path =
GetSanitizedWhitelistPath(crx_id);
const base::FilePath install_directory = sanitized_whitelist_path.DirName();
if (!base::DirectoryExists(install_directory)) {
if (!base::CreateDirectory(install_directory)) {
PLOG(ERROR) << "Could't create directory " << install_directory.value();
return;
}
}
const int size = result.size();
if (base::WriteFile(sanitized_whitelist_path, result.data(), size) != size) {
PLOG(ERROR) << "Couldn't write file " << sanitized_whitelist_path.value();
return;
}
task_runner->PostTask(FROM_HERE, std::move(callback));
}
void CheckForSanitizedWhitelistOnTaskRunner(
const std::string& crx_id,
const base::FilePath& whitelist_path,
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
const base::Closure& callback) {
if (base::PathExists(GetSanitizedWhitelistPath(crx_id))) {
task_runner->PostTask(FROM_HERE, callback);
return;
}
std::string unsafe_json;
if (!base::ReadFileToString(whitelist_path, &unsafe_json)) {
PLOG(ERROR) << "Couldn't read file " << whitelist_path.value();
return;
}
data_decoder::JsonSanitizer::Sanitize(
content::ServiceManagerConnection::GetForProcess()->GetConnector(),
unsafe_json,
base::Bind(&OnWhitelistSanitizationResult, crx_id, task_runner, callback),
base::Bind(&OnWhitelistSanitizationError, whitelist_path));
}
void RemoveUnregisteredWhitelistsOnTaskRunner(
const std::set<std::string>& registered_whitelists) {
base::FilePath base_dir;
PathService::Get(DIR_SUPERVISED_USER_WHITELISTS, &base_dir);
if (!base_dir.empty()) {
base::FileEnumerator file_enumerator(base_dir, false,
base::FileEnumerator::DIRECTORIES);
for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
path = file_enumerator.Next()) {
const std::string crx_id = path.BaseName().MaybeAsASCII();
// Ignore folders that don't have valid CRX ID names. These folders are
// not managed by the component installer, so do not try to remove them.
if (!crx_file::id_util::IdIsValid(crx_id))
continue;
// Ignore folders that correspond to registered whitelists.
if (registered_whitelists.count(crx_id) > 0)
continue;
RecordUncleanUninstall();
DeleteFileOnTaskRunner(path);
}
}
PathService::Get(chrome::DIR_SUPERVISED_USER_INSTALLED_WHITELISTS, &base_dir);
if (!base_dir.empty()) {
base::FilePath pattern(FILE_PATH_LITERAL("*"));
pattern = pattern.AppendASCII(kSanitizedWhitelistExtension);
base::FileEnumerator file_enumerator(
base_dir, false, base::FileEnumerator::FILES, pattern.value());
for (base::FilePath path = file_enumerator.Next(); !path.value().empty();
path = file_enumerator.Next()) {
// Ignore files that don't have valid CRX ID names. These files are not
// managed by the component installer, so do not try to remove them.
const std::string filename = path.BaseName().MaybeAsASCII();
DCHECK(base::EndsWith(filename, kSanitizedWhitelistExtension,
base::CompareCase::SENSITIVE));
const std::string crx_id = filename.substr(
filename.size() - strlen(kSanitizedWhitelistExtension));
if (!crx_file::id_util::IdIsValid(crx_id))
continue;
// Ignore files that correspond to registered whitelists.
if (registered_whitelists.count(crx_id) > 0)
continue;
RecordUncleanUninstall();
DeleteFileOnTaskRunner(path);
}
}
}
class SupervisedUserWhitelistComponentInstallerPolicy
: public ComponentInstallerPolicy {
public:
using RawWhitelistReadyCallback =
base::Callback<void(const base::string16&, /* title */
const base::FilePath&, /* icon_path */
const base::FilePath& /* whitelist_path */)>;
SupervisedUserWhitelistComponentInstallerPolicy(
const std::string& crx_id,
const std::string& name,
const RawWhitelistReadyCallback& callback)
: crx_id_(crx_id), name_(name), callback_(callback) {}
~SupervisedUserWhitelistComponentInstallerPolicy() override {}
private:
// ComponentInstallerPolicy overrides:
bool VerifyInstallation(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const override;
bool SupportsGroupPolicyEnabledComponentUpdates() const override;
bool RequiresNetworkEncryption() const override;
update_client::CrxInstaller::Result OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) override;
void OnCustomUninstall() override;
void ComponentReady(const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) override;
base::FilePath GetRelativeInstallDir() const override;
void GetHash(std::vector<uint8_t>* hash) const override;
std::string GetName() const override;
update_client::InstallerAttributes GetInstallerAttributes() const override;
std::vector<std::string> GetMimeTypes() const override;
std::string crx_id_;
std::string name_;
RawWhitelistReadyCallback callback_;
DISALLOW_COPY_AND_ASSIGN(SupervisedUserWhitelistComponentInstallerPolicy);
};
bool SupervisedUserWhitelistComponentInstallerPolicy::VerifyInstallation(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) const {
// Check whether the whitelist exists at the path specified by the manifest.
// This does not check whether the whitelist is wellformed.
return base::PathExists(GetRawWhitelistPath(manifest, install_dir));
}
bool SupervisedUserWhitelistComponentInstallerPolicy::
SupportsGroupPolicyEnabledComponentUpdates() const {
return false;
}
bool SupervisedUserWhitelistComponentInstallerPolicy::
RequiresNetworkEncryption() const {
return true;
}
update_client::CrxInstaller::Result
SupervisedUserWhitelistComponentInstallerPolicy::OnCustomInstall(
const base::DictionaryValue& manifest,
const base::FilePath& install_dir) {
// Delete the existing sanitized whitelist.
const bool success =
base::DeleteFile(GetSanitizedWhitelistPath(crx_id_), false);
return update_client::CrxInstaller::Result(
success ? update_client::InstallError::NONE
: update_client::InstallError::GENERIC_ERROR);
}
void SupervisedUserWhitelistComponentInstallerPolicy::OnCustomUninstall() {}
void SupervisedUserWhitelistComponentInstallerPolicy::ComponentReady(
const base::Version& version,
const base::FilePath& install_dir,
std::unique_ptr<base::DictionaryValue> manifest) {
// TODO(treib): Before getting the title, we should localize the manifest
// using extension_l10n_util::LocalizeExtension, but that doesn't exist on
// Android. crbug.com/558387
callback_.Run(GetWhitelistTitle(*manifest),
GetLargeIconPath(*manifest, install_dir),
GetRawWhitelistPath(*manifest, install_dir));
}
base::FilePath
SupervisedUserWhitelistComponentInstallerPolicy::GetRelativeInstallDir() const {
return base::FilePath(component_updater::kSupervisedUserWhitelistDirName)
.AppendASCII(crx_id_);
}
void SupervisedUserWhitelistComponentInstallerPolicy::GetHash(
std::vector<uint8_t>* hash) const {
*hash = SupervisedUserWhitelistInstaller::GetHashFromCrxId(crx_id_);
}
std::string SupervisedUserWhitelistComponentInstallerPolicy::GetName() const {
return name_;
}
update_client::InstallerAttributes
SupervisedUserWhitelistComponentInstallerPolicy::GetInstallerAttributes()
const {
return update_client::InstallerAttributes();
}
std::vector<std::string>
SupervisedUserWhitelistComponentInstallerPolicy::GetMimeTypes() const {
return std::vector<std::string>();
}
class SupervisedUserWhitelistInstallerImpl
: public SupervisedUserWhitelistInstaller,
public ProfileAttributesStorage::Observer {
public:
SupervisedUserWhitelistInstallerImpl(
ComponentUpdateService* cus,
ProfileAttributesStorage* profile_attributes_storage,
PrefService* local_state);
~SupervisedUserWhitelistInstallerImpl() override {}
private:
void RegisterComponent(const std::string& crx_id,
const std::string& name,
base::OnceClosure callback);
void RegisterNewComponent(const std::string& crx_id, const std::string& name);
bool UnregisterWhitelistInternal(base::DictionaryValue* pref_dict,
const std::string& client_id,
const std::string& crx_id);
void OnRawWhitelistReady(const std::string& crx_id,
const base::string16& title,
const base::FilePath& large_icon_path,
const base::FilePath& whitelist_path);
void OnSanitizedWhitelistReady(const std::string& crx_id,
const base::string16& title,
const base::FilePath& large_icon_path);
// SupervisedUserWhitelistInstaller overrides:
void RegisterComponents() override;
void Subscribe(const WhitelistReadyCallback& callback) override;
void RegisterWhitelist(const std::string& client_id,
const std::string& crx_id,
const std::string& name) override;
void UnregisterWhitelist(const std::string& client_id,
const std::string& crx_id) override;
// ProfileAttributesStorage::Observer overrides:
void OnProfileWillBeRemoved(const base::FilePath& profile_path) override;
ComponentUpdateService* cus_ = nullptr;
PrefService* local_state_ = nullptr;
std::vector<WhitelistReadyCallback> callbacks_;
ScopedObserver<ProfileAttributesStorage, ProfileAttributesStorage::Observer>
observer_;
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_ =
base::CreateSequencedTaskRunnerWithTraits(kTaskTraits);
base::WeakPtrFactory<SupervisedUserWhitelistInstallerImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SupervisedUserWhitelistInstallerImpl);
};
SupervisedUserWhitelistInstallerImpl::SupervisedUserWhitelistInstallerImpl(
ComponentUpdateService* cus,
ProfileAttributesStorage* profile_attributes_storage,
PrefService* local_state)
: cus_(cus),
local_state_(local_state),
observer_(this),
weak_ptr_factory_(this) {
DCHECK(cus);
DCHECK(local_state);
observer_.Add(profile_attributes_storage);
}
void SupervisedUserWhitelistInstallerImpl::RegisterComponent(
const std::string& crx_id,
const std::string& name,
base::OnceClosure callback) {
std::unique_ptr<ComponentInstallerPolicy> policy =
std::make_unique<SupervisedUserWhitelistComponentInstallerPolicy>(
crx_id, name,
base::Bind(&SupervisedUserWhitelistInstallerImpl::OnRawWhitelistReady,
weak_ptr_factory_.GetWeakPtr(), crx_id));
scoped_refptr<ComponentInstaller> installer =
base::MakeRefCounted<ComponentInstaller>(std::move(policy));
installer->Register(cus_, std::move(callback));
}
void SupervisedUserWhitelistInstallerImpl::RegisterNewComponent(
const std::string& crx_id,
const std::string& name) {
RegisterComponent(
crx_id, name,
base::BindOnce(&SupervisedUserWhitelistInstaller::TriggerComponentUpdate,
&cus_->GetOnDemandUpdater(), crx_id));
}
bool SupervisedUserWhitelistInstallerImpl::UnregisterWhitelistInternal(
base::DictionaryValue* pref_dict,
const std::string& client_id,
const std::string& crx_id) {
base::DictionaryValue* whitelist_dict = nullptr;
bool success =
pref_dict->GetDictionaryWithoutPathExpansion(crx_id, &whitelist_dict);
DCHECK(success);
base::ListValue* clients = nullptr;
success = whitelist_dict->GetList(kClients, &clients);
const bool removed = clients->Remove(base::Value(client_id), nullptr);
if (!clients->empty())
return removed;
pref_dict->RemoveWithoutPathExpansion(crx_id, nullptr);
const bool result = cus_->UnregisterComponent(crx_id);
DCHECK(result);
sequenced_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&DeleteFileOnTaskRunner,
GetSanitizedWhitelistPath(crx_id)));
return removed;
}
void SupervisedUserWhitelistInstallerImpl::OnRawWhitelistReady(
const std::string& crx_id,
const base::string16& title,
const base::FilePath& large_icon_path,
const base::FilePath& whitelist_path) {
// TODO(sorin): avoid using a single thread task runner crbug.com/744718.
auto task_runner = base::CreateSingleThreadTaskRunnerWithTraits(
kTaskTraits, base::SingleThreadTaskRunnerThreadMode::SHARED);
task_runner->PostTask(
FROM_HERE,
base::BindOnce(
&CheckForSanitizedWhitelistOnTaskRunner, crx_id, whitelist_path,
base::ThreadTaskRunnerHandle::Get(),
base::Bind(
&SupervisedUserWhitelistInstallerImpl::OnSanitizedWhitelistReady,
weak_ptr_factory_.GetWeakPtr(), crx_id, title, large_icon_path)));
}
void SupervisedUserWhitelistInstallerImpl::OnSanitizedWhitelistReady(
const std::string& crx_id,
const base::string16& title,
const base::FilePath& large_icon_path) {
for (const WhitelistReadyCallback& callback : callbacks_)
callback.Run(crx_id, title, large_icon_path,
GetSanitizedWhitelistPath(crx_id));
}
void SupervisedUserWhitelistInstallerImpl::RegisterComponents() {
const std::map<std::string, std::string> command_line_whitelists =
SupervisedUserWhitelistService::GetWhitelistsFromCommandLine();
std::set<std::string> registered_whitelists;
std::set<std::string> stale_whitelists;
DictionaryPrefUpdate update(local_state_,
prefs::kRegisteredSupervisedUserWhitelists);
base::DictionaryValue* whitelists = update.Get();
for (base::DictionaryValue::Iterator it(*whitelists); !it.IsAtEnd();
it.Advance()) {
const base::DictionaryValue* dict = nullptr;
it.value().GetAsDictionary(&dict);
const std::string& id = it.key();
// Skip whitelists with no clients. This can happen when a whitelist was
// previously registered on the command line but isn't anymore.
const base::ListValue* clients = nullptr;
if ((!dict->GetList(kClients, &clients) || clients->empty()) &&
command_line_whitelists.count(id) == 0) {
stale_whitelists.insert(id);
continue;
}
std::string name;
const bool result = dict->GetString(kName, &name);
DCHECK(result);
RegisterComponent(id, name, base::OnceClosure());
registered_whitelists.insert(id);
}
// Clean up stale whitelists as determined above.
for (const std::string& id : stale_whitelists)
whitelists->RemoveWithoutPathExpansion(id, nullptr);
sequenced_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&RemoveUnregisteredWhitelistsOnTaskRunner,
registered_whitelists));
}
void SupervisedUserWhitelistInstallerImpl::Subscribe(
const WhitelistReadyCallback& callback) {
return callbacks_.push_back(callback);
}
void SupervisedUserWhitelistInstallerImpl::RegisterWhitelist(
const std::string& client_id,
const std::string& crx_id,
const std::string& name) {
DictionaryPrefUpdate update(local_state_,
prefs::kRegisteredSupervisedUserWhitelists);
base::Value* pref_dict = update.Get();
base::Value* whitelist_dict =
pref_dict->FindKeyOfType(crx_id, base::Value::Type::DICTIONARY);
const bool newly_added = !whitelist_dict;
if (newly_added) {
whitelist_dict =
pref_dict->SetKey(crx_id, base::Value(base::Value::Type::DICTIONARY));
whitelist_dict->SetKey(kName, base::Value(name));
}
if (!client_id.empty()) {
base::Value* clients =
whitelist_dict->FindKeyOfType(kClients, base::Value::Type::LIST);
if (!clients) {
DCHECK(newly_added);
clients = whitelist_dict->SetKey(kClients,
base::Value(base::Value::Type::LIST));
}
base::Value client(client_id);
DCHECK(!base::ContainsValue(clients->GetList(), client));
clients->GetList().push_back(std::move(client));
}
if (!newly_added) {
// Sanity-check that the stored name is equal to the name passed in.
// In release builds this is a no-op.
DCHECK_EQ(name, whitelist_dict->FindKey(kName)->GetString());
return;
}
RegisterNewComponent(crx_id, name);
}
void SupervisedUserWhitelistInstallerImpl::UnregisterWhitelist(
const std::string& client_id,
const std::string& crx_id) {
DictionaryPrefUpdate update(local_state_,
prefs::kRegisteredSupervisedUserWhitelists);
bool removed = UnregisterWhitelistInternal(update.Get(), client_id, crx_id);
DCHECK(removed);
}
void SupervisedUserWhitelistInstallerImpl::OnProfileWillBeRemoved(
const base::FilePath& profile_path) {
std::string client_id = ClientIdForProfilePath(profile_path);
// Go through all registered whitelists and possibly unregister them for this
// client. Because unregistering a whitelist might completely uninstall it, we
// need to make a copy of all the IDs before iterating over them.
DictionaryPrefUpdate update(local_state_,
prefs::kRegisteredSupervisedUserWhitelists);
base::DictionaryValue* pref_dict = update.Get();
std::vector<std::string> crx_ids;
for (base::DictionaryValue::Iterator it(*pref_dict); !it.IsAtEnd();
it.Advance()) {
crx_ids.push_back(it.key());
}
for (const std::string& crx_id : crx_ids)
UnregisterWhitelistInternal(pref_dict, client_id, crx_id);
}
} // namespace
// static
std::unique_ptr<SupervisedUserWhitelistInstaller>
SupervisedUserWhitelistInstaller::Create(
ComponentUpdateService* cus,
ProfileAttributesStorage* profile_attributes_storage,
PrefService* local_state) {
return std::make_unique<SupervisedUserWhitelistInstallerImpl>(
cus, profile_attributes_storage, local_state);
}
// static
void SupervisedUserWhitelistInstaller::RegisterPrefs(
PrefRegistrySimple* registry) {
registry->RegisterDictionaryPref(prefs::kRegisteredSupervisedUserWhitelists);
}
// static
std::string SupervisedUserWhitelistInstaller::ClientIdForProfilePath(
const base::FilePath& profile_path) {
// See ProfileInfoCache::CacheKeyFromProfilePath().
// TODO(anthonyvd): update comment when the refactoring of ProfileInfoCache
// is completed.
return profile_path.BaseName().MaybeAsASCII();
}
// static
std::vector<uint8_t> SupervisedUserWhitelistInstaller::GetHashFromCrxId(
const std::string& crx_id) {
DCHECK(crx_file::id_util::IdIsValid(crx_id));
std::vector<uint8_t> hash;
uint8_t byte = 0;
for (size_t i = 0; i < crx_id.size(); ++i) {
// Uppercase characters in IDs are technically legal.
int val = base::ToLowerASCII(crx_id[i]) - 'a';
DCHECK_GE(val, 0);
DCHECK_LT(val, 16);
if (i % 2 == 0) {
byte = val;
} else {
hash.push_back(16 * byte + val);
byte = 0;
}
}
return hash;
}
// static
void SupervisedUserWhitelistInstaller::TriggerComponentUpdate(
OnDemandUpdater* updater,
const std::string& crx_id) {
// TODO(sorin): use a callback to check the result (crbug.com/639189).
updater->OnDemandUpdate(crx_id, component_updater::Callback());
}
} // namespace component_updater