From 46266cb3478bcbdcec30107a12853c801e825068 Mon Sep 17 00:00:00 2001 From: Erayd Date: Wed, 21 Jun 2017 18:38:29 +1200 Subject: [PATCH 1/5] Add tests for $ref enforcement --- tests/RefTest.php | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 tests/RefTest.php diff --git a/tests/RefTest.php b/tests/RefTest.php new file mode 100644 index 00000000..56ed871e --- /dev/null +++ b/tests/RefTest.php @@ -0,0 +1,61 @@ +validate($document, $schema); + + $this->assertEquals($isValid, $v->isValid()); + } +} From 276e54644f627b29c5f18260221b4c27b2dfa2c4 Mon Sep 17 00:00:00 2001 From: Erayd Date: Wed, 21 Jun 2017 19:24:07 +1200 Subject: [PATCH 2/5] Overriding schema properties from $ref is not allowed From the spec [1]: An object schema with a "$ref" property MUST be interpreted as a "$ref" reference. The value of the "$ref" property MUST be a URI Reference. Resolved against the current URI base, it identifies the URI of a schema to use. All other properties in a "$ref" object MUST be ignored. [1] https://tools.ietf.org/html/draft-wright-json-schema-01#section-8 --- tests/SchemaStorageTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/SchemaStorageTest.php b/tests/SchemaStorageTest.php index 0e440ac8..9fd27bae 100644 --- a/tests/SchemaStorageTest.php +++ b/tests/SchemaStorageTest.php @@ -79,7 +79,7 @@ public function testSchemaWithLocalAndExternalReferencesWithCircularReference() ); // local ref with overriding - $this->assertNotEquals( + $this->assertEquals( $schemaStorage->resolveRef("$mainSchemaPath#/definitions/house/additionalProperties"), $schemaStorage->resolveRef("$mainSchemaPath#/properties/house/additionalProperties") ); From 48b2884158b4e3cfc1c1000b8cec14d599e94685 Mon Sep 17 00:00:00 2001 From: Erayd Date: Wed, 21 Jun 2017 20:37:09 +1200 Subject: [PATCH 3/5] Add test for infinite-loop circular reference --- tests/RefTest.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/RefTest.php b/tests/RefTest.php index 56ed871e..b67852c1 100644 --- a/tests/RefTest.php +++ b/tests/RefTest.php @@ -44,16 +44,33 @@ public function dataRefIgnoresSiblings() '{"propertyOne": 10}', true ), + // #2 infinite-loop / unresolveable circular reference + array( + '{ + "definitions": { + "test1": {"$ref": "#/definitions/test2"}, + "test2": {"$ref": "#/definitions/test1"} + }, + "properties": {"propertyOne": {"$ref": "#/definitions/test1"}} + }', + '{"propertyOne": 5}', + true, + '\JsonSchema\Exception\UnresolvableJsonPointerException' + ) ); } /** @dataProvider dataRefIgnoresSiblings */ - public function testRefIgnoresSiblings($schema, $document, $isValid) + public function testRefIgnoresSiblings($schema, $document, $isValid, $exception = null) { $document = json_decode($document); $schema = json_decode($schema); $v = new Validator(); + if ($exception) { + $this->setExpectedException($exception); + } + $v->validate($document, $schema); $this->assertEquals($isValid, $v->isValid()); From 54abf73bef7f5c67f72a39776fcf3b14d14f10ac Mon Sep 17 00:00:00 2001 From: Erayd Date: Wed, 21 Jun 2017 20:39:13 +1200 Subject: [PATCH 4/5] Return dereferenced schema directly and check for infinite-loops Schemas containing $ref MUST ignore all other properties, so just return the reference target directly. Because some circular references may result in an infinite loop, keep track of the objects that have been dereferenced during the current call to SchemaStorage and abort if the same object is encountered more than once. --- src/JsonSchema/SchemaStorage.php | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 9a1caeb4..377b5554 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -94,7 +94,7 @@ public function getSchema($id) /** * {@inheritdoc} */ - public function resolveRef($ref) + public function resolveRef($ref, $resolveStack = array()) { $jsonPointer = new JsonPointer($ref); @@ -111,9 +111,9 @@ public function resolveRef($ref) $refSchema = $this->getSchema($fileName); foreach ($jsonPointer->getPropertyPaths() as $path) { if (is_object($refSchema) && property_exists($refSchema, $path)) { - $refSchema = $this->resolveRefSchema($refSchema->{$path}); + $refSchema = $this->resolveRefSchema($refSchema->{$path}, $resolveStack); } elseif (is_array($refSchema) && array_key_exists($path, $refSchema)) { - $refSchema = $this->resolveRefSchema($refSchema[$path]); + $refSchema = $this->resolveRefSchema($refSchema[$path], $resolveStack); } else { throw new UnresolvableJsonPointerException(sprintf( 'File: %s is found, but could not resolve fragment: %s', @@ -129,12 +129,17 @@ public function resolveRef($ref) /** * {@inheritdoc} */ - public function resolveRefSchema($refSchema) + public function resolveRefSchema($refSchema, $resolveStack = array()) { if (is_object($refSchema) && property_exists($refSchema, '$ref') && is_string($refSchema->{'$ref'})) { - $newSchema = $this->resolveRef($refSchema->{'$ref'}); - $refSchema = (object) (get_object_vars($refSchema) + get_object_vars($newSchema)); - unset($refSchema->{'$ref'}); + if (in_array($refSchema, $resolveStack, true)) { + throw new UnresolvableJsonPointerException(sprintf( + 'Dereferencing a pointer to %s results in an infinite loop', + $refSchema->{'$ref'} + )); + } + $resolveStack[] = $refSchema; + return $this->resolveRef($refSchema->{'$ref'}, $resolveStack); } return $refSchema; From 1f877bf895884dfca7b16cef157348f95c459b98 Mon Sep 17 00:00:00 2001 From: Erayd Date: Wed, 21 Jun 2017 21:02:07 +1200 Subject: [PATCH 5/5] Code style fix (remove blank line) --- src/JsonSchema/SchemaStorage.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/JsonSchema/SchemaStorage.php b/src/JsonSchema/SchemaStorage.php index 377b5554..de2a1cb2 100644 --- a/src/JsonSchema/SchemaStorage.php +++ b/src/JsonSchema/SchemaStorage.php @@ -139,6 +139,7 @@ public function resolveRefSchema($refSchema, $resolveStack = array()) )); } $resolveStack[] = $refSchema; + return $this->resolveRef($refSchema->{'$ref'}, $resolveStack); }