chromium/ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm

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

#import "ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.h"

#import <UIKit/UIKit.h>

#import <algorithm>
#import <utility>
#import <vector>

#import "base/functional/bind.h"
#import "base/functional/callback.h"
#import "base/functional/callback_helpers.h"
#import "base/strings/sys_string_conversions.h"
#import "base/time/time.h"
#import "base/values.h"
#import "components/enterprise/browser/controller/browser_dm_token_storage.h"
#import "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
#import "components/enterprise/browser/reporting/common_pref_names.h"
#import "components/policy/core/browser/policy_conversions.h"
#import "components/policy/core/browser/webui/json_generation.h"
#import "components/policy/core/browser/webui/machine_level_user_cloud_policy_status_provider.h"
#import "components/policy/core/browser/webui/policy_webui_constants.h"
#import "components/policy/core/browser/webui/statistics_collector.h"
#import "components/policy/core/common/cloud/cloud_policy_core.h"
#import "components/policy/core/common/cloud/machine_level_user_cloud_policy_manager.h"
#import "components/policy/core/common/cloud/user_cloud_policy_manager.h"
#import "components/policy/core/common/local_test_policy_provider.h"
#import "components/policy/core/common/policy_logger.h"
#import "components/policy/core/common/policy_map.h"
#import "components/policy/core/common/policy_pref_names.h"
#import "components/policy/core/common/policy_types.h"
#import "components/policy/core/common/schema.h"
#import "components/policy/core/common/schema_map.h"
#import "components/policy/policy_constants.h"
#import "components/prefs/pref_service.h"
#import "components/signin/public/identity_manager/identity_manager.h"
#import "components/strings/grit/components_strings.h"
#import "components/version_info/version_info.h"
#import "ios/chrome/browser/policy/model/browser_policy_connector_ios.h"
#import "ios/chrome/browser/policy/model/browser_state_policy_connector.h"
#import "ios/chrome/browser/policy/model/policy_conversions_client_ios.h"
#import "ios/chrome/browser/shared/model/application_context/application_context.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/shared/ui/util/pasteboard_util.h"
#import "ios/chrome/browser/signin/model/identity_manager_factory.h"
#import "ios/chrome/browser/webui/ui_bundled/policy/policy_ui.h"
#import "ios/chrome/common/channel_info.h"
#import "ios/chrome/grit/ios_branded_strings.h"
#import "ios/chrome/grit/ios_strings.h"
#import "ui/base/l10n/l10n_util.h"
#import "ui/base/webui/web_ui_util.h"

PolicyUIHandler::PolicyUIHandler() = default;

PolicyUIHandler::~PolicyUIHandler() {
  GetPolicyService()->RemoveObserver(policy::POLICY_DOMAIN_CHROME, this);
  policy::SchemaRegistry* registry = ChromeBrowserState::FromWebUIIOS(web_ui())
                                         ->GetPolicyConnector()
                                         ->GetSchemaRegistry();
  registry->RemoveObserver(this);
  policy::RecordPolicyUIButtonUsage(reload_policies_count_,
                                    /*export_to_json_count=*/0,
                                    copy_to_json_count_, upload_report_count_);
}

void PolicyUIHandler::AddCommonLocalizedStringsToSource(
    web::WebUIIOSDataSource* source) {
  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},
      {"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},
      {"reportUploading", IDS_REPORT_UPLOADING},
      {"reportUploaded", IDS_REPORT_UPLOADED},
      {"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},
  };
  source->AddLocalizedStrings(kStrings);
  source->AddLocalizedStrings(policy::kPolicySources);
  source->UseStringsJs();
}

void PolicyUIHandler::RegisterMessages() {
  policy::MachineLevelUserCloudPolicyManager* manager =
      GetApplicationContext()
          ->GetBrowserPolicyConnector()
          ->machine_level_user_cloud_policy_manager();
  policy::BrowserDMTokenStorage* dm_token_storage =
      policy::BrowserDMTokenStorage::Get();

  if (manager) {
    machine_status_provider_ =
        std::make_unique<policy::MachineLevelUserCloudPolicyStatusProvider>(
            manager->core(), GetApplicationContext()->GetLocalState(),
            new policy::MachineLevelUserCloudPolicyContext(
                {dm_token_storage->RetrieveEnrollmentToken(),
                 dm_token_storage->RetrieveClientId(),
                 enterprise_reporting::kLastUploadSucceededTimestamp}));
    machine_status_provider_observation_.Observe(
        machine_status_provider_.get());
  }

  if (!machine_status_provider_)
    machine_status_provider_ = std::make_unique<policy::PolicyStatusProvider>();

  GetPolicyService()->AddObserver(policy::POLICY_DOMAIN_CHROME, this);

  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromWebUIIOS(web_ui());
  browser_state->GetPolicyConnector()->GetSchemaRegistry()->AddObserver(this);

  policy::UserCloudPolicyManager* user_cloud_policy_manager =
      browser_state->GetUserCloudPolicyManager();
  signin::IdentityManager* identity_manager =
      IdentityManagerFactory::GetForBrowserState(browser_state);
  if (user_cloud_policy_manager && user_cloud_policy_manager->core() &&
      identity_manager) {
    user_policy_status_provider_ =
        std::make_unique<UserCloudPolicyStatusProvider>(
            this, user_cloud_policy_manager->core(), identity_manager);
  } else {
    user_policy_status_provider_ =
        std::make_unique<policy::PolicyStatusProvider>();
  }

  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(
      "uploadReport", base::BindRepeating(&PolicyUIHandler::HandleUploadReport,
                                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "setLocalTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleSetLocalTestPolicies,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getAppliedTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleGetAppliedTestPolicies,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "revertLocalTestPolicies",
      base::BindRepeating(&PolicyUIHandler::HandleRevertLocalTestPolicies,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "restartBrowser",
      base::BindRepeating(&PolicyUIHandler::HandleRestartBrowser,
                          base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      "setUserAffiliation",
      base::BindRepeating(&PolicyUIHandler::HandleSetUserAffiliation,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getPolicyLogs",
      base::BindRepeating(&PolicyUIHandler::HandleGetPolicyLogs,
                          base::Unretained(this)));
}

void PolicyUIHandler::HandleCopyPoliciesJson(const base::Value::List& args) {
  copy_to_json_count_ += 1;
  NSString* jsonString = base::SysUTF8ToNSString(GetPoliciesAsJson());
  StoreTextInPasteboard(jsonString);
}

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 = GetApplicationContext()
                               ->GetBrowserPolicyConnector()
                               ->chrome_browser_cloud_management_controller()
                               ->report_scheduler();
  if (report_scheduler) {
    report_scheduler->UploadFullReport(
        base::BindOnce(&PolicyUIHandler::OnReportUploaded,
                       weak_factory_.GetWeakPtr(), callback_id));
  } else {
    OnReportUploaded(callback_id);
  }
}

void PolicyUIHandler::HandleSetLocalTestPolicies(
    const base::Value::List& args) {
  std::string json_policies_string = args[1].GetString();

  if (!PolicyUI::ShouldLoadTestPage(
          ChromeBrowserState::FromWebUIIOS(web_ui()))) {
    web_ui()->ResolveJavascriptCallback(args[0], true);
    return;
  }

  policy::LocalTestPolicyProvider* local_test_provider =
      static_cast<policy::LocalTestPolicyProvider*>(
          GetApplicationContext()
              ->GetBrowserPolicyConnector()
              ->local_test_policy_provider());

  CHECK(local_test_provider);

  ChromeBrowserState::FromWebUIIOS(web_ui())
      ->GetPolicyConnector()
      ->UseLocalTestPolicyProvider();

  local_test_provider->LoadJsonPolicies(json_policies_string);
  web_ui()->ResolveJavascriptCallback(args[0], true);
}

void PolicyUIHandler::HandleRevertLocalTestPolicies(
    const base::Value::List& args) {
  if (!PolicyUI::ShouldLoadTestPage(
          ChromeBrowserState::FromWebUIIOS(web_ui()))) {
    return;
  }

  ChromeBrowserState::FromWebUIIOS(web_ui())
      ->GetPolicyConnector()
      ->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 = GetApplicationContext()->GetLocalState();
  prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
                   policies);
}

void PolicyUIHandler::HandleSetUserAffiliation(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*>(
      GetApplicationContext()
          ->GetBrowserPolicyConnector()
          ->local_test_policy_provider());
  local_test_provider->SetUserAffiliated(affiliated);
  web_ui()->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*>(
      GetApplicationContext()
          ->GetBrowserPolicyConnector()
          ->local_test_policy_provider());

  web_ui()->ResolveJavascriptCallback(args[0],
                                      local_test_provider->GetPolicies());
}

void PolicyUIHandler::HandleGetPolicyLogs(const base::Value::List& args) {
  web_ui()->ResolveJavascriptCallback(
      args[0], policy::PolicyLogger::GetInstance()->GetAsList());
}

std::string PolicyUIHandler::GetPoliciesAsJson() {
  return policy::GenerateJson(
      /*policy_values=*/policy::PolicyConversions(
          std::make_unique<PolicyConversionsClientIOS>(
              ChromeBrowserState::FromWebUIIOS(web_ui())))
          .ToValueDict(),
      GetStatusValue(),
      policy::JsonGenerationParams()
          .with_application_name(l10n_util::GetStringUTF8(IDS_IOS_PRODUCT_NAME))
          .with_channel_name(std::string(GetChannelString(GetChannel())))
          .with_processor_variation(l10n_util::GetStringUTF8(
              sizeof(void*) == 8 ? IDS_VERSION_UI_64BIT : IDS_VERSION_UI_32BIT))
          .with_os_name(std::string(version_info::GetOSType())));
}

void PolicyUIHandler::OnSchemaRegistryUpdated(bool has_new_schemas) {
  // Update UI when new schema is added.
  if (has_new_schemas) {
    SendPolicies();
    SendSchema();
  }
}

void PolicyUIHandler::OnPolicyUpdated(const policy::PolicyNamespace& ns,
                                      const policy::PolicyMap& previous,
                                      const policy::PolicyMap& current) {
  SendPolicies();
}

void PolicyUIHandler::OnPolicyStatusChanged() {
  SendStatus();
}

base::flat_set<std::string> PolicyUIHandler::GetDeviceAffiliationIds() {
  return GetApplicationContext()
      ->GetBrowserPolicyConnector()
      ->GetDeviceAffiliationIds();
}

void PolicyUIHandler::OnReportUploaded(const std::string& callback_id) {
  web_ui()->ResolveJavascriptCallback(base::Value(callback_id),
                                      /*response=*/base::Value());
  SendStatus();
}

base::Value::Dict PolicyUIHandler::GetPolicyNames() const {
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromWebUIIOS(web_ui());
  policy::SchemaRegistry* registry =
      browser_state->GetPolicyConnector()->GetSchemaRegistry();
  scoped_refptr<policy::SchemaMap> schema_map = registry->schema_map();

  // Add Chrome policy names.
  base::Value::List chrome_policy_names;
  policy::PolicyNamespace chrome_namespace(policy::POLICY_DOMAIN_CHROME, "");
  const policy::Schema* chrome_schema = schema_map->GetSchema(chrome_namespace);
  for (auto it = chrome_schema->GetPropertiesIterator(); !it.IsAtEnd();
       it.Advance()) {
    chrome_policy_names.Append(base::Value(it.key()));
  }

  base::Value::Dict chrome_values;
  chrome_values.Set(policy::kNameKey, policy::kChromePoliciesName);
  chrome_values.Set(policy::kPolicyNamesKey, std::move(chrome_policy_names));

  base::Value::Dict names;
  names.Set(policy::kChromePoliciesId, std::move(chrome_values));
  return names;
}

base::Value::Dict PolicyUIHandler::GetPolicyValues() const {
  base::Value::List policy_ids;
  policy_ids.Append(policy::kChromePoliciesId);

  base::Value::Dict policy_values =
      policy::PolicyConversions(std::make_unique<PolicyConversionsClientIOS>(
                                    ChromeBrowserState::FromWebUIIOS(web_ui())))
          .EnableConvertValues(true)
          .UseChromePolicyConversions()
          .ToValueDict();

  base::Value::Dict dict;
  dict.Set(policy::kPolicyValuesKey, std::move(policy_values));
  dict.Set(policy::kPolicyIdsKey, std::move(policy_ids));
  return dict;
}

void PolicyUIHandler::HandleListenPoliciesUpdates(
    const base::Value::List& args) {
  OnRefreshPoliciesDone();
}

void PolicyUIHandler::HandleReloadPolicies(const base::Value::List& args) {
  reload_policies_count_ += 1;
  GetPolicyService()->RefreshPolicies(
      base::BindOnce(&PolicyUIHandler::OnRefreshPoliciesDone,
                     weak_factory_.GetWeakPtr()),
      policy::PolicyFetchReason::kUserRequest);
}

void PolicyUIHandler::SendPolicies() {
  base::Value::Dict names = GetPolicyNames();
  base::Value::Dict values = GetPolicyValues();
  web_ui()->FireWebUIListener("policies-updated", names, values);
}

void PolicyUIHandler::SendSchema() {
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromWebUIIOS(web_ui());
  if (!PolicyUI::ShouldLoadTestPage(browser_state)) {
    return;
  }

  web_ui()->FireWebUIListener("schema-updated",
                              PolicyUI::GetSchema(browser_state));
}

base::Value::Dict PolicyUIHandler::GetStatusValue() const {
  base::Value::Dict machine_status = machine_status_provider_->GetStatus();
  // Given that it's usual for users to bring their own devices and the fact
  // that device names could expose personal information. We do not show
  // this field in Device Policy Box
  machine_status.Remove(policy::kMachineKey);

  base::Value::Dict status;
  status.Set("machine", std::move(machine_status));
  status.Set("user", user_policy_status_provider_->GetStatus());

  return status;
}

void PolicyUIHandler::SendStatus() {
  base::Value::Dict status = GetStatusValue();
  web_ui()->FireWebUIListener("status-updated", status);
}

void PolicyUIHandler::OnRefreshPoliciesDone() {
  SendPolicies();
  SendStatus();
}

policy::PolicyService* PolicyUIHandler::GetPolicyService() const {
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromWebUIIOS(web_ui());
  return browser_state->GetPolicyConnector()->GetPolicyService();
}