chromium/chrome/browser/ash/system_logs/shill_log_source.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/system_logs/shill_log_source.h"

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "chrome/browser/ash/system_logs/shill_log_pii_identifiers.h"
#include "chromeos/ash/components/dbus/shill/shill_device_client.h"
#include "chromeos/ash/components/dbus/shill/shill_ipconfig_client.h"
#include "chromeos/ash/components/dbus/shill/shill_manager_client.h"
#include "chromeos/ash/components/dbus/shill/shill_service_client.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/components/onc/onc_utils.h"
#include "dbus/object_path.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace {

constexpr char kNetworkDevices[] = "network_devices";
constexpr char kNetworkServices[] = "network_services";
constexpr char kServicePrefix[] = "/service/";
constexpr char kDevicePrefix[] = "/device/";

std::string GetString(const base::Value* value) {
  if (!value)
    return std::string();
  if (!value->is_string()) {
    LOG(ERROR) << "Bad string value: " << *value;
    return std::string();
  }
  return value->GetString();
}

constexpr char kMaskedString[] = "*** MASKED ***";

// Recursively scrubs dictionaries, masking any values in
// system_logs::MakeFixedFlatMap.
void ScrubDictionary(base::Value::Dict& dict) {
  for (auto entry : dict) {
    base::Value& value = entry.second;
    if (value.is_dict()) {
      ScrubDictionary(entry.second.GetDict());
    } else if (base::Contains(system_logs::kShillPIIMaskedMap, entry.first) &&
               system_logs::kShillPIIMaskedMap.at(entry.first) !=
                   redaction::PIIType::kNone &&
               (!value.is_string() || !value.GetString().empty())) {
      entry.second = base::Value(kMaskedString);
    }
  }
}

}  // namespace

namespace system_logs {

ShillLogSource::ShillLogSource(bool scrub)
    : SystemLogsSource("Shill"), scrub_(scrub) {}

ShillLogSource::~ShillLogSource() = default;

void ShillLogSource::Fetch(SysLogsSourceCallback callback) {
  DCHECK(!callback.is_null());
  DCHECK(callback_.is_null());
  callback_ = std::move(callback);

  ash::ShillManagerClient::Get()->GetProperties(base::BindOnce(
      &ShillLogSource::OnGetManagerProperties, weak_ptr_factory_.GetWeakPtr()));
}

void ShillLogSource::OnGetManagerProperties(
    std::optional<base::Value::Dict> result) {
  if (!result) {
    LOG(ERROR) << "ManagerPropertiesCallback Failed";
    std::move(callback_).Run(std::make_unique<SystemLogsResponse>());
    return;
  }

  const base::Value::List* devices = result->FindList(shill::kDevicesProperty);
  if (devices) {
    for (const base::Value& device : *devices) {
      std::string path = GetString(&device);
      if (path.empty())
        continue;
      device_paths_.insert(path);
      ash::ShillDeviceClient::Get()->GetProperties(
          dbus::ObjectPath(path),
          base::BindOnce(&ShillLogSource::OnGetDevice,
                         weak_ptr_factory_.GetWeakPtr(), path));
    }
  }

  const base::Value::List* services =
      result->FindList(shill::kServicesProperty);
  if (services) {
    for (const base::Value& service : *services) {
      std::string path = GetString(&service);
      if (path.empty())
        continue;
      service_paths_.insert(path);
      ash::ShillServiceClient::Get()->GetProperties(
          dbus::ObjectPath(path),
          base::BindOnce(&ShillLogSource::OnGetService,
                         weak_ptr_factory_.GetWeakPtr(), path));
    }
  }

  CheckIfDone();
}

void ShillLogSource::OnGetDevice(const std::string& device_path,
                                 std::optional<base::Value::Dict> properties) {
  if (!properties) {
    LOG(ERROR) << "Get Device Properties Failed for : " << device_path;
  } else {
    AddDeviceAndRequestIPConfigs(device_path, *properties);
  }
  device_paths_.erase(device_path);
  CheckIfDone();
}

void ShillLogSource::AddDeviceAndRequestIPConfigs(
    const std::string& device_path,
    const base::Value::Dict& properties) {
  base::Value* device = devices_.Set(
      device_path, ScrubAndExpandProperties(device_path, properties));

  const base::Value::List* ip_configs =
      properties.FindList(shill::kIPConfigsProperty);
  if (!ip_configs) {
    return;
  }

  for (const base::Value& ip_config : *ip_configs) {
    std::string ip_config_path = GetString(&ip_config);
    if (ip_config_path.empty()) {
      continue;
    }
    ip_config_paths_.insert(ip_config_path);
    ash::ShillIPConfigClient::Get()->GetProperties(
        dbus::ObjectPath(ip_config_path),
        base::BindOnce(&ShillLogSource::OnGetIPConfig,
                       weak_ptr_factory_.GetWeakPtr(), device_path,
                       ip_config_path));
  }
  if (!ip_config_paths_.empty()) {
    device->GetDict().Set(shill::kIPConfigsProperty, base::Value::Dict{});
  }
}

void ShillLogSource::OnGetIPConfig(
    const std::string& device_path,
    const std::string& ip_config_path,
    std::optional<base::Value::Dict> properties) {
  if (!properties) {
    LOG(ERROR) << "Get IPConfig Properties Failed for : " << device_path << ": "
               << ip_config_path;
  } else {
    AddIPConfig(device_path, ip_config_path, *properties);
  }
  // Erase a single matching entry.
  ip_config_paths_.erase(ip_config_paths_.find(ip_config_path));
  CheckIfDone();
}

void ShillLogSource::AddIPConfig(const std::string& device_path,
                                 const std::string& ip_config_path,
                                 const base::Value::Dict& properties) {
  base::Value::Dict* device = devices_.FindDict(device_path);
  DCHECK(device);
  base::Value::Dict* ip_configs = device->FindDict(shill::kIPConfigsProperty);
  DCHECK(ip_configs);
  ip_configs->Set(ip_config_path,
                  ScrubAndExpandProperties(ip_config_path, properties));
}

void ShillLogSource::OnGetService(const std::string& service_path,
                                  std::optional<base::Value::Dict> properties) {
  if (!properties) {
    LOG(ERROR) << "Get Service Properties Failed for : " << service_path;
  } else {
    services_.Set(service_path,
                  ScrubAndExpandProperties(service_path, properties.value()));
  }
  service_paths_.erase(service_path);
  CheckIfDone();
}

base::Value::Dict ShillLogSource::ScrubAndExpandProperties(
    const std::string& object_path,
    const base::Value::Dict& properties) {
  base::Value::Dict dict = properties.Clone();

  // Convert UIData from a string to a dictionary.
  std::string* ui_data = dict.FindString(shill::kUIDataProperty);
  if (ui_data) {
    std::optional<base::Value::Dict> ui_data_dict =
        chromeos::onc::ReadDictionaryFromJson(*ui_data);
    if (ui_data_dict.has_value()) {
      dict.Set(shill::kUIDataProperty, std::move(*ui_data_dict));
    }
  }

  if (!scrub_)
    return dict;

  if (base::StartsWith(object_path, kServicePrefix,
                       base::CompareCase::SENSITIVE)) {
    std::string log_name = ash::NetworkPathId(object_path);  // Not PII
    dict.Set(shill::kNameProperty, log_name);
  } else if (base::StartsWith(object_path, kDevicePrefix,
                              base::CompareCase::SENSITIVE)) {
    dict.Set(shill::kNameProperty, kMaskedString);
    // Only mask "Address" in the top level Device dictionary, not globally
    // (which would mask IPConfigs which get anonymized separately).
    if (dict.contains(shill::kAddressProperty)) {
      dict.Set(shill::kAddressProperty, kMaskedString);
    }
  }

  ScrubDictionary(dict);
  return dict;
}

void ShillLogSource::CheckIfDone() {
  if (!device_paths_.empty() || !ip_config_paths_.empty() ||
      !service_paths_.empty()) {
    return;
  }

  std::map<std::string, std::string> response;
  std::string json;
  base::JSONWriter::WriteWithOptions(
      devices_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
  response[kNetworkDevices] = std::move(json);
  base::JSONWriter::WriteWithOptions(
      services_, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json);
  response[kNetworkServices] = std::move(json);

  // Clear |devices_| and |services_|.
  devices_.clear();
  services_.clear();

  std::move(callback_).Run(
      std::make_unique<SystemLogsResponse>(std::move(response)));
}

}  // namespace system_logs