// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string>
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/test/test_future.h"
#include "base/values.h"
#include "chrome/browser/extensions/chrome_test_extension_loader.h"
#include "chrome/browser/extensions/mixin_based_extension_apitest.h"
#include "chrome/browser/feedback/system_logs/log_sources/device_event_log_source.h"
#include "chrome/browser/policy/extension_force_install_mixin.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "components/device_event_log/device_event_log.h"
#include "components/feedback/system_logs/system_logs_source.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/test/policy_builder.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "content/public/test/browser_test.h"
#include "extensions/browser/api/test/test_api.h"
#include "extensions/common/extension_api.h"
#include "extensions/common/switches.h"
#include "extensions/test/result_catcher.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_switches.h"
#include "chrome/browser/ash/login/test/device_state_mixin.h"
#include "chrome/browser/ash/login/test/login_manager_mixin.h"
#include "chrome/browser/ash/login/test/scoped_policy_update.h"
#include "chrome/browser/ash/login/test/session_manager_state_waiter.h"
#include "chrome/browser/ash/policy/core/device_local_account.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 "chromeos/ash/components/browser_context_helper/browser_context_helper.h"
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/startup/browser_init_params.h"
#endif
namespace extensions {
namespace {
constexpr char kApiExtensionRelativePath[] = "extensions/api_test/system_log";
constexpr char kExtensionPemRelativePath[] =
"extensions/api_test/system_log.pem";
// ID associated with the .pem.
constexpr char kExtensionId[] = "ghbglelacokpaehlgjbgdfmmggnihdcf";
constexpr char kDeviceEventLogEntry[] = "device_event_log";
// Test names for when chrome.systemLog is available to the extension (if policy
// installed) and when chrome.systemLog is undefined (user installed).
constexpr char kSystemLogAvailableTestName[] = "SystemLogAvailable";
constexpr char kSystemLogUndefinedTestName[] = "SystemLogUndefined";
#if BUILDFLAG(IS_CHROMEOS_ASH)
const char kManagedAccountId[] = "managed-guest-account@test";
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
void VerifyDeviceEventLogLevel(const std::string& log_level,
bool on_signin_screen = false) {
const std::string produced_logs = device_event_log::GetAsString(
device_event_log::NEWEST_FIRST, /*format=*/"level",
/*types=*/"extensions",
/*max_level=*/device_event_log::LOG_LEVEL_DEBUG, /*max_events=*/1);
const std::string expected_logs =
base::StringPrintf("%s: [%s]%s: Test log message\n", log_level.c_str(),
kExtensionId, on_signin_screen ? "[signin]" : "");
EXPECT_EQ(expected_logs, produced_logs);
}
bool AreLogsForwardedToFeedbackReport(bool on_signin_screen = false) {
auto log_source = std::make_unique<system_logs::DeviceEventLogSource>();
base::test::TestFuture<std::unique_ptr<system_logs::SystemLogsResponse>>
future;
log_source->Fetch(future.GetCallback());
EXPECT_TRUE(future.Wait());
const std::string expected_feedback_log =
base::StringPrintf("[%s]%s: Test log message\n", kExtensionId,
on_signin_screen ? "[signin]" : "");
system_logs::SystemLogsResponse* response = future.Get().get();
const auto device_event_log_iter = response->find(kDeviceEventLogEntry);
return device_event_log_iter != response->end() &&
device_event_log_iter->second.find(expected_feedback_log) !=
std::string::npos;
}
} // namespace
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Verifies the systemLog API logs on the sign-in screen.
class SystemLogSigninScreenApitest
: public MixinBasedExtensionApiTest,
public ::testing::WithParamInterface<bool> {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(ash::switches::kOobeSkipPostLogin);
command_line->AppendSwitchASCII(switches::kAllowlistedExtensionID,
kExtensionId);
MixinBasedExtensionApiTest::SetUpCommandLine(command_line);
}
void SetUpOnMainThread() override {
MixinBasedExtensionApiTest::SetUpOnMainThread();
extension_force_install_mixin_.InitWithDeviceStateMixin(
GetOriginalSigninProfile(), &device_state_mixin_);
}
void SetSystemLogPolicy(bool system_logging_enabled) {
device_state_mixin_.RequestDevicePolicyUpdate()
->policy_payload()
->mutable_deviceextensionssystemlogenabled()
->set_value(system_logging_enabled);
}
void ForceInstallExtension() {
base::FilePath test_dir_path =
base::PathService::CheckedGet(chrome::DIR_TEST_DATA);
EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromSourceDir(
test_dir_path.AppendASCII(kApiExtensionRelativePath),
test_dir_path.AppendASCII(kExtensionPemRelativePath),
ExtensionForceInstallMixin::WaitMode::kLoad));
}
Profile* GetOriginalSigninProfile() {
return Profile::FromBrowserContext(
ash::BrowserContextHelper::Get()->GetSigninBrowserContext())
->GetOriginalProfile();
}
private:
ash::DeviceStateMixin device_state_mixin_{
&mixin_host_,
ash::DeviceStateMixin::State::OOBE_COMPLETED_CLOUD_ENROLLED};
ash::LoginManagerMixin login_manager_mixin_{&mixin_host_};
ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
};
// Logs EVENT or DEBUG extension logs depending on the
// DeviceExtensionsSystemLogEnabled policy.
IN_PROC_BROWSER_TEST_P(SystemLogSigninScreenApitest, AddLogFromSignInScreen) {
const bool system_logging_enabled = GetParam();
SetSystemLogPolicy(system_logging_enabled);
SetCustomArg(kSystemLogAvailableTestName);
ResultCatcher catcher;
ForceInstallExtension();
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
const std::string log_level = system_logging_enabled ? "DEBUG" : "EVENT";
VerifyDeviceEventLogLevel(log_level, /*on_signin_screen=*/true);
// Logs are forwarded to the feedback report if they are added to the device
// event log with an EVENT log level. Otherwise the logs will be added to the
// feedback report via the system log file.
EXPECT_EQ(AreLogsForwardedToFeedbackReport(/*on_signin_screen=*/true),
!system_logging_enabled);
}
INSTANTIATE_TEST_SUITE_P(All,
SystemLogSigninScreenApitest,
/*system_logging_enabled=*/testing::Bool());
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
// Verifies the systemLog API logs in user sessions.
class SystemLogUserSessionApitest : public MixinBasedExtensionApiTest,
public ::testing::WithParamInterface<bool> {
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
MixinBasedExtensionApiTest::SetUpCommandLine(command_line);
command_line->AppendSwitchASCII(switches::kAllowlistedExtensionID,
kExtensionId);
}
void SetUpInProcessBrowserTestFixture() override {
MixinBasedExtensionApiTest::SetUpInProcessBrowserTestFixture();
mock_policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
mock_policy_provider_.SetAutoRefresh();
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&mock_policy_provider_);
}
void SetUpOnMainThread() override {
extension_force_install_mixin_.InitWithMockPolicyProvider(
profile(), &mock_policy_provider_);
MixinBasedExtensionApiTest::SetUpOnMainThread();
}
void SetSystemLogPolicy(bool system_logging_enabled) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
policy::PolicyMap policy_map =
mock_policy_provider_.policies()
.Get(policy::PolicyNamespace(policy::POLICY_DOMAIN_CHROME,
/*component_id=*/std::string()))
.Clone();
policy_map.Set(policy::key::kDeviceExtensionsSystemLogEnabled,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE,
policy::POLICY_SOURCE_CLOUD, base::Value(true), nullptr);
mock_policy_provider_.UpdateChromePolicy(policy_map);
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
auto params = chromeos::BrowserInitParams::GetForTests()->Clone();
params->session_type = crosapi::mojom::SessionType::kRegularSession;
params->device_settings = crosapi::mojom::DeviceSettings::New();
params->device_settings->device_extensions_system_log_enabled =
system_logging_enabled
? crosapi::mojom::DeviceSettings::OptionalBool::kTrue
: crosapi::mojom::DeviceSettings::OptionalBool::kFalse;
chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
#endif
}
void ForceInstallExtension() {
base::FilePath test_dir_path =
base::PathService::CheckedGet(chrome::DIR_TEST_DATA);
EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromSourceDir(
test_dir_path.AppendASCII(kApiExtensionRelativePath),
test_dir_path.AppendASCII(kExtensionPemRelativePath),
ExtensionForceInstallMixin::WaitMode::kLoad));
}
private:
ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
testing::NiceMock<policy::MockConfigurationPolicyProvider>
mock_policy_provider_;
};
// Logs EVENT extension logs irrespective of the
// DeviceExtensionsSystemLogEnabled policy.
IN_PROC_BROWSER_TEST_P(SystemLogUserSessionApitest, AddLogFromUserSession) {
const bool system_logging_enabled = GetParam();
SetSystemLogPolicy(system_logging_enabled);
SetCustomArg(kSystemLogAvailableTestName);
ResultCatcher catcher;
ForceInstallExtension();
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
// Logs are always added to the device event log buffer with the EVENT level.
VerifyDeviceEventLogLevel("EVENT");
// Logs are always forwarded to the feedback report via the device event log
// buffer.
EXPECT_TRUE(AreLogsForwardedToFeedbackReport());
}
IN_PROC_BROWSER_TEST_P(SystemLogUserSessionApitest,
DeniesNonPolicyInstalledExtensions) {
const bool system_logging_enabled = GetParam();
SetSystemLogPolicy(system_logging_enabled);
SetCustomArg(kSystemLogUndefinedTestName);
ResultCatcher catcher;
// Add user installed extension.
extensions::ChromeTestExtensionLoader loader(profile());
base::FilePath extension_path =
base::PathService::CheckedGet(chrome::DIR_TEST_DATA)
.AppendASCII(kApiExtensionRelativePath);
loader.set_location(extensions::mojom::ManifestLocation::kInternal);
loader.set_pack_extension(true);
loader.set_ignore_manifest_warnings(true);
loader.LoadExtension(extension_path);
EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
}
INSTANTIATE_TEST_SUITE_P(All,
SystemLogUserSessionApitest,
/*system_logging_enabled=*/testing::Bool());
// Verifies the systemLog API logs in managed guest sessions.
#if BUILDFLAG(IS_CHROMEOS_ASH)
class SystemLogManagedGuestSessionApitest
: public policy::DevicePolicyCrosBrowserTest,
public ::testing::WithParamInterface<bool> {
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
class SystemLogManagedGuestSessionApitest
: public MixinBasedExtensionApiTest,
public ::testing::WithParamInterface<bool> {
#endif
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
DevicePolicyCrosBrowserTest::SetUpCommandLine(command_line);
command_line->AppendSwitch(ash::switches::kLoginManager);
command_line->AppendSwitch(ash::switches::kForceLoginManagerInTests);
command_line->AppendSwitch(ash::switches::kOobeSkipPostLogin);
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
MixinBasedExtensionApiTest::SetUpCommandLine(command_line);
#endif
command_line->AppendSwitchASCII(switches::kAllowlistedExtensionID,
kExtensionId);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void SetSystemLogPolicy(bool system_logging_enabled) {
SetDevicePolicies(system_logging_enabled);
ash::test::WaitForPrimaryUserSessionStart();
Profile* profile = GetActiveUserProfile();
SetUpUserPolicyBuilderForPublicAccount(&user_policy_builder_);
extension_force_install_mixin_.InitWithEmbeddedPolicyMixin(
profile, &policy_test_server_mixin_, &user_policy_builder_,
kManagedAccountId, policy::dm_protocol::kChromePublicAccountPolicyType);
}
void SetDevicePolicies(bool system_logging_enabled) {
enterprise_management::ChromeDeviceSettingsProto& proto(
device_policy()->payload());
// Set Managed Guest Session policy.
enterprise_management::DeviceLocalAccountsProto* device_local_accounts =
proto.mutable_device_local_accounts();
enterprise_management::DeviceLocalAccountInfoProto* const account =
device_local_accounts->add_account();
account->set_account_id(kManagedAccountId);
account->set_type(enterprise_management::DeviceLocalAccountInfoProto::
ACCOUNT_TYPE_PUBLIC_SESSION);
device_local_accounts->set_auto_login_id(kManagedAccountId);
device_local_accounts->set_auto_login_delay(0);
// Set System Log policy.
proto.mutable_deviceextensionssystemlogenabled()->set_value(
system_logging_enabled);
RefreshDevicePolicy();
policy_test_server_mixin_.UpdateDevicePolicy(proto);
}
void SetUpUserPolicyBuilderForPublicAccount(
policy::UserPolicyBuilder* user_policy_builder) {
enterprise_management::PolicyData& policy_data =
user_policy_builder->policy_data();
policy_data.set_public_key_version(1);
policy_data.set_policy_type(
policy::dm_protocol::kChromePublicAccountPolicyType);
policy_data.set_username(kManagedAccountId);
policy_data.set_settings_entity_id(kManagedAccountId);
user_policy_builder->SetDefaultSigningKey();
}
Profile* GetActiveUserProfile() {
const user_manager::User* active_user =
user_manager::UserManager::Get()->GetActiveUser();
return Profile::FromBrowserContext(
ash::BrowserContextHelper::Get()->GetBrowserContextByUser(active_user));
}
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
void SetUpInProcessBrowserTestFixture() override {
MixinBasedExtensionApiTest::SetUpInProcessBrowserTestFixture();
mock_policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
mock_policy_provider_.SetAutoRefresh();
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
&mock_policy_provider_);
}
void SetUpOnMainThread() override {
extension_force_install_mixin_.InitWithMockPolicyProvider(
profile(), &mock_policy_provider_);
MixinBasedExtensionApiTest::SetUpOnMainThread();
}
void SetSystemLogPolicy(bool system_logging_enabled) {
auto params = chromeos::BrowserInitParams::GetForTests()->Clone();
params->session_type = crosapi::mojom::SessionType::kPublicSession;
params->device_settings = crosapi::mojom::DeviceSettings::New();
params->device_settings->device_extensions_system_log_enabled =
system_logging_enabled
? crosapi::mojom::DeviceSettings::OptionalBool::kTrue
: crosapi::mojom::DeviceSettings::OptionalBool::kFalse;
chromeos::BrowserInitParams::SetInitParamsForTests(std::move(params));
}
#endif
void ForceInstallExtension() {
base::FilePath test_dir_path =
base::PathService::CheckedGet(chrome::DIR_TEST_DATA);
EXPECT_TRUE(extension_force_install_mixin_.ForceInstallFromSourceDir(
test_dir_path.AppendASCII(kApiExtensionRelativePath),
test_dir_path.AppendASCII(kExtensionPemRelativePath),
ExtensionForceInstallMixin::WaitMode::kLoad));
}
void SetTestCustomArg(const std::string custom_arg) {
config_.Set("customArg", base::Value(custom_arg));
extensions::TestGetConfigFunction::set_test_config_state(&config_);
}
protected:
#if BUILDFLAG(IS_CHROMEOS_ASH)
ash::EmbeddedPolicyTestServerMixin policy_test_server_mixin_{&mixin_host_};
policy::UserPolicyBuilder user_policy_builder_;
#else // BUILDFLAG(IS_CHROMEOS_LACROS)
testing::NiceMock<policy::MockConfigurationPolicyProvider>
mock_policy_provider_;
#endif
base::Value::Dict config_;
ExtensionForceInstallMixin extension_force_install_mixin_{&mixin_host_};
};
// Logs EVENT or DEBUG extension logs depending on the
// DeviceExtensionsSystemLogEnabled policy.
IN_PROC_BROWSER_TEST_P(SystemLogManagedGuestSessionApitest,
AddLogFromManagedGuestSession) {
const bool system_logging_enabled = GetParam();
SetSystemLogPolicy(system_logging_enabled);
SetTestCustomArg(kSystemLogAvailableTestName);
ResultCatcher catcher;
ForceInstallExtension();
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
const std::string log_level = system_logging_enabled ? "DEBUG" : "EVENT";
VerifyDeviceEventLogLevel(log_level);
// Logs are forwarded to the feedback report if they are added to the device
// event log with an EVENT log level. Otherwise the logs will be added to the
// feedback report via the system log file.
EXPECT_EQ(AreLogsForwardedToFeedbackReport(), !system_logging_enabled);
}
INSTANTIATE_TEST_SUITE_P(All,
SystemLogManagedGuestSessionApitest,
/*system_logging_enabled=*/testing::Bool());
} // namespace extensions