chromium/chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h"

#include <memory>
#include <string_view>
#include <utility>

#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/enterprise/signals/device_info_fetcher.h"
#include "chrome/browser/enterprise/signals/signals_common.h"
#include "chrome/browser/enterprise/util/affiliation.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/profiles/profile.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/policy/dm_token_utils.h"
#include "chromeos/dbus/missive/missive_client.h"
#include "components/policy/core/common/cloud/dm_token.h"
#include "components/reporting/client/report_queue_configuration.h"
#include "components/reporting/proto/synced/record.pb.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/reporting/util/statusor.h"
#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
#include <optional>

#include "base/strings/string_util.h"
#include "chrome/browser/enterprise/signals/signals_aggregator_factory.h"
#include "chrome/browser/extensions/api/enterprise_reporting_private/conversion_utils.h"
#include "components/device_signals/core/browser/metrics_utils.h"
#include "components/device_signals/core/browser/signals_aggregator.h"
#include "components/device_signals/core/browser/signals_types.h"
#include "components/device_signals/core/browser/user_context.h"
#include "components/device_signals/core/common/signals_features.h"  // nogncheck
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

#include "components/content_settings/core/common/pref_names.h"
#include "components/enterprise/browser/controller/browser_dm_token_storage.h"
#include "net/cert/x509_util.h"

namespace extensions {

namespace {
#if !BUILDFLAG(IS_CHROMEOS)
const char kEndpointVerificationRetrievalFailed[] =;
const char kEndpointVerificationStoreFailed[] =;
#endif  // !BUILDFLAG(IS_CHROMEOS)

api::enterprise_reporting_private::SettingValue ToInfoSettingValue(
    enterprise_signals::SettingValue value) {}

api::enterprise_reporting_private::ContextInfo ToContextInfo(
    enterprise_signals::ContextInfo&& signals) {}

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

device_signals::SignalsAggregationRequest CreateAggregationRequest(
    device_signals::SignalName signal_name) {}

void StartSignalCollection(
    const std::string& user_id,
    device_signals::SignalsAggregationRequest request,
    content::BrowserContext* browser_context,
    base::OnceCallback<void(device_signals::SignalsAggregationResponse)>
        callback) {}

bool CanReturnResponse(content::BrowserContext* browser_context) {}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

}  // namespace

#if !BUILDFLAG(IS_CHROMEOS)
namespace enterprise_reporting {
const char kDeviceIdNotFound[] =;
}  // namespace enterprise_reporting

// GetDeviceId

EnterpriseReportingPrivateGetDeviceIdFunction::
    EnterpriseReportingPrivateGetDeviceIdFunction() {}

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetDeviceIdFunction::Run() {}

EnterpriseReportingPrivateGetDeviceIdFunction::
    ~EnterpriseReportingPrivateGetDeviceIdFunction() = default;

// getPersistentSecret

#if !BUILDFLAG(IS_LINUX)

EnterpriseReportingPrivateGetPersistentSecretFunction::
    EnterpriseReportingPrivateGetPersistentSecretFunction() = default;
EnterpriseReportingPrivateGetPersistentSecretFunction::
    ~EnterpriseReportingPrivateGetPersistentSecretFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetPersistentSecretFunction::Run() {
  std::optional<api::enterprise_reporting_private::GetPersistentSecret::Params>
      params = api::enterprise_reporting_private::GetPersistentSecret::Params::
          Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);
  bool force_create = params->reset_secret ? *params->reset_secret : false;
  base::ThreadPool::PostTask(
      FROM_HERE,
      {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
      base::BindOnce(
          &RetrieveDeviceSecret, force_create,
          base::BindOnce(
              &EnterpriseReportingPrivateGetPersistentSecretFunction::
                  OnDataRetrieved,
              this, base::SingleThreadTaskRunner::GetCurrentDefault())));
  return RespondLater();
}

void EnterpriseReportingPrivateGetPersistentSecretFunction::OnDataRetrieved(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    const std::string& data,
    int32_t status) {
  task_runner->PostTask(
      FROM_HERE,
      base::BindOnce(
          &EnterpriseReportingPrivateGetPersistentSecretFunction::SendResponse,
          this, data, status));
}

void EnterpriseReportingPrivateGetPersistentSecretFunction::SendResponse(
    const std::string& data,
    int32_t status) {
  if (status == 0) {  // Success.
    VLOG(1) << "The Endpoint Verification secret was retrieved.";
    Respond(WithArguments(base::Value::BlobStorage(
        reinterpret_cast<const uint8_t*>(data.data()),
        reinterpret_cast<const uint8_t*>(data.data() + data.size()))));
  } else {
    VLOG(1) << "Endpoint Verification secret retrieval error: " << status;
    Respond(Error(base::StringPrintf("%d", status)));
  }
}

#endif  // !BUILDFLAG(IS_LINUX)

// getDeviceData

EnterpriseReportingPrivateGetDeviceDataFunction::
    EnterpriseReportingPrivateGetDeviceDataFunction() = default;
EnterpriseReportingPrivateGetDeviceDataFunction::
    ~EnterpriseReportingPrivateGetDeviceDataFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetDeviceDataFunction::Run() {}

void EnterpriseReportingPrivateGetDeviceDataFunction::OnDataRetrieved(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    const std::string& data,
    RetrieveDeviceDataStatus status) {}

void EnterpriseReportingPrivateGetDeviceDataFunction::SendResponse(
    const std::string& data,
    RetrieveDeviceDataStatus status) {}

// setDeviceData

EnterpriseReportingPrivateSetDeviceDataFunction::
    EnterpriseReportingPrivateSetDeviceDataFunction() = default;
EnterpriseReportingPrivateSetDeviceDataFunction::
    ~EnterpriseReportingPrivateSetDeviceDataFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateSetDeviceDataFunction::Run() {}

void EnterpriseReportingPrivateSetDeviceDataFunction::OnDataStored(
    scoped_refptr<base::SequencedTaskRunner> task_runner,
    bool status) {}

void EnterpriseReportingPrivateSetDeviceDataFunction::SendResponse(
    bool status) {}

// getDeviceInfo

EnterpriseReportingPrivateGetDeviceInfoFunction::
    EnterpriseReportingPrivateGetDeviceInfoFunction() = default;
EnterpriseReportingPrivateGetDeviceInfoFunction::
    ~EnterpriseReportingPrivateGetDeviceInfoFunction() = default;

// static
api::enterprise_reporting_private::DeviceInfo
EnterpriseReportingPrivateGetDeviceInfoFunction::ToDeviceInfo(
    const enterprise_signals::DeviceInfo& device_signals) {}

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetDeviceInfoFunction::Run() {}

void EnterpriseReportingPrivateGetDeviceInfoFunction::OnDeviceInfoRetrieved(
    const enterprise_signals::DeviceInfo& device_signals) {}

#endif  // !BUILDFLAG(IS_CHROMEOS)

// getContextInfo

EnterpriseReportingPrivateGetContextInfoFunction::
    EnterpriseReportingPrivateGetContextInfoFunction() = default;
EnterpriseReportingPrivateGetContextInfoFunction::
    ~EnterpriseReportingPrivateGetContextInfoFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetContextInfoFunction::Run() {}

void EnterpriseReportingPrivateGetContextInfoFunction::OnContextInfoRetrieved(
    enterprise_signals::ContextInfo context_info) {}

// getCertificate

EnterpriseReportingPrivateGetCertificateFunction::
    EnterpriseReportingPrivateGetCertificateFunction() = default;
EnterpriseReportingPrivateGetCertificateFunction::
    ~EnterpriseReportingPrivateGetCertificateFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetCertificateFunction::Run() {}

void EnterpriseReportingPrivateGetCertificateFunction::OnClientCertFetched(
    std::unique_ptr<net::ClientCertIdentity> cert) {}

#if BUILDFLAG(IS_CHROMEOS)

// enqueueRecord

EnterpriseReportingPrivateEnqueueRecordFunction::
    EnterpriseReportingPrivateEnqueueRecordFunction() = default;

EnterpriseReportingPrivateEnqueueRecordFunction::
    ~EnterpriseReportingPrivateEnqueueRecordFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateEnqueueRecordFunction::Run() {
  auto* profile = Profile::FromBrowserContext(browser_context());
  DCHECK(profile);

  if (!IsProfileAffiliated(profile)) {
    return RespondNow(Error(kErrorProfileNotAffiliated));
  }

  std::optional<api::enterprise_reporting_private::EnqueueRecord::Params>
      params = api::enterprise_reporting_private::EnqueueRecord::Params::Create(
          args());
  EXTENSION_FUNCTION_VALIDATE(params);

  // Parse params
  const auto event_type = params->request.event_type;
  ::reporting::Record record;
  ::reporting::Priority priority;
  if (!TryParseParams(std::move(params), record, priority)) {
    return RespondNow(Error(kErrorInvalidEnqueueRecordRequest));
  }

  // Attach appropriate DM token to record
  if (!TryAttachDMTokenToRecord(record, event_type)) {
    return RespondNow(Error(kErrorCannotAssociateRecordWithUser));
  }

  // Initiate enqueue and subsequent upload
  auto enqueue_completion_cb = base::BindOnce(
      &EnterpriseReportingPrivateEnqueueRecordFunction::OnRecordEnqueued, this);
  auto* reporting_client = ::chromeos::MissiveClient::Get();
  DCHECK(reporting_client);
  reporting_client->EnqueueRecord(priority, record,
                                  std::move(enqueue_completion_cb));
  return RespondLater();
}

bool EnterpriseReportingPrivateEnqueueRecordFunction::TryParseParams(
    std::optional<api::enterprise_reporting_private::EnqueueRecord::Params>
        params,
    ::reporting::Record& record,
    ::reporting::Priority& priority) {
  if (params->request.record_data.empty()) {
    return false;
  }

  const auto* record_data =
      reinterpret_cast<const char*>(params->request.record_data.data());
  if (!record.ParseFromArray(record_data, params->request.record_data.size())) {
    // Invalid record payload
    return false;
  }

  if (!record.has_timestamp_us()) {
    // Missing record timestamp
    return false;
  }

  if (!::reporting::Priority_IsValid(params->request.priority) ||
      !::reporting::Priority_Parse(
          ::reporting::Priority_Name(params->request.priority), &priority)) {
    // Invalid priority
    return false;
  }

  // Valid
  return true;
}

bool EnterpriseReportingPrivateEnqueueRecordFunction::TryAttachDMTokenToRecord(
    ::reporting::Record& record,
    api::enterprise_reporting_private::EventType event_type) {
  if (event_type == api::enterprise_reporting_private::EventType::kDevice) {
    // Device DM tokens are automatically appended during uploads, so we need
    // not specify them with the record.
    return true;
  }

  auto* profile = Profile::FromBrowserContext(browser_context());

  const policy::DMToken& dm_token = policy::GetDMToken(profile);
  if (!dm_token.is_valid()) {
    return false;
  }

  record.set_dm_token(dm_token.value());
  return true;
}

void EnterpriseReportingPrivateEnqueueRecordFunction::OnRecordEnqueued(
    ::reporting::Status result) {
  if (!result.ok()) {
    Respond(Error(kUnexpectedErrorEnqueueRecordRequest));
    return;
  }

  Respond(NoArguments());
}

bool EnterpriseReportingPrivateEnqueueRecordFunction::IsProfileAffiliated(
    Profile* profile) {
  if (profile_is_affiliated_for_testing_) {
    return true;
  }
  return enterprise_util::IsProfileAffiliated(profile);
}

void EnterpriseReportingPrivateEnqueueRecordFunction::
    SetProfileIsAffiliatedForTesting(bool is_affiliated) {
  profile_is_affiliated_for_testing_ = is_affiliated;
}
#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

// getFileSystemInfo

EnterpriseReportingPrivateGetFileSystemInfoFunction::
    EnterpriseReportingPrivateGetFileSystemInfoFunction() = default;
EnterpriseReportingPrivateGetFileSystemInfoFunction::
    ~EnterpriseReportingPrivateGetFileSystemInfoFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetFileSystemInfoFunction::Run() {}

void EnterpriseReportingPrivateGetFileSystemInfoFunction::OnSignalRetrieved(
    base::TimeTicks start_time,
    size_t request_items_count,
    device_signals::SignalsAggregationResponse response) {}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

// getSettings

EnterpriseReportingPrivateGetSettingsFunction::
    EnterpriseReportingPrivateGetSettingsFunction() = default;
EnterpriseReportingPrivateGetSettingsFunction::
    ~EnterpriseReportingPrivateGetSettingsFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetSettingsFunction::Run() {
  if (!IsNewFunctionEnabled(
          enterprise_signals::features::NewEvFunction::kSettings)) {
    return RespondNow(Error(device_signals::ErrorToString(
        device_signals::SignalCollectionError::kUnsupported)));
  }

  std::optional<api::enterprise_reporting_private::GetSettings::Params> params =
      api::enterprise_reporting_private::GetSettings::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  // Verify that all paths strings are UTF8.
  bool paths_are_all_utf8 = true;
  for (const auto& api_options_param : params->request.options) {
    if (!base::IsStringUTF8(api_options_param.path)) {
      paths_are_all_utf8 = false;
      break;
    }
  }
  EXTENSION_FUNCTION_VALIDATE(paths_are_all_utf8);

  auto aggregation_request = CreateAggregationRequest(signal_name());
  aggregation_request.settings_signal_parameters =
      ConvertSettingsOptions(params->request.options);

  const size_t number_of_items =
      aggregation_request.settings_signal_parameters.size();
  LogSignalCollectionRequestedWithItems(signal_name(), number_of_items);

  StartSignalCollection(
      params->request.user_context.user_id, aggregation_request,
      browser_context(),
      base::BindOnce(
          &EnterpriseReportingPrivateGetSettingsFunction::OnSignalRetrieved,
          this, base::TimeTicks::Now(), number_of_items));

  return RespondLater();
}

void EnterpriseReportingPrivateGetSettingsFunction::OnSignalRetrieved(
    base::TimeTicks start_time,
    size_t request_items_count,
    device_signals::SignalsAggregationResponse response) {
  if (!CanReturnResponse(browser_context())) {
    // The browser is no longer accepting responses, so just bail.
    return;
  }

  std::vector<api::enterprise_reporting_private::GetSettingsResponse> arg_list;
  auto parsed_error = ConvertSettingsResponse(response, &arg_list);

  if (parsed_error) {
    LogSignalCollectionFailed(signal_name(), start_time, parsed_error->error,
                              parsed_error->is_top_level_error);
    Respond(Error(device_signals::ErrorToString(parsed_error->error)));
    return;
  }

  LogSignalCollectionSucceeded(signal_name(), start_time, arg_list.size(),
                               request_items_count);
  Respond(ArgumentList(
      api::enterprise_reporting_private::GetSettings::Results::Create(
          arg_list)));
}

#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)

// getAvInfo

EnterpriseReportingPrivateGetAvInfoFunction::
    EnterpriseReportingPrivateGetAvInfoFunction() = default;
EnterpriseReportingPrivateGetAvInfoFunction::
    ~EnterpriseReportingPrivateGetAvInfoFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetAvInfoFunction::Run() {
  if (!IsNewFunctionEnabled(
          enterprise_signals::features::NewEvFunction::kAntiVirus)) {
    return RespondNow(Error(device_signals::ErrorToString(
        device_signals::SignalCollectionError::kUnsupported)));
  }

  std::optional<api::enterprise_reporting_private::GetAvInfo::Params> params =
      api::enterprise_reporting_private::GetAvInfo::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  StartSignalCollection(
      params->user_context.user_id, CreateAggregationRequest(signal_name()),
      browser_context(),
      base::BindOnce(
          &EnterpriseReportingPrivateGetAvInfoFunction::OnSignalRetrieved, this,
          base::TimeTicks::Now()));

  return RespondLater();
}

void EnterpriseReportingPrivateGetAvInfoFunction::OnSignalRetrieved(
    base::TimeTicks start_time,
    device_signals::SignalsAggregationResponse response) {
  if (!CanReturnResponse(browser_context())) {
    // The browser is no longer accepting responses, so just bail.
    return;
  }

  std::vector<api::enterprise_reporting_private::AntiVirusSignal> arg_list;
  auto parsed_error = ConvertAvProductsResponse(response, &arg_list);

  if (parsed_error) {
    LogSignalCollectionFailed(signal_name(), start_time, parsed_error->error,
                              parsed_error->is_top_level_error);
    Respond(Error(device_signals::ErrorToString(parsed_error->error)));
    return;
  }

  LogSignalCollectionSucceeded(signal_name(), start_time, arg_list.size());
  Respond(ArgumentList(
      api::enterprise_reporting_private::GetAvInfo::Results::Create(arg_list)));
}

// getHotfixes

EnterpriseReportingPrivateGetHotfixesFunction::
    EnterpriseReportingPrivateGetHotfixesFunction() = default;
EnterpriseReportingPrivateGetHotfixesFunction::
    ~EnterpriseReportingPrivateGetHotfixesFunction() = default;

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateGetHotfixesFunction::Run() {
  if (!IsNewFunctionEnabled(
          enterprise_signals::features::NewEvFunction::kHotfix)) {
    return RespondNow(Error(device_signals::ErrorToString(
        device_signals::SignalCollectionError::kUnsupported)));
  }

  std::optional<api::enterprise_reporting_private::GetHotfixes::Params> params =
      api::enterprise_reporting_private::GetHotfixes::Params::Create(args());
  EXTENSION_FUNCTION_VALIDATE(params);

  StartSignalCollection(
      params->user_context.user_id, CreateAggregationRequest(signal_name()),
      browser_context(),
      base::BindOnce(
          &EnterpriseReportingPrivateGetHotfixesFunction::OnSignalRetrieved,
          this, base::TimeTicks::Now()));

  return RespondLater();
}

void EnterpriseReportingPrivateGetHotfixesFunction::OnSignalRetrieved(
    base::TimeTicks start_time,
    device_signals::SignalsAggregationResponse response) {
  if (!CanReturnResponse(browser_context())) {
    // The browser is no longer accepting responses, so just bail.
    return;
  }

  std::vector<api::enterprise_reporting_private::HotfixSignal> arg_list;
  auto parsed_error = ConvertHotfixesResponse(response, &arg_list);

  if (parsed_error) {
    LogSignalCollectionFailed(signal_name(), start_time, parsed_error->error,
                              parsed_error->is_top_level_error);
    Respond(Error(device_signals::ErrorToString(parsed_error->error)));
    return;
  }

  LogSignalCollectionSucceeded(signal_name(), start_time, arg_list.size());
  Respond(ArgumentList(
      api::enterprise_reporting_private::GetHotfixes::Results::Create(
          arg_list)));
}

#endif  // BUILDFLAG(IS_WIN)

}  // namespace extensions