From b138dc6bbab302e7d3832ff551f5212be89a6ae7 Mon Sep 17 00:00:00 2001 From: Martin Sladecek Date: Fri, 8 Dec 2023 01:39:31 +0100 Subject: [PATCH] 665: Can't load JSON schemas with URN value in id field (#906) * 665: Can't load JSON schemas with URN value in id field * Better handling of URNs --- pom.xml | 2 +- .../java/com/networknt/schema/JsonSchema.java | 2 +- .../networknt/schema/JsonSchemaFactory.java | 2 + .../com/networknt/schema/RefValidator.java | 15 ++++++ .../com/networknt/schema/uri/URLFactory.java | 2 +- .../com/networknt/schema/uri/URLFetcher.java | 4 +- .../networknt/schema/uri/URNURIFactory.java | 30 ++++++++++++ .../com/networknt/schema/Issue665Test.java | 49 +++++++++++++++++++ src/test/resources/draft7/urn/issue665.json | 26 ++++++++++ .../draft7/urn/issue665_external_urn_ref.json | 12 +++++ .../urn/issue665_external_urn_subschema.json | 13 +++++ 11 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/networknt/schema/uri/URNURIFactory.java create mode 100644 src/test/java/com/networknt/schema/Issue665Test.java create mode 100644 src/test/resources/draft7/urn/issue665.json create mode 100644 src/test/resources/draft7/urn/issue665_external_urn_ref.json create mode 100644 src/test/resources/draft7/urn/issue665_external_urn_subschema.json diff --git a/pom.xml b/pom.xml index c9ae09a10..cb6966f58 100644 --- a/pom.xml +++ b/pom.xml @@ -23,7 +23,7 @@ com.networknt json-schema-validator - 1.0.87 + 1.0.88 bundle JsonSchemaValidator A json schema validator that supports draft v4, v6, v7, v2019-09 and v2020-12 diff --git a/src/main/java/com/networknt/schema/JsonSchema.java b/src/main/java/com/networknt/schema/JsonSchema.java index 84d8c3186..d5050aee1 100644 --- a/src/main/java/com/networknt/schema/JsonSchema.java +++ b/src/main/java/com/networknt/schema/JsonSchema.java @@ -214,7 +214,7 @@ public JsonNode getRefSchemaNode(String ref) { if (node.isMissingNode()) { node = handleNullNode(ref, schema); } - } else if (ref.startsWith("#") && ref.length() > 1) { + } else if ((ref.startsWith("#") && ref.length() > 1) || (ref.startsWith("urn:") && ref.length() > 4)) { node = this.metaSchema.getNodeByFragmentRef(ref, node); if (node == null) { node = handleNullNode(ref, schema); diff --git a/src/main/java/com/networknt/schema/JsonSchemaFactory.java b/src/main/java/com/networknt/schema/JsonSchemaFactory.java index 77ce452c6..b6fbd2180 100644 --- a/src/main/java/com/networknt/schema/JsonSchemaFactory.java +++ b/src/main/java/com/networknt/schema/JsonSchemaFactory.java @@ -59,6 +59,8 @@ public Builder() { for (final String scheme : URLFactory.SUPPORTED_SCHEMES) { this.uriFactoryMap.put(scheme, urlFactory); } + // Adds support for creating URNs. + this.uriFactoryMap.put(URNURIFactory.SCHEME, new URNURIFactory()); // Adds support for fetching with {@link URL}s. final URIFetcher urlFetcher = new URLFetcher(); diff --git a/src/main/java/com/networknt/schema/RefValidator.java b/src/main/java/com/networknt/schema/RefValidator.java index 356ffbabc..83e8afe56 100644 --- a/src/main/java/com/networknt/schema/RefValidator.java +++ b/src/main/java/com/networknt/schema/RefValidator.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.networknt.schema.CollectorContext.Scope; import com.networknt.schema.uri.URIFactory; +import com.networknt.schema.uri.URNURIFactory; import com.networknt.schema.urn.URNFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +36,7 @@ public class RefValidator extends BaseJsonValidator { private JsonSchema parentSchema; private static final String REF_CURRENT = "#"; + private static final String URN_SCHEME = URNURIFactory.SCHEME; public RefValidator(String schemaPath, JsonNode schemaNode, JsonSchema parentSchema, ValidationContext validationContext) { super(schemaPath, schemaNode, parentSchema, ValidatorTypeCode.REF, validationContext); @@ -78,6 +80,12 @@ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext val if (schemaUri == null) { return null; } + } else if (URN_SCHEME.equals(schemaUri.getScheme())) { + // Try to resolve URN schema as a JsonSchemaRef to some sub-schema of the parent + JsonSchemaRef ref = getJsonSchemaRef(parent, validationContext, schemaUri.toString(), refValueOriginal); + if (ref != null) { + return ref; + } } // This should retrieve schemas regardless of the protocol that is in the uri. @@ -91,6 +99,13 @@ static JsonSchemaRef getRefSchema(JsonSchema parentSchema, ValidationContext val if (refValue.equals(REF_CURRENT)) { return new JsonSchemaRef(parent.findAncestor()); } + return getJsonSchemaRef(parent, validationContext, refValue, refValueOriginal); + } + + private static JsonSchemaRef getJsonSchemaRef(JsonSchema parent, + ValidationContext validationContext, + String refValue, + String refValueOriginal) { JsonNode node = parent.getRefSchemaNode(refValue); if (node != null) { JsonSchemaRef ref = validationContext.getReferenceParsingInProgress(refValueOriginal); diff --git a/src/main/java/com/networknt/schema/uri/URLFactory.java b/src/main/java/com/networknt/schema/uri/URLFactory.java index 5850a3be2..61fa56d1a 100644 --- a/src/main/java/com/networknt/schema/uri/URLFactory.java +++ b/src/main/java/com/networknt/schema/uri/URLFactory.java @@ -30,7 +30,7 @@ */ public final class URLFactory implements URIFactory { // These supported schemes are defined in {@link #URL(String, String, int, String)}. - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet( + public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(new HashSet<>( Arrays.asList("http", "https", "ftp", "file", "jar"))); /** diff --git a/src/main/java/com/networknt/schema/uri/URLFetcher.java b/src/main/java/com/networknt/schema/uri/URLFetcher.java index 14899cf8f..d5bb8d944 100644 --- a/src/main/java/com/networknt/schema/uri/URLFetcher.java +++ b/src/main/java/com/networknt/schema/uri/URLFetcher.java @@ -22,7 +22,9 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.Set; /** @@ -32,7 +34,7 @@ public final class URLFetcher implements URIFetcher { // These supported schemes are defined in {@link #URL(String, String, int, String)}. // This fetcher also supports the {@link URL}s created with the {@link ClasspathURIFactory}. - public static final Set SUPPORTED_SCHEMES = Collections.unmodifiableSet(URLFactory.SUPPORTED_SCHEMES); + public static final Set SUPPORTED_SCHEMES = URLFactory.SUPPORTED_SCHEMES; /** * {@inheritDoc} diff --git a/src/main/java/com/networknt/schema/uri/URNURIFactory.java b/src/main/java/com/networknt/schema/uri/URNURIFactory.java new file mode 100644 index 000000000..8ea7914a3 --- /dev/null +++ b/src/main/java/com/networknt/schema/uri/URNURIFactory.java @@ -0,0 +1,30 @@ +package com.networknt.schema.uri; + +import java.net.URI; +import java.util.Collections; +import java.util.Set; + +/** + * A URIFactory that handles "urn" scheme of {@link URI}s. + */ +public final class URNURIFactory implements URIFactory { + + public static final String SCHEME = "urn"; + + @Override + public URI create(final String uri) { + try { + return URI.create(uri); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Unable to create URI.", e); + } + } + + @Override + public URI create(final URI baseURI, final String segment) { + String urnPart = baseURI.getRawSchemeSpecificPart(); + int pos = urnPart.indexOf(':'); + String namespace = pos < 0 ? urnPart : urnPart.substring(0, pos); + return URI.create(SCHEME + ":" + namespace + ":" + segment); + } +} diff --git a/src/test/java/com/networknt/schema/Issue665Test.java b/src/test/java/com/networknt/schema/Issue665Test.java new file mode 100644 index 000000000..1b9f8bd04 --- /dev/null +++ b/src/test/java/com/networknt/schema/Issue665Test.java @@ -0,0 +1,49 @@ +package com.networknt.schema; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.net.URI; +import java.util.Collections; +import java.util.Set; + +public class Issue665Test extends BaseJsonSchemaValidatorTest { + + @Test + void testUrnUriAsLocalRef() throws IOException { + JsonSchema schema = getJsonSchemaFromClasspath("draft7/urn/issue665.json", SpecVersion.VersionFlag.V7); + Assertions.assertNotNull(schema); + Assertions.assertDoesNotThrow(schema::initializeValidators); + Set messages = schema.validate(getJsonNodeFromStringContent( + "{\"myData\": {\"value\": \"hello\"}}")); + Assertions.assertEquals(messages, Collections.emptySet()); + } + + @Test + void testUrnUriAsLocalRef_ExternalURN() { + JsonSchemaFactory factory = JsonSchemaFactory + .builder(JsonSchemaFactory.getInstance(SpecVersion.VersionFlag.V7)) + .uriFetcher(uri -> uri.equals(URI.create("urn:data")) + ? Thread.currentThread().getContextClassLoader() + .getResourceAsStream("draft7/urn/issue665_external_urn_subschema.json") + : null, + "urn") + .build(); + + try (InputStream is = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("draft7/urn/issue665_external_urn_ref.json")) { + JsonSchema schema = factory.getSchema(is); + Assertions.assertNotNull(schema); + Assertions.assertDoesNotThrow(schema::initializeValidators); + Set messages = schema.validate(getJsonNodeFromStringContent( + "{\"myData\": {\"value\": \"hello\"}}")); + Assertions.assertEquals(messages, Collections.emptySet()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/src/test/resources/draft7/urn/issue665.json b/src/test/resources/draft7/urn/issue665.json new file mode 100644 index 000000000..57a3bf884 --- /dev/null +++ b/src/test/resources/draft7/urn/issue665.json @@ -0,0 +1,26 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "myData" + ], + "properties": { + "myData": { + "$ref": "urn:data" + } + }, + "definitions": { + "data": { + "$id": "urn:data", + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } + } + } +} diff --git a/src/test/resources/draft7/urn/issue665_external_urn_ref.json b/src/test/resources/draft7/urn/issue665_external_urn_ref.json new file mode 100644 index 000000000..2929686b0 --- /dev/null +++ b/src/test/resources/draft7/urn/issue665_external_urn_ref.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": [ + "myData" + ], + "properties": { + "myData": { + "$ref": "urn:data" + } + } +} diff --git a/src/test/resources/draft7/urn/issue665_external_urn_subschema.json b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json new file mode 100644 index 000000000..026f879aa --- /dev/null +++ b/src/test/resources/draft7/urn/issue665_external_urn_subschema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "urn:data", + "type": "object", + "required": [ + "value" + ], + "properties": { + "value": { + "type": "string" + } + } +}