diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc index 9ec534e06ea78c..16117c26847121 100644 --- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc +++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc @@ -215,7 +215,6 @@ void ChromeOSVersionCallback(const std::string& version) { bool ShouldAutoLaunchKioskApp(const base::CommandLine& command_line) { KioskAppManager* app_manager = KioskAppManager::Get(); return command_line.HasSwitch(switches::kLoginManager) && - !command_line.HasSwitch(switches::kForceLoginManagerInTests) && app_manager->IsAutoLaunchEnabled() && KioskAppLaunchError::Get() == KioskAppLaunchError::NONE; } diff --git a/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc b/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc index f0766d984bf7e1..5d65bba3fd2c28 100644 --- a/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc +++ b/chrome/browser/chromeos/login/auto_launched_kiosk_browsertest.cc @@ -21,6 +21,9 @@ #include "chrome/browser/chromeos/app_mode/kiosk_app_launch_error.h" #include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" #include "chrome/browser/chromeos/login/app_launch_controller.h" +#include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h" +#include "chrome/browser/chromeos/login/test/embedded_test_server_mixin.h" +#include "chrome/browser/chromeos/login/test/login_manager_mixin.h" #include "chrome/browser/chromeos/ownership/owner_settings_service_chromeos_factory.h" #include "chrome/browser/chromeos/policy/device_local_account.h" #include "chrome/browser/chromeos/policy/device_policy_builder.h" @@ -31,11 +34,11 @@ #include "chrome/common/chrome_constants.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/pref_names.h" -#include "chromeos/dbus/cryptohome/cryptohome_client.h" #include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/session_manager/fake_session_manager_client.h" #include "chromeos/dbus/shill/shill_manager_client.h" #include "chromeos/tpm/stub_install_attributes.h" +#include "components/crx_file/crx_verifier.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" @@ -47,7 +50,6 @@ #include "extensions/test/extension_test_message_listener.h" #include "extensions/test/result_catcher.h" #include "net/dns/mock_host_resolver.h" -#include "third_party/cros_system_api/switches/chrome_switches.h" namespace em = enterprise_management; @@ -93,168 +95,6 @@ constexpr char kTestManagementApiSecondaryApp[] = constexpr char kTestAccountId[] = "enterprise-kiosk-app@localhost"; -constexpr char kSessionManagerStateCache[] = "test_session_manager_state.json"; - -// Keys for values in dictionary used to preserve session manager state. -constexpr char kLoginArgsKey[] = "login_args"; -constexpr char kExtraArgsKey[] = "extra_args"; -constexpr char kArgNameKey[] = "name"; -constexpr char kArgValueKey[] = "value"; - -// Default set policy switches. -constexpr struct { - const char* name; - const char* value; -} kDefaultPolicySwitches[] = {{"test_switch_1", ""}, - {"test_switch_2", "test_switch_2_value"}}; - -// Fake session manager implementation that persists its state in local file. -// It can be used to preserve session state in PRE_ browser tests. -// Primarily used for testing user/login switches. -class PersistentSessionManagerClient : public FakeSessionManagerClient { - public: - PersistentSessionManagerClient() {} - - ~PersistentSessionManagerClient() override { - PersistFlagsToFile(backing_file_); - } - - // Initializes session state (primarily session flags)- if |backing_file| - // exists, the session state is restored from the file value. Otherwise it's - // set to the default session state. - void Initialize(const base::FilePath& backing_file) { - backing_file_ = backing_file; - - if (ExtractFlagsFromFile(backing_file_)) - return; - - // Failed to extract ached flags - set the default values. - login_args_ = {{"login-manager", ""}}; - - extra_args_ = {{switches::kPolicySwitchesBegin, ""}}; - for (size_t i = 0; i < base::size(kDefaultPolicySwitches); ++i) { - extra_args_.push_back( - {kDefaultPolicySwitches[i].name, kDefaultPolicySwitches[i].value}); - } - extra_args_.push_back({switches::kPolicySwitchesEnd, ""}); - } - - void AppendSwitchesToCommandLine(base::CommandLine* command_line) { - for (const auto& flag : login_args_) - command_line->AppendSwitchASCII(flag.name, flag.value); - for (const auto& flag : extra_args_) - command_line->AppendSwitchASCII(flag.name, flag.value); - } - - void StartSession( - const cryptohome::AccountIdentifier& cryptohome_id) override { - FakeSessionManagerClient::StartSession(cryptohome_id); - - std::string user_id_hash = - CryptohomeClient::GetStubSanitizedUsername(cryptohome_id); - login_args_ = {{"login-user", cryptohome_id.account_id()}, - {"login-profile", user_id_hash}}; - } - - void StopSession() override { - FakeSessionManagerClient::StopSession(); - - login_args_ = {{"login-manager", ""}}; - } - - bool SupportsRestartToApplyUserFlags() const override { return true; } - - void SetFlagsForUser(const cryptohome::AccountIdentifier& identification, - const std::vector& flags) override { - extra_args_.clear(); - FakeSessionManagerClient::SetFlagsForUser(identification, flags); - - std::vector argv = {"" /* Empty program */}; - argv.insert(argv.end(), flags.begin(), flags.end()); - - // Parse flag name-value pairs using command line initialization. - base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); - cmd_line.InitFromArgv(argv); - - for (const auto& flag : cmd_line.GetSwitches()) - extra_args_.push_back({flag.first, flag.second}); - } - - private: - // Keeps information about a switch - its name and value. - struct Switch { - std::string name; - std::string value; - }; - - bool ExtractFlagsFromFile(const base::FilePath& backing_file) { - JSONFileValueDeserializer deserializer(backing_file); - - int error_code = 0; - std::unique_ptr value = - deserializer.Deserialize(&error_code, nullptr); - if (error_code != JSONFileValueDeserializer::JSON_NO_ERROR) - return false; - - std::unique_ptr value_dict = - base::DictionaryValue::From(std::move(value)); - CHECK(value_dict); - - CHECK(InitArgListFromCachedValue(*value_dict, kLoginArgsKey, &login_args_)); - CHECK(InitArgListFromCachedValue(*value_dict, kExtraArgsKey, &extra_args_)); - return true; - } - - bool PersistFlagsToFile(const base::FilePath& backing_file) { - base::DictionaryValue cached_state; - cached_state.Set(kLoginArgsKey, GetArgListValue(login_args_)); - cached_state.Set(kExtraArgsKey, GetArgListValue(extra_args_)); - - JSONFileValueSerializer serializer(backing_file); - return serializer.Serialize(cached_state); - } - - std::unique_ptr GetArgListValue( - const std::vector& args) { - std::unique_ptr result(new base::ListValue()); - for (const auto& arg : args) { - result->Append(extensions::DictionaryBuilder() - .Set(kArgNameKey, arg.name) - .Set(kArgValueKey, arg.value) - .Build()); - } - return result; - } - - bool InitArgListFromCachedValue(const base::DictionaryValue& cache_value, - const std::string& list_key, - std::vector* arg_list_out) { - arg_list_out->clear(); - const base::ListValue* arg_list_value; - if (!cache_value.GetList(list_key, &arg_list_value)) - return false; - for (size_t i = 0; i < arg_list_value->GetSize(); ++i) { - const base::DictionaryValue* arg_value; - if (!arg_list_value->GetDictionary(i, &arg_value)) - return false; - Switch arg; - if (!arg_value->GetStringASCII(kArgNameKey, &arg.name) || - !arg_value->GetStringASCII(kArgValueKey, &arg.value)) { - return false; - } - arg_list_out->push_back(arg); - } - return true; - } - - std::vector login_args_; - std::vector extra_args_; - - base::FilePath backing_file_; - - DISALLOW_COPY_AND_ASSIGN(PersistentSessionManagerClient); -}; - // Used to listen for app termination notification. class TerminationObserver : public content::NotificationObserver { public: @@ -284,15 +124,13 @@ class TerminationObserver : public content::NotificationObserver { } // namespace -class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { +class AutoLaunchedKioskTest : public MixinBasedInProcessBrowserTest { public: AutoLaunchedKioskTest() : install_attributes_( chromeos::StubInstallAttributes::CreateCloudManaged("domain.com", "device_id")), - fake_cws_(new FakeCWS) { - set_chromeos_user_ = false; - } + verifier_format_override_(crx_file::VerifierFormat::CRX3) {} ~AutoLaunchedKioskTest() override = default; @@ -302,22 +140,23 @@ class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { } void SetUp() override { - // Will be destroyed in ChromeBrowserMain. - fake_session_manager_ = new PersistentSessionManagerClient(); - - ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); AppLaunchController::SkipSplashWaitForTesting(); - - extensions::ExtensionApiTest::SetUp(); + login_manager_.set_session_restore_enabled(); + login_manager_.SetDefaultLoginSwitches( + {std::make_pair("test_switch_1", ""), + std::make_pair("test_switch_2", "test_switch_2_value")}); + MixinBasedInProcessBrowserTest::SetUp(); } void SetUpCommandLine(base::CommandLine* command_line) override { - fake_cws_->Init(embedded_test_server()); - fake_cws_->SetUpdateCrx(GetTestAppId(), GetTestAppId() + ".crx", "1.0.0"); + fake_cws_.Init(embedded_test_server()); + fake_cws_.SetUpdateCrx(GetTestAppId(), GetTestAppId() + ".crx", "1.0.0"); + std::vector secondary_apps = GetTestSecondaryAppIds(); for (const auto& secondary_app : secondary_apps) - fake_cws_->SetUpdateCrx(secondary_app, secondary_app + ".crx", "1.0.0"); - extensions::ExtensionApiTest::SetUpCommandLine(command_line); + fake_cws_.SetUpdateCrx(secondary_app, secondary_app + ".crx", "1.0.0"); + + MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line); } bool SetUpUserDataDirectory() override { @@ -332,28 +171,25 @@ class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { if (!CacheDevicePolicyToLocalState(user_data_path)) return false; - // Restore session_manager state and ensure session manager flags are - // applied. - fake_session_manager_->Initialize( - user_data_path.Append(kSessionManagerStateCache)); - fake_session_manager_->AppendSwitchesToCommandLine( - base::CommandLine::ForCurrentProcess()); - - return true; + return MixinBasedInProcessBrowserTest::SetUpUserDataDirectory(); } void SetUpInProcessBrowserTestFixture() override { host_resolver()->AddRule("*", "127.0.0.1"); - fake_session_manager_->set_device_policy( + SessionManagerClient::InitializeFakeInMemory(); + + FakeSessionManagerClient::Get()->set_supports_restart_to_apply_user_flags( + true); + FakeSessionManagerClient::Get()->set_device_policy( device_policy_helper_.device_policy()->GetBlob()); - fake_session_manager_->set_device_local_account_policy( + FakeSessionManagerClient::Get()->set_device_local_account_policy( kTestAccountId, device_local_account_policy_.GetBlob()); // Arbitrary non-empty state keys. - fake_session_manager_->set_server_backed_state_keys({"1"}); + FakeSessionManagerClient::Get()->set_server_backed_state_keys({"1"}); - extensions::ExtensionApiTest::SetUpInProcessBrowserTestFixture(); + MixinBasedInProcessBrowserTest::SetUpInProcessBrowserTestFixture(); } void PreRunTestOnMainThread() override { @@ -370,17 +206,14 @@ class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { void SetUpOnMainThread() override { extensions::browsertest_util::CreateAndInitializeLocalCache(); - - embedded_test_server()->StartAcceptingConnections(); - - extensions::ExtensionApiTest::SetUpOnMainThread(); + MixinBasedInProcessBrowserTest::SetUpOnMainThread(); } void TearDownOnMainThread() override { app_window_loaded_listener_.reset(); termination_observer_.reset(); - extensions::ExtensionApiTest::TearDownOnMainThread(); + MixinBasedInProcessBrowserTest::TearDownOnMainThread(); } void InitDevicePolicy() { @@ -478,13 +311,11 @@ class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { void ExpectCommandLineHasDefaultPolicySwitches( const base::CommandLine& cmd_line) { - for (size_t i = 0u; i < base::size(kDefaultPolicySwitches); ++i) { - EXPECT_TRUE(cmd_line.HasSwitch(kDefaultPolicySwitches[i].name)) - << "Missing flag " << kDefaultPolicySwitches[i].name; - EXPECT_EQ(kDefaultPolicySwitches[i].value, - cmd_line.GetSwitchValueASCII(kDefaultPolicySwitches[i].name)) - << "Invalid value for switch " << kDefaultPolicySwitches[i].name; - } + EXPECT_TRUE(cmd_line.HasSwitch("test_switch_1")); + EXPECT_EQ("", cmd_line.GetSwitchValueASCII("test_switch_1")); + EXPECT_TRUE(cmd_line.HasSwitch("test_switch_2")); + EXPECT_EQ("test_switch_2_value", + cmd_line.GetSwitchValueASCII("test_switch_2")); } protected: @@ -495,9 +326,13 @@ class AutoLaunchedKioskTest : public extensions::ExtensionApiTest { chromeos::ScopedStubInstallAttributes install_attributes_; policy::UserPolicyBuilder device_local_account_policy_; policy::DevicePolicyCrosTestHelper device_policy_helper_; - // Owned by SessionManagerClient global instance. - PersistentSessionManagerClient* fake_session_manager_; - std::unique_ptr fake_cws_; + FakeCWS fake_cws_; + extensions::SandboxedUnpacker::ScopedVerifierFormatOverrideForTest + verifier_format_override_; + + EmbeddedTestServerSetupMixin embedded_test_server_setup_{ + &mixin_host_, embedded_test_server()}; + LoginManagerMixin login_manager_{&mixin_host_, {}}; DISALLOW_COPY_AND_ASSIGN(AutoLaunchedKioskTest); }; diff --git a/chrome/browser/chromeos/login/crash_restore_browsertest.cc b/chrome/browser/chromeos/login/crash_restore_browsertest.cc index 14fc1c5705e5da..e5199c8d308b11 100644 --- a/chrome/browser/chromeos/login/crash_restore_browsertest.cc +++ b/chrome/browser/chromeos/login/crash_restore_browsertest.cc @@ -266,27 +266,10 @@ IN_PROC_BROWSER_TEST_F(CrashRestoreComplexTest, RestoreSessionForThreeUsers) { // Tests crash restore flow for child user. class CrashRestoreChildUserTest : public MixinBasedInProcessBrowserTest { protected: - CrashRestoreChildUserTest() { - if (!content::IsPreTest()) - login_manager_.set_skip_flags_setup(true); - } + CrashRestoreChildUserTest() { login_manager_.set_session_restore_enabled(); } ~CrashRestoreChildUserTest() override = default; // MixinBasedInProcessBrowserTest: - void SetUpCommandLine(base::CommandLine* command_line) override { - if (!content::IsPreTest()) { - const cryptohome::AccountIdentifier cryptohome_id = - cryptohome::CreateAccountIdentifierFromAccountId( - test_user_.account_id); - command_line->AppendSwitchASCII(switches::kLoginUser, - cryptohome_id.account_id()); - command_line->AppendSwitchASCII( - switches::kLoginProfile, - CryptohomeClient::GetStubSanitizedUsername(cryptohome_id)); - } - MixinBasedInProcessBrowserTest::SetUpCommandLine(command_line); - } - void SetUpInProcessBrowserTestFixture() override { // SessionManagerClient has to be in-memory to support setting sessionless // user policy blob. diff --git a/chrome/browser/chromeos/login/session/chrome_session_manager.cc b/chrome/browser/chromeos/login/session/chrome_session_manager.cc index 03bac8d8e26a6d..3b352c416f081c 100644 --- a/chrome/browser/chromeos/login/session/chrome_session_manager.cc +++ b/chrome/browser/chromeos/login/session/chrome_session_manager.cc @@ -63,7 +63,6 @@ namespace { bool ShouldAutoLaunchKioskApp(const base::CommandLine& command_line) { KioskAppManager* app_manager = KioskAppManager::Get(); return command_line.HasSwitch(switches::kLoginManager) && - !command_line.HasSwitch(switches::kForceLoginManagerInTests) && app_manager->IsAutoLaunchEnabled() && KioskAppLaunchError::Get() == KioskAppLaunchError::NONE && // IsOobeCompleted() is needed to prevent kiosk session start in case diff --git a/chrome/browser/chromeos/login/test/login_manager_mixin.cc b/chrome/browser/chromeos/login/test/login_manager_mixin.cc index 0ca84c5d0c9482..d47fdc877a31c9 100644 --- a/chrome/browser/chromeos/login/test/login_manager_mixin.cc +++ b/chrome/browser/chromeos/login/test/login_manager_mixin.cc @@ -136,12 +136,17 @@ LoginManagerMixin::LoginManagerMixin( LoginManagerMixin::~LoginManagerMixin() = default; -void LoginManagerMixin::SetUpCommandLine(base::CommandLine* command_line) { - if (skip_flags_setup_) - return; +void LoginManagerMixin::SetDefaultLoginSwitches( + const std::vector& switches) { + session_flags_manager_.SetDefaultLoginSwitches(switches); +} - command_line->AppendSwitch(chromeos::switches::kLoginManager); - command_line->AppendSwitch(chromeos::switches::kForceLoginManagerInTests); +bool LoginManagerMixin::SetUpUserDataDirectory() { + if (session_restore_enabled_) + session_flags_manager_.SetUpSessionRestore(); + session_flags_manager_.AppendSwitchesToCommandLine( + base::CommandLine::ForCurrentProcess()); + return true; } void LoginManagerMixin::CreatedBrowserMainParts( @@ -158,6 +163,10 @@ void LoginManagerMixin::SetUpOnMainThread() { session_manager_test_api.SetShouldObtainTokenHandleInTests(false); } +void LoginManagerMixin::TearDownOnMainThread() { + session_flags_manager_.Finalize(); +} + void LoginManagerMixin::AttemptLoginUsingAuthenticator( const UserContext& user_context, std::unique_ptr authenticator_builder) { diff --git a/chrome/browser/chromeos/login/test/login_manager_mixin.h b/chrome/browser/chromeos/login/test/login_manager_mixin.h index 536f2a7a18f1f3..3d079fb2b016a6 100644 --- a/chrome/browser/chromeos/login/test/login_manager_mixin.h +++ b/chrome/browser/chromeos/login/test/login_manager_mixin.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "chrome/browser/chromeos/login/mixin_based_in_process_browser_test.h" +#include "chrome/browser/chromeos/login/test/session_flags_manager.h" #include "components/account_id/account_id.h" #include "components/user_manager/user_type.h" @@ -48,11 +49,22 @@ class LoginManagerMixin : public InProcessBrowserTestMixin { ~LoginManagerMixin() override; + // Enables session restore between multi-step test run (not very useful unless + // the browser test has PRE part). + // Should be called before mixin SetUp() is called to take effect. + void set_session_restore_enabled() { session_restore_enabled_ = true; } + + // Sets the list of default policy switches to be added to command line on the + // login screen. + void SetDefaultLoginSwitches( + const std::vector& swiches); + // InProcessBrowserTestMixin: - void SetUpCommandLine(base::CommandLine* command_line) override; + bool SetUpUserDataDirectory() override; void CreatedBrowserMainParts( content::BrowserMainParts* browser_main_parts) override; void SetUpOnMainThread() override; + void TearDownOnMainThread() override; // Starts login attempt for a user, using the stub authenticator provided by // |authenticator_builder|. @@ -75,15 +87,15 @@ class LoginManagerMixin : public InProcessBrowserTestMixin { // Returns whether the newly logged in user is active when the method exits. bool LoginAndWaitForActiveSession(const UserContext& user_context); - // Allows to skip setup of command line switches. It can be used to test - // session restart after test, or restart to apply per-session flags, where - // the session should start with a user logged in. - void set_skip_flags_setup(bool value) { skip_flags_setup_ = value; } - private: - bool skip_flags_setup_ = false; const std::vector initial_users_; + // If set, session_flags_manager_ will be set up with session restore logic + // enabled (it will restore session state between test runs for multi-step + // browser tests). + bool session_restore_enabled_ = false; + test::SessionFlagsManager session_flags_manager_; + DISALLOW_COPY_AND_ASSIGN(LoginManagerMixin); }; diff --git a/chrome/browser/chromeos/login/test/session_flags_manager.cc b/chrome/browser/chromeos/login/test/session_flags_manager.cc new file mode 100644 index 00000000000000..fc801c0e37aad9 --- /dev/null +++ b/chrome/browser/chromeos/login/test/session_flags_manager.cc @@ -0,0 +1,181 @@ +// Copyright 2019 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/chromeos/login/test/session_flags_manager.h" + +#include +#include +#include + +#include "base/base64.h" +#include "base/command_line.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/values.h" +#include "chrome/common/chrome_paths.h" +#include "chromeos/constants/chromeos_switches.h" +#include "chromeos/cryptohome/cryptohome_parameters.h" +#include "chromeos/dbus/session_manager/fake_session_manager_client.h" +#include "third_party/cros_system_api/switches/chrome_switches.h" + +namespace chromeos { +namespace test { + +namespace { + +// Keys for values in dictionary used to preserve session manager state. +constexpr char kUserIdKey[] = "active_user_id"; +constexpr char kUserHashKey[] = "active_user_hash"; +constexpr char kUserFlagsKey[] = "user_flags"; +constexpr char kFlagNameKey[] = "name"; +constexpr char kFlagValueKey[] = "value"; + +constexpr char kCachedSessionStateFile[] = "test_session_manager_state.json"; + +} // namespace + +SessionFlagsManager::SessionFlagsManager() = default; + +SessionFlagsManager::~SessionFlagsManager() { + Finalize(); +} + +void SessionFlagsManager::SetUpSessionRestore() { + DCHECK_EQ(mode_, Mode::LOGIN_SCREEN); + mode_ = Mode::LOGIN_SCREEN_WITH_SESSION_RESTORE; + + base::FilePath user_data_path; + CHECK(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_path)) + << "Unable to get used data dir"; + + backing_file_ = user_data_path.AppendASCII(kCachedSessionStateFile); + LoadStateFromBackingFile(); +} + +void SessionFlagsManager::SetDefaultLoginSwitches( + const std::vector& switches) { + default_switches_ = {{switches::kPolicySwitchesBegin, ""}}; + default_switches_.insert(default_switches_.end(), switches.begin(), + switches.end()); + default_switches_.emplace_back( + std::make_pair(switches::kPolicySwitchesEnd, "")); +} + +void SessionFlagsManager::AppendSwitchesToCommandLine( + base::CommandLine* command_line) { + if (mode_ == Mode::LOGIN_SCREEN || user_id_.empty()) { + command_line->AppendSwitch(switches::kLoginManager); + command_line->AppendSwitch(switches::kForceLoginManagerInTests); + } else { + DCHECK_EQ(mode_, Mode::LOGIN_SCREEN_WITH_SESSION_RESTORE); + command_line->AppendSwitchASCII(switches::kLoginUser, user_id_); + command_line->AppendSwitchASCII(switches::kLoginProfile, user_hash_); + } + + // Session manager uses extra args to pass both default, login policy + // switches and user flag switches. If extra args are not explicitly set for + // current user before resarting chrome (which is represented by user_flags_ + // not being set here), session manager will keep using extra args set by + // default login switches - simulate this behavior. + const std::vector& active_switches = + user_flags_.has_value() ? *user_flags_ : default_switches_; + for (const auto& item : active_switches) { + command_line->AppendSwitchASCII(item.first, item.second); + } +} + +void SessionFlagsManager::Finalize() { + if (finalized_ || mode_ != Mode::LOGIN_SCREEN_WITH_SESSION_RESTORE) + return; + + finalized_ = true; + StoreStateToBackingFile(); +} + +void SessionFlagsManager::LoadStateFromBackingFile() { + DCHECK_EQ(mode_, Mode::LOGIN_SCREEN_WITH_SESSION_RESTORE); + + JSONFileValueDeserializer deserializer(backing_file_); + + int error_code = 0; + std::unique_ptr value = + deserializer.Deserialize(&error_code, nullptr); + if (error_code != JSONFileValueDeserializer::JSON_NO_ERROR) + return; + + DCHECK(value->is_dict()); + const std::string* user_id = value->FindStringKey(kUserIdKey); + if (!user_id || user_id->empty()) + return; + + user_id_ = *user_id; + user_hash_ = *value->FindStringKey(kUserHashKey); + + base::Value* user_flags = value->FindListKey(kUserFlagsKey); + if (!user_flags) + return; + + user_flags_ = std::vector(); + for (const base::Value& flag : user_flags->GetList()) { + DCHECK(flag.is_dict()); + user_flags_->emplace_back(std::make_pair( + *flag.FindStringKey(kFlagNameKey), *flag.FindStringKey(kFlagValueKey))); + } +} + +void SessionFlagsManager::StoreStateToBackingFile() { + const SessionManagerClient::ActiveSessionsMap& sessions = + FakeSessionManagerClient::Get()->user_sessions(); + const bool session_active = + !sessions.empty() && !FakeSessionManagerClient::Get()->session_stopped(); + + // If a user session is not active, clear the backing file so default flags + // are used next time. + if (!session_active) { + base::DeleteFile(backing_file_, false /*recursive*/); + return; + } + + // Currently, only support single user sessions. + DCHECK_EQ(1u, sessions.size()); + const auto& session = sessions.begin(); + + base::Value cached_state(base::Value::Type::DICTIONARY); + cached_state.SetKey(kUserIdKey, base::Value(session->first)); + cached_state.SetKey(kUserHashKey, base::Value(session->second)); + + std::vector user_flag_args; + std::vector raw_flags; + const bool has_user_flags = FakeSessionManagerClient::Get()->GetFlagsForUser( + cryptohome::CreateAccountIdentifierFromIdentification( + cryptohome::Identification::FromString(session->first)), + &raw_flags); + if (has_user_flags) { + std::vector argv = {"" /* Empty program */}; + argv.insert(argv.end(), raw_flags.begin(), raw_flags.end()); + + // Parse flag name-value pairs using command line initialization. + base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM); + cmd_line.InitFromArgv(argv); + + base::Value flag_list(base::Value::Type::LIST); + for (const auto& flag : cmd_line.GetSwitches()) { + base::Value flag_value(base::Value::Type::DICTIONARY); + flag_value.SetKey(kFlagNameKey, base::Value(flag.first)); + flag_value.SetKey(kFlagValueKey, base::Value(flag.second)); + + flag_list.GetList().emplace_back(std::move(flag_value)); + } + cached_state.SetKey(kUserFlagsKey, std::move(flag_list)); + } + + JSONFileValueSerializer serializer(backing_file_); + serializer.Serialize(cached_state); +} + +} // namespace test +} // namespace chromeos diff --git a/chrome/browser/chromeos/login/test/session_flags_manager.h b/chrome/browser/chromeos/login/test/session_flags_manager.h new file mode 100644 index 00000000000000..ef3166604cbb48 --- /dev/null +++ b/chrome/browser/chromeos/login/test/session_flags_manager.h @@ -0,0 +1,109 @@ +// Copyright 2019 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. + +#ifndef CHROME_BROWSER_CHROMEOS_LOGIN_TEST_SESSION_FLAGS_MANAGER_H_ +#define CHROME_BROWSER_CHROMEOS_LOGIN_TEST_SESSION_FLAGS_MANAGER_H_ + +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/macros.h" + +namespace base { +class CommandLine; +} + +namespace chromeos { +namespace test { + +// Test helper that sets up command line for login tests. By default, it +// initializes the command line so tests start on the login manager. +// It supports session restore mode, which can be used during multi step browser +// tests (i.e. tests that have PRE_ test) to keep session state across test +// runs. If a user session was active in the previous run, this will set up +// command line to restore session for that user, which can be useful for +// testing chrome restart to apply per-session flags, or session restore on +// crash. +class SessionFlagsManager { + public: + // Pair of switch name and value. The value can be empty. + using Switch = std::pair; + + SessionFlagsManager(); + ~SessionFlagsManager(); + + // Sets the manager up for session restore.It should be called early in + // test setup, before calling AppendSwitchesToCommandLine(). + // + // When session restore is enabled, SessionStateManager will load session + // state from a file in the test user data directory, and use it to initialize + // the test command line. The file will contain session information saved + // during the previous (PRE_) browser test step. The information includes: + // * the active user information + // * the active user's per-session flags. + // + // If the backing file is not found, or empty, command line will be + // set up with login manager flags so test starts on the login screen. + void SetUpSessionRestore(); + + // Sets the default login policy switches, that will be added to command line + // when initializing it for login screen. + // This should be called before chrome startup starts - i.e. before + // SetUpInProcessBrowserTestFixture(). + void SetDefaultLoginSwitches(const std::vector& switches); + + // Sets up the test command line. If session restore is enabled, and + // persisted session state is found, the command line is set up according to + // that state. Otherwise, it's set up to force login screen. + // + // NOTE: If SetUpSessionRestore() was called, calling this requires the + // user data dir to be set up for test. + void AppendSwitchesToCommandLine(base::CommandLine* command_line); + + // Finalizes this instance - if session restore is enabled, it will save + // current session manager information to a backing file in the user data + // directory. + void Finalize(); + + private: + enum class Mode { + // Sets up command line to start on login screen. + LOGIN_SCREEN, + // If saved user session state exists, sets up command line to continue the + // user session, otherwise sets up command line for login screen. + LOGIN_SCREEN_WITH_SESSION_RESTORE + }; + + void LoadStateFromBackingFile(); + void StoreStateToBackingFile(); + + // The mode this manager is running in. + Mode mode_ = Mode::LOGIN_SCREEN; + + // Whether Finalize has been called. + bool finalized_ = false; + + // List of default switches that should be added to command line when setting + // up command line for login screen. + std::vector default_switches_; + + // If command line should be set up for a user session (e.g. when running in + // session restore mode), the logged in user information. + std::string user_id_; + std::string user_hash_; + base::Optional> user_flags_; + + // If |session_restore_enabled_| is set, the path to the file where session + // state is saved. + base::FilePath backing_file_; + + DISALLOW_COPY_AND_ASSIGN(SessionFlagsManager); +}; + +} // namespace test +} // namespace chromeos + +#endif // CHROME_BROWSER_CHROMEOS_LOGIN_TEST_SESSION_FLAGS_MANAGER_H_ diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn index 31275a30f328ae..3becacaa957019 100644 --- a/chrome/test/BUILD.gn +++ b/chrome/test/BUILD.gn @@ -1915,6 +1915,8 @@ test("browser_tests") { "../browser/chromeos/login/test/network_portal_detector_mixin.h", "../browser/chromeos/login/test/oobe_base_test.cc", "../browser/chromeos/login/test/oobe_base_test.h", + "../browser/chromeos/login/test/session_flags_manager.cc", + "../browser/chromeos/login/test/session_flags_manager.h", "../browser/chromeos/login/ui/captive_portal_window_browsertest.cc", "../browser/chromeos/login/ui/login_feedback_browsertest.cc", "../browser/chromeos/login/ui/login_web_dialog_browsertest.cc", diff --git a/chromeos/dbus/session_manager/fake_session_manager_client.h b/chromeos/dbus/session_manager/fake_session_manager_client.h index 62028a40dfa924..780c256e1ec3c4 100644 --- a/chromeos/dbus/session_manager/fake_session_manager_client.h +++ b/chromeos/dbus/session_manager/fake_session_manager_client.h @@ -240,6 +240,10 @@ class COMPONENT_EXPORT(SESSION_MANAGER) FakeSessionManagerClient bool session_stopped() const { return session_stopped_; } + const SessionManagerClient::ActiveSessionsMap& user_sessions() const { + return user_sessions_; + } + private: bool supports_restart_to_apply_user_flags_ = false;