chromium/chrome/browser/metrics/per_user_state_manager_chromeos_browsertest.cc

// 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/metrics/per_user_state_manager_chromeos.h"

#include "base/run_loop.h"
#include "chrome/browser/ash/login/login_manager_test.h"
#include "chrome/browser/ash/login/test/device_state_mixin.h"
#include "chrome/browser/ash/login/test/guest_session_mixin.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/oobe_base_test.h"
#include "chrome/browser/ash/login/test/oobe_screen_exit_waiter.h"
#include "chrome/browser/ash/login/test/oobe_screens_utils.h"
#include "chrome/browser/ash/login/test/scoped_policy_update.h"
#include "chrome/browser/ash/login/test/user_policy_mixin.h"
#include "chrome/browser/ash/login/ui/login_display_host.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/policy/core/device_policy_builder.h"
#include "chrome/browser/ash/policy/core/device_policy_cros_browser_test.h"
#include "chrome/browser/ash/policy/test_support/embedded_policy_test_server_mixin.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/ash/settings/stats_reporting_controller.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/metrics/chrome_metrics_service_client.h"
#include "chrome/browser/metrics/profile_pref_names.h"
#include "chrome/browser/metrics/testing/metrics_reporting_pref_helper.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/ui/webui/ash/login/consolidated_consent_screen_handler.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/ash/components/dbus/session_manager/fake_session_manager_client.h"
#include "components/metrics/metrics_log_store.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/metrics/metrics_service.h"
#include "components/metrics/metrics_switches.h"
#include "components/ownership/mock_owner_key_util.h"
#include "components/session_manager/core/session_manager.h"
#include "components/user_manager/scoped_user_manager.h"
#include "components/user_manager/user_names.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace metrics {

namespace {

using ::testing::Eq;
using ::testing::Ne;

// Create fake test users in order to test different owner and secondary user
// consent flows.
constexpr char kTestUser1[] = "[email protected]";
constexpr char kTestUser1GaiaId[] = "1111111111";
constexpr char kTestUser2[] = "[email protected]";
constexpr char kTestUser2GaiaId[] = "2222222222";

}  // namespace

// Use LoginManagerTest to mimic actual Chrome OS users being logged in and
// out.
class ChromeOSPerUserMetricsBrowserTestBase : public ash::LoginManagerTest {
 public:
  ChromeOSPerUserMetricsBrowserTestBase() = default;
  ~ChromeOSPerUserMetricsBrowserTestBase() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    EnableMetricsRecordingOnlyForTesting(command_line);
    ash::LoginManagerTest::SetUpCommandLine(command_line);
  }

  void Initialize() {
    // Metrics service must explicitly be told to start recording or nothing
    // will happen.
    g_browser_process->metrics_service()->StartRecordingForTests();

    // Wait for metrics service to finish initializing.
    base::RunLoop().RunUntilIdle();
  }

  // Assumes that a user has logged in.
  void ChangeUserMetricsConsent(bool user_metrics_consent) {
    g_browser_process->metrics_service()->UpdateCurrentUserMetricsConsent(
        user_metrics_consent);
  }

  void CreatedBrowserMainParts(content::BrowserMainParts* parts) override {
    LoginManagerTest::CreatedBrowserMainParts(parts);
    // IsMetricsReportingEnabled() in non-official builds always returns false.
    // Force to check the pref in order to test proper reporting consent.
    ChromeMetricsServiceAccessor::SetForceIsMetricsReportingEnabledPrefLookup(
        true);
  }

  bool GetLocalStateMetricsConsent() const {
    return g_browser_process->local_state()->GetBoolean(
        prefs::kMetricsReportingEnabled);
  }
};

class ChromeOSPerUserRegularUserTest
    : public ChromeOSPerUserMetricsBrowserTestBase,
      public ::testing::WithParamInterface<std::pair<bool, bool>> {
 public:
  ChromeOSPerUserRegularUserTest()
      : owner_key_util_(new ownership::MockOwnerKeyUtil()) {
    login_mixin_.AppendRegularUsers(1);
  }
  ~ChromeOSPerUserRegularUserTest() override = default;

  ash::LoginManagerMixin login_mixin_{&mixin_host_, {}, &fake_gaia_};
  AccountId account_id_{
      AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId)};

 protected:
  void SetUpInProcessBrowserTestFixture() override {
    ChromeOSPerUserMetricsBrowserTestBase::SetUpInProcessBrowserTestFixture();
    owner_consent_ = GetParam().first;
    user_consent_ = GetParam().second;

    // Set the owner parameter.
    test_cros_settings_.device_settings()->SetBoolean(ash::kStatsReportingPref,
                                                      owner_consent_);

    // Establish ownership of the device.
    ash::OwnerSettingsServiceAshFactory::GetInstance()
        ->SetOwnerKeyUtilForTesting(owner_key_util_);
    owner_key_util_->SetPublicKeyFromPrivateKey(
        *policy_helper_.device_policy()->GetSigningKey());
  }

  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_;
  policy::DevicePolicyCrosTestHelper policy_helper_;
  ash::ScopedTestingCrosSettings test_cros_settings_;
  ash::DeviceStateMixin device_state_{
      &mixin_host_,
      ash::DeviceStateMixin::State::OOBE_COMPLETED_CONSUMER_OWNED};

  bool owner_consent_ = false;
  bool user_consent_ = false;

  FakeGaiaMixin fake_gaia_{&mixin_host_};
};

IN_PROC_BROWSER_TEST_P(ChromeOSPerUserRegularUserTest,
                       MetricsConsentForRegularUser) {
  Initialize();

  LoginUser(account_id_);
  base::RunLoop().RunUntilIdle();

  MetricsLogStore* log_store =
      g_browser_process->metrics_service()->LogStoreForTest();

  // Should be using alternate ongoing log store for regular users regardless of
  // owner consent.
  EXPECT_TRUE(log_store->has_alternate_ongoing_log_store());

  // Secondary user consent is initially disabled until after user consent is
  // given.
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  // Try and toggle user metrics consent to |user_consent_|.
  ChangeUserMetricsConsent(user_consent_);

  // Propagating metrics consent through the services happens async.
  base::RunLoop().RunUntilIdle();

  // User metrics consent is independent of owners consent.
  EXPECT_THAT(GetLocalStateMetricsConsent(), Eq(user_consent_));

  // User-Id will be set if user consent is enabled.
  EXPECT_THAT(
      g_browser_process->metrics_service()->GetCurrentUserId().has_value(),
      Eq(user_consent_));
}

INSTANTIATE_TEST_SUITE_P(MetricsConsentForRegularUser,
                         ChromeOSPerUserRegularUserTest,
                         testing::ValuesIn({
                             std::make_pair(true, true),
                             std::make_pair(true, false),
                             std::make_pair(false, true),
                             std::make_pair(false, false),
                         }));

class ChromeOSPerUserGuestUserWithNoOwnerTest
    : public ChromeOSPerUserMetricsBrowserTestBase,
      public ::testing::WithParamInterface<bool> {
 public:
  ChromeOSPerUserGuestUserWithNoOwnerTest() = default;
  ~ChromeOSPerUserGuestUserWithNoOwnerTest() override = default;

 protected:
  ash::GuestSessionMixin guest_session_{&mixin_host_};
};

IN_PROC_BROWSER_TEST_P(ChromeOSPerUserGuestUserWithNoOwnerTest,
                       MetricsConsentForGuestWithNoOwner) {
  Initialize();

  auto* metrics_service = g_browser_process->metrics_service();
  MetricsLogStore* log_store = metrics_service->LogStoreForTest();

  // Device consent should be false if device is not owned.
  EXPECT_FALSE(ash::StatsReportingController::Get()->IsEnabled());
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  bool guest_consent = GetParam();
  ChangeUserMetricsConsent(guest_consent);

  // Propagating metrics consent through the services happens async.
  base::RunLoop().RunUntilIdle();

  // Once consent is set for the first time, log store should be set
  // appropriately. Log store should be the inverse of the first consent since
  // consent means that log store used should be local state.
  EXPECT_THAT(GetLocalStateMetricsConsent(), Eq(guest_consent));

  // Secondary users always set ephemeral partition.
  EXPECT_TRUE(log_store->has_alternate_ongoing_log_store());

  // Guests should have a user id if guest consent is set.
  EXPECT_EQ(metrics_service->GetCurrentUserId().has_value(), guest_consent);

  // Device settings consent should remain disabled since this is a guest
  // session.
  EXPECT_FALSE(ash::StatsReportingController::Get()->IsEnabled());
}

INSTANTIATE_TEST_SUITE_P(MetricsConsentForGuestWithNoOwner,
                         ChromeOSPerUserGuestUserWithNoOwnerTest,
                         ::testing::Bool());

class ChromeOSPerUserOobeConsentTest : public ash::OobeBaseTest {
 public:
  ChromeOSPerUserOobeConsentTest() = default;

  void SetUpOnMainThread() override {
    ash::LoginDisplayHost::default_host()
        ->GetWizardContext()
        ->is_branded_build = true;

    ash::OobeBaseTest::SetUpOnMainThread();
  }

  ChromeOSPerUserOobeConsentTest(const ChromeOSPerUserOobeConsentTest&) =
      delete;
  ChromeOSPerUserOobeConsentTest& operator=(
      const ChromeOSPerUserOobeConsentTest&) = delete;
  ~ChromeOSPerUserOobeConsentTest() override = default;

  void ValidateCurrentUser(const user_manager::User* user) {}

  void Initialize() {
    // Metrics service must explicitly be told to start recording or nothing
    // will happen.
    g_browser_process->metrics_service()->StartRecordingForTests();

    // Wait for metrics service to finish initializing.
    base::RunLoop().RunUntilIdle();
  }

  // Assumes that a user has logged in.
  void ChangeUserMetricsConsent(bool user_metrics_consent) {
    g_browser_process->metrics_service()->UpdateCurrentUserMetricsConsent(
        user_metrics_consent);
  }

  bool GetLocalStateMetricsConsent() const {
    return g_browser_process->local_state()->GetBoolean(
        prefs::kMetricsReportingEnabled);
  }

  ash::LoginManagerMixin login_manager_mixin_{&mixin_host_, {}, &fake_gaia_};

 private:
  FakeGaiaMixin fake_gaia_{&mixin_host_};
};

IN_PROC_BROWSER_TEST_F(ChromeOSPerUserOobeConsentTest,
                       PRE_OwnerConsentAndRegularSecondaryConsent) {
  // Create and login as device owner.
  auto context = ash::LoginManagerMixin::CreateDefaultUserContext(
      ash::LoginManagerMixin::TestUserInfo(
          AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId)));
  login_manager_mixin_.LoginAsNewRegularUser(context);
  ash::OobeScreenExitWaiter(ash::OobeBaseTest::GetFirstSigninScreen()).Wait();

  // These are enough to test OOBE consent enabled/disabled.
  ash::test::WaitForConsolidatedConsentScreen();

  base::RunLoop().RunUntilIdle();

  // Device owner has not consented to reporting in OOBE flow yet.
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  // Device owner accepted consent.
  ash::test::TapConsolidatedConsentAccept();
  ash::test::WaitForSyncConsentScreen();

  // Device owner has consented to reporting in OOBE.
  EXPECT_TRUE(GetLocalStateMetricsConsent());
}

IN_PROC_BROWSER_TEST_F(ChromeOSPerUserOobeConsentTest,
                       OwnerConsentAndRegularSecondaryConsent) {
  auto context = ash::LoginManagerMixin::CreateDefaultUserContext(
      ash::LoginManagerMixin::TestUserInfo(
          AccountId::FromUserEmailGaiaId(kTestUser2, kTestUser2GaiaId)));
  login_manager_mixin_.LoginAsNewRegularUser(context);
  ash::OobeScreenExitWaiter(ash::OobeBaseTest::GetFirstSigninScreen()).Wait();

  // Secondary user has not consented to reporting in OOBE flow yet.
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  ash::test::WaitForConsolidatedConsentScreen();
  ash::test::TapConsolidatedConsentAccept();
  ash::test::WaitForSyncConsentScreen();

  // Secondary user was able to consent.
  EXPECT_TRUE(GetLocalStateMetricsConsent());

  // Try and disable regular user metrics consent.
  // Propagating metrics consent through the services happens async.
  ChangeUserMetricsConsent(false);
  base::RunLoop().RunUntilIdle();

  // User metrics consent is independent of owners consent.
  // The local state metrics consent represents the current active users
  // consent. Owner consent is managed by |ash::StatsReportingController|.
  EXPECT_TRUE(ash::StatsReportingController::Get()->IsEnabled());
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  // User-Id will not be set if user consent is disabled.
  EXPECT_THAT(
      g_browser_process->metrics_service()->GetCurrentUserId().has_value(),
      Eq(false));
}

// Device owner consent is managed by |ash::StatsReportingController|.
// Test verifies that the |PerUserStateManagerChromeOS| allows control of
// secondary user consent regardless of the owners consent.
IN_PROC_BROWSER_TEST_F(ChromeOSPerUserOobeConsentTest,
                       DeviceOwnerDoesNotUsePerUserConsent) {
  auto context = ash::LoginManagerMixin::CreateDefaultUserContext(
      ash::LoginManagerMixin::TestUserInfo(
          AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId),
          ash::test::kDefaultAuthSetup, user_manager::UserType::kRegular));
  login_manager_mixin_.LoginAsNewRegularUser(context);
  ash::OobeScreenExitWaiter(ash::OobeBaseTest::GetFirstSigninScreen()).Wait();

  // Device owner has not consented to reporting in OOBE flow yet.
  EXPECT_FALSE(GetLocalStateMetricsConsent());

  ash::test::WaitForConsolidatedConsentScreen();
  ash::test::TapConsolidatedConsentAccept();
  ash::test::WaitForSyncConsentScreen();

  // Owner user consented during OOBE.
  EXPECT_TRUE(GetLocalStateMetricsConsent());

  // User-Id is never set for device owner.
  EXPECT_THAT(
      g_browser_process->metrics_service()->GetCurrentUserId().has_value(),
      Eq(false));

  // Try and disable device owner user metrics consent.
  // Device owner should not controller by per user metrics consent.
  // Propagating metrics consent through the services happens async.
  ChangeUserMetricsConsent(false);
  base::RunLoop().RunUntilIdle();

  // Owner consent cannot be updated by ChangeUserMetricsConsent since device
  // owner consent is not controller by per user logic.
  EXPECT_TRUE(GetLocalStateMetricsConsent());

  // User-Id is never set for device owner.
  EXPECT_THAT(
      g_browser_process->metrics_service()->GetCurrentUserId().has_value(),
      Eq(false));
}

class ChromeOSPerUserManagedOobeConsentTest
    : public ash::OobeBaseTest,
      public ::testing::WithParamInterface<bool> {
 public:
  ChromeOSPerUserManagedOobeConsentTest() = default;

  void SetUpOnMainThread() override {
    ash::LoginDisplayHost::default_host()
        ->GetWizardContext()
        ->is_branded_build = true;

    ash::OobeBaseTest::SetUpOnMainThread();

    // Sets ownership.
    device_policy_helper_.InstallOwnerKey();

    // Configure device policy.
    auto* device_policy = device_policy_helper_.device_policy();
    device_policy->payload().mutable_metrics_enabled()->set_metrics_enabled(
        GetParam());
    device_policy_helper_.RefreshDevicePolicy();

    // Wait for device policies to be synced.
    base::RunLoop().RunUntilIdle();
  }

  bool SetUpUserDataDirectory() override {
    base::FilePath local_state_path =
        metrics::SetUpUserDataDirectoryForTesting(true);
    return !local_state_path.empty();
  }

  ChromeOSPerUserManagedOobeConsentTest(
      const ChromeOSPerUserManagedOobeConsentTest&) = delete;
  ChromeOSPerUserManagedOobeConsentTest& operator=(
      const ChromeOSPerUserManagedOobeConsentTest&) = delete;
  ~ChromeOSPerUserManagedOobeConsentTest() override = default;

  void ValidateCurrentUser(const user_manager::User* user) {}

  // Assumes that a user has logged in.
  void ChangeUserMetricsConsent(bool user_metrics_consent) {
    g_browser_process->metrics_service()->UpdateCurrentUserMetricsConsent(
        user_metrics_consent);
  }

  bool GetLocalStateMetricsConsent() const {
    return g_browser_process->local_state()->GetBoolean(
        prefs::kMetricsReportingEnabled);
  }

  void LoginManagedUser() {
    user_policy_mixin_.RequestPolicyUpdate();

    auto context =
        ash::LoginManagerMixin::CreateDefaultUserContext(managed_user_);
    login_mixin_.LoginAndWaitForActiveSession(context);
    EXPECT_EQ(user_manager::UserManager::Get()->GetLoggedInUsers().size(), 1u);
    EXPECT_EQ(session_manager::SessionManager::Get()->session_state(),
              session_manager::SessionState::ACTIVE);
  }

 private:
  FakeGaiaMixin fake_gaia_{&mixin_host_};
  const ash::LoginManagerMixin::TestUserInfo managed_user_{
      AccountId::FromUserEmailGaiaId(kTestUser1, kTestUser1GaiaId)};
  ash::LoginManagerMixin login_mixin_{&mixin_host_,
                                      {managed_user_},
                                      &fake_gaia_};
  ash::UserPolicyMixin user_policy_mixin_{&mixin_host_,
                                          managed_user_.account_id};

  // Policy and device state.
  policy::DevicePolicyCrosTestHelper device_policy_helper_;
  ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_};
  ash::DeviceStateMixin device_state_{
      &mixin_host_,
      ash::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
};

IN_PROC_BROWSER_TEST_P(ChromeOSPerUserManagedOobeConsentTest,
                       ManagedDeviceDoesNotUsePerUserConsent) {
  PerUserStateManagerChromeOS::SetIsManagedForTesting(true);

  auto* metrics_service = g_browser_process->metrics_service();
  MetricsLogStore* log_store = metrics_service->LogStoreForTest();

  // Pre-login state.
  bool policy_consent = GetParam();
  EXPECT_EQ(ash::StatsReportingController::Get()->IsEnabled(), policy_consent);
  EXPECT_EQ(GetLocalStateMetricsConsent(), policy_consent);
  EXPECT_FALSE(log_store->has_alternate_ongoing_log_store());

  LoginManagedUser();
  ash::OobeScreenExitWaiter(ash::OobeBaseTest::GetFirstSigninScreen()).Wait();

  // Post-login state.
  EXPECT_THAT(user_manager::UserManager::Get()->GetActiveUser()->GetType(),
              Eq(user_manager::UserType::kRegular));
  EXPECT_TRUE(log_store->has_alternate_ongoing_log_store());

  // Should still follow policy_consent.
  EXPECT_EQ(GetLocalStateMetricsConsent(), policy_consent);

  // Users should not have a user id since they do not have control over the
  // metrics consent.
  EXPECT_THAT(metrics_service->GetCurrentUserId(), Eq(std::nullopt));

  // Try to change the user consent.
  metrics_service->UpdateCurrentUserMetricsConsent(!policy_consent);

  // Managed device users cannot control metrics consent.
  EXPECT_EQ(GetLocalStateMetricsConsent(), policy_consent);
}

INSTANTIATE_TEST_SUITE_P(ManagedDeviceDoesNotUsePerUserConsent,
                         ChromeOSPerUserManagedOobeConsentTest,
                         ::testing::Bool());

}  // namespace metrics