1 /*
2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License, version 2.0,
6 * as published by the Free Software Foundation.
7 *
8 * This program is also distributed with certain software (including
9 * but not limited to OpenSSL) that is licensed under separate terms,
10 * as designated in a particular file or component or in included license
11 * documentation. The authors of MySQL hereby grant you an additional
12 * permission to link the program and your derivative works with the
13 * separately licensed software that they have included with MySQL.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License, version 2.0, for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 */
24
25 #include "plugin/x/src/meta_schema_validator.h"
26
27 #include <rapidjson/error/en.h>
28 #include <rapidjson/stringbuffer.h>
29 #include <rapidjson/writer.h>
30
31 #include "plugin/x/src/xpl_log.h"
32
33 namespace xpl {
34
35 namespace {
36
37 static const char *const k_invalid_document =
38 "Validation schema is not a valid JSON document";
39
get_pointer_string(const rapidjson::Pointer & pointer)40 inline std::string get_pointer_string(const rapidjson::Pointer &pointer) {
41 rapidjson::StringBuffer buff;
42 pointer.StringifyUriFragment(buff);
43 return {buff.GetString(), buff.GetSize()};
44 }
45
46 using Any = Mysqlx::Datatypes::Any;
47 using Array = Mysqlx::Datatypes::Array;
48 using Scalar = Mysqlx::Datatypes::Scalar;
49 using Object = Mysqlx::Datatypes::Object;
50
scalar_to_json(const Scalar & scalar,rapidjson::Value * value)51 bool scalar_to_json(const Scalar &scalar, rapidjson::Value *value) {
52 switch (scalar.type()) {
53 case Scalar::V_SINT:
54 if (!scalar.has_v_signed_int()) return false;
55 value->SetInt64(scalar.v_signed_int());
56 break;
57
58 case Scalar::V_UINT:
59 if (!scalar.has_v_unsigned_int()) return false;
60 value->SetUint64(scalar.v_unsigned_int());
61 break;
62
63 case Scalar::V_NULL:
64 value->SetNull();
65 break;
66
67 case Scalar::V_OCTETS:
68 if (!scalar.has_v_octets() || !scalar.v_octets().has_value())
69 return false;
70 value->SetString(scalar.v_octets().value().c_str(),
71 scalar.v_octets().value().length());
72 break;
73
74 case Scalar::V_DOUBLE:
75 if (!scalar.has_v_double()) return false;
76 value->SetDouble(scalar.v_double());
77 break;
78
79 case Scalar::V_FLOAT:
80 if (!scalar.has_v_float()) return false;
81 value->SetFloat(scalar.v_float());
82 break;
83
84 case Scalar::V_BOOL:
85 if (!scalar.has_v_bool()) return false;
86 value->SetBool(scalar.v_bool());
87 break;
88
89 case Scalar::V_STRING:
90 if (!scalar.has_v_string() || !scalar.v_string().has_value())
91 return false;
92 value->SetString(scalar.v_string().value().c_str(),
93 scalar.v_string().value().length());
94 break;
95 }
96 return true;
97 }
98
99 bool any_to_json(const Any &any, rapidjson::Value *value,
100 rapidjson::Document::AllocatorType *alloc);
101
array_to_json(const Array & array,rapidjson::Value * value,rapidjson::Document::AllocatorType * alloc)102 bool array_to_json(const Array &array, rapidjson::Value *value,
103 rapidjson::Document::AllocatorType *alloc) {
104 value->SetArray();
105 for (const auto &a : array.value()) {
106 rapidjson::Value tmp;
107 if (!any_to_json(a, &tmp, alloc)) return false;
108 value->PushBack(tmp, *alloc);
109 }
110 return true;
111 }
112
object_to_json(const Object & object,rapidjson::Value * value,rapidjson::Document::AllocatorType * alloc)113 bool object_to_json(const Object &object, rapidjson::Value *value,
114 rapidjson::Document::AllocatorType *alloc) {
115 value->SetObject();
116 for (const auto &o : object.fld()) {
117 rapidjson::Value tmp_value;
118 if (!any_to_json(o.value(), &tmp_value, alloc)) return false;
119 rapidjson::Value name;
120 name.SetString(o.key().c_str(), o.key().length());
121 value->AddMember(name, tmp_value, *alloc);
122 }
123 return true;
124 }
125
any_to_json(const Any & any,rapidjson::Value * value,rapidjson::Document::AllocatorType * alloc)126 bool any_to_json(const Any &any, rapidjson::Value *value,
127 rapidjson::Document::AllocatorType *alloc) {
128 switch (any.type()) {
129 case Any::SCALAR:
130 return scalar_to_json(any.scalar(), value);
131
132 case Any::OBJECT:
133 return object_to_json(any.obj(), value, alloc);
134
135 case Any::ARRAY:
136 return array_to_json(any.array(), value, alloc);
137 }
138 return false;
139 }
140
json_to_string(const rapidjson::Document & document,std::string * json_string)141 inline void json_to_string(const rapidjson::Document &document,
142 std::string *json_string) {
143 rapidjson::StringBuffer buff;
144 rapidjson::Writer<rapidjson::StringBuffer> w(buff);
145 document.Accept(w);
146 json_string->assign(buff.GetString(), buff.GetSize());
147 }
148
149 } // namespace
150
validate(const std::string & schema) const151 ngs::Error_code Meta_schema_validator::validate(
152 const std::string &schema) const {
153 rapidjson::Document input_document;
154 const rapidjson::ParseResult ok = input_document.Parse(schema.c_str());
155 if (!ok) {
156 log_debug("JSON schema parse error: %s",
157 rapidjson::GetParseError_En(ok.Code()));
158 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA, k_invalid_document);
159 }
160 return validate_impl(&input_document);
161 }
162
validate(const Any & schema,std::string * schema_string) const163 ngs::Error_code Meta_schema_validator::validate(
164 const Any &schema, std::string *schema_string) const {
165 rapidjson::Document input_document;
166
167 switch (schema.type()) {
168 case Any::OBJECT: {
169 if (!object_to_json(schema.obj(), &input_document,
170 &input_document.GetAllocator()))
171 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA, k_invalid_document);
172 } break;
173
174 case Any::SCALAR: {
175 if (!schema.scalar().has_v_string() ||
176 !schema.scalar().v_string().has_value())
177 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA, k_invalid_document);
178 if (schema.scalar().v_string().value().empty()) {
179 *schema_string = "";
180 return ngs::Success();
181 }
182 if (input_document.Parse(schema.scalar().v_string().value().c_str())
183 .HasParseError())
184 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA, k_invalid_document);
185 } break;
186
187 default: {
188 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA, k_invalid_document);
189 }
190 }
191
192 json_to_string(input_document, schema_string);
193 return validate_impl(&input_document);
194 }
195
validate_impl(rapidjson::Document * input_document) const196 ngs::Error_code Meta_schema_validator::validate_impl(
197 rapidjson::Document *input_document) const {
198 const auto e = pre_validate(input_document);
199 if (e) return e;
200
201 rapidjson::SchemaValidator validator{get_meta_schema()};
202 if (input_document->Accept(validator)) return ngs::Success();
203
204 return ngs::Error(
205 ER_X_INVALID_VALIDATION_SCHEMA,
206 "JSON validation schema location %s failed requirement: "
207 "'%s' at meta schema location '%s'",
208 get_pointer_string(validator.GetInvalidDocumentPointer()).c_str(),
209 validator.GetInvalidSchemaKeyword(),
210 get_pointer_string(validator.GetInvalidSchemaPointer()).c_str());
211 }
212
pre_validate(rapidjson::Document * document) const213 ngs::Error_code Meta_schema_validator::pre_validate(
214 rapidjson::Document *document) const {
215 if (!document->IsObject())
216 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA,
217 "Validation schema is not a valid JSON object");
218 return pre_validate_impl(0, rapidjson::Pointer(), document, document);
219 }
220
221 namespace {
222
is_reference_valid(const rapidjson::Document & document,const rapidjson::Value & reference)223 bool is_reference_valid(const rapidjson::Document &document,
224 const rapidjson::Value &reference) {
225 if (!reference.IsString()) return false;
226 const auto pointer =
227 rapidjson::Pointer(reference.GetString(), reference.GetStringLength());
228 if (!pointer.IsValid()) return false;
229 return pointer.Get(document);
230 }
231
232 } // namespace
233
pre_validate_impl(const uint32_t level,const rapidjson::Pointer & pointer,rapidjson::Document * document,rapidjson::Value * value) const234 ngs::Error_code Meta_schema_validator::pre_validate_impl(
235 const uint32_t level, const rapidjson::Pointer &pointer,
236 rapidjson::Document *document, rapidjson::Value *value) const {
237 if (level > m_max_depth)
238 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA,
239 "Validation schema exceeds the maximum depth on %s",
240 get_pointer_string(pointer).c_str());
241
242 if (value->IsArray()) {
243 for (rapidjson::SizeType i = 0; i < value->Size(); i++) {
244 const auto e = pre_validate_impl(level + 1, pointer.Append(i), document,
245 &(*value)[i]);
246 if (e) return e;
247 }
248 return ngs::Success();
249 }
250
251 if (!value->IsObject()) return ngs::Success();
252
253 using It = rapidjson::Value::MemberIterator;
254 static const rapidjson::Value k_ref("$ref");
255
256 // 'json schema reference' is an object with one member named '$ref'
257 It ref = value->FindMember(k_ref);
258 // if not '$ref' member - proceed as regular object
259 if (ref == value->MemberEnd()) {
260 for (It i = value->MemberBegin(); i != value->MemberEnd(); ++i) {
261 const auto e = pre_validate_impl(level + 1, pointer.Append(i->name),
262 document, &(i->value));
263 if (e) return e;
264 }
265 return ngs::Success();
266 }
267
268 // this is reference object - check reference string
269 if (!is_reference_valid(*document, ref->value))
270 return ngs::Error(ER_X_INVALID_VALIDATION_SCHEMA,
271 "Validation schema reference '%s' is not valid",
272 get_pointer_string(pointer).c_str());
273
274 return ngs::Success();
275 }
276
get_meta_schema() const277 const rapidjson::SchemaDocument &Meta_schema_validator::get_meta_schema()
278 const {
279 static const rapidjson::SchemaDocument meta_schema{get_schema_document()};
280 return meta_schema;
281 }
282
get_schema_document() const283 const rapidjson::Document &Meta_schema_validator::get_schema_document() const {
284 static rapidjson::Document document;
285 document.Parse(k_reference_schema);
286 // additional requirement to raw meta-schema:
287 // - not allow additional properties (to avoid typos).
288 document.AddMember("additionalProperties", false, document.GetAllocator());
289 // - allow '$ref' property as internal json reference
290 rapidjson::Document::ValueType reference;
291 reference.SetObject();
292 reference.AddMember("type", "string", document.GetAllocator());
293 reference.AddMember("pattern", "^#(/(.+))*$", document.GetAllocator());
294 const auto properties = document.FindMember("properties");
295 properties->value.AddMember("$ref", reference, document.GetAllocator());
296 return document;
297 }
298
299 // Meta-schema used for validation of a user provided schemas,
300 // taken from http://json-schema.org/draft-04/schema
301 const char *const Meta_schema_validator::k_reference_schema = R"({
302 "id": "http://json-schema.org/draft-04/schema#",
303 "$schema": "http://json-schema.org/draft-04/schema#",
304 "description": "Core schema meta-schema",
305 "definitions": {
306 "schemaArray": {
307 "type": "array",
308 "minItems": 1,
309 "items": { "$ref": "#" }
310 },
311 "positiveInteger": {
312 "type": "integer",
313 "minimum": 0
314 },
315 "positiveIntegerDefault0": {
316 "allOf": [
317 { "$ref": "#/definitions/positiveInteger" },
318 { "default": 0 }
319 ]
320 },
321 "simpleTypes": {
322 "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
323 },
324 "stringArray": {
325 "type": "array",
326 "items": { "type": "string" },
327 "minItems": 1,
328 "uniqueItems": true
329 }
330 },
331 "type": "object",
332 "properties": {
333 "id": { "type": "string" },
334 "$schema": { "type": "string" },
335 "title": { "type": "string" },
336 "description": { "type": "string" },
337 "default": {},
338 "multipleOf": {
339 "type": "number",
340 "minimum": 0,
341 "exclusiveMinimum": true
342 },
343 "maximum": { "type": "number" },
344 "exclusiveMaximum": {
345 "type": "boolean",
346 "default": false
347 },
348 "minimum": { "type": "number" },
349 "exclusiveMinimum": {
350 "type": "boolean",
351 "default": false
352 },
353 "maxLength": { "$ref": "#/definitions/positiveInteger" },
354 "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
355 "pattern": {
356 "type": "string",
357 "format": "regex"
358 },
359 "additionalItems": {
360 "anyOf": [
361 { "type": "boolean" },
362 { "$ref": "#" }
363 ],
364 "default": {}
365 },
366 "items": {
367 "anyOf": [
368 { "$ref": "#" },
369 { "$ref": "#/definitions/schemaArray" }
370 ],
371 "default": {}
372 },
373 "maxItems": { "$ref": "#/definitions/positiveInteger" },
374 "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
375 "uniqueItems": {
376 "type": "boolean",
377 "default": false
378 },
379 "maxProperties": { "$ref": "#/definitions/positiveInteger" },
380 "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
381 "required": { "$ref": "#/definitions/stringArray" },
382 "additionalProperties": {
383 "anyOf": [
384 { "type": "boolean" },
385 { "$ref": "#" }
386 ],
387 "default": {}
388 },
389 "definitions": {
390 "type": "object",
391 "additionalProperties": { "$ref": "#" },
392 "default": {}
393 },
394 "properties": {
395 "type": "object",
396 "additionalProperties": { "$ref": "#" },
397 "default": {}
398 },
399 "patternProperties": {
400 "type": "object",
401 "additionalProperties": { "$ref": "#" },
402 "default": {}
403 },
404 "dependencies": {
405 "type": "object",
406 "additionalProperties": {
407 "anyOf": [
408 { "$ref": "#" },
409 { "$ref": "#/definitions/stringArray" }
410 ]
411 }
412 },
413 "enum": {
414 "type": "array",
415 "minItems": 1,
416 "uniqueItems": true
417 },
418 "type": {
419 "anyOf": [
420 { "$ref": "#/definitions/simpleTypes" },
421 {
422 "type": "array",
423 "items": { "$ref": "#/definitions/simpleTypes" },
424 "minItems": 1,
425 "uniqueItems": true
426 }
427 ]
428 },
429 "format": { "type": "string" },
430 "allOf": { "$ref": "#/definitions/schemaArray" },
431 "anyOf": { "$ref": "#/definitions/schemaArray" },
432 "oneOf": { "$ref": "#/definitions/schemaArray" },
433 "not": { "$ref": "#" }
434 },
435 "dependencies": {
436 "exclusiveMaximum": [ "maximum" ],
437 "exclusiveMinimum": [ "minimum" ]
438 },
439 "default": {}
440 })";
441
442 } // namespace xpl
443