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

#include "chromeos/network/onc/onc_validator.h"

#include <algorithm>
#include <string>

#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chromeos/network/onc/onc_signature.h"
#include "components/onc/onc_constants.h"

namespace chromeos {
namespace onc {

namespace {

// Copied from policy/configuration_policy_handler.cc.
// TODO(pneubeck): move to a common place like base/.
std::string ValueTypeToString(base::Value::Type type) {
  static const char* strings[] = {
    "null",
    "boolean",
    "integer",
    "double",
    "string",
    "binary",
    "dictionary",
    "list"
  };
  CHECK(static_cast<size_t>(type) < arraysize(strings));
  return strings[type];
}

}  // namespace

Validator::Validator(bool error_on_unknown_field,
                     bool error_on_wrong_recommended,
                     bool error_on_missing_field,
                     bool managed_onc)
    : error_on_unknown_field_(error_on_unknown_field),
      error_on_wrong_recommended_(error_on_wrong_recommended),
      error_on_missing_field_(error_on_missing_field),
      managed_onc_(managed_onc),
      onc_source_(::onc::ONC_SOURCE_NONE) {}

Validator::~Validator() {}

scoped_ptr<base::DictionaryValue> Validator::ValidateAndRepairObject(
    const OncValueSignature* object_signature,
    const base::DictionaryValue& onc_object,
    Result* result) {
  CHECK(object_signature != NULL);
  *result = VALID;
  error_or_warning_found_ = false;
  bool error = false;
  scoped_ptr<base::Value> result_value =
      MapValue(*object_signature, onc_object, &error);
  if (error) {
    *result = INVALID;
    result_value.reset();
  } else if (error_or_warning_found_) {
    *result = VALID_WITH_WARNINGS;
  }
  // The return value should be NULL if, and only if, |result| equals INVALID.
  DCHECK_EQ(result_value.get() == NULL, *result == INVALID);

  base::DictionaryValue* result_dict = NULL;
  if (result_value.get() != NULL) {
    result_value.release()->GetAsDictionary(&result_dict);
    CHECK(result_dict != NULL);
  }

  return make_scoped_ptr(result_dict);
}

scoped_ptr<base::Value> Validator::MapValue(const OncValueSignature& signature,
                                            const base::Value& onc_value,
                                            bool* error) {
  if (onc_value.GetType() != signature.onc_type) {
    LOG(ERROR) << MessageHeader() << "Found value '" << onc_value
               << "' of type '" << ValueTypeToString(onc_value.GetType())
               << "', but type '" << ValueTypeToString(signature.onc_type)
               << "' is required.";
    error_or_warning_found_ = *error = true;
    return scoped_ptr<base::Value>();
  }

  scoped_ptr<base::Value> repaired =
      Mapper::MapValue(signature, onc_value, error);
  if (repaired.get() != NULL)
    CHECK_EQ(repaired->GetType(), signature.onc_type);
  return repaired.Pass();
}

scoped_ptr<base::DictionaryValue> Validator::MapObject(
    const OncValueSignature& signature,
    const base::DictionaryValue& onc_object,
    bool* error) {
  scoped_ptr<base::DictionaryValue> repaired(new base::DictionaryValue);

  bool valid = ValidateObjectDefault(signature, onc_object, repaired.get());
  if (valid) {
    if (&signature == &kToplevelConfigurationSignature)
      valid = ValidateToplevelConfiguration(repaired.get());
    else if (&signature == &kNetworkConfigurationSignature)
      valid = ValidateNetworkConfiguration(repaired.get());
    else if (&signature == &kEthernetSignature)
      valid = ValidateEthernet(repaired.get());
    else if (&signature == &kIPConfigSignature)
      valid = ValidateIPConfig(repaired.get());
    else if (&signature == &kWiFiSignature)
      valid = ValidateWiFi(repaired.get());
    else if (&signature == &kVPNSignature)
      valid = ValidateVPN(repaired.get());
    else if (&signature == &kIPsecSignature)
      valid = ValidateIPsec(repaired.get());
    else if (&signature == &kOpenVPNSignature)
      valid = ValidateOpenVPN(repaired.get());
    else if (&signature == &kVerifyX509Signature)
      valid = ValidateVerifyX509(repaired.get());
    else if (&signature == &kCertificatePatternSignature)
      valid = ValidateCertificatePattern(repaired.get());
    else if (&signature == &kProxySettingsSignature)
      valid = ValidateProxySettings(repaired.get());
    else if (&signature == &kProxyLocationSignature)
      valid = ValidateProxyLocation(repaired.get());
    else if (&signature == &kEAPSignature)
      valid = ValidateEAP(repaired.get());
    else if (&signature == &kCertificateSignature)
      valid = ValidateCertificate(repaired.get());
  }

  if (valid) {
    return repaired.Pass();
  } else {
    DCHECK(error_or_warning_found_);
    error_or_warning_found_ = *error = true;
    return scoped_ptr<base::DictionaryValue>();
  }
}

scoped_ptr<base::Value> Validator::MapField(
    const std::string& field_name,
    const OncValueSignature& object_signature,
    const base::Value& onc_value,
    bool* found_unknown_field,
    bool* error) {
  path_.push_back(field_name);
  bool current_field_unknown = false;
  scoped_ptr<base::Value> result = Mapper::MapField(
      field_name, object_signature, onc_value, &current_field_unknown, error);

  DCHECK_EQ(field_name, path_.back());
  path_.pop_back();

  if (current_field_unknown) {
    error_or_warning_found_ = *found_unknown_field = true;
    std::string message = MessageHeader() + "Field name '" + field_name +
        "' is unknown.";
    if (error_on_unknown_field_)
      LOG(ERROR) << message;
    else
      LOG(WARNING) << message;
  }

  return result.Pass();
}

scoped_ptr<base::ListValue> Validator::MapArray(
    const OncValueSignature& array_signature,
    const base::ListValue& onc_array,
    bool* nested_error) {
  bool nested_error_in_current_array = false;
  scoped_ptr<base::ListValue> result = Mapper::MapArray(
      array_signature, onc_array, &nested_error_in_current_array);

  // Drop individual networks and certificates instead of rejecting all of
  // the configuration.
  if (nested_error_in_current_array &&
      &array_signature != &kNetworkConfigurationListSignature &&
      &array_signature != &kCertificateListSignature) {
    *nested_error = nested_error_in_current_array;
  }
  return result.Pass();
}

scoped_ptr<base::Value> Validator::MapEntry(int index,
                                            const OncValueSignature& signature,
                                            const base::Value& onc_value,
                                            bool* error) {
  std::string str = base::IntToString(index);
  path_.push_back(str);
  scoped_ptr<base::Value> result =
      Mapper::MapEntry(index, signature, onc_value, error);
  DCHECK_EQ(str, path_.back());
  path_.pop_back();
  return result.Pass();
}

bool Validator::ValidateObjectDefault(const OncValueSignature& signature,
                                      const base::DictionaryValue& onc_object,
                                      base::DictionaryValue* result) {
  bool found_unknown_field = false;
  bool nested_error_occured = false;
  MapFields(signature, onc_object, &found_unknown_field, &nested_error_occured,
            result);

  if (found_unknown_field && error_on_unknown_field_) {
    DVLOG(1) << "Unknown field names are errors: Aborting.";
    return false;
  }

  if (nested_error_occured)
    return false;

  return ValidateRecommendedField(signature, result);
}

bool Validator::ValidateRecommendedField(
    const OncValueSignature& object_signature,
    base::DictionaryValue* result) {
  CHECK(result != NULL);

  scoped_ptr<base::ListValue> recommended;
  scoped_ptr<base::Value> recommended_value;
  // This remove passes ownership to |recommended_value|.
  if (!result->RemoveWithoutPathExpansion(::onc::kRecommended,
                                          &recommended_value)) {
    return true;
  }
  base::ListValue* recommended_list = NULL;
  recommended_value.release()->GetAsList(&recommended_list);
  CHECK(recommended_list);

  recommended.reset(recommended_list);

  if (!managed_onc_) {
    error_or_warning_found_ = true;
    LOG(WARNING) << MessageHeader() << "Found the field '"
                 << ::onc::kRecommended
                 << "' in an unmanaged ONC. Removing it.";
    return true;
  }

  scoped_ptr<base::ListValue> repaired_recommended(new base::ListValue);
  for (base::ListValue::iterator it = recommended->begin();
       it != recommended->end(); ++it) {
    std::string field_name;
    if (!(*it)->GetAsString(&field_name)) {
      NOTREACHED();
      continue;
    }

    const OncFieldSignature* field_signature =
        GetFieldSignature(object_signature, field_name);

    bool found_error = false;
    std::string error_cause;
    if (field_signature == NULL) {
      found_error = true;
      error_cause = "unknown";
    } else if (field_signature->value_signature->onc_type ==
               base::Value::TYPE_DICTIONARY) {
      found_error = true;
      error_cause = "dictionary-typed";
    }

    if (found_error) {
      error_or_warning_found_ = true;
      path_.push_back(::onc::kRecommended);
      std::string message = MessageHeader() + "The " + error_cause +
          " field '" + field_name + "' cannot be recommended.";
      path_.pop_back();
      if (error_on_wrong_recommended_) {
        LOG(ERROR) << message;
        return false;
      } else {
        LOG(WARNING) << message;
        continue;
      }
    }

    repaired_recommended->Append((*it)->DeepCopy());
  }

  result->Set(::onc::kRecommended, repaired_recommended.release());
  return true;
}

namespace {

std::string JoinStringRange(const char** range_begin,
                            const char** range_end,
                            const std::string& separator) {
  std::vector<std::string> string_vector;
  std::copy(range_begin, range_end, std::back_inserter(string_vector));
  return JoinString(string_vector, separator);
}

}  // namespace

bool Validator::FieldExistsAndHasNoValidValue(
    const base::DictionaryValue& object,
    const std::string& field_name,
    const char** valid_values) {
  std::string actual_value;
  if (!object.GetStringWithoutPathExpansion(field_name, &actual_value))
    return false;

  const char** it = valid_values;
  for (; *it != NULL; ++it) {
    if (actual_value == *it)
      return false;
  }
  error_or_warning_found_ = true;
  std::string valid_values_str =
      "[" + JoinStringRange(valid_values, it, ", ") + "]";
  path_.push_back(field_name);
  LOG(ERROR) << MessageHeader() << "Found value '" << actual_value <<
      "', but expected one of the values " << valid_values_str;
  path_.pop_back();
  return true;
}

bool Validator::FieldExistsAndIsNotInRange(const base::DictionaryValue& object,
                                           const std::string& field_name,
                                           int lower_bound,
                                           int upper_bound) {
  int actual_value;
  if (!object.GetIntegerWithoutPathExpansion(field_name, &actual_value) ||
      (lower_bound <= actual_value && actual_value <= upper_bound)) {
    return false;
  }
  error_or_warning_found_ = true;
  path_.push_back(field_name);
  LOG(ERROR) << MessageHeader() << "Found value '" << actual_value
             << "', but expected a value in the range [" << lower_bound
             << ", " << upper_bound << "] (boundaries inclusive)";
  path_.pop_back();
  return true;
}

bool Validator::FieldExistsAndIsEmpty(const base::DictionaryValue& object,
                                      const std::string& field_name) {
  const base::Value* value = NULL;
  if (!object.GetWithoutPathExpansion(field_name, &value))
    return false;

  std::string str;
  const base::ListValue* list = NULL;
  if (value->GetAsString(&str)) {
    if (!str.empty())
      return false;
  } else if (value->GetAsList(&list)) {
    if (!list->empty())
      return false;
  } else {
    NOTREACHED();
    return false;
  }

  error_or_warning_found_ = true;
  path_.push_back(field_name);
  LOG(ERROR) << MessageHeader() << "Found an empty string, but expected a "
             << "non-empty string.";
  path_.pop_back();
  return true;
}

bool Validator::RequireField(const base::DictionaryValue& dict,
                             const std::string& field_name) {
  if (dict.HasKey(field_name))
    return true;
  error_or_warning_found_ = true;
  std::string message = MessageHeader() + "The required field '" + field_name +
      "' is missing.";
  if (error_on_missing_field_)
    LOG(ERROR) << message;
  else
    LOG(WARNING) << message;
  return false;
}

bool Validator::CheckGuidIsUniqueAndAddToSet(const base::DictionaryValue& dict,
                                             const std::string& key_guid,
                                             std::set<std::string> *guids) {
  std::string guid;
  if (dict.GetStringWithoutPathExpansion(key_guid, &guid)) {
    if (guids->count(guid) != 0) {
      error_or_warning_found_ = true;
      LOG(ERROR) << MessageHeader() << "Found a duplicate GUID " << guid << ".";
      return false;
    }
    guids->insert(guid);
  }
  return true;
}

bool Validator::IsCertPatternInDevicePolicy(const std::string& cert_type) {
  if (cert_type == ::onc::certificate::kPattern &&
      onc_source_ == ::onc::ONC_SOURCE_DEVICE_POLICY) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "Client certificate patterns are "
               << "prohibited in ONC device policies.";
    return true;
  }
  return false;
}

bool Validator::IsGlobalNetworkConfigInUserImport(
    const base::DictionaryValue& onc_object) {
  if (onc_source_ == ::onc::ONC_SOURCE_USER_IMPORT &&
      onc_object.HasKey(::onc::toplevel_config::kGlobalNetworkConfiguration)) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "GlobalNetworkConfiguration is prohibited "
               << "in ONC user imports";
    return true;
  }
  return false;
}

bool Validator::ValidateToplevelConfiguration(base::DictionaryValue* result) {
  using namespace ::onc::toplevel_config;

  static const char* kValidTypes[] = { kUnencryptedConfiguration,
                                       kEncryptedConfiguration,
                                       NULL };
  if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes))
    return false;

  // Not part of the ONC spec:
  // - We don't require the type field (we assume that it's an
  //   UnencryptedConfiguration then).
  // - We don't require specific toplevel objects to be present (e.g. at least
  //   one of NetworkConfiguration or Certificates).

  if (IsGlobalNetworkConfigInUserImport(*result))
    return false;

  return true;
}

bool Validator::ValidateNetworkConfiguration(base::DictionaryValue* result) {
  using namespace ::onc::network_config;

  static const char* kValidTypes[] = { ::onc::network_type::kEthernet,
                                       ::onc::network_type::kVPN,
                                       ::onc::network_type::kWiFi,
                                       ::onc::network_type::kCellular,
                                       NULL };
  if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes) ||
      FieldExistsAndIsEmpty(*result, kGUID)) {
    return false;
  }

  if (!CheckGuidIsUniqueAndAddToSet(*result, kGUID, &network_guids_))
    return false;

  bool all_required_exist = RequireField(*result, kGUID);

  bool remove = false;
  result->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
  if (!remove) {
    all_required_exist &=
        RequireField(*result, kName) && RequireField(*result, kType);

    std::string type;
    result->GetStringWithoutPathExpansion(kType, &type);

    // Prohibit anything but WiFi and Ethernet for device-level policy (which
    // corresponds to shared networks). See also http://crosbug.com/28741.
    if (onc_source_ == ::onc::ONC_SOURCE_DEVICE_POLICY &&
        type != ::onc::network_type::kWiFi &&
        type != ::onc::network_type::kEthernet) {
      error_or_warning_found_ = true;
      LOG(ERROR) << MessageHeader() << "Networks of type '"
                 << type << "' are prohibited in ONC device policies.";
      return false;
    }

    if (type == ::onc::network_type::kWiFi) {
      all_required_exist &= RequireField(*result, ::onc::network_config::kWiFi);
    } else if (type == ::onc::network_type::kEthernet) {
      all_required_exist &=
          RequireField(*result, ::onc::network_config::kEthernet);
    } else if (type == ::onc::network_type::kCellular) {
      all_required_exist &=
          RequireField(*result, ::onc::network_config::kCellular);
    } else if (type == ::onc::network_type::kVPN) {
      all_required_exist &= RequireField(*result, ::onc::network_config::kVPN);
    } else if (!type.empty()) {
      NOTREACHED();
    }
  }

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateEthernet(base::DictionaryValue* result) {
  using namespace ::onc::ethernet;

  static const char* kValidAuthentications[] = { kNone, k8021X, NULL };
  if (FieldExistsAndHasNoValidValue(
          *result, kAuthentication, kValidAuthentications)) {
    return false;
  }

  bool all_required_exist = true;
  std::string auth;
  result->GetStringWithoutPathExpansion(kAuthentication, &auth);
  if (auth == k8021X)
    all_required_exist &= RequireField(*result, kEAP);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateIPConfig(base::DictionaryValue* result) {
  using namespace ::onc::ipconfig;

  static const char* kValidTypes[] = { kIPv4, kIPv6, NULL };
  if (FieldExistsAndHasNoValidValue(
          *result, ::onc::ipconfig::kType, kValidTypes))
    return false;

  std::string type;
  result->GetStringWithoutPathExpansion(::onc::ipconfig::kType, &type);
  int lower_bound = 1;
  // In case of missing type, choose higher upper_bound.
  int upper_bound = (type == kIPv4) ? 32 : 128;
  if (FieldExistsAndIsNotInRange(
          *result, kRoutingPrefix, lower_bound, upper_bound)) {
    return false;
  }

  bool all_required_exist = RequireField(*result, kIPAddress) &&
                            RequireField(*result, kRoutingPrefix) &&
                            RequireField(*result, ::onc::ipconfig::kType);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateWiFi(base::DictionaryValue* result) {
  using namespace ::onc::wifi;

  static const char* kValidSecurities[] =
      { kNone, kWEP_PSK, kWEP_8021X, kWPA_PSK, kWPA_EAP, NULL };
  if (FieldExistsAndHasNoValidValue(*result, kSecurity, kValidSecurities))
    return false;

  bool all_required_exist =
      RequireField(*result, kSecurity) && RequireField(*result, kSSID);

  std::string security;
  result->GetStringWithoutPathExpansion(kSecurity, &security);
  if (security == kWEP_8021X || security == kWPA_EAP)
    all_required_exist &= RequireField(*result, kEAP);
  else if (security == kWEP_PSK || security == kWPA_PSK)
    all_required_exist &= RequireField(*result, kPassphrase);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateVPN(base::DictionaryValue* result) {
  using namespace ::onc::vpn;

  static const char* kValidTypes[] =
      { kIPsec, kTypeL2TP_IPsec, kOpenVPN, NULL };
  if (FieldExistsAndHasNoValidValue(*result, ::onc::vpn::kType, kValidTypes))
    return false;

  bool all_required_exist = RequireField(*result, ::onc::vpn::kType);
  std::string type;
  result->GetStringWithoutPathExpansion(::onc::vpn::kType, &type);
  if (type == kOpenVPN) {
    all_required_exist &= RequireField(*result, kOpenVPN);
  } else if (type == kIPsec) {
    all_required_exist &= RequireField(*result, kIPsec);
  } else if (type == kTypeL2TP_IPsec) {
    all_required_exist &=
        RequireField(*result, kIPsec) && RequireField(*result, kL2TP);
  }

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateIPsec(base::DictionaryValue* result) {
  using namespace ::onc::ipsec;
  using namespace ::onc::certificate;

  static const char* kValidAuthentications[] = { kPSK, kCert, NULL };
  static const char* kValidCertTypes[] = { kRef, kPattern, NULL };
  if (FieldExistsAndHasNoValidValue(
          *result, kAuthenticationType, kValidAuthentications) ||
      FieldExistsAndHasNoValidValue(
          *result, ::onc::vpn::kClientCertType, kValidCertTypes) ||
      FieldExistsAndIsEmpty(*result, kServerCARefs)) {
    return false;
  }

  if (result->HasKey(kServerCARefs) && result->HasKey(kServerCARef)) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "At most one of " << kServerCARefs
               << " and " << kServerCARef << " can be set.";
    return false;
  }

  bool all_required_exist = RequireField(*result, kAuthenticationType) &&
                            RequireField(*result, kIKEVersion);
  std::string auth;
  result->GetStringWithoutPathExpansion(kAuthenticationType, &auth);
  bool has_server_ca_cert =
      result->HasKey(kServerCARefs) || result->HasKey(kServerCARef);
  if (auth == kCert) {
    all_required_exist &= RequireField(*result, ::onc::vpn::kClientCertType);
    if (!has_server_ca_cert) {
      all_required_exist = false;
      error_or_warning_found_ = true;
      std::string message = MessageHeader() + "The required field '" +
                            kServerCARefs + "' is missing.";
      if (error_on_missing_field_)
        LOG(ERROR) << message;
      else
        LOG(WARNING) << message;
    }
  } else if (has_server_ca_cert) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << kServerCARefs << " (or " << kServerCARef
               << ") can only be set if " << kAuthenticationType
               << " is set to " << kCert << ".";
    return false;
  }

  std::string cert_type;
  result->GetStringWithoutPathExpansion(::onc::vpn::kClientCertType,
                                        &cert_type);

  if (IsCertPatternInDevicePolicy(cert_type))
    return false;

  if (cert_type == kPattern)
    all_required_exist &= RequireField(*result, ::onc::vpn::kClientCertPattern);
  else if (cert_type == kRef)
    all_required_exist &= RequireField(*result, ::onc::vpn::kClientCertRef);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateOpenVPN(base::DictionaryValue* result) {
  using namespace ::onc::openvpn;
  using namespace ::onc::certificate;

  static const char* kValidAuthRetryValues[] =
      { ::onc::openvpn::kNone, kInteract, kNoInteract, NULL };
  static const char* kValidCertTypes[] =
      { ::onc::certificate::kNone, kRef, kPattern, NULL };
  static const char* kValidCertTlsValues[] =
      { ::onc::openvpn::kNone, ::onc::openvpn::kServer, NULL };

  if (FieldExistsAndHasNoValidValue(
          *result, kAuthRetry, kValidAuthRetryValues) ||
      FieldExistsAndHasNoValidValue(
          *result, ::onc::vpn::kClientCertType, kValidCertTypes) ||
      FieldExistsAndHasNoValidValue(
          *result, kRemoteCertTLS, kValidCertTlsValues) ||
      FieldExistsAndIsEmpty(*result, kServerCARefs)) {
    return false;
  }

  if (result->HasKey(kServerCARefs) && result->HasKey(kServerCARef)) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "At most one of " << kServerCARefs
               << " and " << kServerCARef << " can be set.";
    return false;
  }

  bool all_required_exist = RequireField(*result, ::onc::vpn::kClientCertType);
  std::string cert_type;
  result->GetStringWithoutPathExpansion(::onc::vpn::kClientCertType,
                                        &cert_type);

  if (IsCertPatternInDevicePolicy(cert_type))
    return false;

  if (cert_type == kPattern)
    all_required_exist &= RequireField(*result, ::onc::vpn::kClientCertPattern);
  else if (cert_type == kRef)
    all_required_exist &= RequireField(*result, ::onc::vpn::kClientCertRef);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateVerifyX509(base::DictionaryValue* result) {
  using namespace ::onc::verify_x509;

  static const char* kValidTypeValues[] =
    {types::kName, types::kNamePrefix, types::kSubject, NULL};

  if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypeValues))
    return false;

  bool all_required_exist = RequireField(*result, kName);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateCertificatePattern(base::DictionaryValue* result) {
  using namespace ::onc::certificate;

  bool all_required_exist = true;
  if (!result->HasKey(kSubject) && !result->HasKey(kIssuer) &&
      !result->HasKey(kIssuerCARef)) {
    error_or_warning_found_ = true;
    all_required_exist = false;
    std::string message = MessageHeader() + "None of the fields '" + kSubject +
        "', '" + kIssuer + "', and '" + kIssuerCARef +
        "' is present, but at least one is required.";
    if (error_on_missing_field_)
      LOG(ERROR) << message;
    else
      LOG(WARNING) << message;
  }

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateProxySettings(base::DictionaryValue* result) {
  using namespace ::onc::proxy;

  static const char* kValidTypes[] = { kDirect, kManual, kPAC, kWPAD, NULL };
  if (FieldExistsAndHasNoValidValue(*result, ::onc::proxy::kType, kValidTypes))
    return false;

  bool all_required_exist = RequireField(*result, ::onc::proxy::kType);
  std::string type;
  result->GetStringWithoutPathExpansion(::onc::proxy::kType, &type);
  if (type == kManual)
    all_required_exist &= RequireField(*result, kManual);
  else if (type == kPAC)
    all_required_exist &= RequireField(*result, kPAC);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateProxyLocation(base::DictionaryValue* result) {
  using namespace ::onc::proxy;

  bool all_required_exist =
      RequireField(*result, kHost) && RequireField(*result, kPort);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateEAP(base::DictionaryValue* result) {
  using namespace ::onc::eap;
  using namespace ::onc::certificate;

  static const char* kValidInnerValues[] =
      { kAutomatic, kMD5, kMSCHAPv2, kPAP, NULL };
  static const char* kValidOuterValues[] =
      { kPEAP, kEAP_TLS, kEAP_TTLS, kLEAP, kEAP_SIM, kEAP_FAST, kEAP_AKA,
        NULL };
  static const char* kValidCertTypes[] = { kRef, kPattern, NULL };

  if (FieldExistsAndHasNoValidValue(*result, kInner, kValidInnerValues) ||
      FieldExistsAndHasNoValidValue(*result, kOuter, kValidOuterValues) ||
      FieldExistsAndHasNoValidValue(
          *result, kClientCertType, kValidCertTypes) ||
      FieldExistsAndIsEmpty(*result, kServerCARefs)) {
    return false;
  }

  if (result->HasKey(kServerCARefs) && result->HasKey(kServerCARef)) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "At most one of " << kServerCARefs
               << " and " << kServerCARef << " can be set.";
    return false;
  }

  bool all_required_exist = RequireField(*result, kOuter);
  std::string cert_type;
  result->GetStringWithoutPathExpansion(kClientCertType, &cert_type);

  if (IsCertPatternInDevicePolicy(cert_type))
    return false;

  if (cert_type == kPattern)
    all_required_exist &= RequireField(*result, kClientCertPattern);
  else if (cert_type == kRef)
    all_required_exist &= RequireField(*result, kClientCertRef);

  return !error_on_missing_field_ || all_required_exist;
}

bool Validator::ValidateCertificate(base::DictionaryValue* result) {
  using namespace ::onc::certificate;

  static const char* kValidTypes[] = { kClient, kServer, kAuthority, NULL };
  if (FieldExistsAndHasNoValidValue(*result, kType, kValidTypes) ||
      FieldExistsAndIsEmpty(*result, kGUID)) {
    return false;
  }

  std::string type;
  result->GetStringWithoutPathExpansion(kType, &type);
  if (onc_source_ == ::onc::ONC_SOURCE_DEVICE_POLICY &&
      (type == kServer || type == kAuthority)) {
    error_or_warning_found_ = true;
    LOG(ERROR) << MessageHeader() << "Server and authority certificates are "
               << "prohibited in ONC device policies.";
    return false;
  }

  if (!CheckGuidIsUniqueAndAddToSet(*result, kGUID, &certificate_guids_))
    return false;

  bool all_required_exist = RequireField(*result, kGUID);

  bool remove = false;
  result->GetBooleanWithoutPathExpansion(::onc::kRemove, &remove);
  if (!remove) {
    all_required_exist &= RequireField(*result, kType);

    if (type == kClient)
      all_required_exist &= RequireField(*result, kPKCS12);
    else if (type == kServer || type == kAuthority)
      all_required_exist &= RequireField(*result, kX509);
  }

  return !error_on_missing_field_ || all_required_exist;
}

std::string Validator::MessageHeader() {
  std::string path = path_.empty() ? "toplevel" : JoinString(path_, ".");
  std::string message = "At " + path + ": ";
  return message;
}

}  // namespace onc
}  // namespace chromeos
