root/chromeos/network/onc/onc_validator.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. ValueTypeToString
  2. onc_source_
  3. ValidateAndRepairObject
  4. MapValue
  5. MapObject
  6. MapField
  7. MapArray
  8. MapEntry
  9. ValidateObjectDefault
  10. ValidateRecommendedField
  11. JoinStringRange
  12. FieldExistsAndHasNoValidValue
  13. FieldExistsAndIsNotInRange
  14. FieldExistsAndIsEmpty
  15. RequireField
  16. CheckGuidIsUniqueAndAddToSet
  17. IsCertPatternInDevicePolicy
  18. IsGlobalNetworkConfigInUserImport
  19. ValidateToplevelConfiguration
  20. ValidateNetworkConfiguration
  21. ValidateEthernet
  22. ValidateIPConfig
  23. ValidateWiFi
  24. ValidateVPN
  25. ValidateIPsec
  26. ValidateOpenVPN
  27. ValidateVerifyX509
  28. ValidateCertificatePattern
  29. ValidateProxySettings
  30. ValidateProxyLocation
  31. ValidateEAP
  32. ValidateCertificate
  33. MessageHeader

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

  bool all_required_exist = true;

  // Not part of the ONC spec yet:
  // We don't require the type field and default to UnencryptedConfiguration.
  std::string type = kUnencryptedConfiguration;
  result->GetStringWithoutPathExpansion(kType, &type);
  if (type == kUnencryptedConfiguration &&
      !result->HasKey(kNetworkConfigurations) &&
      !result->HasKey(kCertificates)) {
    error_or_warning_found_ = true;
    std::string message = MessageHeader() + "Neither the field '" +
        kNetworkConfigurations + "' nor '" + kCertificates +
        "is present, but at least one is required.";
    if (error_on_missing_field_)
      LOG(ERROR) << message;
    else
      LOG(WARNING) << message;
    all_required_exist = false;
  }

  if (IsGlobalNetworkConfigInUserImport(*result))
    return false;

  return !error_on_missing_field_ || all_required_exist;
}

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

/* [<][>][^][v][top][bottom][index][help] */