chromium/chrome/browser/ash/policy/handlers/lock_to_single_user_manager_unittest.cc

// Copyright 2019 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/policy/handlers/lock_to_single_user_manager.h"

#include <memory>

#include "ash/components/arc/arc_prefs.h"
#include "ash/components/arc/metrics/arc_metrics_service.h"
#include "ash/components/arc/metrics/stability_metrics_manager.h"
#include "ash/components/arc/session/arc_service_manager.h"
#include "ash/components/arc/test/arc_util_test_support.h"
#include "ash/components/arc/test/fake_arc_session.h"
#include "ash/public/cpp/shelf_model.h"
#include "base/command_line.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ash/app_list/arc/arc_app_test.h"
#include "chrome/browser/ash/arc/session/arc_session_manager.h"
#include "chrome/browser/ash/arc/test/test_arc_session_manager.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_manager.h"
#include "chrome/browser/ash/plugin_vm/plugin_vm_manager_factory.h"
#include "chrome/browser/ui/ash/shelf/chrome_shelf_controller.h"
#include "chrome/browser/ui/ash/shelf/shelf_controller_helper.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/browser_with_test_window_test.h"
#include "chromeos/ash/components/dbus/anomaly_detector/anomaly_detector_client.h"
#include "chromeos/ash/components/dbus/chunneld/chunneld_client.h"
#include "chromeos/ash/components/dbus/cicerone/cicerone_client.h"
#include "chromeos/ash/components/dbus/concierge/concierge_client.h"
#include "chromeos/ash/components/dbus/concierge/fake_concierge_client.h"
#include "chromeos/ash/components/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/ash/components/dbus/dlcservice/dlcservice_client.h"
#include "chromeos/ash/components/dbus/seneschal/seneschal_client.h"
#include "chromeos/ash/components/dbus/session_manager/session_manager_client.h"
#include "chromeos/ash/components/dbus/userdataauth/fake_cryptohome_misc_client.h"
#include "chromeos/ash/components/dbus/vm_plugin_dispatcher/vm_plugin_dispatcher_client.h"
#include "chromeos/ash/components/login/session/session_termination_manager.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/account_id/account_id.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/scoped_user_manager.h"

namespace policy {

class LockToSingleUserManagerTest : public BrowserWithTestWindowTest {
 public:
  LockToSingleUserManagerTest() = default;

  LockToSingleUserManagerTest(const LockToSingleUserManagerTest&) = delete;
  LockToSingleUserManagerTest& operator=(const LockToSingleUserManagerTest&) =
      delete;

  ~LockToSingleUserManagerTest() override = default;

  void SetUp() override {
    // This is required for GuestOsStabilityMonitor.
    ash::ChunneldClient::InitializeFake();
    ash::CiceroneClient::InitializeFake();
    ash::ConciergeClient::InitializeFake();
    ash::DebugDaemonClient::InitializeFake();
    ash::DlcserviceClient::InitializeFake();
    ash::SeneschalClient::InitializeFake();
    ash::SessionManagerClient::InitializeFakeInMemory();

    arc::SetArcAvailableCommandLineForTesting(
        base::CommandLine::ForCurrentProcess());
    ash::AnomalyDetectorClient::InitializeFake();
    ash::CryptohomeMiscClient::InitializeFake();
    ash::VmPluginDispatcherClient::InitializeFake();
    lock_to_single_user_manager_ = std::make_unique<LockToSingleUserManager>();
    scoped_feature_list_.InitAndEnableFeature(features::kPluginVm);

    BrowserWithTestWindowTest::SetUp();

    settings_helper_.ReplaceDeviceSettingsProviderWithStub();
    arc::ArcSessionManager::SetUiEnabledForTesting(false);
    arc_service_manager_ = std::make_unique<arc::ArcServiceManager>();
    arc_session_manager_ = arc::CreateTestArcSessionManager(
        std::make_unique<arc::ArcSessionRunner>(
            base::BindRepeating(arc::FakeArcSession::Create)));

    arc_service_manager_->set_browser_context(profile());
    arc::prefs::RegisterLocalStatePrefs(local_state_.registry());
    arc::StabilityMetricsManager::Initialize(&local_state_);
    arc::ArcMetricsService::GetForBrowserContextForTesting(profile());
  }

  void TearDown() override {
    arc::StabilityMetricsManager::Shutdown();
    // lock_to_single_user_manager has to be cleaned up first due to implicit
    // dependency on ArcSessionManager.
    lock_to_single_user_manager_.reset();

    chrome_shelf_controller_.reset();
    shelf_model_.reset();

    arc_session_manager_->Shutdown();
    arc_session_manager_.reset();

    // Must reset browser context reference before profile destruction.
    arc_service_manager_->set_browser_context(nullptr);

    // Destruction order matters here.
    //
    // This line destroys profile, thus indirectly destroys
    // ArcMetricsService, since profile owns keyed services, like
    // ArcMetricsService. DTor of ArcMetricsService calls things in
    // ArcBridgeService, which is owned by ArcServiceManager. Thus
    // ArcServiceManager must still be alive at this line.
    BrowserWithTestWindowTest::TearDown();

    arc_service_manager_.reset();
    ash::VmPluginDispatcherClient::Shutdown();
    ash::CryptohomeMiscClient::Shutdown();
    ash::AnomalyDetectorClient::Shutdown();
    ash::SessionManagerClient::Shutdown();
    ash::SeneschalClient::Shutdown();
    ash::DlcserviceClient::Shutdown();
    ash::DebugDaemonClient::Shutdown();
    ash::ConciergeClient::Shutdown();
    ash::CiceroneClient::Shutdown();
    ash::ChunneldClient::Shutdown();
  }

  // BrowserWithTestWindowTest:
  // Override to do nothing to inject this test's specific behavior.
  // TODO(b/40286020): Consider migrating into BrowserWithTestWindowTest
  // in better way. Current test implementation is different from
  // what we're seeing in production.
  void LogIn(const std::string& email) override {}
  void OnUserProfileCreated(const std::string& email,
                            Profile* profile) override {}
  void SwitchActiveUser(const std::string& email) override {}

  void LogInUser(bool is_affiliated) {
    base::RunLoop run_loop;
    const AccountId account_id(AccountId::FromUserEmailGaiaId(
        profile()->GetProfileUserName(), "1234567890"));
    fake_user_manager_->AddUserWithAffiliation(account_id, is_affiliated);
    fake_user_manager_->LoginUser(account_id);
    // This step should be part of LoginUser(). There's a TODO to add it there,
    // but it breaks many tests.
    fake_user_manager_->SwitchActiveUser(account_id);

    ash::LoginState::Get()->SetLoggedInState(
        ash::LoginState::LOGGED_IN_ACTIVE,
        ash::LoginState::LOGGED_IN_USER_REGULAR);

    arc_session_manager_->SetProfile(profile());
    arc_session_manager_->Initialize();

    // Set up ChromeShelfController to avoid a crash in LaunchPluginVm().
    shelf_model_ = std::make_unique<ash::ShelfModel>();
    chrome_shelf_controller_ =
        std::make_unique<ChromeShelfController>(profile(), shelf_model_.get());
    chrome_shelf_controller_->SetProfileForTest(profile());
    chrome_shelf_controller_->SetShelfControllerHelperForTest(
        std::make_unique<ShelfControllerHelper>(profile()));
    chrome_shelf_controller_->Init();

    run_loop.RunUntilIdle();
  }

  void SetPolicyValue(int value) {
    settings_helper_.SetInteger(ash::kDeviceRebootOnUserSignout, value);
  }

  void StartArc() {
    base::RunLoop run_loop;
    arc_session_manager_->StartArcForTesting();
    run_loop.RunUntilIdle();
  }

  void StartPluginVm() {
    base::RunLoop run_loop;
    plugin_vm::PluginVmManagerFactory::GetForProfile(profile())->LaunchPluginVm(
        base::DoNothing());
    run_loop.RunUntilIdle();
  }

  void StartConciergeVm() {
    base::RunLoop run_loop;
    vm_tools::concierge::VmStartedSignal signal;
    ash::FakeConciergeClient::Get()->NotifyVmStarted(signal);
    run_loop.RunUntilIdle();
  }

  void StartDbusVm() {
    base::RunLoop run_loop;
    lock_to_single_user_manager_->DbusNotifyVmStarting();
    run_loop.RunUntilIdle();
  }

  void CheckIsDeviceLocked(bool should_be_locked) {
    EXPECT_EQ(
        ash::FakeCryptohomeMiscClient::Get()->is_device_locked_to_single_user(),
        should_be_locked);
    EXPECT_EQ(ash::SessionTerminationManager::Get()->IsLockedToSingleUser(),
              should_be_locked);
  }

 private:
  base::test::ScopedFeatureList scoped_feature_list_;
  ash::ScopedCrosSettingsTestHelper settings_helper_{
      /* create_settings_service= */ false};
  raw_ptr<ash::FakeChromeUserManager, DanglingUntriaged> fake_user_manager_{
      new ash::FakeChromeUserManager()};
  user_manager::ScopedUserManager scoped_user_manager_{
      base::WrapUnique(fake_user_manager_.get())};
  std::unique_ptr<arc::ArcServiceManager> arc_service_manager_;
  std::unique_ptr<arc::ArcSessionManager> arc_session_manager_;
  std::unique_ptr<ash::ShelfModel> shelf_model_;
  std::unique_ptr<ChromeShelfController> chrome_shelf_controller_;
  // Required for initialization.
  ash::SessionTerminationManager termination_manager_;
  std::unique_ptr<LockToSingleUserManager> lock_to_single_user_manager_;
  TestingPrefServiceSimple local_state_;
};

TEST_F(LockToSingleUserManagerTest, ArcSessionLockTest) {
  SetPolicyValue(
      enterprise_management::DeviceRebootOnUserSignoutProto::ARC_SESSION);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
  StartConciergeVm();
  StartPluginVm();
  StartDbusVm();
  CheckIsDeviceLocked(false);
  StartArc();
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, ConciergeStartLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::
                     VM_STARTED_OR_ARC_SESSION);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
  StartConciergeVm();
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, PluginVmStartLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::
                     VM_STARTED_OR_ARC_SESSION);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
  StartPluginVm();
  CheckIsDeviceLocked(true);
  StartConciergeVm();
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, DbusVmStartLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::
                     VM_STARTED_OR_ARC_SESSION);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
  StartDbusVm();
  CheckIsDeviceLocked(true);
  StartConciergeVm();
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, ArcSessionOrVmLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::
                     VM_STARTED_OR_ARC_SESSION);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
  StartArc();
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, AlwaysLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::ALWAYS);
  // The device should not be locked at this point because of async affiliation
  // loading.
  CheckIsDeviceLocked(false);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(true);
}

TEST_F(LockToSingleUserManagerTest, NeverLockTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::NEVER);
  LogInUser(false /* is_affiliated */);
  StartPluginVm();
  StartConciergeVm();
  StartArc();
  StartDbusVm();
  CheckIsDeviceLocked(false);
}

TEST_F(LockToSingleUserManagerTest, DbusCallErrorTest) {
  ash::FakeCryptohomeMiscClient::Get()->set_cryptohome_error(
      ::user_data_auth::CryptohomeErrorCode::CRYPTOHOME_ERROR_KEY_NOT_FOUND);
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::ALWAYS);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(false);
}

TEST_F(LockToSingleUserManagerTest, DoesNotAffectAffiliatedUsersTest) {
  SetPolicyValue(enterprise_management::DeviceRebootOnUserSignoutProto::ALWAYS);
  LogInUser(true /* is_affiliated */);
  CheckIsDeviceLocked(false);
}

TEST_F(LockToSingleUserManagerTest, FutureTest) {
  // Unknown values should be the same as ALWAYS
  SetPolicyValue(100);
  LogInUser(false /* is_affiliated */);
  CheckIsDeviceLocked(true);
}

}  // namespace policy