chromium/extensions/browser/api/networking_private/networking_private_chromeos.cc

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

#include "extensions/browser/api/networking_private/networking_private_chromeos.h"

#include <memory>

#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/values.h"
#include "chromeos/ash/components/login/login_state/login_state.h"
#include "chromeos/ash/components/network/device_state.h"
#include "chromeos/ash/components/network/managed_network_configuration_handler.h"
#include "chromeos/ash/components/network/network_certificate_handler.h"
#include "chromeos/ash/components/network/network_connection_handler.h"
#include "chromeos/ash/components/network/network_device_handler.h"
#include "chromeos/ash/components/network/network_event_log.h"
#include "chromeos/ash/components/network/network_state.h"
#include "chromeos/ash/components/network/network_state_handler.h"
#include "chromeos/ash/components/network/network_util.h"
#include "chromeos/ash/components/network/onc/network_onc_utils.h"
#include "chromeos/ash/components/network/onc/onc_translator.h"
#include "chromeos/ash/components/network/portal_detector/network_portal_detector.h"
#include "chromeos/ash/components/network/technology_state_controller.h"
#include "components/onc/onc_constants.h"
#include "content/public/browser/browser_context.h"
#include "extensions/browser/api/networking_private/networking_private_api.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_set.h"
#include "extensions/common/permissions/permissions_data.h"

using ::ash::NetworkCertificateHandler;
using ::ash::NetworkHandler;
using ::ash::NetworkStateHandler;
using ::ash::NetworkTypePattern;
using ::ash::TechnologyStateController;
using extensions::NetworkingPrivateDelegate;

namespace private_api = extensions::api::networking_private;

namespace {

NetworkStateHandler* GetStateHandler() {
  return NetworkHandler::Get()->network_state_handler();
}

TechnologyStateController* GetTechnologyStateController() {
  return NetworkHandler::Get()->technology_state_controller();
}

ash::ManagedNetworkConfigurationHandler* GetManagedConfigurationHandler() {
  return NetworkHandler::Get()->managed_network_configuration_handler();
}

bool GetServicePathFromGuid(const std::string& guid,
                            std::string* service_path,
                            std::string* error) {
  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network) {
    *error = extensions::networking_private::kErrorInvalidNetworkGuid;
    return false;
  }
  *service_path = network->path();
  return true;
}

bool IsSharedNetwork(const std::string& service_path) {
  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromServicePath(
          service_path, true /* configured only */);
  if (!network) {
    return false;
  }

  return !network->IsPrivate();
}

bool GetPrimaryUserIdHash(content::BrowserContext* browser_context,
                          std::string* user_hash,
                          std::string* error) {
  std::string context_user_hash =
      extensions::ExtensionsBrowserClient::Get()->GetUserIdHashFromContext(
          browser_context);

  // Currently Chrome OS only configures networks for the primary user.
  // Configuration attempts from other browser contexts should fail.
  if (context_user_hash != ash::LoginState::Get()->primary_user_hash()) {
    // Disallow class requiring a user id hash from a non-primary user context
    // to avoid complexities with the policy code.
    LOG(ERROR) << "networkingPrivate API call from non primary user: "
               << context_user_hash;
    if (error) {
      *error = "Error.NonPrimaryUser";
    }
    return false;
  }
  if (user_hash) {
    *user_hash = context_user_hash;
  }
  return true;
}

void AppendDeviceState(
    const std::string& type,
    const ash::DeviceState* device,
    NetworkingPrivateDelegate::DeviceStateList& device_state_list) {
  DCHECK(!type.empty());
  NetworkTypePattern pattern = ash::onc::NetworkTypePatternFromOncType(type);
  NetworkStateHandler::TechnologyState technology_state =
      GetStateHandler()->GetTechnologyState(pattern);
  private_api::DeviceStateType state = private_api::DeviceStateType::kNone;
  switch (technology_state) {
    case NetworkStateHandler::TECHNOLOGY_UNAVAILABLE:
      if (!device) {
        return;
      }
      // If we have a DeviceState entry but the technology is not available,
      // assume the technology is not initialized.
      state = private_api::DeviceStateType::kUninitialized;
      break;
    case NetworkStateHandler::TECHNOLOGY_AVAILABLE:
      state = private_api::DeviceStateType::kDisabled;
      break;
    case NetworkStateHandler::TECHNOLOGY_DISABLING:
      state = private_api::DeviceStateType::kDisabled;
      break;
    case NetworkStateHandler::TECHNOLOGY_UNINITIALIZED:
      state = private_api::DeviceStateType::kUninitialized;
      break;
    case NetworkStateHandler::TECHNOLOGY_ENABLING:
      state = private_api::DeviceStateType::kEnabling;
      break;
    case NetworkStateHandler::TECHNOLOGY_ENABLED:
      state = private_api::DeviceStateType::kEnabled;
      break;
    case NetworkStateHandler::TECHNOLOGY_PROHIBITED:
      state = private_api::DeviceStateType::kProhibited;
      break;
  }
  DCHECK_NE(private_api::DeviceStateType::kNone, state);
  private_api::DeviceStateProperties& properties =
      device_state_list.emplace_back();
  properties.type = private_api::ParseNetworkType(type);
  properties.state = state;
  if (device && state == private_api::DeviceStateType::kEnabled) {
    properties.scanning = device->scanning();
  }
  if (device && type == ::onc::network_config::kCellular) {
    bool sim_present = !device->IsSimAbsent();
    properties.sim_present = sim_present;
    if (sim_present) {
      properties.sim_lock_status.emplace();
      properties.sim_lock_status->lock_enabled = device->sim_lock_enabled();
      properties.sim_lock_status->lock_type = device->sim_lock_type();
      properties.sim_lock_status->retries_left = device->sim_retries_left();
    }
  }
  if (device && type == ::onc::network_config::kWiFi) {
    properties.managed_network_available =
        GetStateHandler()->GetAvailableManagedWifiNetwork();
  }
}

void NetworkHandlerFailureCallback(
    NetworkingPrivateDelegate::FailureCallback callback,
    const std::string& error_name) {
  std::move(callback).Run(error_name);
}

// Returns the string corresponding to |key|. If the property is a managed
// dictionary, returns the active value. If the property does not exist or
// has no active value, returns an empty string.
std::string GetStringFromDictionary(const base::Value::Dict& dictionary,
                                    const std::string& key) {
  const std::string* result = dictionary.FindString(key);
  if (result) {
    return *result;
  }
  const base::Value::Dict* managed = dictionary.FindDict(key);
  if (managed) {
    result = managed->FindString(::onc::kAugmentationActiveSetting);
  }
  return result ? *result : std::string();
}

base::Value::Dict* GetThirdPartyVPNDictionary(base::Value::Dict* dictionary) {
  const std::string type =
      GetStringFromDictionary(*dictionary, ::onc::network_config::kType);
  if (type != ::onc::network_config::kVPN) {
    return nullptr;
  }
  base::Value::Dict* vpn_dict =
      dictionary->FindDict(::onc::network_config::kVPN);
  if (!vpn_dict) {
    return nullptr;
  }
  if (GetStringFromDictionary(*vpn_dict, ::onc::vpn::kType) !=
      ::onc::vpn::kThirdPartyVpn) {
    return nullptr;
  }
  base::Value::Dict* third_party_vpn =
      dictionary->FindDict(::onc::vpn::kThirdPartyVpn);
  return third_party_vpn;
}

const ash::DeviceState* GetCellularDeviceState(const std::string& guid) {
  const ash::NetworkState* network_state = nullptr;
  if (!guid.empty()) {
    network_state = GetStateHandler()->GetNetworkStateFromGuid(guid);
  }
  const ash::DeviceState* device_state = nullptr;
  if (network_state) {
    device_state =
        GetStateHandler()->GetDeviceState(network_state->device_path());
  }
  if (!device_state) {
    device_state =
        GetStateHandler()->GetDeviceStateByType(NetworkTypePattern::Cellular());
  }
  return device_state;
}

private_api::Certificate GetCertDictionary(
    const NetworkCertificateHandler::Certificate& cert) {
  private_api::Certificate api_cert;
  api_cert.hash = cert.hash;
  api_cert.issued_by = cert.issued_by;
  api_cert.issued_to = cert.issued_to;
  api_cert.hardware_backed = cert.hardware_backed;
  api_cert.device_wide = cert.device_wide;
  if (!cert.pem.empty()) {
    api_cert.pem = cert.pem;
  }
  if (!cert.pkcs11_id.empty()) {
    api_cert.pkcs11_id = cert.pkcs11_id;
  }
  return api_cert;
}

constexpr char kCaptivePortalStatusUnknown[] = "Unknown";
constexpr char kCaptivePortalStatusOffline[] = "Offline";
constexpr char kCaptivePortalStatusOnline[] = "Online";
constexpr char kCaptivePortalStatusPortal[] = "Portal";
constexpr char kCaptivePortalStatusUnrecognized[] = "Unrecognized";

// This returns backwards compatible strings previously provided by
// NetworkPortalDetector.
// static
std::string PortalStatusString(ash::NetworkState::PortalState portal_state) {
  using PortalState = ash::NetworkState::PortalState;
  switch (portal_state) {
    case PortalState::kUnknown:
      return kCaptivePortalStatusUnknown;
    case PortalState::kOnline:
      return kCaptivePortalStatusOnline;
    case PortalState::kPortalSuspected:
    case PortalState::kPortal:
    case PortalState::kNoInternet:
      return kCaptivePortalStatusPortal;
  }
  return kCaptivePortalStatusUnrecognized;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////

namespace extensions {

NetworkingPrivateChromeOS::NetworkingPrivateChromeOS(
    content::BrowserContext* browser_context)
    : browser_context_(browser_context) {}

NetworkingPrivateChromeOS::~NetworkingPrivateChromeOS() = default;

void NetworkingPrivateChromeOS::GetProperties(const std::string& guid,
                                              PropertiesCallback callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    NET_LOG(ERROR) << "GetProperties failed: " << error;
    std::move(callback).Run(std::nullopt, error);
    return;
  }

  std::string user_id_hash;
  if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
    NET_LOG(ERROR) << "GetProperties failed: " << error;
    std::move(callback).Run(std::nullopt, error);
    return;
  }

  GetManagedConfigurationHandler()->GetProperties(
      user_id_hash, service_path,
      base::BindOnce(&NetworkingPrivateChromeOS::GetPropertiesCallback,
                     weak_ptr_factory_.GetWeakPtr(), guid,
                     std::move(callback)));
}

void NetworkingPrivateChromeOS::GetManagedProperties(
    const std::string& guid,
    PropertiesCallback callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    NET_LOG(ERROR) << "GetManagedProperties failed: " << error;
    std::move(callback).Run(std::nullopt, error);
    return;
  }

  std::string user_id_hash;
  if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
    NET_LOG(ERROR) << "GetManagedProperties failed: " << error;
    std::move(callback).Run(std::nullopt, error);
    return;
  }

  GetManagedConfigurationHandler()->GetManagedProperties(
      user_id_hash, service_path,
      base::BindOnce(&NetworkingPrivateChromeOS::GetPropertiesCallback,
                     weak_ptr_factory_.GetWeakPtr(), guid,
                     std::move(callback)));
}

void NetworkingPrivateChromeOS::GetState(const std::string& guid,
                                         DictionaryCallback success_callback,
                                         FailureCallback failure_callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    std::move(failure_callback).Run(error);
    return;
  }

  const ash::NetworkState* network_state =
      GetStateHandler()->GetNetworkStateFromServicePath(
          service_path, false /* configured_only */);
  if (!network_state) {
    std::move(failure_callback)
        .Run(networking_private::kErrorNetworkUnavailable);
    return;
  }

  base::Value::Dict network_properties =
      ash::network_util::TranslateNetworkStateToONC(network_state);
  AppendThirdPartyProviderName(&network_properties);

  std::move(success_callback).Run(std::move(network_properties));
}

void NetworkingPrivateChromeOS::SetProperties(
    const std::string& guid,
    base::Value::Dict properties,
    bool allow_set_shared_config,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network) {
    std::move(failure_callback)
        .Run(extensions::networking_private::kErrorInvalidNetworkGuid);
    return;
  }
  if (network->profile_path().empty()) {
    std::move(failure_callback)
        .Run(extensions::networking_private::kErrorUnconfiguredNetwork);
    return;
  }
  if (IsSharedNetwork(network->path())) {
    if (!allow_set_shared_config) {
      std::move(failure_callback)
          .Run(networking_private::kErrorAccessToSharedConfig);
      return;
    }
  } else {
    std::string user_id_hash;
    std::string error;
    // Do not allow changing a non-shared network from secondary users.
    if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
      std::move(failure_callback).Run(error);
      return;
    }
  }

  NET_LOG(USER) << "networkingPrivate.setProperties for: "
                << NetworkId(network);
  GetManagedConfigurationHandler()->SetProperties(
      network->path(), properties, std::move(success_callback),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)));
}

void NetworkHandlerCreateCallback(
    NetworkingPrivateDelegate::StringCallback callback,
    const std::string& service_path,
    const std::string& guid) {
  std::move(callback).Run(guid);
}

void NetworkingPrivateChromeOS::CreateNetwork(
    bool shared,
    base::Value::Dict properties,
    StringCallback success_callback,
    FailureCallback failure_callback) {
  std::string user_id_hash, error;
  // Do not allow configuring a non-shared network from a non-primary user.
  if (!shared &&
      !GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error)) {
    std::move(failure_callback).Run(error);
    return;
  }

  const std::string guid =
      GetStringFromDictionary(properties, ::onc::network_config::kGUID);
  NET_LOG(USER) << "networkingPrivate.CreateNetwork. GUID=" << guid;
  GetManagedConfigurationHandler()->CreateConfiguration(
      user_id_hash, properties,
      base::BindOnce(&NetworkHandlerCreateCallback,
                     std::move(success_callback)),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)));
}

void NetworkingPrivateChromeOS::ForgetNetwork(
    const std::string& guid,
    bool allow_forget_shared_config,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    std::move(failure_callback).Run(error);
    return;
  }

  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromServicePath(
          service_path, true /* configured only */);
  if (!network) {
    std::move(failure_callback)
        .Run(networking_private::kErrorNetworkUnavailable);
    return;
  }

  std::string user_id_hash;
  // Don't allow non-primary user to remove private configs - the private
  // configs belong to the primary user (non-primary users' network configs
  // never get loaded by shill).
  if (!GetPrimaryUserIdHash(browser_context_, &user_id_hash, &error) &&
      network->IsPrivate()) {
    std::move(failure_callback).Run(error);
    return;
  }

  if (!allow_forget_shared_config && !network->IsPrivate()) {
    std::move(failure_callback)
        .Run(networking_private::kErrorAccessToSharedConfig);
    return;
  }

  onc::ONCSource onc_source = onc::ONC_SOURCE_UNKNOWN;
  if (GetManagedConfigurationHandler()->FindPolicyByGUID(user_id_hash, guid,
                                                         &onc_source)) {
    // Prevent a policy controlled configuration removal.
    if (onc_source == onc::ONC_SOURCE_DEVICE_POLICY) {
      allow_forget_shared_config = false;
    } else {
      std::move(failure_callback)
          .Run(networking_private::kErrorPolicyControlled);
      return;
    }
  }

  if (allow_forget_shared_config) {
    GetManagedConfigurationHandler()->RemoveConfiguration(
        service_path, std::move(success_callback),
        base::BindOnce(&NetworkHandlerFailureCallback,
                       std::move(failure_callback)));
  } else {
    GetManagedConfigurationHandler()->RemoveConfigurationFromCurrentProfile(
        service_path, std::move(success_callback),
        base::BindOnce(&NetworkHandlerFailureCallback,
                       std::move(failure_callback)));
  }
}

void NetworkingPrivateChromeOS::GetNetworks(
    const std::string& network_type,
    bool configured_only,
    bool visible_only,
    int limit,
    NetworkListCallback success_callback,
    FailureCallback failure_callback) {
  // When requesting configured Ethernet networks, include EthernetEAP.
  NetworkTypePattern pattern =
      (!visible_only && network_type == ::onc::network_type::kEthernet)
          ? NetworkTypePattern::EthernetOrEthernetEAP()
          : ash::onc::NetworkTypePatternFromOncType(network_type);
  base::Value::List network_properties_list =
      ash::network_util::TranslateNetworkListToONC(pattern, configured_only,
                                                   visible_only, limit);

  for (auto& value : network_properties_list) {
    base::Value::Dict& value_dict = value.GetDict();
    if (GetThirdPartyVPNDictionary(&value_dict)) {
      AppendThirdPartyProviderName(&value_dict);
    }
  }

  std::move(success_callback).Run(std::move(network_properties_list));
}

void NetworkingPrivateChromeOS::StartConnect(const std::string& guid,
                                             VoidCallback success_callback,
                                             FailureCallback failure_callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    std::move(failure_callback).Run(error);
    return;
  }

  NetworkHandler::Get()->network_connection_handler()->ConnectToNetwork(
      service_path, std::move(success_callback),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)),
      true /* check_error_state */, ash::ConnectCallbackMode::ON_STARTED);
}

void NetworkingPrivateChromeOS::StartDisconnect(
    const std::string& guid,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  std::string service_path, error;
  if (!GetServicePathFromGuid(guid, &service_path, &error)) {
    std::move(failure_callback).Run(error);
    return;
  }

  NetworkHandler::Get()->network_connection_handler()->DisconnectNetwork(
      service_path, std::move(success_callback),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)));
}

void NetworkingPrivateChromeOS::StartActivate(
    const std::string& guid,
    const std::string& specified_carrier,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network) {
    std::move(failure_callback)
        .Run(extensions::networking_private::kErrorInvalidNetworkGuid);
    return;
  }

  if (ui_delegate()) {
    ui_delegate()->ShowAccountDetails(guid);
  }
  std::move(success_callback).Run();
}

void NetworkingPrivateChromeOS::GetCaptivePortalStatus(
    const std::string& guid,
    StringCallback success_callback,
    FailureCallback failure_callback) {
  const ash::NetworkState* network =
      GetStateHandler()->GetNetworkStateFromGuid(guid);
  if (!network) {
    std::move(failure_callback)
        .Run(extensions::networking_private::kErrorInvalidNetworkGuid);
    return;
  }
  if (!network->IsConnectedState()) {
    std::move(success_callback).Run(kCaptivePortalStatusOffline);
    return;
  }
  std::move(success_callback)
      .Run(PortalStatusString(network->GetPortalState()));
}

void NetworkingPrivateChromeOS::UnlockCellularSim(
    const std::string& guid,
    const std::string& pin,
    const std::string& puk,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  const ash::DeviceState* device_state = GetCellularDeviceState(guid);
  if (!device_state) {
    std::move(failure_callback)
        .Run(networking_private::kErrorNetworkUnavailable);
    return;
  }
  std::string lock_type = device_state->sim_lock_type();
  if (lock_type.empty()) {
    // Sim is already unlocked.
    std::move(failure_callback)
        .Run(networking_private::kErrorInvalidNetworkOperation);
    return;
  }

  // Unblock or unlock the SIM.
  if (lock_type == shill::kSIMLockPuk) {
    NetworkHandler::Get()->network_device_handler()->UnblockPin(
        device_state->path(), puk, pin, std::move(success_callback),
        base::BindOnce(&NetworkHandlerFailureCallback,
                       std::move(failure_callback)));
  } else {
    NetworkHandler::Get()->network_device_handler()->EnterPin(
        device_state->path(), pin, std::move(success_callback),
        base::BindOnce(&NetworkHandlerFailureCallback,
                       std::move(failure_callback)));
  }
}

void NetworkingPrivateChromeOS::SetCellularSimState(
    const std::string& guid,
    bool require_pin,
    const std::string& current_pin,
    const std::string& new_pin,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  const ash::DeviceState* device_state = GetCellularDeviceState(guid);
  if (!device_state) {
    std::move(failure_callback)
        .Run(networking_private::kErrorNetworkUnavailable);
    return;
  }
  if (!device_state->sim_lock_type().empty()) {
    // The SIM needs to be unlocked before the state can be changed.
    std::move(failure_callback).Run(networking_private::kErrorSimLocked);
    return;
  }

  // TODO(benchan): Add more checks to validate the parameters of this method
  // and the state of the SIM lock on the cellular device. Consider refactoring
  // some of the code by moving the logic into shill instead.

  // If |new_pin| is empty, we're trying to enable (require_pin == true) or
  // disable (require_pin == false) SIM locking.
  if (new_pin.empty()) {
    NetworkHandler::Get()->network_device_handler()->RequirePin(
        device_state->path(), require_pin, current_pin,
        std::move(success_callback),
        base::BindOnce(&NetworkHandlerFailureCallback,
                       std::move(failure_callback)));
    return;
  }

  // Otherwise, we're trying to change the PIN from |current_pin| to
  // |new_pin|, which also requires SIM locking to be enabled, i.e.
  // require_pin == true.
  if (!require_pin) {
    std::move(failure_callback).Run(networking_private::kErrorInvalidArguments);
    return;
  }

  NetworkHandler::Get()->network_device_handler()->ChangePin(
      device_state->path(), current_pin, new_pin, std::move(success_callback),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)));
}

void NetworkingPrivateChromeOS::SelectCellularMobileNetwork(
    const std::string& guid,
    const std::string& network_id,
    VoidCallback success_callback,
    FailureCallback failure_callback) {
  const ash::DeviceState* device_state = GetCellularDeviceState(guid);
  if (!device_state) {
    std::move(failure_callback)
        .Run(networking_private::kErrorNetworkUnavailable);
    return;
  }
  NetworkHandler::Get()->network_device_handler()->RegisterCellularNetwork(
      device_state->path(), network_id, std::move(success_callback),
      base::BindOnce(&NetworkHandlerFailureCallback,
                     std::move(failure_callback)));
}

void NetworkingPrivateChromeOS::GetEnabledNetworkTypes(
    EnabledNetworkTypesCallback callback) {
  NetworkStateHandler* state_handler = GetStateHandler();

  base::Value::List network_list;

  if (state_handler->IsTechnologyEnabled(NetworkTypePattern::Ethernet())) {
    network_list.Append(::onc::network_type::kEthernet);
  }
  if (state_handler->IsTechnologyEnabled(NetworkTypePattern::WiFi())) {
    network_list.Append(::onc::network_type::kWiFi);
  }
  if (state_handler->IsTechnologyEnabled(NetworkTypePattern::Cellular())) {
    network_list.Append(::onc::network_type::kCellular);
  }

  std::move(callback).Run(std::move(network_list));
}

void NetworkingPrivateChromeOS::GetDeviceStateList(
    DeviceStateListCallback callback) {
  std::set<std::string> technologies_found;
  NetworkStateHandler::DeviceStateList devices;
  NetworkHandler::Get()->network_state_handler()->GetDeviceList(&devices);

  DeviceStateList device_state_list;
  for (const ash::DeviceState* device : devices) {
    std::string onc_type =
        ash::network_util::TranslateShillTypeToONC(device->type());
    AppendDeviceState(onc_type, device, device_state_list);
    technologies_found.insert(onc_type);
  }

  // For any technologies that we do not have a DeviceState entry for, append
  // an entry if the technology is available.
  const char* technology_types[] = {::onc::network_type::kEthernet,
                                    ::onc::network_type::kWiFi,
                                    ::onc::network_type::kCellular};
  for (const char* technology : technology_types) {
    if (base::Contains(technologies_found, technology)) {
      continue;
    }
    AppendDeviceState(technology, nullptr /* device */, device_state_list);
  }
  std::move(callback).Run(std::move(device_state_list));
}

void NetworkingPrivateChromeOS::GetGlobalPolicy(
    GetGlobalPolicyCallback callback) {
  base::Value::Dict result;
  const base::Value::Dict* global_network_config =
      GetManagedConfigurationHandler()->GetGlobalConfigFromPolicy(
          std::string() /* no username hash, device policy */);

  if (global_network_config) {
    result.Merge(global_network_config->Clone());
  }
  std::move(callback).Run(std::move(result));
}

void NetworkingPrivateChromeOS::GetCertificateLists(
    GetCertificateListsCallback callback) {
  private_api::CertificateLists result;
  const std::vector<NetworkCertificateHandler::Certificate>& server_cas =
      NetworkHandler::Get()
          ->network_certificate_handler()
          ->server_ca_certificates();
  for (const auto& cert : server_cas) {
    result.server_ca_certificates.push_back(GetCertDictionary(cert));
  }

  std::vector<private_api::Certificate> user_cert_list;
  const std::vector<NetworkCertificateHandler::Certificate>& user_certs =
      NetworkHandler::Get()
          ->network_certificate_handler()
          ->client_certificates();
  for (const auto& cert : user_certs) {
    result.user_certificates.push_back(GetCertDictionary(cert));
  }
  std::move(callback).Run(result.ToValue());
}

void NetworkingPrivateChromeOS::EnableNetworkType(const std::string& type,
                                                  BoolCallback callback) {
  NetworkTypePattern pattern = ash::onc::NetworkTypePatternFromOncType(type);

  NET_LOG(USER) << __func__ << ":" << type;
  GetTechnologyStateController()->SetTechnologiesEnabled(
      pattern, true, ash::network_handler::ErrorCallback());

  std::move(callback).Run(true);
}

void NetworkingPrivateChromeOS::DisableNetworkType(const std::string& type,
                                                   BoolCallback callback) {
  NetworkTypePattern pattern = ash::onc::NetworkTypePatternFromOncType(type);

  NET_LOG(USER) << __func__ << ":" << type;
  GetTechnologyStateController()->SetTechnologiesEnabled(
      pattern, false, ash::network_handler::ErrorCallback());

  std::move(callback).Run(true);
}

void NetworkingPrivateChromeOS::RequestScan(const std::string& type,
                                            BoolCallback callback) {
  NetworkTypePattern pattern = ash::onc::NetworkTypePatternFromOncType(
      type.empty() ? ::onc::network_type::kAllTypes : type);
  GetStateHandler()->RequestScan(pattern);

  std::move(callback).Run(true);
}

// Private methods

void NetworkingPrivateChromeOS::GetPropertiesCallback(
    const std::string& guid,
    PropertiesCallback callback,
    const std::string& service_path,
    std::optional<base::Value::Dict> dictionary,
    std::optional<std::string> error) {
  if (dictionary) {
    AppendThirdPartyProviderName(&dictionary.value());
  }
  std::move(callback).Run(std::move(dictionary), std::move(error));
}

void NetworkingPrivateChromeOS::AppendThirdPartyProviderName(
    base::Value::Dict* dictionary) {
  base::Value::Dict* third_party_vpn = GetThirdPartyVPNDictionary(dictionary);
  if (!third_party_vpn) {
    return;
  }

  const ExtensionId extension_id = GetStringFromDictionary(
      *third_party_vpn, ::onc::third_party_vpn::kExtensionID);
  const ExtensionSet& extensions =
      ExtensionRegistry::Get(browser_context_)->enabled_extensions();
  for (const auto& extension : extensions) {
    if (extension->permissions_data()->HasAPIPermission(
            mojom::APIPermissionID::kVpnProvider) &&
        extension->id() == extension_id) {
      third_party_vpn->Set(::onc::third_party_vpn::kProviderName,
                           extension->name());
      break;
    }
  }
}

}  // namespace extensions