chromium/chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter.cc

// 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 "chrome/browser/ash/policy/reporting/os_updates/os_updates_reporter.h"

#include <memory>
#include <optional>
#include <string>

#include "base/memory/ptr_util.h"
#include "base/system/sys_info.h"
#include "base/time/time.h"
#include "chrome/browser/ash/policy/reporting/event_based_logs/event_based_log_uploader.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/messaging_layer/proto/synced/os_events.pb.h"
#include "chrome/browser/upgrade_detector/build_state.h"
#include "chrome/common/channel_info.h"
#include "chromeos/ash/components/dbus/update_engine/update_engine_client.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/version/version_loader.h"
#include "components/reporting/proto/synced/record_constants.pb.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/version_info.h"

namespace reporting {

namespace {

bool ShouldReportStatus(const update_engine::StatusResult& status) {
  // If the status change is for an installation, this means that DLCs are being
  // installed and has nothing to with the OS. Ignore this status change.
  if (status.is_install()) {
    return false;
  }

  // We are only interested in the following operations: ERROR,
  // REPORTING_ERROR_EVENT and UPDATED_NEED_REBOOT. All the other
  // ones will be ignored.
  if (status.current_operation() != update_engine::Operation::ERROR &&
      status.current_operation() !=
          update_engine::Operation::REPORTING_ERROR_EVENT &&
      status.current_operation() !=
          update_engine::Operation::UPDATED_NEED_REBOOT) {
    return false;
  }

  // We don't report the rollback success here as the process still needs to
  // boot into the new image, powerwash, and recover the data.
  if (status.current_operation() ==
          update_engine::Operation::UPDATED_NEED_REBOOT &&
      status.is_enterprise_rollback()) {
    return false;
  }
  return true;
}

}  // namespace

// static
std::unique_ptr<OsUpdatesReporter> OsUpdatesReporter::Create() {
  return base::WrapUnique(new OsUpdatesReporter(
      std::make_unique<UserEventReporterHelper>(Destination::OS_EVENTS)));
}

// static
std::unique_ptr<OsUpdatesReporter> OsUpdatesReporter::CreateForTesting(
    std::unique_ptr<UserEventReporterHelper> helper) {
  return base::WrapUnique(new OsUpdatesReporter(std::move(helper)));
}

OsUpdatesReporter::~OsUpdatesReporter() {
  if (ash::SessionManagerClient::Get()) {
    ash::SessionManagerClient::Get()->RemoveObserver(this);
  }
  if (ash::UpdateEngineClient::Get()) {
    ash::UpdateEngineClient::Get()->RemoveObserver(this);
  }
}

void OsUpdatesReporter::MaybeReportEvent(
    ash::reporting::OsEventsRecord record) {
  if (!helper_->ReportingEnabled(ash::kReportOsUpdateStatus)) {
    return;
  }

  std::optional<std::string> os_version = chromeos::version_loader::GetVersion(
      chromeos::version_loader::VERSION_SHORT);
  record.set_current_os_version(os_version.value_or("0.0.0.0"));

  record.set_current_channel(
      std::string(version_info::GetChannelString(chrome::GetChannel())));

  record.set_event_timestamp_sec(base::Time::Now().ToTimeT());

  auto log_upload_id = NotifyOsUpdateFailed();
  if (log_upload_id.has_value()) {
    record.set_log_upload_id(log_upload_id.value());
  }

  helper_->ReportEvent(
      std::make_unique<ash::reporting::OsEventsRecord>(std::move(record)),
      ::reporting::Priority::SECURITY);
}

void OsUpdatesReporter::UpdateStatusChanged(
    const update_engine::StatusResult& status) {
  if (!ShouldReportStatus(status)) {
    return;
  }

  ::ash::reporting::OsEventsRecord record;
  // Report that an update was successfully applied, just waiting to reboot.
  if (status.current_operation() ==
      update_engine::Operation::UPDATED_NEED_REBOOT) {
    record.mutable_update_event();
    record.set_os_operation_type(ash::reporting::OsOperationType::SUCCESS);
  }

  //  Report a failure downloading or updating the rollback/update image.
  if (status.current_operation() == update_engine::Operation::ERROR ||
      status.current_operation() ==
          update_engine::Operation::REPORTING_ERROR_EVENT) {
    if (status.is_enterprise_rollback()) {
      record.mutable_rollback_event();
    } else {
      record.mutable_update_event();
    }
    record.set_os_operation_type(ash::reporting::OsOperationType::FAILURE);
  }

  record.set_target_os_version(status.new_version());
  MaybeReportEvent(std::move(record));
}

void OsUpdatesReporter::PowerwashRequested(bool remote_request) {
  base::Time time_now = base::Time::Now();

  ::ash::reporting::OsEventsRecord record;
  record.mutable_powerwash_event()->set_remote_request(remote_request);
  record.set_os_operation_type(ash::reporting::OsOperationType::INITIATED);
  // If the powerwash is remote requested we need to do additional checks
  // but if its user requested we always want to report it.
  if (remote_request) {
    // Powerwashes initiated by remote commands trigger call this method twice a
    // few seconds apart, adding this conditions to verify that we only report
    // the powerwash once.
    if (time_now - last_powerwash_attempt_time_ < base::Seconds(10)) {
      return;
    }
    last_powerwash_attempt_time_ = time_now;
  }
  MaybeReportEvent(std::move(record));
}

void OsUpdatesReporter::AddObserver(OsUpdateEventBasedLogObserver* observer) {
  observers_.AddObserver(observer);
}

void OsUpdatesReporter::RemoveObserver(
    const OsUpdateEventBasedLogObserver* observer) {
  observers_.RemoveObserver(observer);
}

std::optional<std::string> OsUpdatesReporter::NotifyOsUpdateFailed() {
  // We won't generate an upload ID if there's no observers to trigger the log
  // upload.
  if (observers_.empty()) {
    return std::nullopt;
  }
  std::string upload_id = policy::EventBasedLogUploader::GenerateUploadId();
  for (auto& observer : observers_) {
    observer.OnOsUpdateFailed(upload_id);
  }
  return upload_id;
}

OsUpdatesReporter::OsUpdatesReporter(
    std::unique_ptr<UserEventReporterHelper> helper)
    : helper_(std::move(helper)) {
  if (ash::UpdateEngineClient::Get()) {
    ash::UpdateEngineClient::Get()->AddObserver(this);
  }
  if (ash::SessionManagerClient::Get()) {
    ash::SessionManagerClient::Get()->AddObserver(this);
  }
}
}  // namespace reporting