chromium/chrome/browser/ash/policy/reporting/install_event_log_util.cc

// 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.

#include "chrome/browser/ash/policy/reporting/install_event_log_util.h"

#include <set>

#include "base/hash/md5.h"
#include "base/i18n/time_formatting.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chromeos/ash/components/system/statistics_provider.h"

namespace em = enterprise_management;

namespace policy {

namespace {
// Common Key names used when building the dictionary to pass to the Chrome
// Reporting API.
constexpr char kEventId[] = "eventId";
constexpr char kEventType[] = "eventType";
constexpr char kOnline[] = "online";
constexpr char kSerialNumber[] = "serialNumber";
constexpr char kSessionStateChangeType[] = "sessionStateChangeType";
constexpr char kStatefulTotal[] = "statefulTotal";
constexpr char kStatefulFree[] = "statefulFree";
constexpr char kTime[] = "time";

// Key names used for ARC++ apps when building the dictionary to pass to the
// Chrome Reporting API.
constexpr char kAndroidId[] = "androidId";
constexpr char kAppPackage[] = "appPackage";
constexpr char kCloudDpsResponse[] = "clouddpsResponse";
constexpr char kAndroidAppInstallEvent[] = "androidAppInstallEvent";

// Key names used for extensions when building the dictionary to pass to the
// Chrome Reporting API.
constexpr char kExtensionId[] = "extensionId";
constexpr char kExtensionInstallEvent[] = "extensionAppInstallEvent";
constexpr char kDownloadingStage[] = "downloadingStage";
constexpr char kFailureReason[] = "failureReason";
constexpr char kInstallationStage[] = "installationStage";
constexpr char kExtensionType[] = "extensionType";
constexpr char kUserType[] = "userType";
constexpr char kIsNewUser[] = "isNewUser";
constexpr char kIsMisconfigurationFailure[] = "isMisconfigurationFailure";
constexpr char kInstallCreationStage[] = "installCreationStage";
constexpr char kDownloadCacheStatus[] = "downloadCacheStatus";
constexpr char kUnpackerFailureReason[] = "unpackerFailureReason";
constexpr char kManifestInvalidError[] = "manifestInvalidError";
constexpr char kCrxInstallErrorDetail[] = "crxInstallErrorDetail";
constexpr char kFetchErrorCode[] = "fetchErrorCode";
constexpr char kFetchTries[] = "fetchTries";

// Calculates hash for the given |event| and |context|, and stores the hash in
// |hash|. Returns true if |event| and |context| are json serializable and
// |hash| is not nullptr, otherwise return false.
bool GetHash(const base::Value::Dict& event,
             const base::Value::Dict& context,
             std::string* hash) {
  if (hash == nullptr) {
    return false;
  }

  std::string serialized_string;
  JSONStringValueSerializer serializer(&serialized_string);
  if (!serializer.Serialize(event)) {
    return false;
  }

  base::MD5Context ctx;
  base::MD5Init(&ctx);
  base::MD5Update(&ctx, serialized_string);

  if (!serializer.Serialize(context)) {
    return false;
  }
  base::MD5Update(&ctx, serialized_string);

  base::MD5Digest digest;
  base::MD5Final(&digest, &ctx);
  *hash = base::MD5DigestToBase16(digest);
  return true;
}

}  // namespace

std::string GetSerialNumber() {
  return std::string(
      ash::system::StatisticsProvider::GetInstance()->GetMachineID().value_or(
          ""));
}

base::Value::List ConvertExtensionProtoToValue(
    const em::ExtensionInstallReportRequest* extension_install_report_request,
    const base::Value::Dict& context) {
  DCHECK(extension_install_report_request);

  base::Value::List event_list;
  std::set<extensions::ExtensionId> seen_ids;

  for (const em::ExtensionInstallReport& extension_install_report :
       extension_install_report_request->extension_install_reports()) {
    for (const em::ExtensionInstallReportLogEvent&
             extension_install_report_log_event :
         extension_install_report.logs()) {
      base::Value::Dict wrapper = ConvertExtensionEventToValue(
          extension_install_report.has_extension_id()
              ? extension_install_report.extension_id()
              : "",
          extension_install_report_log_event, context);
      auto* id = wrapper.FindString(kEventId);
      if (id) {
        if (seen_ids.find(*id) != seen_ids.end()) {
          LOG(WARNING) << "Skipping duplicate event (" << *id
                       << "): " << wrapper;
          continue;
        }
        seen_ids.insert(*id);
      }
      event_list.Append(std::move(wrapper));
    }
  }

  return event_list;
}

base::Value::Dict ConvertExtensionEventToValue(
    const extensions::ExtensionId& extension_id,
    const em::ExtensionInstallReportLogEvent&
        extension_install_report_log_event,
    const base::Value::Dict& context) {
  base::Value::Dict event;
  if (!extension_id.empty()) {
    event.Set(kExtensionId, extension_id);
  }

  if (extension_install_report_log_event.has_event_type()) {
    event.Set(kEventType, extension_install_report_log_event.event_type());
  }

  if (extension_install_report_log_event.has_stateful_total()) {
    // 64-bit ints aren't supported by JSON - must be stored as strings
    event.Set(kStatefulTotal,
              base::NumberToString(
                  extension_install_report_log_event.stateful_total()));
  }

  if (extension_install_report_log_event.has_stateful_free()) {
    // 64-bit ints aren't supported by JSON - must be stored as strings
    event.Set(kStatefulFree,
              base::NumberToString(
                  extension_install_report_log_event.stateful_free()));
  }

  if (extension_install_report_log_event.has_online()) {
    event.Set(kOnline, extension_install_report_log_event.online());
  }

  if (extension_install_report_log_event.has_session_state_change_type()) {
    event.Set(kSessionStateChangeType,
              extension_install_report_log_event.session_state_change_type());
  }

  if (extension_install_report_log_event.has_downloading_stage()) {
    event.Set(kDownloadingStage,
              extension_install_report_log_event.downloading_stage());
  }

  event.Set(kSerialNumber, GetSerialNumber());

  if (extension_install_report_log_event.has_failure_reason()) {
    event.Set(kFailureReason,
              extension_install_report_log_event.failure_reason());
  }

  if (extension_install_report_log_event.has_user_type()) {
    event.Set(kUserType, extension_install_report_log_event.user_type());
    DCHECK(extension_install_report_log_event.has_is_new_user());
    event.Set(kIsNewUser, extension_install_report_log_event.is_new_user());
  }

  if (extension_install_report_log_event.has_installation_stage()) {
    event.Set(kInstallationStage,
              extension_install_report_log_event.installation_stage());
  }

  if (extension_install_report_log_event.has_extension_type()) {
    event.Set(kExtensionType,
              extension_install_report_log_event.extension_type());
  }

  if (extension_install_report_log_event.has_is_misconfiguration_failure()) {
    event.Set(kIsMisconfigurationFailure,
              extension_install_report_log_event.is_misconfiguration_failure());
  }

  if (extension_install_report_log_event.has_install_creation_stage()) {
    event.Set(kInstallCreationStage,
              extension_install_report_log_event.install_creation_stage());
  }

  if (extension_install_report_log_event.has_download_cache_status()) {
    event.Set(kDownloadCacheStatus,
              extension_install_report_log_event.download_cache_status());
  }

  if (extension_install_report_log_event.has_unpacker_failure_reason()) {
    event.Set(kUnpackerFailureReason,
              extension_install_report_log_event.unpacker_failure_reason());
  }

  if (extension_install_report_log_event.has_manifest_invalid_error()) {
    event.Set(kManifestInvalidError,
              extension_install_report_log_event.manifest_invalid_error());
  }

  if (extension_install_report_log_event.has_fetch_error_code()) {
    event.Set(kFetchErrorCode,
              extension_install_report_log_event.fetch_error_code());
  }

  if (extension_install_report_log_event.has_fetch_tries()) {
    event.Set(kFetchTries, extension_install_report_log_event.fetch_tries());
  }

  if (extension_install_report_log_event.has_crx_install_error_detail()) {
    event.Set(kCrxInstallErrorDetail,
              extension_install_report_log_event.crx_install_error_detail());
  }

  auto wrapper =
      base::Value::Dict().Set(kExtensionInstallEvent, std::move(event));

  if (extension_install_report_log_event.has_timestamp()) {
    // Format the current time (UTC) in RFC3339 format
    base::Time timestamp =
        base::Time::UnixEpoch() +
        base::Microseconds(extension_install_report_log_event.timestamp());
    wrapper.Set(kTime, base::TimeFormatAsIso8601(timestamp));
  }

  std::string event_id;
  if (GetHash(wrapper, context, &event_id)) {
    wrapper.Set(kEventId, event_id);
  }

  return wrapper;
}

base::Value::List ConvertArcAppProtoToValue(
    const em::AppInstallReportRequest* app_install_report_request,
    const base::Value::Dict& context) {
  DCHECK(app_install_report_request);

  base::Value::List event_list;
  std::set<std::string> seen_ids;

  for (const em::AppInstallReport& app_install_report :
       app_install_report_request->app_install_reports()) {
    for (const em::AppInstallReportLogEvent& app_install_report_log_event :
         app_install_report.logs()) {
      base::Value::Dict wrapper = ConvertArcAppEventToValue(
          app_install_report.has_package() ? app_install_report.package() : "",
          app_install_report_log_event, context);
      auto* id = wrapper.FindString(kEventId);
      if (id) {
        if (seen_ids.find(*id) != seen_ids.end()) {
          LOG(WARNING) << "Skipping duplicate event (" << *id
                       << "): " << wrapper;
          continue;
        }
        seen_ids.insert(*id);
      }
      event_list.Append(std::move(wrapper));
    }
  }

  return event_list;
}

base::Value::Dict ConvertArcAppEventToValue(
    const std::string& package,
    const em::AppInstallReportLogEvent& app_install_report_log_event,
    const base::Value::Dict& context) {
  base::Value::Dict event;

  if (!package.empty()) {
    event.Set(kAppPackage, package);
  }

  if (app_install_report_log_event.has_event_type()) {
    event.Set(kEventType, app_install_report_log_event.event_type());
  }

  if (app_install_report_log_event.has_stateful_total()) {
    // 64-bit ints aren't supported by JSON - must be stored as strings
    event.Set(
        kStatefulTotal,
        base::NumberToString(app_install_report_log_event.stateful_total()));
  }

  if (app_install_report_log_event.has_stateful_free()) {
    // 64-bit ints aren't supported by JSON - must be stored as strings
    event.Set(kStatefulFree, base::NumberToString(
                                 app_install_report_log_event.stateful_free()));
  }

  if (app_install_report_log_event.has_clouddps_response()) {
    event.Set(kCloudDpsResponse,
              app_install_report_log_event.clouddps_response());
  }

  if (app_install_report_log_event.has_online()) {
    event.Set(kOnline, app_install_report_log_event.online());
  }

  if (app_install_report_log_event.has_session_state_change_type()) {
    event.Set(kSessionStateChangeType,
              app_install_report_log_event.session_state_change_type());
  }

  if (app_install_report_log_event.has_android_id()) {
    // 64-bit ints aren't supporetd by JSON - must be stored as strings
    event.Set(kAndroidId,
              base::NumberToString(app_install_report_log_event.android_id()));
  }

  event.Set(kSerialNumber, GetSerialNumber());

  auto wrapper =
      base::Value::Dict().Set(kAndroidAppInstallEvent, std::move(event));

  if (app_install_report_log_event.has_timestamp()) {
    // Format the current time (UTC) in RFC3339 format
    base::Time timestamp =
        base::Time::UnixEpoch() +
        base::Microseconds(app_install_report_log_event.timestamp());
    wrapper.Set(kTime, base::TimeFormatAsIso8601(timestamp));
  }

  std::string event_id;
  if (GetHash(wrapper, context, &event_id)) {
    wrapper.Set(kEventId, event_id);
  }

  return wrapper;
}

reporting::AndroidAppInstallEvent CreateAndroidAppInstallEvent(
    const std::string& package,
    const enterprise_management::AppInstallReportLogEvent& event) {
  auto result = reporting::AndroidAppInstallEvent();
  result.set_app_package(package);
  result.set_serial_number(GetSerialNumber());
  result.set_event_type(event.event_type());
  result.set_stateful_total(event.stateful_total());
  result.set_stateful_free(event.stateful_free());
  result.set_clouddps_response(event.clouddps_response());
  result.set_online(event.online());
  result.set_session_state_change_type(event.session_state_change_type());
  result.set_android_id(event.android_id());
  return result;
}

}  // namespace policy