// 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/crosapi/browser_data_migrator.h"
#include <string>
#include "ash/constants/ash_features.h"
#include "ash/constants/ash_switches.h"
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/test/bind.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/app_mode/kiosk_controller.h"
#include "chrome/browser/ash/crosapi/browser_util.h"
#include "chrome/browser/ash/crosapi/move_migrator.h"
#include "chrome/browser/ash/login/app_mode/test/kiosk_base_test.h"
#include "chrome/browser/ash/login/existing_user_controller.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/local_state_mixin.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/profile_prepared_waiter.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "chromeos/ash/components/standalone_browser/lacros_availability.h"
#include "chromeos/ash/components/standalone_browser/migrator_util.h"
#include "chromeos/ash/components/standalone_browser/standalone_browser_features.h"
#include "components/account_id/account_id.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/user_manager/fake_user_manager.h"
#include "components/user_manager/user_manager_pref_names.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_launcher.h"
namespace ash {
namespace {
const char kUserEmail[] = "[email protected]";
const char kGaiaID[] = "22222";
constexpr char kUserIdHash[] = "abcdefg";
// This creates <profile directory>/Preferences file for the account so that
// when `Profile` instance is created, it is considered a profile for an
// existing user. This is to avoid profile migration being marked as completed
// for a new user.
bool CreatePreferenceFileForProfile(const AccountId& account_id) {
base::FilePath user_data_dir;
base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir);
const base::FilePath profile_data_dir =
ProfileHelper::GetProfilePathByUserIdHash(
user_manager::FakeUserManager::GetFakeUsernameHash(account_id));
{
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
if (!(base::CreateDirectory(user_data_dir) &&
base::CreateDirectory(profile_data_dir) &&
base::WriteFile(profile_data_dir.Append("Preferences"), "{}"))) {
LOG(ERROR) << "Creating `Preferences` file failed.";
return false;
}
}
return true;
}
void SetLacrosAvailability(
ash::standalone_browser::LacrosAvailability lacros_availability) {
policy::PolicyMap policy;
policy.Set(policy::key::kLacrosAvailability, policy::POLICY_LEVEL_MANDATORY,
policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
base::Value(GetLacrosAvailabilityPolicyName(lacros_availability)),
/*external_data_fetcher=*/nullptr);
crosapi::browser_util::CacheLacrosAvailability(policy);
}
} // namespace
// Used to test whether migration gets triggered during the signin flow.
// Concretely it tests `MaybeRestartToMigrate()` called from
// `UserSessionManager::DoBrowserLaunchInternal()` and
// `MaybeForceResumeMoveMigration()` called from
// `ExistingUserController::OnAuthSuccess()`.
class BrowserDataMigratorOnSignIn : public ash::LoginManagerTest {
public:
BrowserDataMigratorOnSignIn() = default;
BrowserDataMigratorOnSignIn(BrowserDataMigratorOnSignIn&) = delete;
BrowserDataMigratorOnSignIn& operator=(BrowserDataMigratorOnSignIn&) = delete;
~BrowserDataMigratorOnSignIn() override = default;
const LoginManagerMixin::TestUserInfo regular_user_{
AccountId::FromUserEmailGaiaId(kUserEmail, kGaiaID)};
bool LoginAsExistingRegularUser() {
return CreatePreferenceFileForProfile(regular_user_.account_id) &&
LoginAsRegularUser();
}
bool LoginAsRegularUser() {
ExistingUserController* controller =
ExistingUserController::current_controller();
if (!controller) {
return false;
}
const UserContext user_context =
CreateUserContext(regular_user_.account_id, kPassword);
SetExpectedCredentials(user_context);
controller->Login(user_context, SigninSpecifics());
return true;
}
void SetUpInProcessBrowserTestFixture() override {
SessionManagerClient::InitializeFakeInMemory();
}
protected:
LoginManagerMixin login_manager_mixin_{&mixin_host_, {regular_user_}};
};
class BrowserDataMigratorMoveMigrateOnSignInByPolicy
: public BrowserDataMigratorOnSignIn {
public:
BrowserDataMigratorMoveMigrateOnSignInByPolicy() = default;
BrowserDataMigratorMoveMigrateOnSignInByPolicy(
BrowserDataMigratorMoveMigrateOnSignInByPolicy&) = delete;
BrowserDataMigratorMoveMigrateOnSignInByPolicy& operator=(
BrowserDataMigratorMoveMigrateOnSignInByPolicy&) = delete;
~BrowserDataMigratorMoveMigrateOnSignInByPolicy() override = default;
};
// Enabling LacrosOnly by policy should trigger move migration during signin.
// Disabled since Lacros can no longer be enabled via policy.
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorMoveMigrateOnSignInByPolicy,
DISABLED_MigrateOnSignIn) {
base::test::TestFuture<void> future;
ScopedRestartAttemptForTesting scoped_restart_attempt(
future.GetRepeatingCallback());
SetLacrosAvailability(
ash::standalone_browser::LacrosAvailability::kLacrosOnly);
ASSERT_TRUE(LoginAsExistingRegularUser());
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
class BrowserDataMigratorMoveMigrateOnSignInByFeature
: public BrowserDataMigratorOnSignIn {
public:
BrowserDataMigratorMoveMigrateOnSignInByFeature() = default;
BrowserDataMigratorMoveMigrateOnSignInByFeature(
BrowserDataMigratorMoveMigrateOnSignInByFeature&) = delete;
BrowserDataMigratorMoveMigrateOnSignInByFeature& operator=(
BrowserDataMigratorMoveMigrateOnSignInByFeature&) = delete;
~BrowserDataMigratorMoveMigrateOnSignInByFeature() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
ash::switches::kEnableLacrosForTesting);
BrowserDataMigratorOnSignIn::SetUp();
}
};
// Enabling LacrosOnly with feature flags should trigger move migration during
// signin.
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorMoveMigrateOnSignInByFeature,
MigrateOnSignIn) {
base::test::TestFuture<void> future;
ScopedRestartAttemptForTesting scoped_restart_attempt(
future.GetRepeatingCallback());
ASSERT_TRUE(LoginAsExistingRegularUser());
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
// Check that migration marked as completed for a new user and thus migration is
// not triggered from signin flow.
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorMoveMigrateOnSignInByFeature,
SkipMigrateOnSignInForNewUser) {
ash::test::ProfilePreparedWaiter profile_prepared(regular_user_.account_id);
ASSERT_TRUE(LoginAsRegularUser());
// Note that `ProfilePreparedWaiter` waits for
// `ExistingUserController::OnProfilePrepared()` to be called and this is
// called after `UserSessionManager::InitializeUserSession()` is called, which
// leads to `BrowserDataMigratorImpl::MaybeRestartToMigrate()`. Therefore by
// the time the wait ends, migration check would have happened.
profile_prepared.Wait();
EXPECT_FALSE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
const std::string user_id_hash =
user_manager::FakeUserManager::GetFakeUsernameHash(
regular_user_.account_id);
EXPECT_TRUE(ash::standalone_browser::migrator_util::
IsProfileMigrationCompletedForUser(
g_browser_process->local_state(), user_id_hash));
EXPECT_EQ(
ash::standalone_browser::migrator_util::GetCompletedMigrationMode(
g_browser_process->local_state(), user_id_hash),
ash::standalone_browser::migrator_util::MigrationMode::kSkipForNewUser);
}
class BrowserDataMigratorResumeOnSignIn : public BrowserDataMigratorOnSignIn,
public LocalStateMixin::Delegate {
public:
BrowserDataMigratorResumeOnSignIn() = default;
BrowserDataMigratorResumeOnSignIn(BrowserDataMigratorResumeOnSignIn&) =
delete;
BrowserDataMigratorResumeOnSignIn& operator=(
BrowserDataMigratorResumeOnSignIn&) = delete;
~BrowserDataMigratorResumeOnSignIn() override = default;
// LocalStateMixin::Delegate:
void SetUpLocalState() override {
const auto& user = login_manager_mixin_.users()[0];
const std::string user_id_hash =
user_manager::FakeUserManager::GetFakeUsernameHash(user.account_id);
// Setting this pref triggers a restart to resume move migration. Check
// `BrowserDataMigratorImpl::MaybeForceResumeMoveMigration()`.
MoveMigrator::SetResumeStep(g_browser_process->local_state(), user_id_hash,
MoveMigrator::ResumeStep::kMoveLacrosItems);
}
private:
LocalStateMixin local_state_mixin_{&mixin_host_, this};
};
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorResumeOnSignIn, ForceResumeOnLogin) {
// Test `MaybeForceResumeMoveMigration()` in
// `ExistingUserController::OnAuthSuccess()`.
base::test::TestFuture<void> future;
ScopedRestartAttemptForTesting scoped_restart_attempt(
future.GetRepeatingCallback());
ASSERT_TRUE(LoginAsExistingRegularUser());
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
// Used to test whether migration gets triggered upon restart in session.
// Concretely it tests `MaybeRestartToMigrate()` or
// `MaybeForceResumeMoveMigration()` called from
// `ChromeBrowserMainPartsAsh::PreProfileInit()`. Since `PreProfileInit()` gets
// called before the body of tests are run, all the setups have to happen in
// early setup stages like `SetUp()` or `SetUpCommandLine()`.
class BrowserDataMigratorRestartInSession
: public MixinBasedInProcessBrowserTest,
public LocalStateMixin::Delegate {
public:
BrowserDataMigratorRestartInSession()
: scoped_attempt_restart_(
std::make_unique<ScopedRestartAttemptForTesting>(
base::DoNothing())) {}
BrowserDataMigratorRestartInSession(BrowserDataMigratorRestartInSession&) =
delete;
BrowserDataMigratorRestartInSession& operator=(
BrowserDataMigratorRestartInSession&) = delete;
~BrowserDataMigratorRestartInSession() override = default;
// LocalStateMixin::Delegate
void SetUpLocalState() override {
// Add `kUserIdHash`@gmail.com to kRegularUsersPref so that
// `UserManager::FindUser()` is able to find this user in
// `BrowserDataMigrator::MaybeRestartToMigrate()` and
// `BrowserDataMigrator::RestartToMigrate()`.
base::Value::List users;
users.Append(base::Value(std::string(kUserIdHash) + "@gmail.com"));
g_browser_process->local_state()->SetList(
user_manager::prefs::kRegularUsersPref, std::move(users));
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// By setting these flags, Ash is launched as if it's restarted inside a
// user session.
command_line->AppendSwitchASCII(switches::kLoginUser, kUserIdHash);
command_line->AppendSwitchASCII(switches::kLoginProfile, kUserIdHash);
MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line);
}
void SetUpInProcessBrowserTestFixture() override {
SessionManagerClient::InitializeFakeInMemory();
}
protected:
// Since triggering migration means calling `chrome::AttemptRestart()`, it has
// to be substituted.
std::unique_ptr<ScopedRestartAttemptForTesting> scoped_attempt_restart_;
LocalStateMixin local_state_mixin_{&mixin_host_, this};
};
class BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature
: public BrowserDataMigratorRestartInSession {
public:
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature() = default;
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature(
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature&) = delete;
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature& operator=(
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature&) = delete;
~BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature() override =
default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
ash::switches::kEnableLacrosForTesting);
BrowserDataMigratorRestartInSession::SetUp();
}
};
// Test that enabling LacrosOnly by feature flags triggers move migration during
// restart.
IN_PROC_BROWSER_TEST_F(
BrowserDataMigratorMoveMigrateOnRestartInSessionByFeature,
RunMoveMigration) {
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
class BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy
: public BrowserDataMigratorRestartInSession {
public:
BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy() = default;
BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy(
BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy&) = delete;
BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy& operator=(
BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy&) = delete;
~BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy() override =
default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(
ash::standalone_browser::kLacrosAvailabilityPolicySwitch,
ash::standalone_browser::kLacrosAvailabilityPolicyLacrosOnly);
BrowserDataMigratorRestartInSession::SetUpCommandLine(command_line);
}
};
// Test that enabling LacrosOnly by policy triggers move migration during
// restart.
// Disabled since Lacros can no longer be enabled via policy.
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorMoveMigrateOnRestartInSessionByPolicy,
DISABLED_RunMoveMigration) {
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
class BrowserDataMigratorResumeRestartInSession
: public BrowserDataMigratorRestartInSession {
public:
BrowserDataMigratorResumeRestartInSession() = default;
BrowserDataMigratorResumeRestartInSession(
BrowserDataMigratorResumeRestartInSession&) = delete;
BrowserDataMigratorResumeRestartInSession& operator=(
BrowserDataMigratorResumeRestartInSession&) = delete;
~BrowserDataMigratorResumeRestartInSession() override = default;
// LocalStateMixin::Delegate
void SetUpLocalState() override {
// Setting this pref triggers a restart to resume move migration. Check
// `BrowserDataMigratorImpl::MaybeForceResumeMoveMigration()`.
MoveMigrator::SetResumeStep(g_browser_process->local_state(), kUserIdHash,
MoveMigrator::ResumeStep::kMoveLacrosItems);
BrowserDataMigratorRestartInSession::SetUpLocalState();
}
};
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorResumeRestartInSession,
ResumeMigration) {
// Test `MaybeForceResumeMoveMigration()` in
// `ChromeBrowserMainPartsAsh::PreProfileInit()`.
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
EXPECT_TRUE(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_called());
EXPECT_EQ(FakeSessionManagerClient::Get()
->request_browser_data_migration_mode_value(),
"move");
}
class BrowserDataMigratorForKiosk : public KioskBaseTest {
public:
BrowserDataMigratorForKiosk() = default;
BrowserDataMigratorForKiosk(BrowserDataMigratorForKiosk&) = delete;
BrowserDataMigratorForKiosk& operator=(BrowserDataMigratorForKiosk&) = delete;
~BrowserDataMigratorForKiosk() override = default;
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitch(
ash::switches::kEnableLacrosForTesting);
KioskBaseTest::SetUp();
}
};
IN_PROC_BROWSER_TEST_F(BrowserDataMigratorForKiosk, MigrateOnKioskLaunch) {
SetLacrosAvailability(
ash::standalone_browser::LacrosAvailability::kUserChoice);
// Register app in `KioskController` so its `AccountId` can be retrieved.
PrepareAppLaunch();
CreatePreferenceFileForProfile(test_kiosk_app().id().account_id);
base::test::TestFuture<void> future;
ScopedRestartAttemptForTesting scoped_restart_attempt(
future.GetRepeatingCallback());
StartAppLaunchFromLoginScreen(NetworkStatus::kOnline);
EXPECT_TRUE(future.Wait());
EXPECT_TRUE(
FakeSessionManagerClient::Get()->request_browser_data_migration_called());
}
} // namespace ash