chromium/chrome/browser/ash/settings/cros_settings_unittest.cc

// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/settings/cros_settings.h"

#include <map>
#include <memory>
#include <optional>
#include <string>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "chrome/browser/ash/ownership/owner_key_loader.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash.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/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/cros_settings_holder.h"
#include "chrome/browser/ash/settings/device_settings_provider.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/net/fake_nss_service.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/browser_context_helper/annotated_account_id.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/ownership/mock_owner_key_util.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/proto/chrome_device_policy.pb.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/user_manager/user_type.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_utils.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace em = enterprise_management;

namespace ash {

namespace {
// For a user to be recognized as an owner, it needs to be the author of the
// device settings. So use the default user name that DevicePolicyBuilder uses.
const char* const kOwner = policy::PolicyBuilder::kFakeUsername;
constexpr char kUser1[] = "h@xxor";

void NotReached() {
  NOTREACHED_IN_MIGRATION()
      << "This should not be called: cros settings should already be trusted";
}

}  // namespace

class CrosSettingsTest : public testing::Test {
 protected:
  CrosSettingsTest() = default;
  ~CrosSettingsTest() override = default;

  void SetUp() override {
    // Disable owner key migration.
    feature_list_.InitWithFeatures(
        /*enabled_features=*/{kStoreOwnerKeyInPrivateSlot},
        /*disabled_features=*/{kMigrateOwnerKeyToPrivateSlot});

    device_policy_.Build();

    fake_session_manager_client_.set_device_policy(device_policy_.GetBlob());

    // Initialize ProfileHelper including BrowserContextHelper.
    ProfileHelper::Get();

    owner_key_util_->SetPublicKeyFromPrivateKey(
        *device_policy_.GetSigningKey());
    owner_key_util_->ImportPrivateKeyAndSetPublicKey(
        device_policy_.GetSigningKey());
    OwnerSettingsServiceAshFactory::GetInstance()->SetOwnerKeyUtilForTesting(
        owner_key_util_);
    DeviceSettingsService::Get()->SetSessionManager(
        &fake_session_manager_client_, owner_key_util_);
    DeviceSettingsService::Get()->Load();
    task_environment_.RunUntilIdle();
  }

  void TearDown() override {
    DeviceSettingsService::Get()->UnsetSessionManager();
  }

  // Some tests below use an OwnerSettingsService so they can change settings
  // partway through the test - this sets one up for those tests that need it.
  // Other tests below cannot use an OwnerSettingsService, since they change
  // |device_policy_| to something that would not be allowed by
  // OwnerSettingsServiceAsh::FixupLocalOwnerPolicy.
  OwnerSettingsServiceAsh* CreateOwnerSettingsService(
      const std::string& owner_email) {
    const AccountId account_id = AccountId::FromUserEmail(owner_email);
    profile_ = std::make_unique<TestingProfile>();
    profile_->set_profile_name(account_id.GetUserEmail());
    ash::AnnotatedAccountId::Set(profile_.get(), account_id);

    FakeNssService::InitializeForBrowserContext(profile_.get(),
                                                /*enable_system_slot=*/false);
    OwnerSettingsServiceAsh* service =
        OwnerSettingsServiceAshFactory::GetForBrowserContext(profile_.get());
    DCHECK(service);

    service->OnTPMTokenReady();
    task_environment_.RunUntilIdle();
    DCHECK(service->IsOwner());
    return service;
  }

  void StoreDevicePolicy() {
    device_policy_.Build();
    fake_session_manager_client_.set_device_policy(device_policy_.GetBlob());
    DeviceSettingsService::Get()->OwnerKeySet(true);
    task_environment_.RunUntilIdle();
  }

  void ExpectPref(const std::string& pref, const base::Value& expected_value) {
    DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
    // RunUntilIdle ensures that any changes recently made to CrosSettings will
    // be complete by the time that we make the assertions below.
    task_environment_.RunUntilIdle();

    // ExpectPref checks that the given pref has the given value, and that the
    // value is TRUSTED - that means is not just a best-effort value that is
    // being returned until we can load the real value.
    // Calling RunUntilIdle() above ensures that there is enough time to find
    // the TRUSTED values.
    EXPECT_EQ(
        CrosSettingsProvider::TRUSTED,
        CrosSettings::Get()->PrepareTrustedValues(base::BindOnce(&NotReached)));

    const base::Value* pref_value = CrosSettings::Get()->GetPref(pref);
    EXPECT_TRUE(pref_value) << "for pref=" << pref;
    if (pref_value) {
      EXPECT_EQ(expected_value, *pref_value) << "for pref=" << pref;
    }
  }

  bool IsAllowlisted(const std::string& username) {
    return CrosSettings::Get()->FindEmailInList(kAccountsPrefUsers, username,
                                                nullptr);
  }

  bool IsUserAllowed(const std::string& username,
                     const std::optional<user_manager::UserType>& user_type) {
    return CrosSettings::Get()->IsUserAllowlisted(username, nullptr, user_type);
  }

  base::test::ScopedFeatureList feature_list_;
  content::BrowserTaskEnvironment task_environment_{
      content::BrowserTaskEnvironment::IO_MAINLOOP};

  ScopedTestingLocalState local_state_{TestingBrowserProcess::GetGlobal()};
  ScopedStubInstallAttributes scoped_install_attributes_;
  ScopedTestDeviceSettingsService scoped_test_device_settings_;
  CrosSettingsHolder cros_settings_holder_{ash::DeviceSettingsService::Get(),
                                           local_state_.Get()};

  FakeSessionManagerClient fake_session_manager_client_;
  scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util_{
      base::MakeRefCounted<ownership::MockOwnerKeyUtil>()};
  policy::DevicePolicyBuilder device_policy_;
  std::unique_ptr<TestingProfile> profile_;
  base::HistogramTester histogram_tester_;
};

TEST_F(CrosSettingsTest, GetAndSetPref) {
  // False is the expected default value:
  ExpectPref(kDevicePeripheralDataAccessEnabled, base::Value(false));

  // Make sure we can set the value to true:
  auto* oss = CreateOwnerSettingsService(kOwner);
  oss->Set(kDevicePeripheralDataAccessEnabled, base::Value(true));
  ExpectPref(kDevicePeripheralDataAccessEnabled, base::Value(true));
}

TEST_F(CrosSettingsTest, SetAllowlistWithListOps) {
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  device_policy_.payload().mutable_user_allowlist()->clear_user_allowlist();
  StoreDevicePolicy();

  auto* oss = CreateOwnerSettingsService(kOwner);

  base::Value::List original_list;
  original_list.Append(kOwner);
  oss->Set(kAccountsPrefUsers, base::Value(std::move(original_list)));
  task_environment_.RunUntilIdle();

  base::Value::List modified_list;
  modified_list.Append(kOwner);
  modified_list.Append(kUser1);

  // Add some user to the allowlist.
  oss->AppendToList(kAccountsPrefUsers, base::Value(kUser1));
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(modified_list)));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
}

// The following tests check that the allowlist / allow_new_users logic in
// DeviceSettings:Provider::DecodeLoginPolicies still works properly at this
// level, the CrosSettings API.
// They do not use OwnerSettingsService since having a local
// OwnerSettingsService constrains the policies in certain ways - see
// OwnerSettingsServiceAsh::FixupLocalOwnerPolicy.

TEST_F(CrosSettingsTest, AllowAnyUserToSignIn) {
  // Set an empty allowlist.
  device_policy_.payload().mutable_user_allowlist()->clear_user_allowlist();
  // Set allow_new_users to true.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(true);
  StoreDevicePolicy();

  // Expect the same - an empty allowlist and new users allowed.
  ExpectPref(kAccountsPrefUsers, base::Value(base::Value::Type::LIST));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(true));
}

TEST_F(CrosSettingsTest, RestrictSignInToAListOfUsers) {
  // Set a non-empty allowlist.
  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
  // Set allow_new_users to false.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  StoreDevicePolicy();

  // Expect the same - a non-empty allowlist and no new users allowed.
  base::Value::List allowlist;
  allowlist.Append(kOwner);
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
}

TEST_F(CrosSettingsTest, DoNotAllowAnyUserToSignIn) {
  // Set an empty allowlist.
  device_policy_.payload().mutable_user_allowlist()->clear_user_allowlist();
  // Set allow_new_users to false.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  StoreDevicePolicy();

  // Expect the same - an empty allowlist and no new users allowed.
  ExpectPref(kAccountsPrefUsers, base::Value(base::Value::Type::LIST));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
}

TEST_F(CrosSettingsTest, DefaultPolicyValues) {
  // Set an empty allowlist.
  device_policy_.payload().clear_user_allowlist();
  // Clear allow_new_users, so it is not set to true or false.
  device_policy_.payload().mutable_allow_new_users()->clear_allow_new_users();
  StoreDevicePolicy();

  ExpectPref(kAccountsPrefUsers, base::Value(base::Value::Type::LIST));
  // When an empty allowlist is set, allow_new_user defaults to true.
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(true));
}

// This case is not a valid DM server combination, but it is possible
// for consumer devices, it should be semantically equivalent to
// allowing all users to sign in
TEST_F(CrosSettingsTest, ConsumerOwnedDefaultState) {
  // Set a non-empty allowlist.
  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
  // Set allow_new_users to true.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(true);
  StoreDevicePolicy();

  // Expect the same - a non-empty allowlist and new users allowed.
  base::Value::List allowlist;
  allowlist.Append(kOwner);
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(true));
}

// It's possible for the user_allowlist to be not present, and for the
// user_whitelist to be present instead. This test simulates this by
// doing something similar to the "RestrictSignInToAListOfUsers" test
// but using user_whitelist instead
TEST_F(CrosSettingsTest, WhitelistUsedWhenAllowlistNotPresent) {
  // clear user_allowlist
  device_policy_.payload().clear_user_allowlist();
  // set non-empty user_whitelist
  device_policy_.payload().mutable_user_whitelist()->add_user_whitelist(kOwner);
  // Set allow_new_users to false.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  StoreDevicePolicy();

  histogram_tester_.ExpectUniqueSample(kAllowlistCOILFallbackHistogram, true,
                                       1);

  // Expect the same - a non-empty allowlist and no new users allowed.
  base::Value::List allowlist;
  allowlist.Append(kOwner);
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
}

// In cases where both the user_allowlist and the user_whitelist are present,
// we should use the user_allowlist. This test simulates this by
// doing something similar to the "RestrictSignInToAListOfUsers" test
// but providing both user_allowlist and user_whitelist, and asserting that
// user_allowlist is being used.
TEST_F(CrosSettingsTest, AllowlistUsedWhenAllowlistAndWhitelistPresent) {
  // clear user_allowlist
  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kUser1);
  // set non-empty user_whitelist
  device_policy_.payload().mutable_user_whitelist()->add_user_whitelist(kOwner);
  // Set allow_new_users to false.
  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  StoreDevicePolicy();

  histogram_tester_.ExpectUniqueSample(kAllowlistCOILFallbackHistogram, false,
                                       1);

  // Expect the same - a non-empty allowlist and no new users allowed.
  base::Value::List allowlist;
  allowlist.Append(kUser1);
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
}

TEST_F(CrosSettingsTest, FindEmailInList) {
  auto* oss = CreateOwnerSettingsService(kOwner);

  base::Value::List list;
  list.Append("[email protected]");
  list.Append("nodomain");
  list.Append("[email protected]");
  list.Append("[email protected]");

  oss->Set(kAccountsPrefUsers, base::Value(std::move(list)));
  task_environment_.RunUntilIdle();

  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_FALSE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_FALSE(IsAllowlisted("user"));

  EXPECT_TRUE(IsAllowlisted("nodomain"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("NO.DOMAIN"));

  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_TRUE(IsAllowlisted("WITHDOTS"));

  EXPECT_TRUE(IsAllowlisted("[email protected]"));
  EXPECT_FALSE(IsAllowlisted("[email protected]"));
  EXPECT_FALSE(IsAllowlisted("Upper"));
  EXPECT_TRUE(IsAllowlisted("[email protected]"));
}

TEST_F(CrosSettingsTest, FindEmailInListWildcard) {
  auto* oss = CreateOwnerSettingsService(kOwner);

  base::Value::List list;
  list.Append("[email protected]");
  list.Append("*@example.com");

  oss->Set(kAccountsPrefUsers, base::Value(std::move(list)));
  task_environment_.RunUntilIdle();

  bool wildcard_match = false;
  EXPECT_TRUE(CrosSettings::Get()->FindEmailInList(
      kAccountsPrefUsers, "[email protected]", &wildcard_match));
  EXPECT_TRUE(wildcard_match);
  EXPECT_TRUE(CrosSettings::Get()->FindEmailInList(
      kAccountsPrefUsers, "[email protected]", &wildcard_match));
  EXPECT_FALSE(wildcard_match);
  EXPECT_TRUE(CrosSettings::Get()->FindEmailInList(
      kAccountsPrefUsers, "*@example.com", &wildcard_match));
  EXPECT_TRUE(wildcard_match);
}

// DeviceFamilyLinkAccountsAllowed should not have any effect if allowlist is
// not set.
TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithEmptyAllowlist) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kFamilyLinkOnSchoolDevice);

  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  device_policy_.payload().mutable_user_allowlist()->clear_user_allowlist();
  device_policy_.payload()
      .mutable_family_link_accounts_allowed()
      ->set_family_link_accounts_allowed(true);

  StoreDevicePolicy();

  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
  ExpectPref(kAccountsPrefUsers, base::Value(base::Value::Type::LIST));
  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(false));

  EXPECT_FALSE(IsUserAllowed(kUser1, std::nullopt));
  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::UserType::kChild));
  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::UserType::kRegular));
}

// DeviceFamilyLinkAccountsAllowed should not have any effect if the feature is
// disabled.
TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithFeatureDisabled) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndDisableFeature(
      features::kFamilyLinkOnSchoolDevice);

  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
  device_policy_.payload()
      .mutable_family_link_accounts_allowed()
      ->set_family_link_accounts_allowed(true);

  StoreDevicePolicy();

  base::Value::List allowlist;
  allowlist.Append(kOwner);
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(false));

  EXPECT_TRUE(IsUserAllowed(kOwner, std::nullopt));
  EXPECT_FALSE(IsUserAllowed(kUser1, std::nullopt));
  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::UserType::kChild));
  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::UserType::kRegular));
}

TEST_F(CrosSettingsTest, AllowFamilyLinkAccountsWithAllowlist) {
  base::test::ScopedFeatureList scoped_feature_list;
  scoped_feature_list.InitAndEnableFeature(features::kFamilyLinkOnSchoolDevice);

  device_policy_.payload().mutable_allow_new_users()->set_allow_new_users(
      false);
  device_policy_.payload().mutable_user_allowlist()->add_user_allowlist(kOwner);
  device_policy_.payload()
      .mutable_family_link_accounts_allowed()
      ->set_family_link_accounts_allowed(true);

  StoreDevicePolicy();

  base::Value::List allowlist;
  allowlist.Append(kOwner);
  ExpectPref(kAccountsPrefAllowNewUser, base::Value(false));
  ExpectPref(kAccountsPrefUsers, base::Value(std::move(allowlist)));
  ExpectPref(kAccountsPrefFamilyLinkAccountsAllowed, base::Value(true));

  EXPECT_TRUE(IsUserAllowed(kOwner, std::nullopt));
  EXPECT_FALSE(IsUserAllowed(kUser1, std::nullopt));
  EXPECT_TRUE(IsUserAllowed(kUser1, user_manager::UserType::kChild));
  EXPECT_FALSE(IsUserAllowed(kUser1, user_manager::UserType::kRegular));
}

}  // namespace ash