blob: 801fbc8934a41cec089b4f0da5b8a646839eadc1 [file] [log] [blame]
// Copyright 2021 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/ash/system_extensions/system_extensions_install_manager.h"
#include "ash/constants/ash_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/one_shot_event.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_piece.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/ash/system_extensions/system_extension.h"
#include "chrome/browser/ash/system_extensions/system_extensions_persistent_storage.h"
#include "chrome/browser/ash/system_extensions/system_extensions_profile_utils.h"
#include "chrome/browser/ash/system_extensions/system_extensions_registry_manager.h"
#include "chrome/browser/ash/system_extensions/system_extensions_service_worker_manager.h"
#include "chrome/browser/ash/system_extensions/system_extensions_webui_config.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/webui_config_map.h"
#include "content/public/common/url_constants.h"
#include "third_party/blink/public/mojom/chromeos/system_extensions/window_management/cros_window_management.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace ash {
SystemExtensionsInstallManager::SystemExtensionsInstallManager(
Profile* profile,
SystemExtensionsRegistryManager& registry_manager,
SystemExtensionsRegistry& registry,
SystemExtensionsServiceWorkerManager& service_worker_manager,
SystemExtensionsPersistentStorage& persistent_storage)
: profile_(profile),
service_worker_manager_(service_worker_manager),
registry_manager_(registry_manager),
registry_(registry),
persistent_storage_(persistent_storage) {
RegisterPreviouslyPersistedSystemExtensions();
InstallFromCommandLineIfNecessary();
}
SystemExtensionsInstallManager::~SystemExtensionsInstallManager() = default;
void SystemExtensionsInstallManager::
RegisterPreviouslyPersistedSystemExtensions() {
const std::vector<SystemExtensionPersistedInfo> persisted_infos =
persistent_storage_->GetAll();
for (const auto& persisted_info : persisted_infos) {
InstallStatusOrSystemExtension status_or_extension =
sandboxed_unpacker_.GetSystemExtensionFromValue(
persisted_info.manifest);
if (!status_or_extension.ok()) {
LOG(ERROR) << "Failed to register System Extension from Persistence "
<< "Manager.";
continue;
}
RegisterSystemExtension(std::move(status_or_extension).value());
}
on_register_previously_persisted_finished_.Signal();
}
void SystemExtensionsInstallManager::InstallUnpackedExtensionFromDir(
const base::FilePath& unpacked_system_extension_dir,
OnceInstallCallback final_callback) {
DCHECK(on_register_previously_persisted_finished_.is_signaled());
StartInstallation(std::move(final_callback), unpacked_system_extension_dir);
}
void SystemExtensionsInstallManager::InstallFromCommandLineIfNecessary() {
DCHECK(on_register_previously_persisted_finished_.is_signaled());
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(ash::switches::kInstallSystemExtension)) {
return;
}
base::FilePath system_extension_dir =
command_line->GetSwitchValuePath(ash::switches::kInstallSystemExtension);
StartInstallation(
base::BindOnce(
&SystemExtensionsInstallManager::OnInstallFromCommandLineFinished,
weak_ptr_factory_.GetWeakPtr()),
system_extension_dir);
}
void SystemExtensionsInstallManager::OnInstallFromCommandLineFinished(
InstallStatusOrSystemExtensionId result) {
if (!result.ok()) {
LOG(ERROR) << "Failed to install extension from command line: "
<< static_cast<int32_t>(result.status());
}
on_command_line_install_finished_.Signal();
}
void SystemExtensionsInstallManager::StartInstallation(
OnceInstallCallback final_callback,
const base::FilePath& unpacked_system_extension_dir) {
// Installation Step #1: Convert a manifest into a SystemExtension object.
sandboxed_unpacker_.GetSystemExtensionFromDir(
unpacked_system_extension_dir,
base::BindOnce(
&SystemExtensionsInstallManager::OnGetSystemExtensionFromDir,
weak_ptr_factory_.GetWeakPtr(), std::move(final_callback),
unpacked_system_extension_dir));
}
void SystemExtensionsInstallManager::OnGetSystemExtensionFromDir(
OnceInstallCallback final_callback,
const base::FilePath& unpacked_system_extension_dir,
InstallStatusOrSystemExtension result) {
if (!result.ok()) {
std::move(final_callback).Run(result.status());
return;
}
SystemExtensionId system_extension_id = result.value().id;
const base::FilePath dest_dir =
GetDirectoryForSystemExtension(*profile_, system_extension_id);
const base::FilePath system_extensions_dir =
GetSystemExtensionsProfileDir(*profile_);
// Installation Step #2: Copy the System Extensions assets to a profile
// directory.
io_helper_.AsyncCall(&IOHelper::CopyExtensionAssets)
.WithArgs(unpacked_system_extension_dir, dest_dir, system_extensions_dir)
.Then(base::BindOnce(
&SystemExtensionsInstallManager::OnAssetsCopiedToProfileDir,
weak_ptr_factory_.GetWeakPtr(), std::move(final_callback),
std::move(result).value()));
}
void SystemExtensionsInstallManager::OnAssetsCopiedToProfileDir(
OnceInstallCallback final_callback,
SystemExtension system_extension,
bool did_succeed) {
if (!did_succeed) {
std::move(final_callback)
.Run(SystemExtensionsInstallStatus::kFailedToCopyAssetsToProfileDir);
return;
}
// Installation Step #3: Persist the System Extension across restarts.
persistent_storage_->Add(system_extension);
SystemExtensionId id = system_extension.id;
RegisterSystemExtension(std::move(system_extension));
std::move(final_callback).Run(std::move(id));
}
void SystemExtensionsInstallManager::RegisterSystemExtension(
SystemExtension system_extension) {
// Installation Step #4: Create a WebUIConfig so that resources are served.
auto config = std::make_unique<SystemExtensionsWebUIConfig>(system_extension);
content::WebUIConfigMap::GetInstance().AddUntrustedWebUIConfig(
std::move(config));
// Installation Step #5: Add the System Extension to the registry.
SystemExtensionId id = system_extension.id;
registry_manager_->AddSystemExtension(std::move(system_extension));
// Installation Step #6: Register a Service Worker for the System Extension.
service_worker_manager_->RegisterServiceWorker(id);
}
void SystemExtensionsInstallManager::Uninstall(
const SystemExtensionId& system_extension_id) {
auto* system_extension = registry_->GetById(system_extension_id);
if (!system_extension) {
return;
}
const url::Origin& origin = url::Origin::Create(system_extension->base_url);
// Uninstallation Step #1: Unregister the Service Worker.
service_worker_manager_->UnregisterServiceWorker(system_extension_id);
// Uninstallation Step #2: Remove the WebUIConfig for the System Extension.
content::WebUIConfigMap::GetInstance().RemoveConfig(origin);
// Installation Step #3: Remove the System Extension from persistent storage.
persistent_storage_->Remove(system_extension_id);
// Uninstallation Step #4: Remove System Extension from the registry.
registry_manager_->RemoveSystemExtension(system_extension_id);
// Uninstallation Step #5: Delete the System Extension assets.
io_helper_.AsyncCall(&IOHelper::RemoveExtensionAssets)
.WithArgs(GetDirectoryForSystemExtension(*profile_, system_extension_id))
.Then(base::BindOnce(&SystemExtensionsInstallManager::NotifyAssetsRemoved,
weak_ptr_factory_.GetWeakPtr(),
system_extension_id));
}
void SystemExtensionsInstallManager::NotifyAssetsRemoved(
const SystemExtensionId& system_extension_id,
bool succeeded) {
if (!succeeded)
LOG(ERROR) << "Failed to remove System Extension assets.";
for (auto& observer : observers_)
observer.OnSystemExtensionAssetsDeleted(system_extension_id, succeeded);
}
bool SystemExtensionsInstallManager::IOHelper::CopyExtensionAssets(
const base::FilePath& unpacked_extension_dir,
const base::FilePath& dest_dir,
const base::FilePath& system_extensions_dir) {
// TODO(crbug.com/1267802): Perform more checks when moving files or share
// code with Extensions.
// Create the System Extensions directory if it doesn't exist already e.g.
// `/{profile_path}/System Extensions/`
if (!base::PathExists(system_extensions_dir)) {
if (!base::CreateDirectory(system_extensions_dir)) {
LOG(ERROR) << "Failed to create the System Extensions dir.";
return false;
}
}
// Delete existing System Extension directory if necessary.
if (!base::DeletePathRecursively(dest_dir)) {
LOG(ERROR) << "Target System Extension dir already exists and couldn't be"
<< " deleted.";
return false;
}
// Copy assets to their destination System Extensions directory e.g.
// `/{profile_path}/System Extensions/{system_extension_id}/`
if (!base::CopyDirectory(unpacked_extension_dir, dest_dir,
/*recursive=*/true)) {
LOG(ERROR) << "Failed to copy System Extension assets.";
return false;
}
return true;
}
bool SystemExtensionsInstallManager::IOHelper::RemoveExtensionAssets(
const base::FilePath& system_extension_dir) {
if (!base::DeletePathRecursively(system_extension_dir)) {
LOG(ERROR) << "Failed to delete System Extension assets.";
return false;
}
return true;
}
} // namespace ash