blob: d655542a21fdfab84a781bb9927df3cf55a9ed5e [file] [log] [blame]
// Copyright 2017 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/ui/webui/chromeos/login/encryption_migration_screen_handler.h"
#include <string>
#include <utility>
#include "ash/system/devicetype_utils.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/sys_info.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/chromeos_switches.h"
#include "chromeos/cryptohome/async_method_caller.h"
#include "chromeos/cryptohome/homedir_methods.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"
#include "chromeos/dbus/power_manager_client.h"
#include "components/login/localized_values_builder.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "device/power_save_blocker/power_save_blocker.h"
#include "ui/base/text/bytes_formatting.h"
namespace {
constexpr char kJsScreenPath[] = "login.EncryptionMigrationScreen";
// Path to the mount point to check the available space.
constexpr char kCheckStoragePath[] = "/home";
// The minimum size of available space to start the migration.
constexpr int64_t kMinimumAvailableStorage = 10LL * 1024 * 1024; // 10MB
// The minimum battery level to start the migration.
constexpr double kMinimumBatteryPercent = 30;
// JS API callbacks names.
constexpr char kJsApiStartMigration[] = "startMigration";
constexpr char kJsApiSkipMigration[] = "skipMigration";
constexpr char kJsApiRequestRestart[] = "requestRestart";
bool IsTestingUI() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kTestEncryptionMigrationUI);
}
} // namespace
namespace chromeos {
EncryptionMigrationScreenHandler::EncryptionMigrationScreenHandler()
: BaseScreenHandler(kScreenId), weak_ptr_factory_(this) {
set_call_js_prefix(kJsScreenPath);
}
EncryptionMigrationScreenHandler::~EncryptionMigrationScreenHandler() {
DBusThreadManager::Get()->GetPowerManagerClient()->RemoveObserver(this);
if (delegate_)
delegate_->OnViewDestroyed(this);
}
void EncryptionMigrationScreenHandler::Show() {
if (!page_is_ready() || !delegate_) {
show_on_init_ = true;
return;
}
ShowScreen(kScreenId);
}
void EncryptionMigrationScreenHandler::Hide() {
show_on_init_ = false;
}
void EncryptionMigrationScreenHandler::SetDelegate(Delegate* delegate) {
delegate_ = delegate;
if (page_is_ready())
Initialize();
}
void EncryptionMigrationScreenHandler::SetUserContext(
const UserContext& user_context) {
user_context_ = user_context;
}
void EncryptionMigrationScreenHandler::SetShouldResume(bool should_resume) {
should_resume_ = should_resume;
CallJS("setIsResuming", should_resume_);
}
void EncryptionMigrationScreenHandler::SetContinueLoginCallback(
ContinueLoginCallback callback) {
continue_login_callback_ = std::move(callback);
}
void EncryptionMigrationScreenHandler::SetupInitialView() {
CheckAvailableStorage();
}
void EncryptionMigrationScreenHandler::DeclareLocalizedValues(
::login::LocalizedValuesBuilder* builder) {
builder->Add("migrationReadyTitle", IDS_ENCRYPTION_MIGRATION_READY_TITLE);
builder->Add("migrationReadyDescription",
ash::SubstituteChromeOSDeviceType(
IDS_ENCRYPTION_MIGRATION_READY_DESCRIPTION));
builder->Add("migrationMigratingTitle",
IDS_ENCRYPTION_MIGRATION_MIGRATING_TITLE);
builder->Add("migrationMigratingDescription",
ash::SubstituteChromeOSDeviceType(
IDS_ENCRYPTION_MIGRATION_MIGRATING_DESCRIPTION));
builder->Add("migrationProgressLabel",
IDS_ENCRYPTION_MIGRATION_PROGRESS_LABEL);
builder->Add("migrationBatteryWarningLabel",
IDS_ENCRYPTION_MIGRATION_BATTERY_WARNING_LABEL);
builder->Add("migrationAskChargeMessage",
ash::SubstituteChromeOSDeviceType(
IDS_ENCRYPTION_MIGRATION_ASK_CHARGE_MESSAGE));
builder->Add("migrationNecessaryBatteryLevelLabel",
IDS_ENCRYPTION_MIGRATION_NECESSARY_BATTERY_LEVEL_MESSAGE);
builder->Add("migrationChargingLabel",
IDS_ENCRYPTION_MIGRATION_CHARGING_LABEL);
builder->Add("migrationFailedTitle", IDS_ENCRYPTION_MIGRATION_FAILED_TITLE);
builder->Add("migrationFailedSubtitle",
IDS_ENCRYPTION_MIGRATION_FAILED_SUBTITLE);
builder->Add("migrationFailedMessage",
ash::SubstituteChromeOSDeviceType(
IDS_ENCRYPTION_MIGRATION_FAILED_MESSAGE));
builder->Add("migrationNospaceWarningLabel",
IDS_ENCRYPTION_MIGRATION_NOSPACE_WARNING_LABEL);
builder->Add("migrationAskFreeSpaceMessage",
IDS_ENCRYPTION_MIGRATION_ASK_FREE_SPACE_MESSAGE);
builder->Add("migrationAvailableSpaceLabel",
IDS_ENCRYPTION_MIGRATION_AVAILABLE_SPACE_LABEL);
builder->Add("migrationNecessarySpaceLabel",
IDS_ENCRYPTION_MIGRATION_NECESSARY_SPACE_LABEL);
builder->Add("migrationButtonUpdate", IDS_ENCRYPTION_MIGRATION_BUTTON_UPDATE);
builder->Add("migrationButtonSkip", IDS_ENCRYPTION_MIGRATION_BUTTON_SKIP);
builder->Add("migrationButtonRestart",
IDS_ENCRYPTION_MIGRATION_BUTTON_RESTART);
builder->Add("migrationButtonContinue",
IDS_ENCRYPTION_MIGRATION_BUTTON_CONTINUE);
builder->Add("migrationButtonSignIn", IDS_ENCRYPTION_MIGRATION_BUTTON_SIGNIN);
}
void EncryptionMigrationScreenHandler::Initialize() {
if (!page_is_ready() || !delegate_)
return;
DBusThreadManager::Get()->GetPowerManagerClient()->AddObserver(this);
if (show_on_init_) {
Show();
show_on_init_ = false;
}
}
void EncryptionMigrationScreenHandler::RegisterMessages() {
AddCallback(kJsApiStartMigration,
&EncryptionMigrationScreenHandler::HandleStartMigration);
AddCallback(kJsApiSkipMigration,
&EncryptionMigrationScreenHandler::HandleSkipMigration);
AddCallback(kJsApiRequestRestart,
&EncryptionMigrationScreenHandler::HandleRequestRestart);
}
void EncryptionMigrationScreenHandler::PowerChanged(
const power_manager::PowerSupplyProperties& proto) {
current_battery_percent_ = proto.battery_percent();
CallJS("setBatteryState", current_battery_percent_,
current_battery_percent_ >= kMinimumBatteryPercent,
proto.battery_state() ==
power_manager::PowerSupplyProperties_BatteryState_CHARGING);
// If the migration was already requested and the bettery level is enough now,
// The migration should start immediately.
if (current_battery_percent_ >= kMinimumBatteryPercent &&
should_migrate_on_enough_battery_) {
should_migrate_on_enough_battery_ = false;
StartMigration();
}
}
void EncryptionMigrationScreenHandler::HandleStartMigration() {
WaitBatteryAndMigrate();
}
void EncryptionMigrationScreenHandler::HandleSkipMigration() {
// If the user skips migration, we mount the cryptohome without performing the
// migration by reusing UserContext and LoginPerformer which were used in the
// previous attempt and dropping |is_forcing_dircrypto| flag in UserContext.
// In this case, the user can not launch ARC apps in the session, and will be
// asked to do the migration again in the next log-in attempt.
if (!continue_login_callback_.is_null()) {
user_context_.SetIsForcingDircrypto(false);
std::move(continue_login_callback_).Run(user_context_);
}
}
void EncryptionMigrationScreenHandler::HandleRequestRestart() {
DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
}
void EncryptionMigrationScreenHandler::UpdateUIState(UIState state) {
if (state == current_ui_state_)
return;
current_ui_state_ = state;
CallJS("setUIState", static_cast<int>(state));
// When this handler is about to show the READY screen, we should get the
// latest battery status and show it on the screen.
if (state == UIState::READY)
DBusThreadManager::Get()->GetPowerManagerClient()->RequestStatusUpdate();
// We should block power save during migration.
if (state == UIState::MIGRATING)
StartBlockingPowerSave();
else
StopBlockingPowerSave();
}
void EncryptionMigrationScreenHandler::CheckAvailableStorage() {
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
base::TaskTraits().MayBlock().WithPriority(
base::TaskPriority::USER_VISIBLE),
base::Bind(&base::SysInfo::AmountOfFreeDiskSpace,
base::FilePath(kCheckStoragePath)),
base::Bind(&EncryptionMigrationScreenHandler::OnGetAvailableStorage,
weak_ptr_factory_.GetWeakPtr()));
}
void EncryptionMigrationScreenHandler::OnGetAvailableStorage(int64_t size) {
if (size >= kMinimumAvailableStorage || IsTestingUI()) {
if (should_resume_) {
WaitBatteryAndMigrate();
} else {
UpdateUIState(UIState::READY);
}
} else {
CallJS("setAvailableSpaceInString", ui::FormatBytes(size));
CallJS("setNecessarySpaceInString",
ui::FormatBytes(kMinimumAvailableStorage));
UpdateUIState(UIState::NOT_ENOUGH_STORAGE);
}
}
void EncryptionMigrationScreenHandler::WaitBatteryAndMigrate() {
if (current_battery_percent_ >= kMinimumBatteryPercent) {
StartMigration();
return;
}
UpdateUIState(UIState::READY);
should_migrate_on_enough_battery_ = true;
DBusThreadManager::Get()->GetPowerManagerClient()->RequestStatusUpdate();
}
void EncryptionMigrationScreenHandler::StartMigration() {
UpdateUIState(UIState::MIGRATING);
// Mount the existing eCryptfs vault to a temporary location for migration.
cryptohome::MountParameters mount(false);
mount.to_migrate_from_ecryptfs = true;
cryptohome::HomedirMethods::GetInstance()->MountEx(
cryptohome::Identification(user_context_.GetAccountId()),
cryptohome::Authorization(GetAuthKey()), mount,
base::Bind(&EncryptionMigrationScreenHandler::OnMountExistingVault,
weak_ptr_factory_.GetWeakPtr()));
}
void EncryptionMigrationScreenHandler::OnMountExistingVault(
bool success,
cryptohome::MountError return_code,
const std::string& mount_hash) {
if (!success || return_code != cryptohome::MOUNT_ERROR_NONE) {
UpdateUIState(UIState::MIGRATION_FAILED);
return;
}
DBusThreadManager::Get()
->GetCryptohomeClient()
->SetDircryptoMigrationProgressHandler(
base::Bind(&EncryptionMigrationScreenHandler::OnMigrationProgress,
weak_ptr_factory_.GetWeakPtr()));
cryptohome::HomedirMethods::GetInstance()->MigrateToDircrypto(
cryptohome::Identification(user_context_.GetAccountId()),
base::Bind(&EncryptionMigrationScreenHandler::OnMigrationRequested,
weak_ptr_factory_.GetWeakPtr()));
}
void EncryptionMigrationScreenHandler::StartBlockingPowerSave() {
if (!power_save_blocker_) {
power_save_blocker_.reset(new device::PowerSaveBlocker(
device::PowerSaveBlocker::kPowerSaveBlockPreventAppSuspension,
device::PowerSaveBlocker::kReasonOther,
"Encryption migration is in progress...",
content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::UI),
content::BrowserThread::GetTaskRunnerForThread(
content::BrowserThread::FILE)));
}
}
void EncryptionMigrationScreenHandler::StopBlockingPowerSave() {
if (power_save_blocker_.get()) {
power_save_blocker_.reset();
}
}
void EncryptionMigrationScreenHandler::RemoveCryptohome() {
// Set invalid token status so that user is forced to go through Gaia on the
// next sign-in.
user_manager::UserManager::Get()->SaveUserOAuthStatus(
user_context_.GetAccountId(),
user_manager::User::OAUTH2_TOKEN_STATUS_INVALID);
cryptohome::AsyncMethodCaller::GetInstance()->AsyncRemove(
cryptohome::Identification(user_context_.GetAccountId()),
base::Bind(&EncryptionMigrationScreenHandler::OnRemoveCryptohome,
weak_ptr_factory_.GetWeakPtr()));
}
void EncryptionMigrationScreenHandler::OnRemoveCryptohome(
bool success,
cryptohome::MountError return_code) {
LOG_IF(ERROR, !success) << "Removing cryptohome failed. return code: "
<< return_code;
UpdateUIState(UIState::MIGRATION_FAILED);
}
cryptohome::KeyDefinition EncryptionMigrationScreenHandler::GetAuthKey() {
// |auth_key| is created in the same manner as CryptohomeAuthenticator.
const Key* key = user_context_.GetKey();
// If the |key| is a plain text password, crash rather than attempting to
// mount the cryptohome with a plain text password.
CHECK_NE(Key::KEY_TYPE_PASSWORD_PLAIN, key->GetKeyType());
// Set the authentication's key label to an empty string, which is a wildcard
// allowing any key to match. This is necessary because cryptohomes created by
// Chrome OS M38 and older will have a legacy key with no label while those
// created by Chrome OS M39 and newer will have a key with the label
// kCryptohomeGAIAKeyLabel.
return cryptohome::KeyDefinition(key->GetSecret(), std::string(),
cryptohome::PRIV_DEFAULT);
}
void EncryptionMigrationScreenHandler::OnMigrationProgress(
cryptohome::DircryptoMigrationStatus status,
uint64_t current,
uint64_t total) {
switch (status) {
case cryptohome::DIRCRYPTO_MIGRATION_INITIALIZING:
UpdateUIState(UIState::MIGRATING);
break;
case cryptohome::DIRCRYPTO_MIGRATION_IN_PROGRESS:
UpdateUIState(UIState::MIGRATING);
CallJS("setMigrationProgress", static_cast<double>(current) / total);
break;
case cryptohome::DIRCRYPTO_MIGRATION_SUCCESS:
// Restart immediately after successful migration.
DBusThreadManager::Get()->GetPowerManagerClient()->RequestRestart();
break;
case cryptohome::DIRCRYPTO_MIGRATION_FAILED:
// Stop listening to the progress updates.
DBusThreadManager::Get()
->GetCryptohomeClient()
->SetDircryptoMigrationProgressHandler(
CryptohomeClient::DircryptoMigrationProgessHandler());
// Shows error screen after removing user directory is completed.
RemoveCryptohome();
break;
default:
break;
}
}
void EncryptionMigrationScreenHandler::OnMigrationRequested(bool success) {
LOG_IF(ERROR, !success) << "Requesting MigrateToDircrypto failed.";
UpdateUIState(UIState::MIGRATION_FAILED);
}
} // namespace chromeos