chromium/chromeos/services/network_config/public/cpp/cros_network_config_util.cc

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

#include "chromeos/services/network_config/public/cpp/cros_network_config_util.h"
#include "components/device_event_log/device_event_log.h"
#include "components/onc/onc_constants.h"

namespace chromeos::network_config {

const char kMojoKeySecurity[] = "security";
const char kMojoKeySsid[] = "ssid";
const char kMojoKeyPassphrase[] = "passphrase";
const char kMojoKeyEapInner[] = "inner";
const char kMojoKeyEapOuter[] = "outer";
const char kMojoKeyEapIdentity[] = "identity";
const char kMojoKeyEapAnonymousIdentity[] = "anonymousIdentity";
const char kMojoKeyEapPassword[] = "password";
const char kMojoKeyEap[] = "eap";
const char kMojoKeyWifi[] = "wifi";
const char kMojoKeyTypeConfig[] = "typeConfig";

namespace {

std::optional<std::string> GetString(const base::Value::Dict& onc_apn,
                                     const char* key) {
  const std::string* v = onc_apn.FindString(key);
  return v ? std::make_optional<std::string>(*v) : std::nullopt;
}

std::string GetRequiredString(const base::Value::Dict& onc_apn,
                              const char* key) {
  const std::string* v = onc_apn.FindString(key);
  if (!v) {
    NOTREACHED_IN_MIGRATION() << "Required key missing: " << key;
    return std::string();
  }
  return *v;
}

std::vector<std::string> GetRequiredStringList(const base::Value::Dict& dict,
                                               const char* key) {
  const base::Value::List* v = dict.FindList(key);
  if (!v) {
    NOTREACHED_IN_MIGRATION() << "Required key missing: " << key;
    return {};
  }
  std::vector<std::string> result;
  result.reserve(v->size());
  for (const base::Value& e : *v) {
    if (!e.is_string()) {
      NOTREACHED_IN_MIGRATION() << "Expected string, found: " << e;
      break;
    }
    result.push_back(e.GetString());
  }
  return result;
}

mojom::ApnAuthenticationType OncApnAuthenticationTypeToMojo(
    const std::optional<std::string>& authentication_type) {
  if (!authentication_type.has_value() || authentication_type->empty() ||
      authentication_type == ::onc::cellular_apn::kAuthenticationAutomatic) {
    return mojom::ApnAuthenticationType::kAutomatic;
  }
  if (authentication_type == ::onc::cellular_apn::kAuthenticationPap) {
    return mojom::ApnAuthenticationType::kPap;
  }
  if (authentication_type == ::onc::cellular_apn::kAuthenticationChap) {
    return mojom::ApnAuthenticationType::kChap;
  }

  NOTREACHED_IN_MIGRATION() << "Unexpected ONC APN Authentication type: "
                            << authentication_type.value();
  return mojom::ApnAuthenticationType::kAutomatic;
}

mojom::ApnIpType OncApnIpTypeToMojo(const std::optional<std::string>& ip_type) {
  if (!ip_type.has_value() || ip_type->empty() ||
      ip_type == ::onc::cellular_apn::kIpTypeAutomatic) {
    return mojom::ApnIpType::kAutomatic;
  }
  if (ip_type == ::onc::cellular_apn::kIpTypeIpv4) {
    return mojom::ApnIpType::kIpv4;
  }
  if (ip_type == ::onc::cellular_apn::kIpTypeIpv6) {
    return mojom::ApnIpType::kIpv6;
  }
  if (ip_type == ::onc::cellular_apn::kIpTypeIpv4Ipv6) {
    return mojom::ApnIpType::kIpv4Ipv6;
  }

  NOTREACHED_IN_MIGRATION()
      << "Unexpected ONC APN IP type: " << ip_type.value();
  return mojom::ApnIpType::kAutomatic;
}

mojom::ApnSource OncApnSourceToMojo(const std::optional<std::string>& source) {
  if (!source.has_value() || source->empty() ||
      source == ::onc::cellular_apn::kSourceModem) {
    return mojom::ApnSource::kModem;
  }
  if (source == ::onc::cellular_apn::kSourceModb) {
    return mojom::ApnSource::kModb;
  }
  if (source == ::onc::cellular_apn::kSourceUi) {
    return mojom::ApnSource::kUi;
  }

  // TODO(b/5429735): Add mojom::ApnSource::kAdmin in follow up CL

  NET_LOG(DEBUG) << "Unexpected APN source: " << source.value();
  return mojom::ApnSource::kModem;
}

}  // namespace

bool GetBoolean(const base::Value::Dict* dict,
                const char* key,
                bool value_if_key_missing_from_dict) {
  const base::Value* v = dict->Find(key);
  if (v && !v->is_bool()) {
    NET_LOG(ERROR) << "Expected bool, found: " << *v;
    return false;
  }
  return v ? v->GetBool() : value_if_key_missing_from_dict;
}

std::optional<std::string> GetString(const base::Value::Dict* dict,
                                     const char* key) {
  const base::Value* v = dict->Find(key);
  if (v && !v->is_string()) {
    NET_LOG(ERROR) << "Expected string, found: " << *v;
    return std::nullopt;
  }
  return v ? std::make_optional(v->GetString()) : std::nullopt;
}

const base::Value::Dict* GetDictionary(const base::Value::Dict* dict,
                                       const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v) {
    return nullptr;
  }
  if (!v->is_dict()) {
    NET_LOG(ERROR) << "Expected dictionary, found: " << *v;
    return nullptr;
  }
  return &v->GetDict();
}

ManagedDictionary GetManagedDictionary(const base::Value::Dict* onc_dict) {
  ManagedDictionary result;

  // When available, the active value (i.e. the value from Shill) is used.
  if (onc_dict->contains(::onc::kAugmentationActiveSetting)) {
    result.active_value =
        onc_dict->Find(::onc::kAugmentationActiveSetting)->Clone();
  }

  std::optional<std::string> effective =
      GetString(onc_dict, ::onc::kAugmentationEffectiveSetting);
  if (!effective) {
    return result;
  }

  // If no active value is set (e.g. the network is not visible), use the
  // effective value.
  if (result.active_value.is_none() && onc_dict->contains(effective.value())) {
    result.active_value = onc_dict->Find(effective.value())->Clone();
  }
  if (result.active_value.is_none()) {
    // No active or effective value, return a default dictionary.
    return result;
  }

  // If the effective value is set by an extension, use kActiveExtension.
  if (effective == ::onc::kAugmentationActiveExtension) {
    result.policy_source = mojom::PolicySource::kActiveExtension;
    result.policy_value = result.active_value.Clone();
    return result;
  }

  // Set policy properties based on the effective source and policies.
  // NOTE: This does not enforce valid ONC. See onc_merger.cc for details.
  const base::Value* user_policy =
      onc_dict->Find(::onc::kAugmentationUserPolicy);
  const base::Value* device_policy =
      onc_dict->Find(::onc::kAugmentationDevicePolicy);
  bool user_enforced = !GetBoolean(onc_dict, ::onc::kAugmentationUserEditable);
  bool device_enforced =
      !GetBoolean(onc_dict, ::onc::kAugmentationDeviceEditable);
  if (effective == ::onc::kAugmentationUserPolicy ||
      (user_policy && effective != ::onc::kAugmentationDevicePolicy)) {
    // Set the policy source to "User" when:
    // * The effective value is set to "UserPolicy" OR
    // * A User policy exists and the effective value is not "DevicePolicy",
    //   i.e. no enforced device policy is overriding a recommended user policy.
    result.policy_source = user_enforced
                               ? mojom::PolicySource::kUserPolicyEnforced
                               : mojom::PolicySource::kUserPolicyRecommended;
    if (user_policy) {
      result.policy_value = user_policy->Clone();
    }
  } else if (effective == ::onc::kAugmentationDevicePolicy || device_policy) {
    // Set the policy source to "Device" when:
    // * The effective value is set to "DevicePolicy" OR
    // * A Device policy exists (since we checked for a user policy first).
    result.policy_source = device_enforced
                               ? mojom::PolicySource::kDevicePolicyEnforced
                               : mojom::PolicySource::kDevicePolicyRecommended;
    if (device_policy) {
      result.policy_value = device_policy->Clone();
    }
  } else if (effective == ::onc::kAugmentationUserSetting ||
             effective == ::onc::kAugmentationSharedSetting) {
    // User or shared setting, no policy source.
  } else {
    // Unexpected ONC. No policy source or value will be set.
    NET_LOG(ERROR) << "Unexpected ONC property: " << *onc_dict;
  }

  DCHECK(result.policy_value.is_none() ||
         result.policy_value.type() == result.active_value.type());
  return result;
}

mojom::ManagedStringPtr GetManagedString(const base::Value::Dict* dict,
                                         const char* key) {
  const base::Value* v = dict->Find(key);
  if (!v) {
    return nullptr;
  }
  if (v->is_string()) {
    auto result = mojom::ManagedString::New();
    result->active_value = v->GetString();
    return result;
  }
  if (v->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&v->GetDict());
    if (!managed_dict.active_value.is_string()) {
      NET_LOG(ERROR) << "No active or effective value for: " << key;
      return nullptr;
    }
    auto result = mojom::ManagedString::New();
    result->active_value = managed_dict.active_value.GetString();
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none()) {
      result->policy_value = managed_dict.policy_value.GetString();
    }
    return result;
  }
  NET_LOG(ERROR) << "Expected string or dictionary, found: " << *v;
  return nullptr;
}

mojom::ManagedStringPtr GetRequiredManagedString(const base::Value::Dict* dict,
                                                 const char* key) {
  mojom::ManagedStringPtr result = GetManagedString(dict, key);
  if (!result) {
    // Return an empty string with no policy source.
    result = mojom::ManagedString::New();
  }
  return result;
}

mojom::ManagedApnPropertiesPtr GetManagedApnProperties(
    const base::Value::Dict* cellular_dict,
    const char* key) {
  const base::Value::Dict* apn_dict = cellular_dict->FindDict(key);
  if (!apn_dict) {
    return nullptr;
  }
  auto apn = mojom::ManagedApnProperties::New();
  apn->access_point_name =
      GetRequiredManagedString(apn_dict, ::onc::cellular_apn::kAccessPointName);
  CHECK(apn->access_point_name);
  apn->authentication =
      GetManagedString(apn_dict, ::onc::cellular_apn::kAuthentication);
  apn->language = GetManagedString(apn_dict, ::onc::cellular_apn::kLanguage);
  apn->localized_name =
      GetManagedString(apn_dict, ::onc::cellular_apn::kLocalizedName);
  apn->name = GetManagedString(apn_dict, ::onc::cellular_apn::kName);
  apn->password = GetManagedString(apn_dict, ::onc::cellular_apn::kPassword);
  apn->username = GetManagedString(apn_dict, ::onc::cellular_apn::kUsername);
  apn->attach = GetManagedString(apn_dict, ::onc::cellular_apn::kAttach);
  return apn;
}

// This matches logic in NetworkTypePattern and should be kept in sync.
bool NetworkTypeMatchesType(mojom::NetworkType network_type,
                            mojom::NetworkType match_type) {
  switch (match_type) {
    case mojom::NetworkType::kAll:
      return true;
    case mojom::NetworkType::kMobile:
      return network_type == mojom::NetworkType::kCellular ||
             network_type == mojom::NetworkType::kTether;
    case mojom::NetworkType::kWireless:
      return network_type == mojom::NetworkType::kCellular ||
             network_type == mojom::NetworkType::kTether ||
             network_type == mojom::NetworkType::kWiFi;
    case mojom::NetworkType::kCellular:
    case mojom::NetworkType::kEthernet:
    case mojom::NetworkType::kTether:
    case mojom::NetworkType::kVPN:
    case mojom::NetworkType::kWiFi:
      return network_type == match_type;
  }
  NOTREACHED_IN_MIGRATION();
  return false;
}

bool NetworkStateMatchesType(const mojom::NetworkStateProperties* network,
                             mojom::NetworkType type) {
  return NetworkTypeMatchesType(network->type, type);
}

bool StateIsConnected(mojom::ConnectionStateType connection_state) {
  switch (connection_state) {
    case mojom::ConnectionStateType::kOnline:
    case mojom::ConnectionStateType::kConnected:
    case mojom::ConnectionStateType::kPortal:
      return true;
    case mojom::ConnectionStateType::kConnecting:
    case mojom::ConnectionStateType::kNotConnected:
      return false;
  }
  NOTREACHED_IN_MIGRATION();
  return false;
}

int GetWirelessSignalStrength(const mojom::NetworkStateProperties* network) {
  switch (network->type) {
    case mojom::NetworkType::kCellular:
      return network->type_state->get_cellular()->signal_strength;
    case mojom::NetworkType::kEthernet:
      return 0;
    case mojom::NetworkType::kTether:
      return network->type_state->get_tether()->signal_strength;
    case mojom::NetworkType::kVPN:
      return 0;
    case mojom::NetworkType::kWiFi:
      return network->type_state->get_wifi()->signal_strength;
    case mojom::NetworkType::kAll:
    case mojom::NetworkType::kMobile:
    case mojom::NetworkType::kWireless:
      break;
  }
  NOTREACHED_IN_MIGRATION();
  return 0;
}

bool IsInhibited(const mojom::DeviceStateProperties* device) {
  return device->inhibit_reason != mojom::InhibitReason::kNotInhibited;
}

base::Value::Dict CustomApnListToOnc(const std::string& network_guid,
                                     const base::Value::List* custom_apn_list) {
  CHECK(custom_apn_list);
  base::Value::Dict onc;
  onc.Set(::onc::network_config::kGUID, network_guid);
  onc.Set(::onc::network_config::kType, ::onc::network_type::kCellular);
  base::Value::Dict type_dict;
  type_dict.Set(::onc::cellular::kCustomAPNList, custom_apn_list->Clone());
  onc.Set(::onc::network_type::kCellular, std::move(type_dict));
  return onc;
}

std::vector<mojom::ApnType> OncApnTypesToMojo(
    const std::vector<std::string>& apn_types) {
  std::vector<mojom::ApnType> apn_types_result;
  if (apn_types.empty()) {
    NET_LOG(ERROR) << "APN types is empty";
    return apn_types_result;
  }

  apn_types_result.reserve(apn_types.size());
  for (const std::string& apn_type : apn_types) {
    if (apn_type == ::onc::cellular_apn::kApnTypeDefault) {
      apn_types_result.push_back(mojom::ApnType::kDefault);
      continue;
    }
    if (apn_type == ::onc::cellular_apn::kApnTypeAttach) {
      apn_types_result.push_back(mojom::ApnType::kAttach);
      continue;
    }
    if (apn_type == ::onc::cellular_apn::kApnTypeTether) {
      apn_types_result.push_back(mojom::ApnType::kTether);
      continue;
    }

    NOTREACHED_IN_MIGRATION() << "Unexpected ONC APN Type: " << apn_type;
  }

  return apn_types_result;
}

mojom::ApnPropertiesPtr GetApnProperties(const base::Value::Dict& onc_apn,
                                         bool is_apn_revamp_enabled) {
  auto apn = mojom::ApnProperties::New();
  apn->access_point_name =
      GetRequiredString(onc_apn, ::onc::cellular_apn::kAccessPointName);
  apn->authentication = OncApnAuthenticationTypeToMojo(
      GetString(onc_apn, ::onc::cellular_apn::kAuthentication));
  apn->language = GetString(onc_apn, ::onc::cellular_apn::kLanguage);
  apn->localized_name = GetString(onc_apn, ::onc::cellular_apn::kLocalizedName);
  apn->name = GetString(onc_apn, ::onc::cellular_apn::kName);
  apn->password = GetString(onc_apn, ::onc::cellular_apn::kPassword);
  apn->username = GetString(onc_apn, ::onc::cellular_apn::kUsername);
  apn->attach = GetString(onc_apn, ::onc::cellular_apn::kAttach);

  if (is_apn_revamp_enabled) {
    apn->id = GetString(onc_apn, ::onc::cellular_apn::kId);
    apn->ip_type =
        OncApnIpTypeToMojo(GetString(onc_apn, ::onc::cellular_apn::kIpType));
    apn->apn_types = OncApnTypesToMojo(
        GetRequiredStringList(onc_apn, ::onc::cellular_apn::kApnTypes));
    apn->source =
        OncApnSourceToMojo(GetString(onc_apn, ::onc::cellular_apn::kSource));
  }

  return apn;
}

mojom::ManagedApnListPtr GetManagedApnList(const base::Value* value,
                                           bool is_apn_revamp_enabled) {
  if (!value) {
    return nullptr;
  }
  if (value->is_list()) {
    auto result = mojom::ManagedApnList::New();
    std::vector<mojom::ApnPropertiesPtr> active;
    for (const base::Value& e : value->GetList()) {
      active.push_back(GetApnProperties(e.GetDict(), is_apn_revamp_enabled));
    }
    result->active_value = std::move(active);
    return result;
  } else if (value->is_dict()) {
    ManagedDictionary managed_dict = GetManagedDictionary(&value->GetDict());
    if (!managed_dict.active_value.is_list()) {
      NET_LOG(ERROR) << "No active or effective value for APNList";
      return nullptr;
    }
    auto result = mojom::ManagedApnList::New();
    for (const base::Value& e : managed_dict.active_value.GetList()) {
      result->active_value.push_back(
          GetApnProperties(e.GetDict(), is_apn_revamp_enabled));
    }
    result->policy_source = managed_dict.policy_source;
    if (!managed_dict.policy_value.is_none()) {
      result->policy_value = std::vector<mojom::ApnPropertiesPtr>();
      for (const base::Value& e : managed_dict.policy_value.GetList()) {
        result->policy_value->push_back(
            GetApnProperties(e.GetDict(), is_apn_revamp_enabled));
      }
    }
    return result;
  }
  NET_LOG(ERROR) << "Expected list or dictionary, found: " << *value;
  return nullptr;
}

base::Value::Dict WiFiConfigPropertiesToMojoJsValue(
    const mojo::StructPtr<
        chromeos::network_config::mojom::WiFiConfigProperties>& wifi_config) {
  base::Value::Dict prefilled_wifi_config;
  prefilled_wifi_config.Set(kMojoKeySecurity,
                            static_cast<int>(wifi_config->security));
  if (wifi_config->ssid.has_value()) {
    prefilled_wifi_config.Set(kMojoKeySsid, *(wifi_config->ssid));
  }
  if (wifi_config->passphrase.has_value()) {
    prefilled_wifi_config.Set(kMojoKeyPassphrase, *(wifi_config->passphrase));
  }
  if (!wifi_config->eap.is_null()) {
    auto& eap_config = wifi_config->eap;
    base::Value::Dict prefilled_eap_config;
    if (eap_config->inner.has_value()) {
      prefilled_eap_config.Set(kMojoKeyEapInner, *(eap_config->inner));
    }
    if (eap_config->outer.has_value()) {
      prefilled_eap_config.Set(kMojoKeyEapOuter, *(eap_config->outer));
    }
    if (eap_config->identity.has_value()) {
      prefilled_eap_config.Set(kMojoKeyEapIdentity, *(eap_config->identity));
    }
    if (eap_config->anonymous_identity.has_value()) {
      prefilled_eap_config.Set(kMojoKeyEapAnonymousIdentity,
                               *(eap_config->anonymous_identity));
    }
    if (eap_config->password.has_value()) {
      prefilled_eap_config.Set(kMojoKeyEapPassword, *(eap_config->password));
    }
    prefilled_wifi_config.Set(kMojoKeyEap, prefilled_eap_config.Clone());
  }
  base::Value::Dict type_config;
  type_config.Set(kMojoKeyWifi, prefilled_wifi_config.Clone());
  base::Value::Dict config;
  config.Set(kMojoKeyTypeConfig, type_config.Clone());
  return config;
}

}  // namespace chromeos::network_config