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