blob: d2fc1782fcecc0ee0f58ae499930c9f9a0ebca09 [file] [log] [blame]
// 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 "chrome/browser/ui/webui/policy/policy_ui_handler.h"
#include <stddef.h>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/barrier_closure.h"
#include "base/check.h"
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_string_value_serializer.h"
#include "base/json/json_writer.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/enterprise/reporting/cloud_profile_reporting_service.h"
#include "chrome/browser/enterprise/reporting/cloud_profile_reporting_service_factory.h"
#include "chrome/browser/enterprise/util/affiliation.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/policy/policy_ui_utils.h"
#include "chrome/browser/policy/policy_value_and_status_aggregator.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/schema_registry_service.h"
#include "chrome/browser/policy/status_provider/status_provider_util.h"
#include "chrome/browser/policy/value_provider/chrome_policies_value_provider.h"
#include "chrome/browser/policy/value_provider/value_provider_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/chrome_select_file_policy.h"
#include "chrome/browser/ui/webui/policy/policy_ui.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/grit/branded_strings.h"
#include "components/crx_file/id_util.h"
#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#include "components/enterprise/browser/reporting/common_pref_names.h"
#include "components/policy/core/browser/configuration_policy_handler_list.h"
#include "components/policy/core/browser/policy_conversions.h"
#include "components/policy/core/browser/webui/json_generation.h"
#include "components/policy/core/browser/webui/policy_webui_constants.h"
#include "components/policy/core/browser/webui/statistics_collector.h"
#include "components/policy/core/common/cloud/cloud_policy_manager.h"
#include "components/policy/core/common/cloud/cloud_policy_refresh_scheduler.h"
#include "components/policy/core/common/cloud/cloud_policy_util.h"
#include "components/policy/core/common/local_test_policy_loader.h"
#include "components/policy/core/common/local_test_policy_provider.h"
#include "components/policy/core/common/policy_details.h"
#include "components/policy/core/common/policy_logger.h"
#include "components/policy/core/common/policy_pref_names.h"
#include "components/policy/core/common/policy_scheduler.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/core/common/remote_commands/remote_commands_service.h"
#include "components/policy/core/common/schema.h"
#include "components/policy/core/common/schema_map.h"
#include "components/policy/policy_constants.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ash/policy/core/browser_policy_connector_ash.h"
#include "chrome/browser/ash/policy/core/device_cloud_policy_store_ash.h"
#include "chrome/browser/ash/policy/core/device_local_account_policy_service.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/ash/policy/off_hours/device_off_hours_controller.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "components/user_manager/user_manager.h"
#else
#include "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#endif
namespace {
// Key under which extension policies are grouped in JSON policy exports.
constexpr char kExtensionsKey[] = "extensions";
} // namespace
PolicyUIHandler::PolicyUIHandler() = default;
PolicyUIHandler::~PolicyUIHandler() {
policy::RecordPolicyUIButtonUsage(reload_policies_count_,
export_to_json_count_, copy_to_json_count_,
upload_report_count_);
}
void PolicyUIHandler::AddCommonLocalizedStringsToSource(
content::WebUIDataSource* source) {
source->AddLocalizedStrings(policy::kPolicySources);
static constexpr webui::LocalizedString kStrings[] = {
{"conflict", IDS_POLICY_LABEL_CONFLICT},
{"superseding", IDS_POLICY_LABEL_SUPERSEDING},
{"conflictValue", IDS_POLICY_LABEL_CONFLICT_VALUE},
{"supersededValue", IDS_POLICY_LABEL_SUPERSEDED_VALUE},
{"headerLevel", IDS_POLICY_HEADER_LEVEL},
{"headerName", IDS_POLICY_HEADER_NAME},
{"headerScope", IDS_POLICY_HEADER_SCOPE},
{"headerSource", IDS_POLICY_HEADER_SOURCE},
{"headerStatus", IDS_POLICY_HEADER_STATUS},
{"headerValue", IDS_POLICY_HEADER_VALUE},
{"warning", IDS_POLICY_HEADER_WARNING},
{"levelMandatory", IDS_POLICY_LEVEL_MANDATORY},
{"levelRecommended", IDS_POLICY_LEVEL_RECOMMENDED},
{"error", IDS_POLICY_LABEL_ERROR},
{"deprecated", IDS_POLICY_LABEL_DEPRECATED},
{"future", IDS_POLICY_LABEL_FUTURE},
{"info", IDS_POLICY_LABEL_INFO},
{"ignored", IDS_POLICY_LABEL_IGNORED},
{"notSpecified", IDS_POLICY_NOT_SPECIFIED},
{"ok", IDS_POLICY_OK},
{"scopeDevice", IDS_POLICY_SCOPE_DEVICE},
{"scopeUser", IDS_POLICY_SCOPE_USER},
{"scopeAllUsers", IDS_POLICY_SCOPE_ALL_USERS},
{"title", IDS_POLICY_TITLE},
{"unknown", IDS_POLICY_UNKNOWN},
{"unset", IDS_POLICY_UNSET},
{"value", IDS_POLICY_LABEL_VALUE},
{"sourceDefault", IDS_POLICY_SOURCE_DEFAULT},
{"reloadingPolicies", IDS_POLICY_RELOADING_POLICIES},
{"reloadPoliciesDone", IDS_POLICY_RELOAD_POLICIES_DONE},
{"copyPoliciesDone", IDS_COPY_POLICIES_DONE},
{"exportPoliciesDone", IDS_EXPORT_POLICIES_JSON_DONE},
{"sort", IDS_POLICY_TABLE_COLUMN_SORT},
{"sortAscending", IDS_POLICY_TABLE_COLUMN_SORT_ASCENDING},
{"sortDescending", IDS_POLICY_TABLE_COLUMN_SORT_DESCENDING},
#if !BUILDFLAG(IS_CHROMEOS)
{"reportUploading", IDS_REPORT_UPLOADING},
{"reportUploaded", IDS_REPORT_UPLOADED},
#endif // !BUILDFLAG(IS_CHROMEOS)
};
source->AddLocalizedStrings(kStrings);
source->UseStringsJs();
}
void PolicyUIHandler::RegisterMessages() {
auto update_callback(base::BindRepeating(&PolicyUIHandler::SendStatus,
base::Unretained(this)));
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(g_browser_process->local_state());
pref_change_registrar_->Add(
enterprise_reporting::kLastUploadSucceededTimestamp, update_callback);
policy_value_and_status_aggregator_ = policy::PolicyValueAndStatusAggregator::
CreateDefaultPolicyValueAndStatusAggregator(Profile::FromWebUI(web_ui()));
policy_value_and_status_observation_.Observe(
policy_value_and_status_aggregator_.get());
const auto* policy_schema_registry_service =
Profile::FromWebUI(web_ui())->GetPolicySchemaRegistryService();
// In case web_ui() represents an OffTheRecordProfileImpl object (like in a
// guest session), there's no PolicySchemaRegistryService, so nothing to
// observe there. The profile has no policies anyway.
if (policy_schema_registry_service) {
schema_registry_observation_.Observe(
policy_schema_registry_service->registry());
}
web_ui()->RegisterMessageCallback(
"exportPoliciesJSON",
base::BindRepeating(&PolicyUIHandler::HandleExportPoliciesJson,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"listenPoliciesUpdates",
base::BindRepeating(&PolicyUIHandler::HandleListenPoliciesUpdates,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"reloadPolicies",
base::BindRepeating(&PolicyUIHandler::HandleReloadPolicies,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"copyPoliciesJSON",
base::BindRepeating(&PolicyUIHandler::HandleCopyPoliciesJson,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setLocalTestPolicies",
base::BindRepeating(&PolicyUIHandler::HandleSetLocalTestPolicies,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"revertLocalTestPolicies",
base::BindRepeating(&PolicyUIHandler::HandleRevertLocalTestPolicies,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getPolicyLogs",
base::BindRepeating(&PolicyUIHandler::HandleGetPolicyLogs,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"restartBrowser",
base::BindRepeating(&PolicyUIHandler::HandleRestartBrowser,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setUserAffiliation",
base::BindRepeating(&PolicyUIHandler::HandleSetUserAffiliated,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getAppliedTestPolicies",
base::BindRepeating(&PolicyUIHandler::HandleGetAppliedTestPolicies,
base::Unretained(this)));
#if !BUILDFLAG(IS_CHROMEOS)
web_ui()->RegisterMessageCallback(
"uploadReport", base::BindRepeating(&PolicyUIHandler::HandleUploadReport,
base::Unretained(this)));
#endif // !BUILDFLAG(IS_CHROMEOS)
}
void PolicyUIHandler::OnPolicyValueAndStatusChanged() {
SendPolicies();
// Send also the status to UI because when policy value is updated, policy
// status also might be updated and PolicyStatusProviders may not be listening
// this change.
SendStatus();
}
void PolicyUIHandler::OnSchemaRegistryUpdated(bool has_new_schemas) {
SendSchema();
}
void PolicyUIHandler::SendSchema() {
Profile* profile = Profile::FromWebUI(web_ui());
if (!IsJavascriptAllowed() || !PolicyUI::ShouldLoadTestPage(profile)) {
return;
}
FireWebUIListener("schema-updated", PolicyUI::GetSchema(profile));
}
void PolicyUIHandler::HandleExportPoliciesJson(const base::Value::List& args) {
export_to_json_count_ += 1;
if (!IsJavascriptAllowed()) {
DVLOG(1) << "Tried to export policies as JSON but executing JavaScript is "
"not allowed.";
return;
}
FireWebUIListener("download-json", base::Value(GetPoliciesAsJson()));
}
void PolicyUIHandler::HandleListenPoliciesUpdates(
const base::Value::List& args) {
// Send initial policy values and status to UI page.
AllowJavascript();
SendSchema();
SendPolicies();
SendStatus();
}
void PolicyUIHandler::HandleReloadPolicies(const base::Value::List& args) {
reload_policies_count_ += 1;
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Allow user to manually fetch remote commands. Useful for testing or when
// the invalidation service is not working properly.
policy::CloudPolicyManager* const device_manager =
g_browser_process->platform_part()
->browser_policy_connector_ash()
->GetDeviceCloudPolicyManager();
Profile* const profile = Profile::FromWebUI(web_ui());
policy::CloudPolicyManager* const user_manager =
profile->GetUserCloudPolicyManagerAsh();
// Fetch both device and user remote commands.
for (policy::CloudPolicyManager* manager : {device_manager, user_manager}) {
// Active Directory management has no CloudPolicyManager.
if (manager) {
policy::RemoteCommandsService* const remote_commands_service =
manager->core()->remote_commands_service();
if (remote_commands_service) {
remote_commands_service->FetchRemoteCommands();
}
}
}
#endif
policy_value_and_status_aggregator_->Refresh();
}
void PolicyUIHandler::HandleCopyPoliciesJson(const base::Value::List& args) {
copy_to_json_count_ += 1;
std::string policies_json = GetPoliciesAsJson();
ui::ScopedClipboardWriter scw(ui::ClipboardBuffer::kCopyPaste);
scw.WriteText(base::UTF8ToUTF16(policies_json));
}
void PolicyUIHandler::HandleSetLocalTestPolicies(
const base::Value::List& args) {
std::string policies = args[1].GetString();
policy::LocalTestPolicyProvider* local_test_provider =
static_cast<policy::LocalTestPolicyProvider*>(
g_browser_process->browser_policy_connector()
->local_test_policy_provider());
CHECK(local_test_provider);
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
std::string profile_separation_policy_response = args[2].GetString();
Profile::FromWebUI(web_ui())->GetPrefs()->ClearPref(
prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage);
Profile::FromWebUI(web_ui())->GetPrefs()->SetDefaultPrefValue(
prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage,
base::Value(profile_separation_policy_response));
#endif
Profile::FromWebUI(web_ui())
->GetProfilePolicyConnector()
->UseLocalTestPolicyProvider();
local_test_provider->LoadJsonPolicies(policies);
AllowJavascript();
ResolveJavascriptCallback(args[0], true);
}
void PolicyUIHandler::HandleRevertLocalTestPolicies(
const base::Value::List& args) {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_CHROMEOS)
Profile::FromWebUI(web_ui())->GetPrefs()->ClearPref(
prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage);
Profile::FromWebUI(web_ui())->GetPrefs()->SetDefaultPrefValue(
prefs::kUserCloudSigninPolicyResponseFromPolicyTestPage,
base::Value(std::string()));
#endif
Profile::FromWebUI(web_ui())
->GetProfilePolicyConnector()
->RevertUseLocalTestPolicyProvider();
}
void PolicyUIHandler::HandleRestartBrowser(const base::Value::List& args) {
CHECK(args.size() == 2);
std::string policies = args[1].GetString();
// Set policies to preference
PrefService* prefs = g_browser_process->local_state();
prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
policies);
// Restart browser
chrome::AttemptRestart();
}
void PolicyUIHandler::HandleSetUserAffiliated(const base::Value::List& args) {
CHECK_EQ(static_cast<int>(args.size()), 2);
bool affiliated = args[1].GetBool();
auto* local_test_provider = static_cast<policy::LocalTestPolicyProvider*>(
g_browser_process->browser_policy_connector()
->local_test_policy_provider());
local_test_provider->SetUserAffiliated(affiliated);
AllowJavascript();
ResolveJavascriptCallback(args[0], true);
}
void PolicyUIHandler::HandleGetAppliedTestPolicies(
const base::Value::List& args) {
CHECK_EQ(static_cast<int>(args.size()), 1);
auto* local_test_provider = static_cast<policy::LocalTestPolicyProvider*>(
g_browser_process->browser_policy_connector()
->local_test_policy_provider());
AllowJavascript();
ResolveJavascriptCallback(args[0], local_test_provider->GetPolicies());
}
void PolicyUIHandler::HandleGetPolicyLogs(const base::Value::List& args) {
AllowJavascript();
ResolveJavascriptCallback(args[0],
policy::PolicyLogger::GetInstance()->GetAsList());
}
#if !BUILDFLAG(IS_CHROMEOS)
void PolicyUIHandler::HandleUploadReport(const base::Value::List& args) {
upload_report_count_ += 1;
DCHECK_EQ(1u, args.size());
std::string callback_id = args[0].GetString();
auto* report_scheduler = g_browser_process->browser_policy_connector()
->chrome_browser_cloud_management_controller()
->report_scheduler();
auto* profile_report_scheduler =
enterprise_reporting::CloudProfileReportingServiceFactory::GetForProfile(
Profile::FromWebUI(web_ui()))
->report_scheduler();
CHECK(profile_report_scheduler);
if (report_scheduler) {
const auto on_report_uploaded = base::BarrierClosure(
2, base::BindOnce(&PolicyUIHandler::OnReportUploaded,
weak_factory_.GetWeakPtr(), callback_id));
report_scheduler->UploadFullReport(on_report_uploaded);
profile_report_scheduler->UploadFullReport(on_report_uploaded);
} else {
profile_report_scheduler->UploadFullReport(
base::BindOnce(&PolicyUIHandler::OnReportUploaded,
weak_factory_.GetWeakPtr(), callback_id));
}
}
#endif // !BUILDFLAG(IS_CHROMEOS)
void PolicyUIHandler::SendPolicies() {
if (!IsJavascriptAllowed()) {
return;
}
FireWebUIListener(
"policies-updated",
base::Value(
policy_value_and_status_aggregator_->GetAggregatedPolicyNames()),
base::Value(
policy_value_and_status_aggregator_->GetAggregatedPolicyValues()));
}
void PolicyUIHandler::SendStatus() {
if (!IsJavascriptAllowed()) {
return;
}
FireWebUIListener(
"status-updated",
policy_value_and_status_aggregator_->GetAggregatedPolicyStatus());
}
#if !BUILDFLAG(IS_CHROMEOS)
void PolicyUIHandler::OnReportUploaded(const std::string& callback_id) {
if (!IsJavascriptAllowed()) {
return;
}
ResolveJavascriptCallback(base::Value(callback_id),
/*response=*/base::Value());
SendStatus();
}
#endif // !BUILDFLAG(IS_CHROMEOS)
std::string PolicyUIHandler::GetPoliciesAsJson() {
base::Value::Dict policy_values =
policy_value_and_status_aggregator_->GetAggregatedPolicyValues();
policy_values.Remove(policy::kPolicyIdsKey);
base::Value::Dict* extensions_dict =
policy_values.FindDict(policy::kPolicyValuesKey)
->EnsureDict(kExtensionsKey);
// Iterate through all policy headings to identify extension policies.
for (auto entry : *policy_values.FindDict(policy::kPolicyValuesKey)) {
if (crx_file::id_util::IdIsValid(entry.first)) {
extensions_dict->Set(entry.first, base::Value::Dict());
}
}
// Extract identified extension policies into their own category.
for (auto entry : *extensions_dict) {
extensions_dict->Set(entry.first,
policy_values.FindDict(policy::kPolicyValuesKey)
->Extract(entry.first)
.value_or(base::Value()));
}
return policy::GenerateJson(
std::move(policy_values),
policy_value_and_status_aggregator_->GetAggregatedPolicyStatus(),
/*params=*/
policy::GetChromeMetadataParams(
/*application_name=*/l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)));
}