summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorAlexander Hendrich <hendrich@chromium.org>2018-08-29 18:39:22 +0900
committerQijiang Fan <fqj@google.com>2020-06-05 10:24:33 +0900
commite2dbc3b3f14b243febc1150f4c20d02b30fc8c09 (patch)
tree46f00a3a82c7849968b4e40186f3124ef82b728a /components
parentd59f0fbbc3a4e5b0175845e68549c3ded706e7c6 (diff)
downloadlibchrome-e2dbc3b3f14b243febc1150f4c20d02b30fc8c09.tar.gz
Move schema validation from json_schema_validator.h/cc to schema.h/cc
This CL is part of a larger clean-up operation and moves the schema validation from json_schema_validator.h/cc into schema.h/cc. json_schema_constants.h/cc is moved to components/policy/core/common. Also removes src/components/json_schema. Final clean-up of schema/value validation will follow in the next CL. Bug: 873641 Change-Id: I2275e5e91c8e75204528d9a6a7ee5eba447e4bfc Reviewed-on: https://chromium-review.googlesource.com/1181426 Reviewed-by: Cait Phillips <caitkp@chromium.org> Reviewed-by: Lutz Justen <ljusten@chromium.org> Commit-Queue: Alexander Hendrich <hendrich@chromium.org> Cr-Commit-Position: refs/heads/master@{#587051} CrOS-Libchrome-Original-Commit: 6f09586aa55208c90b80b6671433435199e1c581
Diffstat (limited to 'components')
-rw-r--r--components/json_schema/README6
-rw-r--r--components/json_schema/json_schema_validator.cc341
-rw-r--r--components/json_schema/json_schema_validator.h83
-rw-r--r--components/json_schema/json_schema_validator_unittest.cc165
-rw-r--r--components/policy/core/common/json_schema_constants.cc (renamed from components/json_schema/json_schema_constants.cc)2
-rw-r--r--components/policy/core/common/json_schema_constants.h (renamed from components/json_schema/json_schema_constants.h)6
-rw-r--r--components/policy/core/common/schema.cc314
-rw-r--r--components/policy/core/common/schema.h24
-rw-r--r--components/policy/core/common/schema_unittest.cc160
9 files changed, 497 insertions, 604 deletions
diff --git a/components/json_schema/README b/components/json_schema/README
deleted file mode 100644
index c7453db06d..0000000000
--- a/components/json_schema/README
+++ /dev/null
@@ -1,6 +0,0 @@
-The //components/json_schema component provides:
-
-a) JSON schema constants, which can be used to inspect schema objects.
-
-b) The JSONSchemaValidator class, which can be used to parse and validate JSON
-schemas, and to validate JSON objects against the parsed schema.
diff --git a/components/json_schema/json_schema_validator.cc b/components/json_schema/json_schema_validator.cc
deleted file mode 100644
index 18ed7625a8..0000000000
--- a/components/json_schema/json_schema_validator.cc
+++ /dev/null
@@ -1,341 +0,0 @@
-// Copyright 2013 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 "components/json_schema/json_schema_validator.h"
-
-#include <stddef.h>
-
-#include <algorithm>
-#include <cfloat>
-#include <cmath>
-#include <memory>
-#include <vector>
-
-#include "base/json/json_reader.h"
-#include "base/logging.h"
-#include "base/macros.h"
-#include "base/memory/ptr_util.h"
-#include "base/strings/string_number_conversions.h"
-#include "base/strings/string_util.h"
-#include "base/strings/stringprintf.h"
-#include "base/values.h"
-#include "components/json_schema/json_schema_constants.h"
-#include "third_party/re2/src/re2/re2.h"
-
-namespace schema = json_schema_constants;
-
-namespace {
-
-bool IsValidType(const std::string& type) {
- static const char* kValidTypes[] = {
- schema::kAny,
- schema::kArray,
- schema::kBoolean,
- schema::kInteger,
- schema::kNull,
- schema::kNumber,
- schema::kObject,
- schema::kString,
- };
- const char** end = kValidTypes + arraysize(kValidTypes);
- return std::find(kValidTypes, end, type) != end;
-}
-
-// Maps a schema attribute name to its expected type.
-struct ExpectedType {
- const char* key;
- base::Value::Type type;
-};
-
-// Helper for std::lower_bound.
-bool CompareToString(const ExpectedType& entry, const std::string& key) {
- return entry.key < key;
-}
-
-// If |value| is a dictionary, returns the "name" attribute of |value| or NULL
-// if |value| does not contain a "name" attribute. Otherwise, returns |value|.
-const base::Value* ExtractNameFromDictionary(const base::Value* value) {
- const base::DictionaryValue* value_dict = nullptr;
- const base::Value* name_value = nullptr;
- if (value->GetAsDictionary(&value_dict)) {
- value_dict->Get("name", &name_value);
- return name_value;
- }
- return value;
-}
-
-bool IsValidSchema(const base::DictionaryValue* dict,
- int options,
- std::string* error) {
- // This array must be sorted, so that std::lower_bound can perform a
- // binary search.
- static const ExpectedType kExpectedTypes[] = {
- // Note: kRef == "$ref", kSchema == "$schema"
- { schema::kRef, base::Value::Type::STRING },
- { schema::kSchema, base::Value::Type::STRING },
-
- { schema::kAdditionalProperties, base::Value::Type::DICTIONARY },
- { schema::kChoices, base::Value::Type::LIST },
- { schema::kDescription, base::Value::Type::STRING },
- { schema::kEnum, base::Value::Type::LIST },
- { schema::kId, base::Value::Type::STRING },
- { schema::kMaxItems, base::Value::Type::INTEGER },
- { schema::kMaxLength, base::Value::Type::INTEGER },
- { schema::kMaximum, base::Value::Type::DOUBLE },
- { schema::kMinItems, base::Value::Type::INTEGER },
- { schema::kMinLength, base::Value::Type::INTEGER },
- { schema::kMinimum, base::Value::Type::DOUBLE },
- { schema::kOptional, base::Value::Type::BOOLEAN },
- { schema::kPattern, base::Value::Type::STRING },
- { schema::kPatternProperties, base::Value::Type::DICTIONARY },
- { schema::kProperties, base::Value::Type::DICTIONARY },
- { schema::kRequired, base::Value::Type::LIST },
- { schema::kTitle, base::Value::Type::STRING },
- };
-
- bool has_type_or_ref = false;
- const base::ListValue* list_value = nullptr;
- const base::DictionaryValue* dictionary_value = nullptr;
- std::string string_value;
-
- const base::ListValue* required_properties_value = nullptr;
- const base::DictionaryValue* properties_value = nullptr;
-
- for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
- // Validate the "type" attribute, which may be a string or a list.
- if (it.key() == schema::kType) {
- switch (it.value().type()) {
- case base::Value::Type::STRING:
- it.value().GetAsString(&string_value);
- if (!IsValidType(string_value)) {
- *error = "Invalid value for type attribute";
- return false;
- }
- break;
- case base::Value::Type::LIST:
- it.value().GetAsList(&list_value);
- for (size_t i = 0; i < list_value->GetSize(); ++i) {
- if (!list_value->GetString(i, &string_value) ||
- !IsValidType(string_value)) {
- *error = "Invalid value for type attribute";
- return false;
- }
- }
- break;
- default:
- *error = "Invalid value for type attribute";
- return false;
- }
- has_type_or_ref = true;
- continue;
- }
-
- // Validate the "items" attribute, which is a schema or a list of schemas.
- if (it.key() == schema::kItems) {
- if (it.value().GetAsDictionary(&dictionary_value)) {
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- } else if (it.value().GetAsList(&list_value)) {
- for (size_t i = 0; i < list_value->GetSize(); ++i) {
- if (!list_value->GetDictionary(i, &dictionary_value)) {
- *error = base::StringPrintf(
- "Invalid entry in items attribute at index %d",
- static_cast<int>(i));
- return false;
- }
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- }
- } else {
- *error = "Invalid value for items attribute";
- return false;
- }
- continue;
- }
-
- // All the other attributes have a single valid type.
- const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes);
- const ExpectedType* entry = std::lower_bound(
- kExpectedTypes, end, it.key(), CompareToString);
- if (entry == end || entry->key != it.key()) {
- if (options & JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES)
- continue;
- *error = base::StringPrintf("Invalid attribute %s", it.key().c_str());
- return false;
- }
-
- // Integer can be converted to double.
- if (!(it.value().type() == entry->type ||
- (it.value().is_int() && entry->type == base::Value::Type::DOUBLE))) {
- *error = base::StringPrintf("Invalid value for %s attribute",
- it.key().c_str());
- return false;
- }
-
- // base::Value::Type::INTEGER attributes must be >= 0.
- // This applies to "minItems", "maxItems", "minLength" and "maxLength".
- if (it.value().is_int()) {
- int integer_value;
- it.value().GetAsInteger(&integer_value);
- if (integer_value < 0) {
- *error = base::StringPrintf("Value of %s must be >= 0, got %d",
- it.key().c_str(), integer_value);
- return false;
- }
- }
-
- // Validate the "properties" attribute. Each entry maps a key to a schema.
- if (it.key() == schema::kProperties) {
- it.value().GetAsDictionary(&properties_value);
- for (base::DictionaryValue::Iterator iter(*properties_value);
- !iter.IsAtEnd(); iter.Advance()) {
- if (!iter.value().GetAsDictionary(&dictionary_value)) {
- *error = "properties must be a dictionary";
- return false;
- }
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- }
- }
-
- // Validate the "patternProperties" attribute. Each entry maps a regular
- // expression to a schema. The validity of the regular expression expression
- // won't be checked here for performance reasons. Instead, invalid regular
- // expressions will be caught as validation errors in Validate().
- if (it.key() == schema::kPatternProperties) {
- it.value().GetAsDictionary(&dictionary_value);
- for (base::DictionaryValue::Iterator iter(*dictionary_value);
- !iter.IsAtEnd(); iter.Advance()) {
- if (!iter.value().GetAsDictionary(&dictionary_value)) {
- *error = "patternProperties must be a dictionary";
- return false;
- }
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- }
- }
-
- // Validate "additionalProperties" attribute, which is a schema.
- if (it.key() == schema::kAdditionalProperties) {
- it.value().GetAsDictionary(&dictionary_value);
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- }
-
- // Validate "required" attribute.
- if (it.key() == schema::kRequired) {
- it.value().GetAsList(&required_properties_value);
- for (const base::Value& value : *required_properties_value) {
- if (value.type() != base::Value::Type::STRING) {
- *error = "Invalid value in 'required' attribute";
- return false;
- }
- }
- }
-
- // Validate the values contained in an "enum" attribute.
- if (it.key() == schema::kEnum) {
- it.value().GetAsList(&list_value);
- for (size_t i = 0; i < list_value->GetSize(); ++i) {
- const base::Value* value = nullptr;
- list_value->Get(i, &value);
- // Sometimes the enum declaration is a dictionary with the enum value
- // under "name".
- value = ExtractNameFromDictionary(value);
- if (!value) {
- *error = "Invalid value in enum attribute";
- return false;
- }
- switch (value->type()) {
- case base::Value::Type::NONE:
- case base::Value::Type::BOOLEAN:
- case base::Value::Type::INTEGER:
- case base::Value::Type::DOUBLE:
- case base::Value::Type::STRING:
- break;
- default:
- *error = "Invalid value in enum attribute";
- return false;
- }
- }
- }
-
- // Validate the schemas contained in a "choices" attribute.
- if (it.key() == schema::kChoices) {
- it.value().GetAsList(&list_value);
- for (size_t i = 0; i < list_value->GetSize(); ++i) {
- if (!list_value->GetDictionary(i, &dictionary_value)) {
- *error = "Invalid choices attribute";
- return false;
- }
- if (!IsValidSchema(dictionary_value, options, error)) {
- DCHECK(!error->empty());
- return false;
- }
- }
- }
-
- if (it.key() == schema::kRef)
- has_type_or_ref = true;
- }
-
- // Check that properties in'required' are in the 'properties' object.
- if (required_properties_value) {
- for (const base::Value& value : required_properties_value->GetList()) {
- const std::string& name = value.GetString();
- if (!properties_value || !properties_value->HasKey(name)) {
- *error = "Property '" + name +
- "' was listed in 'required', but not defined in 'properties'.";
- return false;
- }
- }
- }
-
- if (!has_type_or_ref) {
- *error = "Schema must have a type or a $ref attribute";
- return false;
- }
-
- return true;
-}
-
-} // namespace
-
-// static
-std::unique_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
- const std::string& schema,
- std::string* error) {
- return JSONSchemaValidator::IsValidSchema(schema, 0, error);
-}
-
-// static
-std::unique_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema(
- const std::string& schema,
- int validator_options,
- std::string* error) {
- base::JSONParserOptions json_options = base::JSON_PARSE_RFC;
- std::unique_ptr<base::Value> json = base::JSONReader::ReadAndReturnError(
- schema, json_options, nullptr, error);
- if (!json)
- return std::unique_ptr<base::DictionaryValue>();
- base::DictionaryValue* dict = nullptr;
- if (!json->GetAsDictionary(&dict)) {
- *error = "Schema must be a JSON object";
- return std::unique_ptr<base::DictionaryValue>();
- }
- if (!::IsValidSchema(dict, validator_options, error))
- return std::unique_ptr<base::DictionaryValue>();
- ignore_result(json.release());
- return base::WrapUnique(dict);
-}
diff --git a/components/json_schema/json_schema_validator.h b/components/json_schema/json_schema_validator.h
deleted file mode 100644
index 0b2f4bd463..0000000000
--- a/components/json_schema/json_schema_validator.h
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2013 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.
-
-#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
-#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
-
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-#include "base/macros.h"
-
-namespace base {
-class DictionaryValue;
-}
-
-//==============================================================================
-// This class implements a subset of JSON Schema.
-// See: http://www.json.com/json-schema-proposal/ for more details.
-//
-// There is also an older JavaScript implementation of the same functionality in
-// chrome/renderer/resources/json_schema.js.
-//
-// The following features of JSON Schema are not implemented:
-// - requires
-// - unique
-// - disallow
-// - union types (but replaced with 'choices')
-// - number.maxDecimal
-//
-// The following properties are not applicable to the interface exposed by
-// this class:
-// - options
-// - readonly
-// - title
-// - description
-// - format
-// - default
-// - transient
-// - hidden
-//
-// There are also these departures from the JSON Schema proposal:
-// - null counts as 'unspecified' for optional values
-// - added the 'choices' property, to allow specifying a list of possible types
-// for a value
-// - by default an "object" typed schema does not allow additional properties.
-// if present, "additionalProperties" is to be a schema against which all
-// additional properties will be validated.
-// - regular expression supports all syntaxes that re2 accepts.
-// See https://github.com/google/re2/blob/master/doc/syntax.txt for details.
-//==============================================================================
-class JSONSchemaValidator {
- public:
- enum Options {
- // Ignore unknown attributes. If this option is not set then unknown
- // attributes will make the schema validation fail.
- OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES = 1 << 0,
- };
-
- // Verifies if |schema| is a valid JSON v3 schema. When this validation passes
- // then |schema| is valid JSON that can be parsed into a DictionaryValue,
- // and that DictionaryValue can be used to build a JSONSchemaValidator.
- // Returns the parsed DictionaryValue when |schema| validated, otherwise
- // returns NULL. In that case, |error| contains an error description.
- // For performance reasons, currently IsValidSchema() won't check the
- // correctness of regular expressions used in "pattern" and
- // "patternProperties" and in Validate() invalid regular expression don't
- // accept any strings.
- static std::unique_ptr<base::DictionaryValue> IsValidSchema(
- const std::string& schema,
- std::string* error);
-
- // Same as above but with |options|, which is a bitwise-OR combination of the
- // Options above.
- static std::unique_ptr<base::DictionaryValue>
- IsValidSchema(const std::string& schema, int options, std::string* error);
-
- DISALLOW_COPY_AND_ASSIGN(JSONSchemaValidator);
-};
-
-#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_
diff --git a/components/json_schema/json_schema_validator_unittest.cc b/components/json_schema/json_schema_validator_unittest.cc
deleted file mode 100644
index cd96325fbc..0000000000
--- a/components/json_schema/json_schema_validator_unittest.cc
+++ /dev/null
@@ -1,165 +0,0 @@
-// Copyright 2013 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 <stddef.h>
-
-#include "base/values.h"
-#include "components/json_schema/json_schema_validator.h"
-#include "testing/gtest/include/gtest/gtest.h"
-
-class JSONSchemaValidatorCPPTest : public testing::Test {
- public:
- JSONSchemaValidatorCPPTest() {}
-
-};
-
-TEST(JSONSchemaValidator, IsValidSchema) {
- std::string error;
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\0", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("string", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\"string\"", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("[]", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("{}", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{ \"type\": 123 }", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{ \"type\": \"invalid\" }", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"object\","
- " \"properties\": []" // Invalid properties type.
- "}", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"string\","
- " \"maxLength\": -1" // Must be >= 0.
- "}", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"string\","
- " \"enum\": [ {} ]" // "enum" dict values must contain "name".
- "}", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"string\","
- " \"enum\": [ { \"name\": {} } ]" // "enum" name must be a simple value.
- "}", &error));
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"array\","
- " \"items\": [ 123 ]," // "items" must contain a schema or schemas.
- "}", &error));
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
- "{ \"type\": \"object\" }", &error)) << error;
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
- "{ \"type\": [\"object\", \"array\"] }", &error)) << error;
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": [\"object\", \"array\"],"
- " \"properties\": {"
- " \"string-property\": {"
- " \"type\": \"string\","
- " \"minLength\": 1,"
- " \"maxLength\": 100,"
- " \"title\": \"The String Policy\","
- " \"description\": \"This policy controls the String widget.\""
- " },"
- " \"integer-property\": {"
- " \"type\": \"number\","
- " \"minimum\": 1000.0,"
- " \"maximum\": 9999.0"
- " },"
- " \"enum-property\": {"
- " \"type\": \"integer\","
- " \"enum\": [0, 1, {\"name\": 10}, 100]"
- " },"
- " \"items-property\": {"
- " \"type\": \"array\","
- " \"items\": {"
- " \"type\": \"string\""
- " }"
- " },"
- " \"items-list-property\": {"
- " \"type\": \"array\","
- " \"items\": ["
- " { \"type\": \"string\" },"
- " { \"type\": \"integer\" }"
- " ]"
- " }"
- " },"
- " \"additionalProperties\": {"
- " \"type\": \"any\""
- " }"
- "}", &error)) << error;
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"object\","
- " \"patternProperties\": {"
- " \".\": { \"type\": \"any\" },"
- " \"foo\": { \"type\": \"any\" },"
- " \"^foo$\": { \"type\": \"any\" },"
- " \"foo+\": { \"type\": \"any\" },"
- " \"foo?\": { \"type\": \"any\" },"
- " \"fo{2,4}\": { \"type\": \"any\" },"
- " \"(left)|(right)\": { \"type\": \"any\" }"
- " }"
- "}", &error)) << error;
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"object\","
- " \"unknown attribute\": \"that should just be ignored\""
- "}",
- JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES,
- &error)) << error;
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(
- "{"
- " \"type\": \"object\","
- " \"unknown attribute\": \"that will cause a failure\""
- "}", 0, &error)) << error;
-
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
- {
- "type": "object",
- "properties": {"foo": {"type": "number"}},
- "required": 123
- })",
- 0, &error))
- << error;
-
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
- {
- "type": "object",
- "properties": {"foo": {"type": "number"}},
- "required": [ 123 ]
- })",
- 0, &error))
- << error;
-
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
- {
- "type": "object",
- "properties": {"foo": {"type": "number"}},
- "required": ["bar"]
- })",
- 0, &error))
- << error;
-
- EXPECT_FALSE(JSONSchemaValidator::IsValidSchema(R"(
- {
- "type": "object",
- "required": ["bar"]
- })",
- 0, &error))
- << error;
-
- EXPECT_TRUE(JSONSchemaValidator::IsValidSchema(R"(
- {
- "type": "object",
- "properties": {"foo": {"type": "number"}},
- "required": ["foo"]
- })",
- 0, &error))
- << error;
-}
diff --git a/components/json_schema/json_schema_constants.cc b/components/policy/core/common/json_schema_constants.cc
index 79c5eaf415..1a6d1ac0fd 100644
--- a/components/json_schema/json_schema_constants.cc
+++ b/components/policy/core/common/json_schema_constants.cc
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#include "components/json_schema/json_schema_constants.h"
+#include "components/policy/core/common/json_schema_constants.h"
namespace json_schema_constants {
diff --git a/components/json_schema/json_schema_constants.h b/components/policy/core/common/json_schema_constants.h
index 19394a915f..66b7941d46 100644
--- a/components/json_schema/json_schema_constants.h
+++ b/components/policy/core/common/json_schema_constants.h
@@ -2,8 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-#ifndef COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_
-#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_
+#ifndef COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
+#define COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
// These constants are shared by code that uses JSON schemas.
namespace json_schema_constants {
@@ -40,4 +40,4 @@ extern const char kType[];
} // namespace json_schema_constants
-#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_
+#endif // COMPONENTS_POLICY_CORE_COMMON_JSON_SCHEMA_CONSTANTS_H_
diff --git a/components/policy/core/common/schema.cc b/components/policy/core/common/schema.cc
index a864ea5592..be475c4ff2 100644
--- a/components/policy/core/common/schema.cc
+++ b/components/policy/core/common/schema.cc
@@ -15,12 +15,13 @@
#include "base/compiler_specific.h"
#include "base/containers/flat_set.h"
+#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/macros.h"
+#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
-#include "components/json_schema/json_schema_constants.h"
-#include "components/json_schema/json_schema_validator.h"
+#include "components/policy/core/common/json_schema_constants.h"
#include "components/policy/core/common/schema_internal.h"
#include "third_party/re2/src/re2/re2.h"
@@ -154,6 +155,283 @@ void AddDictKeyPrefixToPath(const std::string& key, std::string* path) {
}
}
+bool IsValidType(const std::string& type) {
+ static const char* kValidTypes[] = {
+ schema::kAny, schema::kArray, schema::kBoolean, schema::kInteger,
+ schema::kNull, schema::kNumber, schema::kObject, schema::kString,
+ };
+ const char** end = kValidTypes + base::size(kValidTypes);
+ return std::find(kValidTypes, end, type) != end;
+}
+
+// Maps a schema attribute name to its expected type.
+struct ExpectedType {
+ const char* key;
+ base::Value::Type type;
+};
+
+// Helper for std::lower_bound.
+bool CompareToString(const ExpectedType& entry, const std::string& key) {
+ return entry.key < key;
+}
+
+// If |value| is a dictionary, returns the "name" attribute of |value| or NULL
+// if |value| does not contain a "name" attribute. Otherwise, returns |value|.
+const base::Value* ExtractNameFromDictionary(const base::Value* value) {
+ const base::DictionaryValue* value_dict = nullptr;
+ const base::Value* name_value = nullptr;
+ if (value->GetAsDictionary(&value_dict)) {
+ value_dict->Get("name", &name_value);
+ return name_value;
+ }
+ return value;
+}
+
+bool IsValidSchema(const base::DictionaryValue* dict,
+ int options,
+ std::string* error) {
+ // This array must be sorted, so that std::lower_bound can perform a
+ // binary search.
+ static const ExpectedType kExpectedTypes[] = {
+ // Note: kRef == "$ref", kSchema == "$schema"
+ {schema::kRef, base::Value::Type::STRING},
+ {schema::kSchema, base::Value::Type::STRING},
+
+ {schema::kAdditionalProperties, base::Value::Type::DICTIONARY},
+ {schema::kChoices, base::Value::Type::LIST},
+ {schema::kDescription, base::Value::Type::STRING},
+ {schema::kEnum, base::Value::Type::LIST},
+ {schema::kId, base::Value::Type::STRING},
+ {schema::kMaxItems, base::Value::Type::INTEGER},
+ {schema::kMaxLength, base::Value::Type::INTEGER},
+ {schema::kMaximum, base::Value::Type::DOUBLE},
+ {schema::kMinItems, base::Value::Type::INTEGER},
+ {schema::kMinLength, base::Value::Type::INTEGER},
+ {schema::kMinimum, base::Value::Type::DOUBLE},
+ {schema::kOptional, base::Value::Type::BOOLEAN},
+ {schema::kPattern, base::Value::Type::STRING},
+ {schema::kPatternProperties, base::Value::Type::DICTIONARY},
+ {schema::kProperties, base::Value::Type::DICTIONARY},
+ {schema::kRequired, base::Value::Type::LIST},
+ {schema::kTitle, base::Value::Type::STRING},
+ };
+
+ bool has_type_or_ref = false;
+ const base::ListValue* list_value = nullptr;
+ const base::DictionaryValue* dictionary_value = nullptr;
+ std::string string_value;
+
+ const base::ListValue* required_properties_value = nullptr;
+ const base::DictionaryValue* properties_value = nullptr;
+
+ for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) {
+ // Validate the "type" attribute, which may be a string or a list.
+ if (it.key() == schema::kType) {
+ switch (it.value().type()) {
+ case base::Value::Type::STRING:
+ it.value().GetAsString(&string_value);
+ if (!IsValidType(string_value)) {
+ *error = "Invalid value for type attribute";
+ return false;
+ }
+ break;
+ case base::Value::Type::LIST:
+ it.value().GetAsList(&list_value);
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ if (!list_value->GetString(i, &string_value) ||
+ !IsValidType(string_value)) {
+ *error = "Invalid value for type attribute";
+ return false;
+ }
+ }
+ break;
+ default:
+ *error = "Invalid value for type attribute";
+ return false;
+ }
+ has_type_or_ref = true;
+ continue;
+ }
+
+ // Validate the "items" attribute, which is a schema or a list of schemas.
+ if (it.key() == schema::kItems) {
+ if (it.value().GetAsDictionary(&dictionary_value)) {
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ } else if (it.value().GetAsList(&list_value)) {
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ if (!list_value->GetDictionary(i, &dictionary_value)) {
+ *error = base::StringPrintf(
+ "Invalid entry in items attribute at index %d",
+ static_cast<int>(i));
+ return false;
+ }
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ }
+ } else {
+ *error = "Invalid value for items attribute";
+ return false;
+ }
+ continue;
+ }
+
+ // All the other attributes have a single valid type.
+ const ExpectedType* end = kExpectedTypes + base::size(kExpectedTypes);
+ const ExpectedType* entry =
+ std::lower_bound(kExpectedTypes, end, it.key(), CompareToString);
+ if (entry == end || entry->key != it.key()) {
+ if (options & Schema::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES)
+ continue;
+ *error = base::StringPrintf("Invalid attribute %s", it.key().c_str());
+ return false;
+ }
+
+ // Integer can be converted to double.
+ if (!(it.value().type() == entry->type ||
+ (it.value().is_int() && entry->type == base::Value::Type::DOUBLE))) {
+ *error = base::StringPrintf("Invalid value for %s attribute",
+ it.key().c_str());
+ return false;
+ }
+
+ // base::Value::Type::INTEGER attributes must be >= 0.
+ // This applies to "minItems", "maxItems", "minLength" and "maxLength".
+ if (it.value().is_int()) {
+ int integer_value;
+ it.value().GetAsInteger(&integer_value);
+ if (integer_value < 0) {
+ *error = base::StringPrintf("Value of %s must be >= 0, got %d",
+ it.key().c_str(), integer_value);
+ return false;
+ }
+ }
+
+ // Validate the "properties" attribute. Each entry maps a key to a schema.
+ if (it.key() == schema::kProperties) {
+ it.value().GetAsDictionary(&properties_value);
+ for (base::DictionaryValue::Iterator iter(*properties_value);
+ !iter.IsAtEnd(); iter.Advance()) {
+ if (!iter.value().GetAsDictionary(&dictionary_value)) {
+ *error = "properties must be a dictionary";
+ return false;
+ }
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ }
+ }
+
+ // Validate the "patternProperties" attribute. Each entry maps a regular
+ // expression to a schema. The validity of the regular expression expression
+ // won't be checked here for performance reasons. Instead, invalid regular
+ // expressions will be caught as validation errors in Validate().
+ if (it.key() == schema::kPatternProperties) {
+ it.value().GetAsDictionary(&dictionary_value);
+ for (base::DictionaryValue::Iterator iter(*dictionary_value);
+ !iter.IsAtEnd(); iter.Advance()) {
+ if (!iter.value().GetAsDictionary(&dictionary_value)) {
+ *error = "patternProperties must be a dictionary";
+ return false;
+ }
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ }
+ }
+
+ // Validate "additionalProperties" attribute, which is a schema.
+ if (it.key() == schema::kAdditionalProperties) {
+ it.value().GetAsDictionary(&dictionary_value);
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ }
+
+ // Validate "required" attribute.
+ if (it.key() == schema::kRequired) {
+ it.value().GetAsList(&required_properties_value);
+ for (const base::Value& value : *required_properties_value) {
+ if (value.type() != base::Value::Type::STRING) {
+ *error = "Invalid value in 'required' attribute";
+ return false;
+ }
+ }
+ }
+
+ // Validate the values contained in an "enum" attribute.
+ if (it.key() == schema::kEnum) {
+ it.value().GetAsList(&list_value);
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ const base::Value* value = nullptr;
+ list_value->Get(i, &value);
+ // Sometimes the enum declaration is a dictionary with the enum value
+ // under "name".
+ value = ExtractNameFromDictionary(value);
+ if (!value) {
+ *error = "Invalid value in enum attribute";
+ return false;
+ }
+ switch (value->type()) {
+ case base::Value::Type::NONE:
+ case base::Value::Type::BOOLEAN:
+ case base::Value::Type::INTEGER:
+ case base::Value::Type::DOUBLE:
+ case base::Value::Type::STRING:
+ break;
+ default:
+ *error = "Invalid value in enum attribute";
+ return false;
+ }
+ }
+ }
+
+ // Validate the schemas contained in a "choices" attribute.
+ if (it.key() == schema::kChoices) {
+ it.value().GetAsList(&list_value);
+ for (size_t i = 0; i < list_value->GetSize(); ++i) {
+ if (!list_value->GetDictionary(i, &dictionary_value)) {
+ *error = "Invalid choices attribute";
+ return false;
+ }
+ if (!IsValidSchema(dictionary_value, options, error)) {
+ DCHECK(!error->empty());
+ return false;
+ }
+ }
+ }
+
+ if (it.key() == schema::kRef)
+ has_type_or_ref = true;
+ }
+
+ // Check that properties in'required' are in the 'properties' object.
+ if (required_properties_value) {
+ for (const base::Value& value : required_properties_value->GetList()) {
+ const std::string& name = value.GetString();
+ if (!properties_value || !properties_value->HasKey(name)) {
+ *error = "Property '" + name +
+ "' was listed in 'required', but not defined in 'properties'.";
+ return false;
+ }
+ }
+ }
+
+ if (!has_type_or_ref) {
+ *error = "Schema must have a type or a $ref attribute";
+ return false;
+ }
+
+ return true;
+}
+
} // namespace
// Contains the internal data representation of a Schema. This can either wrap
@@ -1089,9 +1367,7 @@ Schema Schema::Parse(const std::string& content, std::string* error) {
// Validate as a generic JSON schema, and ignore unknown attributes; they
// may become used in a future version of the schema format.
std::unique_ptr<base::DictionaryValue> dict =
- JSONSchemaValidator::IsValidSchema(
- content, JSONSchemaValidator::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES,
- error);
+ IsValidSchema(content, Schema::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES, error);
if (!dict)
return Schema();
@@ -1119,6 +1395,34 @@ Schema Schema::Parse(const std::string& content, std::string* error) {
return Schema(storage, storage->root_node());
}
+// static
+std::unique_ptr<base::DictionaryValue> Schema::IsValidSchema(
+ const std::string& schema,
+ std::string* error) {
+ return Schema::IsValidSchema(schema, 0, error);
+}
+
+// static
+std::unique_ptr<base::DictionaryValue> Schema::IsValidSchema(
+ const std::string& schema,
+ int validator_options,
+ std::string* error) {
+ base::JSONParserOptions json_options = base::JSON_ALLOW_TRAILING_COMMAS;
+ std::unique_ptr<base::Value> json = base::JSONReader::ReadAndReturnError(
+ schema, json_options, nullptr, error);
+ if (!json)
+ return nullptr;
+ std::unique_ptr<base::DictionaryValue> dict =
+ base::DictionaryValue::From(std::move(json));
+ if (!dict) {
+ *error = "Schema must be a JSON object";
+ return nullptr;
+ }
+ if (!policy::IsValidSchema(dict.get(), validator_options, error))
+ return nullptr;
+ return dict;
+}
+
base::Value::Type Schema::type() const {
CHECK(valid());
return node_->type;
diff --git a/components/policy/core/common/schema.h b/components/policy/core/common/schema.h
index 1eb1c9819a..44ca58a15c 100644
--- a/components/policy/core/common/schema.h
+++ b/components/policy/core/common/schema.h
@@ -62,6 +62,12 @@ typedef std::vector<Schema> SchemaList;
// standard.
class POLICY_EXPORT Schema {
public:
+ enum Options {
+ // Ignore unknown attributes. If this option is not set then unknown
+ // attributes will make the schema validation fail.
+ OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES = 1 << 0,
+ };
+
// Used internally to store shared data.
class InternalStorage;
@@ -85,6 +91,24 @@ class POLICY_EXPORT Schema {
// is returned and |error| contains a reason for the failure.
static Schema Parse(const std::string& schema, std::string* error);
+ // Verifies if |schema| is a valid JSON v3 schema. When this validation passes
+ // then |schema| is valid JSON that can be parsed into a DictionaryValue,
+ // and that DictionaryValue can be used to build a |Schema|.
+ // Returns the parsed DictionaryValue when |schema| validated, otherwise
+ // returns NULL. In that case, |error| contains an error description.
+ // For performance reasons, currently IsValidSchema() won't check the
+ // correctness of regular expressions used in "pattern" and
+ // "patternProperties" and in Validate() invalid regular expression don't
+ // accept any strings.
+ static std::unique_ptr<base::DictionaryValue> IsValidSchema(
+ const std::string& schema,
+ std::string* error);
+
+ // Same as above but with |options|, which is a bitwise-OR combination of the
+ // Options above.
+ static std::unique_ptr<base::DictionaryValue>
+ IsValidSchema(const std::string& schema, int options, std::string* error);
+
// Returns true if this Schema is valid. Schemas returned by the methods below
// may be invalid, and in those cases the other methods must not be used.
bool valid() const { return node_ != NULL; }
diff --git a/components/policy/core/common/schema_unittest.cc b/components/policy/core/common/schema_unittest.cc
index b7a443495e..1fe75c7647 100644
--- a/components/policy/core/common/schema_unittest.cc
+++ b/components/policy/core/common/schema_unittest.cc
@@ -1357,4 +1357,164 @@ TEST(SchemaTest, RangedRestriction) {
})")));
}
+TEST(SchemaTest, IsValidSchema) {
+ std::string error;
+ EXPECT_FALSE(Schema::IsValidSchema("", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("\0", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("string", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("\"string\"", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("[]", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("{}", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("{ \"type\": 123 }", &error));
+ EXPECT_FALSE(Schema::IsValidSchema("{ \"type\": \"invalid\" }", &error));
+ EXPECT_FALSE(
+ Schema::IsValidSchema("{"
+ " \"type\": \"object\","
+ " \"properties\": []" // Invalid properties type.
+ "}",
+ &error));
+ EXPECT_FALSE(
+ Schema::IsValidSchema("{"
+ " \"type\": \"string\","
+ " \"maxLength\": -1" // Must be >= 0.
+ "}",
+ &error));
+ EXPECT_FALSE(Schema::IsValidSchema(
+ "{"
+ " \"type\": \"string\","
+ " \"enum\": [ {} ]" // "enum" dict values must contain "name".
+ "}",
+ &error));
+ EXPECT_FALSE(Schema::IsValidSchema(
+ "{"
+ " \"type\": \"string\","
+ " \"enum\": [ { \"name\": {} } ]" // "enum" name must be a simple value.
+ "}",
+ &error));
+ EXPECT_FALSE(Schema::IsValidSchema(
+ "{"
+ " \"type\": \"array\","
+ " \"items\": [ 123 ]," // "items" must contain a schema or schemas.
+ "}",
+ &error));
+ EXPECT_TRUE(Schema::IsValidSchema("{ \"type\": \"object\" }", &error))
+ << error;
+ EXPECT_TRUE(
+ Schema::IsValidSchema("{ \"type\": [\"object\", \"array\"] }", &error))
+ << error;
+ EXPECT_TRUE(Schema::IsValidSchema(
+ "{"
+ " \"type\": [\"object\", \"array\"],"
+ " \"properties\": {"
+ " \"string-property\": {"
+ " \"type\": \"string\","
+ " \"minLength\": 1,"
+ " \"maxLength\": 100,"
+ " \"title\": \"The String Policy\","
+ " \"description\": \"This policy controls the String widget.\""
+ " },"
+ " \"integer-property\": {"
+ " \"type\": \"number\","
+ " \"minimum\": 1000.0,"
+ " \"maximum\": 9999.0"
+ " },"
+ " \"enum-property\": {"
+ " \"type\": \"integer\","
+ " \"enum\": [0, 1, {\"name\": 10}, 100]"
+ " },"
+ " \"items-property\": {"
+ " \"type\": \"array\","
+ " \"items\": {"
+ " \"type\": \"string\""
+ " }"
+ " },"
+ " \"items-list-property\": {"
+ " \"type\": \"array\","
+ " \"items\": ["
+ " { \"type\": \"string\" },"
+ " { \"type\": \"integer\" }"
+ " ]"
+ " }"
+ " },"
+ " \"additionalProperties\": {"
+ " \"type\": \"any\""
+ " }"
+ "}",
+ &error))
+ << error;
+ EXPECT_TRUE(
+ Schema::IsValidSchema("{"
+ " \"type\": \"object\","
+ " \"patternProperties\": {"
+ " \".\": { \"type\": \"any\" },"
+ " \"foo\": { \"type\": \"any\" },"
+ " \"^foo$\": { \"type\": \"any\" },"
+ " \"foo+\": { \"type\": \"any\" },"
+ " \"foo?\": { \"type\": \"any\" },"
+ " \"fo{2,4}\": { \"type\": \"any\" },"
+ " \"(left)|(right)\": { \"type\": \"any\" }"
+ " }"
+ "}",
+ &error))
+ << error;
+ EXPECT_TRUE(Schema::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"unknown attribute\": \"that should just be ignored\""
+ "}",
+ Schema::OPTIONS_IGNORE_UNKNOWN_ATTRIBUTES, &error))
+ << error;
+ EXPECT_FALSE(Schema::IsValidSchema(
+ "{"
+ " \"type\": \"object\","
+ " \"unknown attribute\": \"that will cause a failure\""
+ "}",
+ 0, &error))
+ << error;
+
+ EXPECT_FALSE(Schema::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": 123
+ })",
+ 0, &error))
+ << error;
+
+ EXPECT_FALSE(Schema::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": [ 123 ]
+ })",
+ 0, &error))
+ << error;
+
+ EXPECT_FALSE(Schema::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": ["bar"]
+ })",
+ 0, &error))
+ << error;
+
+ EXPECT_FALSE(Schema::IsValidSchema(R"(
+ {
+ "type": "object",
+ "required": ["bar"]
+ })",
+ 0, &error))
+ << error;
+
+ EXPECT_TRUE(Schema::IsValidSchema(R"(
+ {
+ "type": "object",
+ "properties": {"foo": {"type": "number"}},
+ "required": ["foo"]
+ })",
+ 0, &error))
+ << error;
+}
+
} // namespace policy