// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <vector>
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/ranges/algorithm.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/ash/extensions/users_private/users_private_delegate.h"
#include "chrome/browser/ash/extensions/users_private/users_private_delegate_factory.h"
#include "chrome/browser/ash/login/lock/screen_locker.h"
#include "chrome/browser/ash/login/lock/screen_locker_tester.h"
#include "chrome/browser/ash/login/test/oobe_base_test.h"
#include "chrome/browser/ash/ownership/owner_settings_service_ash_factory.h"
#include "chrome/browser/ash/settings/scoped_testing_cros_settings.h"
#include "chrome/browser/ash/settings/stub_cros_settings_provider.h"
#include "chrome/browser/extensions/api/settings_private/prefs_util.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/api/users_private.h"
#include "chromeos/ash/components/install_attributes/stub_install_attributes.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/ownership/mock_owner_key_util.h"
#include "components/prefs/pref_service.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "crypto/rsa_private_key.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/common/switches.h"
namespace extensions {
namespace {
class TestPrefsUtil : public PrefsUtil {
public:
explicit TestPrefsUtil(Profile* profile) : PrefsUtil(profile) {}
std::optional<api::settings_private::PrefObject> GetPref(
const std::string& name) override {
if (name != "cros.accounts.users")
return PrefsUtil::GetPref(name);
api::settings_private::PrefObject pref_object;
pref_object.key = name;
pref_object.type = api::settings_private::PrefType::kList;
base::Value::List value;
for (auto& email : user_list_) {
value.Append(email);
}
pref_object.value = base::Value(std::move(value));
return pref_object;
}
bool AppendToListCrosSetting(const std::string& pref_name,
const base::Value& value) override {
std::string email;
if (value.is_string())
email = value.GetString();
for (auto& user : user_list_) {
if (email == user)
return false;
}
user_list_.push_back(email);
return true;
}
bool RemoveFromListCrosSetting(const std::string& pref_name,
const base::Value& value) override {
std::string email;
if (value.is_string())
email = value.GetString();
auto iter = base::ranges::find(user_list_, email);
if (iter != user_list_.end())
user_list_.erase(iter);
return true;
}
private:
std::vector<std::string> user_list_;
};
class TestDelegate : public UsersPrivateDelegate {
public:
explicit TestDelegate(Profile* profile) : UsersPrivateDelegate(profile) {
profile_ = profile;
prefs_util_ = nullptr;
}
TestDelegate(const TestDelegate&) = delete;
TestDelegate& operator=(const TestDelegate&) = delete;
~TestDelegate() override = default;
PrefsUtil* GetPrefsUtil() override {
if (!prefs_util_)
prefs_util_ = std::make_unique<TestPrefsUtil>(profile_);
return prefs_util_.get();
}
private:
raw_ptr<Profile, LeakedDanglingUntriaged> profile_; // weak
std::unique_ptr<TestPrefsUtil> prefs_util_;
};
class UsersPrivateApiTest : public ExtensionApiTest {
public:
UsersPrivateApiTest() {
// Mock owner key pairs. Note this needs to happen before
// OwnerSettingsServiceAsh is created.
scoped_refptr<ownership::MockOwnerKeyUtil> owner_key_util =
new ownership::MockOwnerKeyUtil();
owner_key_util->ImportPrivateKeyAndSetPublicKey(
crypto::RSAPrivateKey::Create(2048));
ash::OwnerSettingsServiceAshFactory::GetInstance()
->SetOwnerKeyUtilForTesting(owner_key_util);
scoped_testing_cros_settings_.device_settings()->Set(
ash::kDeviceOwner, base::Value("[email protected]"));
}
UsersPrivateApiTest(const UsersPrivateApiTest&) = delete;
UsersPrivateApiTest& operator=(const UsersPrivateApiTest&) = delete;
~UsersPrivateApiTest() override = default;
static std::unique_ptr<KeyedService> GetUsersPrivateDelegate(
content::BrowserContext* profile) {
CHECK(s_test_delegate_);
return base::WrapUnique(s_test_delegate_);
}
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
if (!s_test_delegate_)
s_test_delegate_ = new TestDelegate(profile());
UsersPrivateDelegateFactory::GetInstance()->SetTestingFactory(
profile(),
base::BindRepeating(&UsersPrivateApiTest::GetUsersPrivateDelegate));
content::RunAllPendingInMessageLoop();
}
protected:
bool RunSubtest(const std::string& subtest) {
const std::string extension_url = "main.html?" + subtest;
return RunExtensionTest("users_private",
{.extension_url = extension_url.c_str()},
{.load_as_component = true});
}
// Static pointer to the TestDelegate so that it can be accessed in
// GetUsersPrivateDelegate() passed to SetTestingFactory().
static TestDelegate* s_test_delegate_;
private:
ash::ScopedStubInstallAttributes scoped_stub_install_attributes_;
ash::ScopedTestingCrosSettings scoped_testing_cros_settings_;
};
// static
TestDelegate* UsersPrivateApiTest::s_test_delegate_ = nullptr;
class LoginStatusTestConfig {
public:
LoginStatusTestConfig() = default;
~LoginStatusTestConfig() = default;
void Init() {
extensions::TestGetConfigFunction::set_test_config_state(&test_config_);
}
void Reset() {
extensions::TestGetConfigFunction::set_test_config_state(nullptr);
}
void SetConfig(bool logged_in, bool screen_locked) {
test_config_.SetByDottedPath("loginStatus.isLoggedIn",
base::Value(logged_in));
test_config_.SetByDottedPath("loginStatus.isScreenLocked",
base::Value(screen_locked));
}
private:
base::Value::Dict test_config_;
};
class UsersPrivateApiLoginStatusTest : public ExtensionApiTest {
protected:
void SetUpOnMainThread() override {
ExtensionApiTest::SetUpOnMainThread();
test_config_.Init();
test_config_.SetConfig(true /*logged_in*/, false /*screen_locked*/);
}
void TearDownOnMainThread() override {
ExtensionApiTest::TearDownOnMainThread();
test_config_.Reset();
}
LoginStatusTestConfig test_config_;
};
class UsersPrivateApiLockStatusTest : public UsersPrivateApiLoginStatusTest {
protected:
void SetUpOnMainThread() override {
UsersPrivateApiLoginStatusTest::SetUpOnMainThread();
test_config_.SetConfig(true /*logged_in*/, true /*screen_locked*/);
}
};
} // namespace
IN_PROC_BROWSER_TEST_F(UsersPrivateApiTest, AddUser) {
EXPECT_TRUE(RunSubtest("addUser")) << message_;
}
IN_PROC_BROWSER_TEST_F(UsersPrivateApiTest, AddAndRemoveUsers) {
EXPECT_TRUE(RunSubtest("addAndRemoveUsers")) << message_;
}
IN_PROC_BROWSER_TEST_F(UsersPrivateApiTest, IsUserInList) {
EXPECT_TRUE(RunSubtest("isUserInList")) << message_;
}
IN_PROC_BROWSER_TEST_F(UsersPrivateApiTest, IsOwner) {
EXPECT_TRUE(RunSubtest("isOwner")) << message_;
}
// User profile - logged in, screen not locked.
IN_PROC_BROWSER_TEST_F(UsersPrivateApiLoginStatusTest, User) {
EXPECT_TRUE(RunExtensionTest("users_private",
{.extension_url = "main.html?getLoginStatus"},
{.load_as_component = true}))
<< message_;
}
// TODO(achuith): Signin profile - not logged in, screen not locked.
// Screenlock - logged in, screen locked.
IN_PROC_BROWSER_TEST_F(UsersPrivateApiLockStatusTest, ScreenLock) {
ash::ScreenLockerTester().Lock();
EXPECT_TRUE(RunExtensionTest("users_private",
{.extension_url = "main.html?getLoginStatus"},
{.load_as_component = true}))
<< message_;
}
} // namespace extensions