// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// The implementation of TranslateONCObjectToShill is structured in two parts:
// - The recursion through the existing ONC hierarchy
//     see TranslateONCHierarchy
// - The local translation of an object depending on the associated signature
//     see LocalTranslator::TranslateFields

#include <memory>
#include <string>
#include <utility>

#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/network/client_cert_util.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/onc/onc_signature.h"
#include "chromeos/network/onc/onc_translation_tables.h"
#include "chromeos/network/onc/onc_translator.h"
#include "chromeos/network/onc/onc_utils.h"
#include "chromeos/network/shill_property_util.h"
#include "components/onc/onc_constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"

namespace chromeos {
namespace onc {

namespace {

std::unique_ptr<base::Value> ConvertValueToString(const base::Value& value) {
  std::string str;
  if (!value.GetAsString(&str))
    base::JSONWriter::Write(value, &str);
  return std::make_unique<base::Value>(str);
}

// Sets any client cert properties when ClientCertType is PKCS11Id.
void SetClientCertProperties(client_cert::ConfigType config_type,
                             const base::DictionaryValue* onc_object,
                             base::DictionaryValue* shill_dictionary) {
  std::string cert_type;
  onc_object->GetStringWithoutPathExpansion(::onc::client_cert::kClientCertType,
                                            &cert_type);
  if (cert_type != ::onc::client_cert::kPKCS11Id)
    return;

  std::string pkcs11_id;
  onc_object->GetStringWithoutPathExpansion(
      ::onc::client_cert::kClientCertPKCS11Id, &pkcs11_id);
  if (pkcs11_id.empty()) {
    // If the cert type is PKCS11 but the pkcs11 id is empty, set empty
    // properties to indicate 'no certificate'.
    client_cert::SetEmptyShillProperties(config_type, shill_dictionary);
    return;
  }

  int slot_id;
  std::string cert_id =
      client_cert::GetPkcs11AndSlotIdFromEapCertId(pkcs11_id, &slot_id);
  client_cert::SetShillProperties(config_type, slot_id, cert_id,
                                  shill_dictionary);
}

// This class is responsible to translate the local fields of the given
// |onc_object| according to |onc_signature| into |shill_dictionary|. This
// translation should consider (if possible) only fields of this ONC object and
// not nested objects because recursion is handled by the calling function
// TranslateONCHierarchy.
class LocalTranslator {
 public:
  LocalTranslator(const OncValueSignature& onc_signature,
                  const base::DictionaryValue& onc_object,
                  base::DictionaryValue* shill_dictionary)
      : onc_signature_(&onc_signature),
        onc_object_(&onc_object),
        shill_dictionary_(shill_dictionary) {
    field_translation_table_ = GetFieldTranslationTable(onc_signature);
  }

  void TranslateFields();

 private:
  void TranslateEthernet();
  void TranslateOpenVPN();
  void TranslateIPsec();
  void TranslateL2TP();
  void TranslateVPN();
  void TranslateWiFi();
  void TranslateEAP();
  void TranslateNetworkConfiguration();

  // Copies all entries from |onc_object_| to |shill_dictionary_| for which a
  // translation (shill_property_name) is defined by the translation table for
  // |onc_signature_|.
  void CopyFieldsAccordingToSignature();

  // If existent, copies the value of field |onc_field_name| from |onc_object_|
  // to the property |shill_property_name| in |shill_dictionary_|.
  void CopyFieldFromONCToShill(const std::string& onc_field_name,
                               const std::string& shill_property_name);

  // Adds |value| to |shill_dictionary| at the field shill_property_name given
  // by the associated signature. Takes ownership of |value|. Does nothing if
  // |value| is NULL or the property name cannot be read from the signature.
  void AddValueAccordingToSignature(const std::string& onc_field_name,
                                    std::unique_ptr<base::Value> value);

  // Translates the value |onc_value| using |table|. It is an error if no
  // matching table entry is found. Writes the result as entry at
  // |shill_property_name| in |shill_dictionary_|.
  void TranslateWithTableAndSet(const std::string& onc_value,
                                const StringTranslationEntry table[],
                                const std::string& shill_property_name);

  const OncValueSignature* onc_signature_;
  const FieldTranslationEntry* field_translation_table_;
  const base::DictionaryValue* onc_object_;
  base::DictionaryValue* shill_dictionary_;

  DISALLOW_COPY_AND_ASSIGN(LocalTranslator);
};

void LocalTranslator::TranslateFields() {
  if (onc_signature_ == &kNetworkConfigurationSignature)
    TranslateNetworkConfiguration();
  else if (onc_signature_ == &kEthernetSignature)
    TranslateEthernet();
  else if (onc_signature_ == &kVPNSignature)
    TranslateVPN();
  else if (onc_signature_ == &kOpenVPNSignature)
    TranslateOpenVPN();
  else if (onc_signature_ == &kIPsecSignature)
    TranslateIPsec();
  else if (onc_signature_ == &kL2TPSignature)
    TranslateL2TP();
  else if (onc_signature_ == &kWiFiSignature)
    TranslateWiFi();
  else if (onc_signature_ == &kEAPSignature)
    TranslateEAP();
  else
    CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateEthernet() {
  std::string authentication;
  onc_object_->GetStringWithoutPathExpansion(::onc::ethernet::kAuthentication,
                                             &authentication);

  const char* shill_type = shill::kTypeEthernet;
  if (authentication == ::onc::ethernet::k8021X)
    shill_type = shill::kTypeEthernetEap;
  shill_dictionary_->SetKey(shill::kTypeProperty, base::Value(shill_type));

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateOpenVPN() {
  // SaveCredentials needs special handling when translating from Shill -> ONC
  // so handle it explicitly here.
  CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
                          shill::kSaveCredentialsProperty);

  std::string user_auth_type;
  onc_object_->GetStringWithoutPathExpansion(
      ::onc::openvpn::kUserAuthenticationType, &user_auth_type);
  // The default behavior (if user_auth_type is empty) is to use both password
  // and OTP in a static challenge and only the password otherwise. As long as
  // Shill doe not know about the exact user authentication type, this is
  // identical to kPasswordAndOTP.
  if (user_auth_type.empty())
    user_auth_type = ::onc::openvpn_user_auth_type::kPasswordAndOTP;
  NET_LOG(DEBUG) << "USER AUTH TYPE: " << user_auth_type;
  if (user_auth_type == ::onc::openvpn_user_auth_type::kPassword ||
      user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP) {
    CopyFieldFromONCToShill(::onc::openvpn::kPassword,
                            shill::kOpenVPNPasswordProperty);
  }
  if (user_auth_type == ::onc::openvpn_user_auth_type::kPasswordAndOTP)
    CopyFieldFromONCToShill(::onc::openvpn::kOTP, shill::kOpenVPNOTPProperty);
  if (user_auth_type == ::onc::openvpn_user_auth_type::kOTP)
    CopyFieldFromONCToShill(::onc::openvpn::kOTP, shill::kOpenVPNTokenProperty);

  // Shill supports only one RemoteCertKU but ONC specifies a list, so copy only
  // the first entry if the lists exists. Otherwise copy an empty string to
  // reset any previous configuration.
  const base::ListValue* cert_kus = nullptr;
  std::string cert_ku;
  if (onc_object_->GetListWithoutPathExpansion(::onc::openvpn::kRemoteCertKU,
                                               &cert_kus)) {
    cert_kus->GetString(0, &cert_ku);
  }
  shill_dictionary_->SetKey(shill::kOpenVPNRemoteCertKUProperty,
                            base::Value(cert_ku));

  SetClientCertProperties(client_cert::CONFIG_TYPE_OPENVPN, onc_object_,
                          shill_dictionary_);

  // Modified CopyFieldsAccordingToSignature to handle RemoteCertKU and
  // ServerCAPEMs and handle all other fields as strings.
  for (base::DictionaryValue::Iterator it(*onc_object_); !it.IsAtEnd();
       it.Advance()) {
    std::string key = it.key();
    std::unique_ptr<base::Value> translated;
    if (key == ::onc::openvpn::kRemoteCertKU ||
        key == ::onc::openvpn::kServerCAPEMs ||
        key == ::onc::openvpn::kExtraHosts) {
      translated.reset(it.value().DeepCopy());
    } else {
      // Shill wants all Provider/VPN fields to be strings.
      translated = ConvertValueToString(it.value());
    }
    AddValueAccordingToSignature(key, std::move(translated));
  }
}

void LocalTranslator::TranslateIPsec() {
  SetClientCertProperties(client_cert::CONFIG_TYPE_IPSEC, onc_object_,
                          shill_dictionary_);

  // SaveCredentials needs special handling when translating from Shill -> ONC
  // so handle it explicitly here.
  CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
                          shill::kSaveCredentialsProperty);

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateL2TP() {
  // SaveCredentials needs special handling when translating from Shill -> ONC
  // so handle it explicitly here.
  CopyFieldFromONCToShill(::onc::vpn::kSaveCredentials,
                          shill::kSaveCredentialsProperty);

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateVPN() {
  std::string onc_type;
  if (onc_object_->GetStringWithoutPathExpansion(::onc::vpn::kType,
                                                 &onc_type)) {
    TranslateWithTableAndSet(onc_type, kVPNTypeTable,
                             shill::kProviderTypeProperty);
  }
  if (onc_type == ::onc::vpn::kThirdPartyVpn) {
    // For third-party VPNs, |shill::kProviderHostProperty| is used to store the
    // provider's extension ID.
    const base::DictionaryValue* onc_third_party_vpn = nullptr;
    onc_object_->GetDictionaryWithoutPathExpansion(::onc::vpn::kThirdPartyVpn,
                                                   &onc_third_party_vpn);
    std::string onc_extension_id;
    if (onc_third_party_vpn &&
        onc_third_party_vpn->GetStringWithoutPathExpansion(
            ::onc::third_party_vpn::kExtensionID, &onc_extension_id)) {
      shill_dictionary_->SetKey(shill::kProviderHostProperty,
                                base::Value(onc_extension_id));
    }
  } else {
    CopyFieldFromONCToShill(::onc::vpn::kHost, shill::kProviderHostProperty);
  }

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateWiFi() {
  std::string security;
  if (onc_object_->GetStringWithoutPathExpansion(::onc::wifi::kSecurity,
                                                 &security)) {
    TranslateWithTableAndSet(security, kWiFiSecurityTable,
                             shill::kSecurityClassProperty);
    if (security == ::onc::wifi::kWEP_8021X) {
      shill_dictionary_->SetKey(shill::kEapKeyMgmtProperty,
                                base::Value(shill::kKeyManagementIEEE8021X));
    }
  }

  // We currently only support managed and no adhoc networks.
  shill_dictionary_->SetKey(shill::kModeProperty,
                            base::Value(shill::kModeManaged));

  bool allow_gateway_arp_polling;
  if (onc_object_->GetBooleanWithoutPathExpansion(
          ::onc::wifi::kAllowGatewayARPPolling, &allow_gateway_arp_polling)) {
    shill_dictionary_->SetKey(shill::kLinkMonitorDisableProperty,
                              base::Value(!allow_gateway_arp_polling));
  }

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateEAP() {
  // Note: EAP.Outer may be empty for WiMAX configurations.
  std::string outer;
  onc_object_->GetStringWithoutPathExpansion(::onc::eap::kOuter, &outer);
  if (!outer.empty())
    TranslateWithTableAndSet(outer, kEAPOuterTable, shill::kEapMethodProperty);

  // Translate the inner protocol only for outer tunneling protocols.
  if (outer == ::onc::eap::kPEAP || outer == ::onc::eap::kEAP_TTLS) {
    // In ONC the Inner protocol defaults to "Automatic".
    std::string inner = ::onc::eap::kAutomatic;
    // ONC's Inner == "Automatic" translates to omitting the Phase2 property in
    // Shill.
    onc_object_->GetStringWithoutPathExpansion(::onc::eap::kInner, &inner);
    if (inner != ::onc::eap::kAutomatic) {
      const StringTranslationEntry* table = outer == ::onc::eap::kPEAP
                                                ? kEAP_PEAP_InnerTable
                                                : kEAP_TTLS_InnerTable;
      TranslateWithTableAndSet(inner, table, shill::kEapPhase2AuthProperty);
    }
  }

  SetClientCertProperties(client_cert::CONFIG_TYPE_EAP, onc_object_,
                          shill_dictionary_);

  // Set shill::kEapUseLoginPasswordProperty according to whether or not the
  // password substitution variable is set.
  const base::Value* password_field =
      onc_object_->FindKey(::onc::eap::kPassword);
  if (password_field && password_field->GetString() ==
                            ::onc::substitutes::kPasswordPlaceholderVerbatim) {
    shill_dictionary_->SetKey(shill::kEapUseLoginPasswordProperty,
                              base::Value(true));
  }

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::TranslateNetworkConfiguration() {
  std::string type;
  onc_object_->GetStringWithoutPathExpansion(::onc::network_config::kType,
                                             &type);

  // Set the type except for Ethernet which is set in TranslateEthernet.
  if (type != ::onc::network_type::kEthernet)
    TranslateWithTableAndSet(type, kNetworkTypeTable, shill::kTypeProperty);

  // Shill doesn't allow setting the name for non-VPN networks.
  if (type == ::onc::network_type::kVPN)
    CopyFieldFromONCToShill(::onc::network_config::kName, shill::kNameProperty);

  std::string ip_address_config_type, name_servers_config_type;
  onc_object_->GetStringWithoutPathExpansion(
      ::onc::network_config::kIPAddressConfigType, &ip_address_config_type);
  onc_object_->GetStringWithoutPathExpansion(
      ::onc::network_config::kNameServersConfigType, &name_servers_config_type);
  if ((ip_address_config_type == ::onc::network_config::kIPConfigTypeDHCP) ||
      (name_servers_config_type == ::onc::network_config::kIPConfigTypeDHCP)) {
    // If either type is set to DHCP, provide an empty dictionary to ensure
    // that any unset properties are cleared. Note: if either type is specified,
    // the other type defaults to DHCP if not specified.
    shill_dictionary_->SetWithoutPathExpansion(
        shill::kStaticIPConfigProperty,
        std::make_unique<base::DictionaryValue>());
  }

  const base::DictionaryValue* proxy_settings = nullptr;
  if (onc_object_->GetDictionaryWithoutPathExpansion(
          ::onc::network_config::kProxySettings, &proxy_settings)) {
    base::Value proxy_config =
        ConvertOncProxySettingsToProxyConfig(*proxy_settings);
    std::string proxy_config_str;
    base::JSONWriter::Write(proxy_config, &proxy_config_str);
    shill_dictionary_->SetKey(shill::kProxyConfigProperty,
                              base::Value(proxy_config_str));
  }

  CopyFieldsAccordingToSignature();
}

void LocalTranslator::CopyFieldsAccordingToSignature() {
  for (base::DictionaryValue::Iterator it(*onc_object_); !it.IsAtEnd();
       it.Advance()) {
    AddValueAccordingToSignature(it.key(),
                                 base::WrapUnique(it.value().DeepCopy()));
  }
}

void LocalTranslator::CopyFieldFromONCToShill(
    const std::string& onc_field_name,
    const std::string& shill_property_name) {
  const base::Value* value = NULL;
  if (!onc_object_->GetWithoutPathExpansion(onc_field_name, &value))
    return;

  const OncFieldSignature* field_signature =
      GetFieldSignature(*onc_signature_, onc_field_name);
  if (field_signature) {
    base::Value::Type expected_type =
        field_signature->value_signature->onc_type;
    if (value->type() != expected_type) {
      NET_LOG(ERROR) << "Found field " << onc_field_name << " of type "
                     << value->type() << " but expected type " << expected_type;
      return;
    }
  } else {
    NET_LOG(ERROR)
        << "Attempt to translate a field that is not part of the ONC format.";
    return;
  }
  shill_dictionary_->SetKey(shill_property_name, value->Clone());
}

void LocalTranslator::AddValueAccordingToSignature(
    const std::string& onc_name,
    std::unique_ptr<base::Value> value) {
  if (!value || !field_translation_table_)
    return;
  std::string shill_property_name;
  if (!GetShillPropertyName(onc_name, field_translation_table_,
                            &shill_property_name)) {
    return;
  }

  shill_dictionary_->SetWithoutPathExpansion(shill_property_name,
                                             std::move(value));
}

void LocalTranslator::TranslateWithTableAndSet(
    const std::string& onc_value,
    const StringTranslationEntry table[],
    const std::string& shill_property_name) {
  std::string shill_value;
  if (TranslateStringToShill(table, onc_value, &shill_value)) {
    shill_dictionary_->SetKey(shill_property_name, base::Value(shill_value));
    return;
  }
  // As we previously validate ONC, this case should never occur. If it still
  // occurs, we should check here. Otherwise the failure will only show up much
  // later in Shill.
  NET_LOG(ERROR) << "Value '" << onc_value
                 << "' cannot be translated to Shill property: "
                 << shill_property_name;
}

// Iterates recursively over |onc_object| and its |signature|. At each object
// applies the local translation using LocalTranslator::TranslateFields. The
// results are written to |shill_dictionary|.
void TranslateONCHierarchy(const OncValueSignature& signature,
                           const base::DictionaryValue& onc_object,
                           base::Value* shill_dictionary) {
  const std::vector<std::string> path =
      GetPathToNestedShillDictionary(signature);
  const std::vector<base::StringPiece> path_pieces(path.begin(), path.end());
  base::Value* target_shill_dictionary = shill_dictionary->FindPathOfType(
      path_pieces, base::Value::Type::DICTIONARY);
  if (!target_shill_dictionary) {
    target_shill_dictionary = shill_dictionary->SetPath(
        path_pieces, base::Value(base::Value::Type::DICTIONARY));
  }

  // Translates fields of |onc_object| and writes them to
  // |target_shill_dictionary_| nested in |shill_dictionary|.
  LocalTranslator translator(
      signature, onc_object,
      static_cast<base::DictionaryValue*>(target_shill_dictionary));
  translator.TranslateFields();

  // Recurse into nested objects.
  for (base::DictionaryValue::Iterator it(onc_object); !it.IsAtEnd();
       it.Advance()) {
    const base::DictionaryValue* inner_object = NULL;
    if (!it.value().GetAsDictionary(&inner_object))
      continue;

    const OncFieldSignature* field_signature =
        GetFieldSignature(signature, it.key());

    TranslateONCHierarchy(*field_signature->value_signature, *inner_object,
                          shill_dictionary);
  }
}

}  // namespace

std::unique_ptr<base::DictionaryValue> TranslateONCObjectToShill(
    const OncValueSignature* onc_signature,
    const base::DictionaryValue& onc_object) {
  CHECK(onc_signature != NULL);
  std::unique_ptr<base::DictionaryValue> shill_dictionary(
      new base::DictionaryValue);
  TranslateONCHierarchy(*onc_signature, onc_object, shill_dictionary.get());
  return shill_dictionary;
}

}  // namespace onc
}  // namespace chromeos
