From 01ae592862f06e6b1f85a2cf553a37ee3cf35612 Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Mon, 4 May 2020 14:56:36 +0200 Subject: [PATCH 1/5] more tests --- .../validationtool/config/XPathBuilder.java | 23 ++-- .../impl/ContentRepository.java | 2 +- .../config/XPathBuilderTest.java | 116 ++++++++++++++++++ 3 files changed, 129 insertions(+), 12 deletions(-) create mode 100644 src/test/java/de/kosit/validationtool/config/XPathBuilderTest.java diff --git a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java index 83cbe5a..c5f60b8 100644 --- a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java @@ -13,7 +13,6 @@ import org.apache.commons.lang3.StringUtils; import lombok.AccessLevel; import lombok.Data; -import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -42,9 +41,15 @@ class XPathBuilder implements Builder { private XPathExecutable executable; @Setter(AccessLevel.PACKAGE) - @Getter(AccessLevel.PACKAGE) private Map namespaces; + Map getNamespaces() { + if (this.namespaces == null) { + this.namespaces = new HashMap<>(); + } + return this.namespaces; + } + /** * Returns the xpath expression. * @@ -66,7 +71,7 @@ class XPathBuilder implements Builder { } try { if (this.executable == null) { - this.executable = repository.createXPath(this.xpath, this.namespaces); + this.executable = repository.createXPath(this.xpath, getNamespaces()); } else { this.xpath = extractExpression(); extractNamespaces(); @@ -81,19 +86,15 @@ class XPathBuilder implements Builder { } private void extractNamespaces() { - if (this.namespaces == null) { - this.namespaces = new HashMap<>(); - } + final Map ns = new HashMap<>(); final Iterator iterator = this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext() .iteratePrefixes(); final Iterable iterable = () -> iterator; StreamSupport.stream(iterable.spliterator(), false).filter(e -> !ArrayUtils.contains(IGNORED_PREFIXES, e)) - .filter(StringUtils::isNotBlank).forEach(e -> { - ns.put(e, this.executable.getUnderlyingExpression().getInternalExpression().getRetainedStaticContext() - .getURIForPrefix(e, false)); - }); - this.namespaces.putAll(ns); + .filter(StringUtils::isNotBlank).forEach(e -> ns.put(e, this.executable.getUnderlyingExpression().getInternalExpression() + .getRetainedStaticContext().getURIForPrefix(e, false))); + getNamespaces().putAll(ns); } diff --git a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java index 6fd7a5b..84757d4 100644 --- a/src/main/java/de/kosit/validationtool/impl/ContentRepository.java +++ b/src/main/java/de/kosit/validationtool/impl/ContentRepository.java @@ -143,7 +143,7 @@ public class ContentRepository { throw new IllegalStateException("Can not compile xslt executable for uri " + uri, e); } finally { if (!listener.hasErrors() && listener.hasEvents()) { - log.warn("Received warnings while loading a xslt script {}", uri); + log.warn("Received warnings or errors while loading a xslt script {}", uri); listener.getErrors().forEach(e -> e.log(log)); } } diff --git a/src/test/java/de/kosit/validationtool/config/XPathBuilderTest.java b/src/test/java/de/kosit/validationtool/config/XPathBuilderTest.java new file mode 100644 index 0000000..30d3af1 --- /dev/null +++ b/src/test/java/de/kosit/validationtool/config/XPathBuilderTest.java @@ -0,0 +1,116 @@ +package de.kosit.validationtool.config; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.Test; + +import de.kosit.validationtool.impl.ContentRepository; +import de.kosit.validationtool.impl.Helper.Simple; +import de.kosit.validationtool.impl.model.Result; + +import net.sf.saxon.s9api.XPathExecutable; + +/** + * Tests {@link XPathBuilder}. + * + * @author Andreas Penski + */ +public class XPathBuilderTest { + + @Test + public void testSimpleString() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final XPathBuilder b = new XPathBuilder(name); + b.setXpath("//*"); + final Result result = b.build(Simple.createContentRepository()); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isTrue(); + assertThat(b.getNamespaces()).isNotNull(); + assertThat(b.getNamespaces()).isEmpty(); + assertThat(b.getXPath()).isNotEmpty(); + assertThat(b.getName()).isNotEmpty(); + } + + @Test + public void testStringWithNamespace() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final XPathBuilder b = new XPathBuilder(name); + final Map ns = new HashMap<>(); + ns.put("p", "http://somens"); + b.setNamespaces(ns); + b.setXpath("//p:*"); + final Result result = b.build(Simple.createContentRepository()); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isTrue(); + assertThat(b.getNamespaces()).isNotEmpty(); + assertThat(b.getXPath()).isNotEmpty(); + } + + @Test + public void testStringWithUnknownNamespace() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final XPathBuilder b = new XPathBuilder(name); + final Map ns = new HashMap<>(); + ns.put("p", "http://somens"); + b.setNamespaces(ns); + b.setXpath("//u:*"); + final Result result = b.build(Simple.createContentRepository()); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isFalse(); + } + + @Test + public void testExecutable() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final ContentRepository repository = Simple.createContentRepository(); + final XPathExecutable xpath = repository.createXPath("//*", Collections.emptyMap()); + final XPathBuilder b = new XPathBuilder(name); + b.setExecutable(xpath); + final Result result = b.build(repository); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isTrue(); + assertThat(b.getNamespaces()).isEmpty(); + assertThat(b.getXPath()).isNotEmpty(); + } + + @Test + public void testExecutableWithNamespace() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final ContentRepository repository = Simple.createContentRepository(); + final Map ns = new HashMap<>(); + ns.put("p", "http://somens"); + final XPathExecutable xpath = repository.createXPath("//p:*", ns); + final XPathBuilder b = new XPathBuilder(name); + b.setExecutable(xpath); + final Result result = b.build(repository); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isTrue(); + assertThat(b.getNamespaces()).isNotEmpty(); + assertThat(b.getNamespaces()).containsKey("p"); + assertThat(b.getXPath()).isNotEmpty(); + } + + @Test + public void testNoName() { + final XPathBuilder b = new XPathBuilder(null); + b.setXpath("//*"); + final Result result = b.build(Simple.createContentRepository()); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isTrue(); + assertThat(b.getName()).isNull(); + } + + @Test + public void testNoConfig() { + final String name = RandomStringUtils.randomAlphanumeric(5); + final XPathBuilder b = new XPathBuilder(name); + final Result result = b.build(Simple.createContentRepository()); + assertThat(result).isNotNull(); + assertThat(result.isValid()).isFalse(); + } +} From 5f15c99c04324d3547d29b28c7c98cb50c9f42a1 Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Mon, 4 May 2020 14:57:20 +0200 Subject: [PATCH 2/5] sleep a little longer --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 77cb2b6..34e62a2 100644 --- a/pom.xml +++ b/pom.xml @@ -447,7 +447,7 @@ - + ${jacoco.tcp.port} From 7671977249c2a67b04987f6b008cb29a0956f324 Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Mon, 4 May 2020 14:59:21 +0200 Subject: [PATCH 3/5] reduce visibility --- .../java/de/kosit/validationtool/config/XPathBuilder.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java index c5f60b8..7dc6509 100644 --- a/src/main/java/de/kosit/validationtool/config/XPathBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/XPathBuilder.java @@ -12,7 +12,7 @@ import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import lombok.AccessLevel; -import lombok.Data; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -28,7 +28,8 @@ import net.sf.saxon.s9api.XPathExecutable; * @author Andreas Penski */ @RequiredArgsConstructor -@Data +@Getter +@Setter @Slf4j class XPathBuilder implements Builder { From edb8427deccba517948e8f904f2f0a1318132acd Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Mon, 4 May 2020 16:27:55 +0200 Subject: [PATCH 4/5] create tests for resolvers --- .../impl/xml/RemoteResolvingStrategy.java | 10 ++- .../de/kosit/validationtool/impl/Helper.java | 21 +++-- .../impl/TestObjectFactory.java | 79 +------------------ ...ava => BaseResolverConfigurationTest.java} | 2 +- .../impl/xml/RemoteResolvingStrategyTest.java | 41 ++++++++++ .../impl/{ => xml}/SaxonSecurityTest.java | 5 +- .../impl/xml/StrictLocalResolvingTest.java | 43 ++++++++++ .../impl/xml/StrictRelativeResolvingTest.java | 45 +++++++++++ .../resources/examples/resolving/main.xsd | 12 +++ .../resolving/resources/reference.xsd | 9 +++ .../examples/resolving/withRemote.xsd | 14 ++++ 11 files changed, 187 insertions(+), 94 deletions(-) rename src/test/java/de/kosit/validationtool/impl/xml/{BaseResolverTest.java => BaseResolverConfigurationTest.java} (98%) create mode 100644 src/test/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategyTest.java rename src/test/java/de/kosit/validationtool/impl/{ => xml}/SaxonSecurityTest.java (96%) create mode 100644 src/test/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingTest.java create mode 100644 src/test/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingTest.java create mode 100644 src/test/resources/examples/resolving/main.xsd create mode 100644 src/test/resources/examples/resolving/resources/reference.xsd create mode 100644 src/test/resources/examples/resolving/withRemote.xsd diff --git a/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java b/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java index 3be4a65..ebebeb5 100644 --- a/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java +++ b/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java @@ -1,7 +1,13 @@ package de.kosit.validationtool.impl.xml; +import javax.xml.validation.SchemaFactory; + public class RemoteResolvingStrategy extends StrictLocalResolvingStrategy { - - + @Override + public SchemaFactory createSchemaFactory() { + final SchemaFactory schemaFactory = super.createSchemaFactory(); + allowExternalSchema(schemaFactory, "https,http,file"); + return schemaFactory; + } } diff --git a/src/test/java/de/kosit/validationtool/impl/Helper.java b/src/test/java/de/kosit/validationtool/impl/Helper.java index d0ca817..1fa0837 100644 --- a/src/test/java/de/kosit/validationtool/impl/Helper.java +++ b/src/test/java/de/kosit/validationtool/impl/Helper.java @@ -19,7 +19,6 @@ package de.kosit.validationtool.impl; -import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; @@ -84,7 +83,6 @@ public class Helper { return new ContentRepository(strategy, Simple.REPOSITORY_URI); } - public static URI getSchemaLocation() { return SCHEMA; } @@ -99,6 +97,15 @@ public class Helper { public static final URI SCENARIOS_ILLFORMED = ROOT.resolve("scenarios-illformed.xml"); } + public static class Resolving { + + public static final URI ROOT = EXAMPLES_DIR.resolve("resolving/"); + + public static final URI SCHEMA_WITH_REMOTE_REFERENCE = ROOT.resolve("withRemote.xsd"); + + public static final URI SCHEMA_WITH_REFERENCE = ROOT.resolve("main.xsd"); + } + public static final URI MODEL_ROOT = Paths.get("src/main/model").toUri(); public static final URI ASSERTION_SCHEMA = MODEL_ROOT.resolve("xsd/assertions.xsd"); @@ -135,16 +142,6 @@ public class Helper { return c.readXml(url.toURI(), type); } - /** - * Lädt das default test repository mit Artefacten für Unit-Tests - * - * @return ein {@link ContentRepository} - */ - public static ContentRepository loadTestRepository() { - return new ContentRepository(ResolvingMode.STRICT_RELATIVE.getStrategy(), - new File("src/test/resources/examples/repository").toURI()); - } - public static String serialize(final XdmNode node) { try ( final StringWriter writer = new StringWriter() ) { final Processor processor = Helper.getTestProcessor(); diff --git a/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java index f239d5b..e647843 100644 --- a/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java +++ b/src/test/java/de/kosit/validationtool/impl/TestObjectFactory.java @@ -1,94 +1,19 @@ package de.kosit.validationtool.impl; -import java.io.Reader; -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; +import de.kosit.validationtool.impl.xml.StrictLocalResolvingStrategy; -import javax.xml.transform.Result; -import javax.xml.transform.TransformerException; - -import net.sf.saxon.Configuration; -import net.sf.saxon.expr.XPathContext; -import net.sf.saxon.lib.CollectionFinder; -import net.sf.saxon.lib.FeatureKeys; -import net.sf.saxon.lib.OutputURIResolver; -import net.sf.saxon.lib.ResourceCollection; -import net.sf.saxon.lib.UnparsedTextURIResolver; import net.sf.saxon.s9api.Processor; -import net.sf.saxon.trans.XPathException; /** * @author Andreas Penski */ public class TestObjectFactory { - private static class SecureUriResolver implements CollectionFinder, OutputURIResolver, UnparsedTextURIResolver { - - public static final String MESSAGE = "Configuration error. Resolving ist not allowed"; - - @Override - public OutputURIResolver newInstance() { - return this; - } - - @Override - public Result resolve(final String href, final String base) throws TransformerException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public void close(final Result result) throws TransformerException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public Reader resolve(final URI absoluteURI, final String encoding, final Configuration config) throws XPathException { - throw new IllegalStateException(MESSAGE); - } - - @Override - public ResourceCollection findCollection(final XPathContext context, final String collectionURI) throws XPathException { - throw new IllegalStateException(MESSAGE); - } - } - - private static final String DISSALLOW_DOCTYPE_DECL_FEATURE = "http://apache.org/xml/features/disallow-doctype-decl"; - - private static final String LOAD_EXTERNAL_DTD_FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; - - private static final String FEATURE_SECURE_PROCESSING = "http://javax.xml.XMLConstants/feature/secure-processing"; - private static Processor processor; - private static String encode(final String input) { - try { - return URLEncoder.encode(input, StandardCharsets.UTF_8.name()); - } catch (final UnsupportedEncodingException e) { - throw new IllegalStateException("Error encoding property while initializing saxon", e); - } - } - public static Processor createProcessor() { if (processor == null) { - processor = new Processor(false); - // verhindere global im Prinzip alle resolving strategien - final SecureUriResolver resolver = new SecureUriResolver(); - processor.getUnderlyingConfiguration().setCollectionFinder(resolver); - processor.getUnderlyingConfiguration().setOutputURIResolver(resolver); - processor.getUnderlyingConfiguration().setUnparsedTextURIResolver(resolver); - - // grundsätzlich Feature-konfiguration: - processor.setConfigurationProperty(FeatureKeys.DTD_VALIDATION, false); - processor.setConfigurationProperty(FeatureKeys.ENTITY_RESOLVER_CLASS, ""); - processor.setConfigurationProperty(FeatureKeys.XINCLUDE, false); - processor.setConfigurationProperty(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, false); - - // Konfiguration des zu verwendenden Parsers, wenn Saxon selbst einen erzeugen muss, bspw. beim XSL parsen - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(FEATURE_SECURE_PROCESSING), true); - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(DISSALLOW_DOCTYPE_DECL_FEATURE), true); - processor.setConfigurationProperty(FeatureKeys.XML_PARSER_FEATURE + encode(LOAD_EXTERNAL_DTD_FEATURE), false); + processor = new StrictLocalResolvingStrategy().getProcessor(); } return processor; } diff --git a/src/test/java/de/kosit/validationtool/impl/xml/BaseResolverTest.java b/src/test/java/de/kosit/validationtool/impl/xml/BaseResolverConfigurationTest.java similarity index 98% rename from src/test/java/de/kosit/validationtool/impl/xml/BaseResolverTest.java rename to src/test/java/de/kosit/validationtool/impl/xml/BaseResolverConfigurationTest.java index 51a2871..a37718d 100644 --- a/src/test/java/de/kosit/validationtool/impl/xml/BaseResolverTest.java +++ b/src/test/java/de/kosit/validationtool/impl/xml/BaseResolverConfigurationTest.java @@ -23,7 +23,7 @@ import lombok.RequiredArgsConstructor; * * @author Andreas Penski */ -public class BaseResolverTest { +public class BaseResolverConfigurationTest { @RequiredArgsConstructor private class TestResolvingStrategy extends StrictRelativeResolvingStrategy { diff --git a/src/test/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategyTest.java b/src/test/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategyTest.java new file mode 100644 index 0000000..a60244c --- /dev/null +++ b/src/test/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategyTest.java @@ -0,0 +1,41 @@ +package de.kosit.validationtool.impl.xml; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import de.kosit.validationtool.api.ResolvingConfigurationStrategy; +import de.kosit.validationtool.impl.Helper.Resolving; + +/** + * Tests {@link RemoteResolvingStrategy}. + * + * @author Andreas Penski + */ +public class RemoteResolvingStrategyTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testRemoteSchemaResolving() throws Exception { + final ResolvingConfigurationStrategy s = new RemoteResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + final Schema schema = schemaFactory.newSchema(Resolving.SCHEMA_WITH_REMOTE_REFERENCE.toURL()); + assertThat(schema).isNotNull(); + } + + @Test + public void testLocalSchemaResolving() throws Exception { + final ResolvingConfigurationStrategy s = new StrictLocalResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + final Schema schema = schemaFactory.newSchema(Resolving.SCHEMA_WITH_REFERENCE.toURL()); + assertThat(schema).isNotNull(); + } + +} diff --git a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java b/src/test/java/de/kosit/validationtool/impl/xml/SaxonSecurityTest.java similarity index 96% rename from src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java rename to src/test/java/de/kosit/validationtool/impl/xml/SaxonSecurityTest.java index 70ac28c..0a63475 100644 --- a/src/test/java/de/kosit/validationtool/impl/SaxonSecurityTest.java +++ b/src/test/java/de/kosit/validationtool/impl/xml/SaxonSecurityTest.java @@ -17,7 +17,7 @@ * under the License. */ -package de.kosit.validationtool.impl; +package de.kosit.validationtool.impl.xml; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; @@ -35,9 +35,10 @@ import org.junit.Test; import lombok.extern.slf4j.Slf4j; import de.kosit.validationtool.api.InputFactory; +import de.kosit.validationtool.impl.Helper; import de.kosit.validationtool.impl.Helper.Simple; +import de.kosit.validationtool.impl.TestObjectFactory; import de.kosit.validationtool.impl.model.Result; -import de.kosit.validationtool.impl.xml.RelativeUriResolver; import de.kosit.validationtool.model.reportInput.XMLSyntaxError; import net.sf.saxon.s9api.Processor; diff --git a/src/test/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingTest.java b/src/test/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingTest.java new file mode 100644 index 0000000..7be18d8 --- /dev/null +++ b/src/test/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingTest.java @@ -0,0 +1,43 @@ +package de.kosit.validationtool.impl.xml; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.xml.sax.SAXParseException; + +import de.kosit.validationtool.api.ResolvingConfigurationStrategy; +import de.kosit.validationtool.impl.Helper.Resolving; + +/** + * Tests {@link StrictLocalResolvingStrategy} + * + * @author Andreas Penski + */ +public class StrictLocalResolvingTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testRemoteSchemaResolving() throws Exception { + this.expectedException.expect(SAXParseException.class); + this.expectedException.expectMessage(Matchers.containsString("schema_reference")); + final ResolvingConfigurationStrategy s = new StrictLocalResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + schemaFactory.newSchema(Resolving.SCHEMA_WITH_REMOTE_REFERENCE.toURL()); + } + + @Test + public void testLocalSchemaResolving() throws Exception { + final ResolvingConfigurationStrategy s = new StrictLocalResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + final Schema schema = schemaFactory.newSchema(Resolving.SCHEMA_WITH_REFERENCE.toURL()); + assertThat(schema).isNotNull(); + } +} diff --git a/src/test/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingTest.java b/src/test/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingTest.java new file mode 100644 index 0000000..c6a1fff --- /dev/null +++ b/src/test/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingTest.java @@ -0,0 +1,45 @@ +package de.kosit.validationtool.impl.xml; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.hamcrest.Matchers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.xml.sax.SAXParseException; + +import de.kosit.validationtool.api.ResolvingConfigurationStrategy; +import de.kosit.validationtool.impl.Helper.Resolving; + +/** + * Tests {@link StrictRelativeResolvingStrategy}. + * + * @author Andreas Penski + */ +public class StrictRelativeResolvingTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void testRemoteSchemaResolving() throws Exception { + this.expectedException.expect(SAXParseException.class); + this.expectedException.expectMessage(Matchers.containsString("schema_reference")); + final ResolvingConfigurationStrategy s = new StrictLocalResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + schemaFactory.newSchema(Resolving.SCHEMA_WITH_REMOTE_REFERENCE.toURL()); + } + + @Test + public void testLocalSchemaResolving() throws Exception { + final ResolvingConfigurationStrategy s = new StrictLocalResolvingStrategy(); + final SchemaFactory schemaFactory = s.createSchemaFactory(); + final Schema schema = schemaFactory.newSchema(Resolving.SCHEMA_WITH_REFERENCE.toURL()); + assertThat(schema).isNotNull(); + } + + // TODO loading schema from location outside of the repository - this is still possible yet +} diff --git a/src/test/resources/examples/resolving/main.xsd b/src/test/resources/examples/resolving/main.xsd new file mode 100644 index 0000000..16234da --- /dev/null +++ b/src/test/resources/examples/resolving/main.xsd @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/examples/resolving/resources/reference.xsd b/src/test/resources/examples/resolving/resources/reference.xsd new file mode 100644 index 0000000..6f92671 --- /dev/null +++ b/src/test/resources/examples/resolving/resources/reference.xsd @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/test/resources/examples/resolving/withRemote.xsd b/src/test/resources/examples/resolving/withRemote.xsd new file mode 100644 index 0000000..89c51b9 --- /dev/null +++ b/src/test/resources/examples/resolving/withRemote.xsd @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file From 649ea6c0c4f61105fdb53b6876e0951c1979f50e Mon Sep 17 00:00:00 2001 From: "Andreas Penski (init)" Date: Wed, 6 May 2020 10:49:12 +0200 Subject: [PATCH 5/5] documentation --- README.md | 15 ++- docs/api.md | 93 +++++++++++++++++++ .../kosit/validationtool/config/Builder.java | 10 +- .../config/ConfigurationBuilder.java | 58 +++++++++++- .../validationtool/impl/ResolvingMode.java | 3 +- 5 files changed, 174 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 483ba8b..ee08010 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ The general way using the CLI is: java -jar validationtool--standalone.jar -s [OPTIONS] [FILE] [FILE] [FILE] ... ``` -You can more CLI options by +You can see more CLI options with ```shell java -jar validationtool--standalone.jar --help @@ -68,7 +68,18 @@ A concrete example with a specific validator configuration can be found on [GitH ### Application User Interface (API / embedded usage) -The validator can also be used in own Java Applications via the API. Details can be [found here](./docs/api.md). +The validator can also be used in own Java Applications via the API. Usage would be something like this: +``` +Path scenarios = Paths.get("scenarios.xml"); +Configuration config = Configuration.load(scenarios.toUri()); +Input document = InputFactory.read(testDocument); + +Check validator = new DefaultCheck(config); +Result validationResult = validator.checkInput(document); + +// examine the result here +``` +Details and further configuration options can be [found here](./docs/api.md). ### Daemon-Mode diff --git a/docs/api.md b/docs/api.md index e00f0ea..c59bc23 100644 --- a/docs/api.md +++ b/docs/api.md @@ -121,3 +121,96 @@ recommendation. This allows to have control over what validation result is to be considered _acceptable_ for your own application context. E.g. you can overrule schematron validation errors with _acceptMatch_ configuration and consider certain errors as _acceptable_. Nevertheless you can *not* overrule schema errors with accept match. + +## Building scenario configurations with the Builder API +Despite using preconfigured [scenario files](configurations.md) it is also possible to create a validator configuration using +a builder API. A valid configuration consists of the following: + +* at least one valid scenario configuration, this includes + * a valid match configuration to identify/activate this scenario + * a valid XML schema configuration + * a valid report transformation configuration + * valid schematron validation configurations (optional) + * a valid accept match configuration to compute acceptance information (optional) +* valid a fallback scenario configuration + +A simple configuration looks like this: + +```java +import static de.kosit.validationtool.config.ConfigurationBuilder.*; +import de.kosit.validationtool.api.Configuration; +import java.net.URI; +import java.nio.file.Path; + +public class MyValidator { + + public static void main(String[] args) { + Configuration config = Configuration.create().name("myconfiguration") + .with(scenario("firstScenario") + .match("//myNode") + .validate(schema("Sample Schema").schemaLocation(URI.create("simple.xsd"))) + .validate(schematron("my rules").source("myRules.xsl")) + .with(report("my report").source("report.xsl"))) + .with(fallback().name("default-report").source("fallback.xsl")) + .useRepository(Paths.get("/opt/myrepository")) + .build(); + Check validator = new DefaultCheck(config); + // .. run your checks + } +} +``` + +There a various methods provided by the builder API to configure your scenarios and the validation process. + +It is also possible to provide runtime artifacts like `XsltExecutable`, `XPathExecutalbe` or `Schema` to configure the validator. +This gives you complete control how to load these artifacts. +--- +**Note:** Creating this objects requires usage of the same instance of the saxon `Processor` as used during validation later. So you +need to supply a custom `ResolvingConfigurationStrategy` or use the internal one to create these objects. See below. + +--- +## Configure xml security an resolving + +When using xml related technologies you are supposed to handle certein security issues properly. The KoSIT validator pursues a rather strict +strategy. The default configuration + +* disable DTD validation completely +* allows loading/resolving only from a configured local content repository (a specific folder) +* tries to prevent known XML security issues (see [OWASP XML_Security_Cheat_Sheet.html](https://cheatsheetseries.owasp.org/cheatsheets/XML_Security_Cheat_Sheet.html)) +* only works with OpenJDK based XML stacks + +However, you can configure certain aspects related to resolving and security yourself. The validator uses a single interface for accessing +or creating the neccessary XML API objects like `SchemaFactory`, `Validator`,`URIResolver` or `Processor`: [ResolvingConfigurationStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/api/ResolvingConfigurationStrategy.java) + +There are 3 implemenations available out of the box: + +1. [StrictRelativeResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/StrictRelativeResolvingStrategy.java) +which is the **default**, prevents known XML attacks and only allows loading from a specific local repository location +1. [StrictLocalResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/StrictLocalResolvingStrategy.java) +which opens the first to load resource from local location +1. [RemoteResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/RemoteResolvingStrategy.java) +which opens the first to load resource also from remote locations via http and https + +You can configure usage of one of this implemenations via + +````java +Conifuguration config = Configuration.load(URI.create("myscenarios.xml")) + .resolvingMode(ResolvingMode.STRICT_LOCAL) + .build(); +```` + +If you decide to implement your own strategy you can configure this via: + +````java +Conifuguration config = Configuration.load(URI.create("myscenarios.xml")) + .resolvingStrategy(new MyCustomResolvingConfigurationStrategy()) + .build(); +```` + +--- +**Attention:** If you decide to implement a custom strategy you need to handle xml security risk. Please make sure, that you prevent XXE attacks and +other kind of attacks. Consider using [BaseResolvingStrategy.java](https://github.com/itplr-kosit/validator/tree/master/src/main/java/de/kosit/validationtool/impl/xml/BaseResolvingStrategy.java) +and the protected methods within to disable certain features. + +--- + \ No newline at end of file diff --git a/src/main/java/de/kosit/validationtool/config/Builder.java b/src/main/java/de/kosit/validationtool/config/Builder.java index bdee7fd..6182e30 100644 --- a/src/main/java/de/kosit/validationtool/config/Builder.java +++ b/src/main/java/de/kosit/validationtool/config/Builder.java @@ -4,9 +4,17 @@ import de.kosit.validationtool.impl.ContentRepository; import de.kosit.validationtool.impl.model.Result; /** + * Internal interface for creating object builders. + * * @author Andreas Penski */ -public interface Builder { +interface Builder { + /** + * Creates an object based on artifacts provided via a defined {@link ContentRepository}. + * + * @param repository the {@link ContentRepository} + * @return the result of building the object + */ Result build(ContentRepository repository); } diff --git a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java index fab99b4..ce199b3 100644 --- a/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java +++ b/src/main/java/de/kosit/validationtool/config/ConfigurationBuilder.java @@ -3,6 +3,7 @@ package de.kosit.validationtool.config; import static de.kosit.validationtool.impl.DateFactory.createTimestamp; import java.net.URI; +import java.nio.file.Path; import java.time.LocalDate; import java.util.ArrayList; import java.util.Date; @@ -75,6 +76,12 @@ public class ConfigurationBuilder { return this; } + /** + * Add a specific nam to this configuration + * + * @param name the name of the configuration + * @return this + */ public ConfigurationBuilder name(final String name) { this.name = name; return this; @@ -103,11 +110,25 @@ public class ConfigurationBuilder { return date(date != null ? LocalDate.ofEpochDay(date.getTime()) : null); } + /** + * Adds a {@link Scenario} to this list of know scenarios. Note: order of calling this methods defines order of + * scenarios when determining the target scenario for a given xml file. + * + * @param scenarioBuilder the {@link ScenarioBuilder} building the {@link Scenario} + * @return this + */ public ConfigurationBuilder with(final ScenarioBuilder scenarioBuilder) { this.scenarios.add(scenarioBuilder); return this; } + /** + * Sets a specific fallback scenario configuration. Note: calling this more than once is possible, but the last call + * will define the actual fallback scenario used. There can be only one + * + * @param builder the {@link FallbackBuilder} + * @return this + */ public ConfigurationBuilder with(final FallbackBuilder builder) { if (this.fallbackBuilder != null) { log.warn("Overriding previously created fallback scenario"); @@ -116,6 +137,12 @@ public class ConfigurationBuilder { return this; } + /** + * Adds a description to this configuration. + * + * @param description the descriptioin + * @return this + */ public ConfigurationBuilder description(final String description) { this.description = description; return this; @@ -219,6 +246,12 @@ public class ConfigurationBuilder { return new ReportBuilder().name(name); } + /** + * Builds the actual {@link Configuration} by validating all builder inputs and constructing neccessary objects. + * + * @return a valid configuration + * @throws IllegalStateException when the configuration is not valid/complete + */ public Configuration build() { final ResolvingConfigurationStrategy resolving = getResolvingConfigurationStrategy(); if (this.processor == null) { @@ -269,7 +302,7 @@ public class ConfigurationBuilder { } private List initializeScenarios(final ContentRepository contentRepository) { - if (this.scenarios.size() == 0) { + if (this.scenarios.isEmpty()) { throw new IllegalStateException("No scenario specified"); } return this.scenarios.stream().map(s -> { @@ -291,6 +324,13 @@ public class ConfigurationBuilder { return this.resolvingMode.getStrategy(); } + /** + * Sets a specific resolving mode, for resolving xml artifacts for this configuration. See {@link ResolvingMode} for + * details. + * + * @param mode the mode + * @return this + */ public ConfigurationBuilder resolvingMode(final ResolvingMode mode) { this.resolvingMode = mode; return this; @@ -307,8 +347,24 @@ public class ConfigurationBuilder { return this; } + /** + * Set a specific repository location for resolving artifacts for scenarios. + * + * @param repository the repository location + * @return this + */ public ConfigurationBuilder useRepository(final URI repository) { this.repository = repository; return this; } + + /** + * Set a specific repository location for resolving artifacts for scenarios. + * + * @param repository the repository location + * @return this + */ + public ConfigurationBuilder useRepository(final Path repository) { + return useRepository(repository.toUri()); + } } diff --git a/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java index 885674b..1ca8517 100644 --- a/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java +++ b/src/main/java/de/kosit/validationtool/impl/ResolvingMode.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import de.kosit.validationtool.api.ResolvingConfigurationStrategy; +import de.kosit.validationtool.impl.xml.RemoteResolvingStrategy; import de.kosit.validationtool.impl.xml.StrictLocalResolvingStrategy; import de.kosit.validationtool.impl.xml.StrictRelativeResolvingStrategy; @@ -24,7 +25,7 @@ public enum ResolvingMode { STRICT_LOCAL(new StrictLocalResolvingStrategy()), - JDK_SUPPORTED(null), + ALLOW_REMOTE(new RemoteResolvingStrategy()), CUSTOM(null);