[java-metadata-aggregator] 02/02: MDA-231 - Add stage to validate selected DOM attributes as strings

Ian Young ian at iay.org.uk
Fri Oct 21 16:54:45 UTC 2022


This is an automated email from the git hooks/post-receive script.

iay pushed a commit to branch main
in repository java-metadata-aggregator.

View the commit online:
http://git.shibboleth.net/view/?p=java-metadata-aggregator.git;a=commit;h=bd1cdc6d2ff034fc11b259368a9cdeb7b6d24595

commit bd1cdc6d2ff034fc11b259368a9cdeb7b6d24595
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Fri Oct 21 17:54:40 2022 +0100

    MDA-231 - Add stage to validate selected DOM attributes as strings
    
    https://shibboleth.atlassian.net/browse/MDA-231
---
 .../dom/AbstractAttributeValidationStage.java      | 175 +++++++++++++++++++++
 .../dom/AbstractElementValidationStage.java        |   7 +-
 ...ge.java => StringAttributeValidationStage.java} |  14 +-
 .../metadata/dom/StringElementValidationStage.java |   6 +-
 .../resources/net/shibboleth/metadata/beans.xml    |   3 +
 .../net/shibboleth/metadata/dom/BaseDOMTest.java   |  18 ++-
 .../dom/StringAttributeValidationStageTest.java    | 148 +++++++++++++++++
 .../validate/testing/CollectingValidator.java      |  77 +++++++++
 .../metadata/validate/testing/package-info.java}   |  22 +--
 .../StringAttributeValidationStage-multiple.xml    | 132 ++++++++++++++++
 10 files changed, 569 insertions(+), 33 deletions(-)

diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractAttributeValidationStage.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractAttributeValidationStage.java
new file mode 100644
index 0000000..9a64c27
--- /dev/null
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractAttributeValidationStage.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.metadata.dom;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.GuardedBy;
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.annotation.constraint.Unmodifiable;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Abstract base class allowing a selected subset of XML attributes in a DOM document
+ * to be validated as a given type.
+ *
+ * @param <T> type to convert each attribute to for validation
+ *
+ * @since 0.10.0
+ */
+public abstract class AbstractAttributeValidationStage<T> extends AbstractElementValidationStage<T> {
+
+    /**
+     * Collection of attribute names for those attributes we will be visiting.
+     * 
+     * <p>Internally, this is always held as a {@link Set} of {@link QName}s,
+     * and that's how it will be returned by the getter. There are four setters,
+     * however, for both collections and singletons, and both simple attribute
+     * names and qualified names (@link QName}s).</p>
+     * 
+     * <p>Because of type erasure, those setters can't all be overloads of the
+     * same method name, so the {@link String}-based unqualified name forms
+     * (which will by far dominate use cases) are given the preferred
+     * identifiers.</p>
+     */
+    @SuppressWarnings("null")
+    @NonnullElements @Unmodifiable @GuardedBy("this")
+    private @Nonnull Set<@Nonnull QName> attributeNames = Set.of();
+
+    /**
+     * Gets the collection of attribute names to visit.
+     * 
+     * @return collection of attribute names to visit
+     */
+    public final synchronized @Nonnull Collection<@Nonnull QName> getAttributeNames() {
+        return attributeNames;
+    }
+
+    /**
+     * Sets the collection of attribute names to visit, as a collection of unqualified
+     * {@link String} values.
+     *
+     * @param names collection of attribute names to visit
+     */
+    public final synchronized void setAttributeNames(@Nonnull final Collection<@Nonnull String> names) {
+        checkSetterPreconditions();
+        Constraint.isNotNull(names, "attributeNames may not be null");
+        final var qnames = new HashSet<QName>();
+        for (final var name : names) {
+            qnames.add(new QName(name));
+        }
+        attributeNames = Set.copyOf(qnames);
+    }
+    
+    /**
+     * Sets a single attribute name to be visited.
+     * 
+     * <p>Shorthand for use when a single unqualified attribute name, expressed as a
+     * {@link String}, is used.</p>
+     * 
+     * @param name name for the attribute to be visited
+     */
+    public final synchronized void setAttributeName(@Nonnull final String name) {
+        checkSetterPreconditions();
+        Constraint.isNotNull(name, "unqualifiedAttributeName may not be null");
+        attributeNames = Set.of(new QName(name));
+    }
+    
+    /**
+     * Sets the collection of attribute names to visit.
+     * 
+     * @param names collection of qualified attribute names to visit
+     */
+    public final synchronized void setQualifiedAttributeNames(@Nonnull final Collection<@Nonnull QName> names) {
+        checkSetterPreconditions();
+        Constraint.isNotNull(names, "attributeNames may not be null");
+        attributeNames = Set.copyOf(names);
+    }
+    
+    /**
+     * Sets a single attribute name to be visited.
+     * 
+     * @param name {@link QName} for the attribute to be visited
+     */
+    public final synchronized void setQualifiedAttributeName(@Nonnull final QName name) {
+        checkSetterPreconditions();
+        Constraint.isNotNull(name, "attributeName may not be null");
+        attributeNames = Set.of(name);
+    }
+    
+    @Override
+    protected @Nonnull T convert(final @Nonnull Element element) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected void visit(@Nonnull final Element element, @Nonnull final DOMTraversalContext context)
+            throws StageProcessingException {
+        
+        // Look at the attributes. If there are none, we're done.
+        final var attributes = element.getAttributes();
+        if (attributes == null) return;
+
+        // Iterate through the attributes on this element.
+        for (int i = 0; i < attributes.getLength(); i++) {
+            final var attr = (Attr)attributes.item(i);
+            if (attr != null && applicable(attr, context)) {
+                applyValidators(convert(attr), context);
+            }
+        }
+    }
+    
+    /**
+     * Convert the visited {@link Attr} to the type to be validated.
+     *
+     * @param element {@link Attr} being validated
+     * @return converted value
+     */
+    protected abstract @Nonnull T convert(final @Nonnull Attr attr);
+
+    /**
+     * Returns whether the given attribute is applicable to our traversal.
+     *
+     * @param attr {@link Attr} candidate for visiting
+     * @param context 
+     * @return
+     */
+    protected boolean applicable(@Nonnull final Attr attr, @Nonnull final DOMTraversalContext context) {
+        final var attrName = new QName(attr.getNamespaceURI(), attr.getLocalName());
+        return attributeNames.contains(attrName);
+    }
+
+    @Override
+    protected synchronized void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (attributeNames.isEmpty()) {
+            throw new ComponentInitializationException("attributeNames may not be empty");
+        }
+    }
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractElementValidationStage.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractElementValidationStage.java
index fa77de6..1a731c4 100644
--- a/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractElementValidationStage.java
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/AbstractElementValidationStage.java
@@ -39,6 +39,8 @@ import net.shibboleth.utilities.java.support.logic.Constraint;
  * to be validated as a given type.
  *
  * @param <T> type to convert each {@link Element} to for validation
+ *
+ * @since 0.10.0
  */
 @ThreadSafe
 public abstract class AbstractElementValidationStage<T> extends AbstractDOMValidationStage<T, DOMTraversalContext> {
@@ -70,7 +72,7 @@ public abstract class AbstractElementValidationStage<T> extends AbstractDOMValid
     /**
      * Sets a single element name to be visited.
      * 
-     * Shorthand for {@link #setElementNames} with a singleton set.
+     * <p>Shorthand for {@link #setElementNames} with a singleton set.</p>
      * 
      * @param name {@link QName} for the element to be visited.
      */
@@ -92,8 +94,7 @@ public abstract class AbstractElementValidationStage<T> extends AbstractDOMValid
      * @param element {@link Element} being validated
      * @return converted value
      */
-    @Nonnull
-    protected abstract T convert(@Nonnull final Element element);
+    protected abstract @Nonnull T convert(@Nonnull final Element element);
 
     @Override
     protected void visit(@Nonnull final Element element, @Nonnull final DOMTraversalContext context)
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringAttributeValidationStage.java
similarity index 74%
copy from mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
copy to mda-framework/src/main/java/net/shibboleth/metadata/dom/StringAttributeValidationStage.java
index a4593be..42e9f00 100644
--- a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringAttributeValidationStage.java
@@ -20,20 +20,22 @@ package net.shibboleth.metadata.dom;
 import javax.annotation.Nonnull;
 import javax.annotation.concurrent.ThreadSafe;
 
-import org.w3c.dom.Element;
+import org.w3c.dom.Attr;
 
 import net.shibboleth.metadata.pipeline.Stage;
 
 /**
- * A {@link Stage} allowing validation of DOM {@link Element}s treated as {@link String}s.
+ * A {@link Stage} allowing validation of DOM {@link Attr}s treated as {@link String}s.
+ *
+ * @since 0.10.0
  */
 @ThreadSafe
-public class StringElementValidationStage extends AbstractElementValidationStage<String> {
+public class StringAttributeValidationStage extends AbstractAttributeValidationStage<String> {
 
+    @SuppressWarnings("null")
     @Override
-    @Nonnull
-    protected String convert(@Nonnull final Element element) {
-        return element.getTextContent();
+    protected @Nonnull String convert(@Nonnull final Attr attr) {
+        return attr.getTextContent();
     }
 
 }
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java b/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
index a4593be..3af2ed9 100644
--- a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
+++ b/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
@@ -26,13 +26,15 @@ import net.shibboleth.metadata.pipeline.Stage;
 
 /**
  * A {@link Stage} allowing validation of DOM {@link Element}s treated as {@link String}s.
+ *
+ * @since 0.10.0
  */
 @ThreadSafe
 public class StringElementValidationStage extends AbstractElementValidationStage<String> {
 
+    @SuppressWarnings("null")
     @Override
-    @Nonnull
-    protected String convert(@Nonnull final Element element) {
+    protected @Nonnull String convert(@Nonnull final Element element) {
         return element.getTextContent();
     }
 
diff --git a/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml b/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
index 67c5ad1..d0bfb14 100644
--- a/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
+++ b/mda-framework/src/main/resources/net/shibboleth/metadata/beans.xml
@@ -79,6 +79,9 @@
     <bean id="mda.NamespaceStrippingStage" abstract="true" parent="mda.stage_parent"
         class="net.shibboleth.metadata.dom.NamespaceStrippingStage"/>
 
+    <bean id="mda.StringAttributeValidationStage" abstract="true" parent="mda.stage_parent"
+        class="net.shibboleth.metadata.dom.StringAttributeValidationStage"/>
+
     <bean id="mda.StringElementValidationStage" abstract="true" parent="mda.stage_parent"
         class="net.shibboleth.metadata.dom.StringElementValidationStage"/>
 
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
index edfa449..ec90f72 100644
--- a/mda-framework/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/dom/BaseDOMTest.java
@@ -18,6 +18,7 @@
 package net.shibboleth.metadata.dom;
 
 import java.io.InputStream;
+import java.io.StringReader;
 import java.util.List;
 
 import javax.annotation.Nonnull;
@@ -129,15 +130,28 @@ public abstract class BaseDOMTest extends BaseTest {
      * 
      * @param path classpath path to the data file, never null
      * 
-     * @return an {@link Item} wrapping the document representing the data file, never null
+     * @return an {@link Item} wrapping the document representing the data file, never <code>null</code>
      * 
      * @throws XMLParserException if the file does not exist or there is a problem parsing it
      */
-    public Item<Element> readDOMItem(final String path) throws XMLParserException {
+    public @Nonnull Item<Element> readDOMItem(final String path) throws XMLParserException {
         final Element e = readXMLData(path);
         return new DOMElementItem(e);
     }
 
+    /**
+     * Create a DOM {@link Item} from a {@link String}.
+     *
+     * @param text text to turn into a DOM item
+     * @return DOM item corresponding to the provided text
+     * @throws XMLParserException 
+     */
+    protected @Nonnull Item<Element> parseDOMItem(final @Nonnull String text) throws XMLParserException {
+        try (var reader = new StringReader(text)) {
+            return new DOMElementItem(getParserPool().parse(reader));
+        }
+    }
+
     /**
      * Checks whether two nodes are identical.
      *
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/dom/StringAttributeValidationStageTest.java b/mda-framework/src/test/java/net/shibboleth/metadata/dom/StringAttributeValidationStageTest.java
new file mode 100644
index 0000000..5676e12
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/dom/StringAttributeValidationStageTest.java
@@ -0,0 +1,148 @@
+
+package net.shibboleth.metadata.dom;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+
+import javax.xml.namespace.QName;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.ErrorStatus;
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.saml.SAMLMetadataSupport;
+import net.shibboleth.metadata.validate.RejectAllValidator;
+import net.shibboleth.metadata.validate.Validator;
+import net.shibboleth.metadata.validate.testing.CollectingValidator;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.xml.XMLConstants;
+
+public class StringAttributeValidationStageTest extends BaseDOMTest {
+
+    protected StringAttributeValidationStageTest() {
+        super(StringAttributeValidationStage.class);
+    }
+
+    @Test
+    public void testLifecycle() throws Exception {
+        var stage = new StringAttributeValidationStage();
+        stage.setId("test");
+        stage.setElementName(new QName("test"));
+        stage.setAttributeName("test");
+        stage.initialize();
+        stage.destroy();
+    }
+
+    @Test(expectedExceptions = ComponentInitializationException.class)
+    public void noAttributeNames() throws Exception {
+        var stage = new StringAttributeValidationStage();
+        stage.setId("test");
+        stage.setElementName(new QName("test"));
+        stage.initialize();
+        stage.destroy();
+    }
+    
+    /**
+     * Simple test to see if we can reach a singleton attribute
+     * on an unqualified element.
+     *
+     * @throws Exception if something goes wrong
+     */
+    @Test
+    public void testSimpleXY() throws Exception {
+        var item = parseDOMItem("<a><x y='a'/></a>");
+        var items = new ArrayList<Item<Element>>();
+        items.add(item);
+        
+        var reject = new RejectAllValidator<String>();
+        reject.setId("reject");
+        reject.initialize();
+        var collect = CollectingValidator.<String>getInstance("collect");
+        var validators = new ArrayList<Validator<String>>();
+        validators.add(collect);
+        validators.add(reject);
+
+        var stage = new StringAttributeValidationStage();
+        stage.setId("test");
+        stage.setElementName(new QName("x"));
+        stage.setAttributeName("y");
+        stage.setValidators(validators);
+        stage.initialize();
+        stage.execute(items);
+
+        stage.destroy();
+        reject.destroy();
+        
+        var values = collect.getValues();
+        collect.destroy();
+        Assert.assertEquals(values.size(), 1);
+        Assert.assertEquals(values.get(0), "a");
+
+        var errors = item.getItemMetadata().get(ErrorStatus.class);
+        Assert.assertEquals(errors.size(), 1);
+        var error = errors.get(0);
+        Assert.assertEquals(error.getStatusMessage(), "value rejected: 'a'");
+    }
+    
+    /**
+     * Comprehensive test with multiple applicable elements and attributes,
+     * both with namespaces and without.
+     *
+     * @throws Exception if something goes wrong
+     */
+    @Test
+    public void testMultiple() throws Exception {
+        var item = readDOMItem("multiple.xml");
+        var items = new ArrayList<Item<Element>>();
+        items.add(item);
+
+        /*
+         * We are going to pick off a few attributes:
+         *
+         * - The entityID from each of three EntityDescriptor elements (3)
+         * - The index from each of 12 AssertionConsumerService elements (0..5, twice)
+         * - The xml:lang from each of three OrganizationName elements (3)
+         *
+         * Total of: 18.
+         * 
+         * This will happen by intersecting the element names with the
+         * attribute names.
+         */
+        var elements = new HashSet<QName>();
+        elements.add(SAMLMetadataSupport.ENTITY_DESCRIPTOR_NAME);
+        elements.add(new QName(SAMLMetadataSupport.MD_NS, "AssertionConsumerService"));
+        elements.add(SAMLMetadataSupport.ORGANIZATIONNAME_NAME);
+        var attributes = new HashSet<QName>();
+        attributes.add(new QName("entityID"));
+        attributes.add(new QName("index"));
+        attributes.add(XMLConstants.XML_LANG_ATTRIB_NAME);
+
+        var collect = CollectingValidator.<String>getInstance("collect");
+        var validators = new ArrayList<Validator<String>>();
+        validators.add(collect);
+
+        var stage = new StringAttributeValidationStage();
+        stage.setId("test");
+        stage.setElementNames(elements);
+        stage.setQualifiedAttributeNames(attributes);
+        stage.setValidators(validators);
+        stage.initialize();
+        stage.execute(items);
+
+        stage.destroy();
+
+        var values = collect.getValues();
+        collect.destroy();
+
+        System.out.println(values);
+        Assert.assertEquals(values.size(), 18);
+        // Count how many "en"s there are
+        Assert.assertEquals(values.stream().filter(x -> x.equals("en")).count(), 3);
+        // Count how many "shibboleth.net"s there are
+        Assert.assertEquals(values.stream().filter(x -> x.contains("shibboleth.net")).count(), 3);
+        // Count how many indexes (matching single digits) there are
+        Assert.assertEquals(values.stream().filter(x -> x.matches("\\d")).count(), 12);
+    }
+}
diff --git a/mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/CollectingValidator.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/CollectingValidator.java
new file mode 100644
index 0000000..b799aca
--- /dev/null
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/CollectingValidator.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.metadata.validate.testing;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.metadata.validate.BaseValidator;
+import net.shibboleth.metadata.validate.Validator;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/**
+ * A {@link Validator} implementation which collects the values passed to it for
+ * validation.
+ * 
+ * <p>This can be used in tests to record the nodes which are visited, and the
+ * order in which this happens.</p>
+ *
+ * @param <T> type of the values to be validated
+ */
+public class CollectingValidator<T> extends BaseValidator implements Validator<T> {
+
+    /** Values this validator has seen, in order. */
+    private final @Nonnull List<@Nonnull T> values = new ArrayList<>();
+    
+    /**
+     * Return the values recorded by this validator.
+     * 
+     * @return the values recorded by this validator
+     */
+    public @Nonnull List<@Nonnull T> getValues() {
+        return values;
+    }
+
+    /**
+     * Convenience method to return an instance of this validator.
+     *
+     * @param id identifier for this instance
+     * @param <TT> type validated by the instance to create
+     * @return new validator instance
+     * @throws ComponentInitializationException if something goes wrong in initialisation
+     */
+    public static @Nonnull <TT> CollectingValidator<TT> getInstance(final @Nonnull String id)
+            throws ComponentInitializationException {
+        final var instance = new CollectingValidator<TT>();
+        instance.setId(id);
+        instance.initialize();
+        return instance;
+    }
+
+    @Override
+    public @Nonnull Action validate(@Nonnull T e, @Nonnull Item<?> item, @Nonnull String stageId)
+            throws StageProcessingException {
+        values.add(e);
+        return Action.CONTINUE;
+    }
+    
+}
diff --git a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java b/mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/package-info.java
similarity index 61%
copy from mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
copy to mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/package-info.java
index a4593be..9c7b22b 100644
--- a/mda-framework/src/main/java/net/shibboleth/metadata/dom/StringElementValidationStage.java
+++ b/mda-framework/src/test/java/net/shibboleth/metadata/validate/testing/package-info.java
@@ -15,25 +15,7 @@
  * limitations under the License.
  */
 
-package net.shibboleth.metadata.dom;
-
-import javax.annotation.Nonnull;
-import javax.annotation.concurrent.ThreadSafe;
-
-import org.w3c.dom.Element;
-
-import net.shibboleth.metadata.pipeline.Stage;
-
 /**
- * A {@link Stage} allowing validation of DOM {@link Element}s treated as {@link String}s.
+ * Test utility classes for the validation framework.
  */
- at ThreadSafe
-public class StringElementValidationStage extends AbstractElementValidationStage<String> {
-
-    @Override
-    @Nonnull
-    protected String convert(@Nonnull final Element element) {
-        return element.getTextContent();
-    }
-
-}
+package net.shibboleth.metadata.validate.testing;
diff --git a/mda-framework/src/test/resources/net/shibboleth/metadata/dom/StringAttributeValidationStage-multiple.xml b/mda-framework/src/test/resources/net/shibboleth/metadata/dom/StringAttributeValidationStage-multiple.xml
new file mode 100644
index 0000000..0f34a2a
--- /dev/null
+++ b/mda-framework/src/test/resources/net/shibboleth/metadata/dom/StringAttributeValidationStage-multiple.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntitiesDescriptor Name="urn:example.org:test" cacheDuration="PT4H" validUntil="2050-01-01T00:00:00Z"
+    xmlns="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+
+    <EntityDescriptor entityID="https://idp.shibboleth.net/idp/shibboleth" cacheDuration="PT3H" validUntil="2049-01-01T00:00:00Z"> 
+    
+        <IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+            
+            <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+            
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp.shibboleth.net/idp/profile/SAML2/POST/SSO"/>
+            
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://idp.shibboleth.net/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+            
+            <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.shibboleth.net/idp/profile/SAML2/Redirect/SSO"/>
+
+        </IDPSSODescriptor>
+        
+        <Organization>
+            <OrganizationName xml:lang="en">Shibboleth.net</OrganizationName>
+            <OrganizationDisplayName xml:lang="en">Shibboleth.net</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en">http://www.shibboleth.net</OrganizationURL>
+        </Organization>
+        
+        <ContactPerson contactType="support">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>mailto:contact at shibboleth.net</EmailAddress><!--good-->
+        </ContactPerson>
+        
+        <ContactPerson contactType="bogus">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>contact at shibboleth.net</EmailAddress><!-- bad -->
+        </ContactPerson>
+        
+    </EntityDescriptor>
+    
+    <EntityDescriptor entityID="https://issues.shibboleth.net/shibboleth" cacheDuration="PT2H" validUntil="2048-01-01T00:00:00Z">
+        
+        <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:1.0:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+                        
+            <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/Artifact/SOAP" index="0"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SLO/Artifact"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SLO/POST"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SLO/Redirect"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SLO/SOAP"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML/Artifact" index="0"/>
+
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML/POST" index="1"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML2/Artifact" index="2"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML2/ECP" index="3"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML2/POST" index="4"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://issues.shibboleth.net/jira/Shibboleth.sso/SAML2/POST-SimpleSign" index="5"/>
+            
+            <AttributeConsumingService index="1">
+                <ServiceName xml:lang="en">Shibboleth Federated Issue Tracking</ServiceName>
+                <ServiceDescription xml:lang="en"> An issue (bugs, feature requests, tasks) tracking 
+                    service with automatic registration for users who can supply a supported identifier, 
+                    such as eduPersonPrincipalName or swissEduPersonUniqueID. </ServiceDescription>
+
+                <RequestedAttribute FriendlyName="eduPersonPrincipalName" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true"/>
+                <RequestedAttribute FriendlyName="mail" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
+                <RequestedAttribute FriendlyName="displayName" Name="urn:oid:2.16.840.1.113730.3.1.241" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
+            </AttributeConsumingService>
+        </SPSSODescriptor>
+        
+        <Organization>
+            <OrganizationName xml:lang="en">Shibboleth Consortium</OrganizationName>
+            <OrganizationDisplayName xml:lang="en">Shibboleth Consortium</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en">http://www.shibboleth.net/</OrganizationURL>
+        </Organization>
+        
+        <ContactPerson contactType="technical">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>contact at shibboleth.net</EmailAddress><!-- bad -->
+        </ContactPerson>
+        <ContactPerson contactType="support">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>mailto:contact at shibboleth.net</EmailAddress><!-- good -->
+        </ContactPerson>
+        <ContactPerson contactType="administrative">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>mailto:contact at shibboleth.net</EmailAddress><!-- good -->
+        </ContactPerson>
+        <ContactPerson contactType="billing">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>mailto:contact at shibboleth.net</EmailAddress><!-- good -->
+        </ContactPerson>
+        <ContactPerson contactType="other">
+            <GivenName>Shibboleth.Net Technical Support</GivenName>
+            <EmailAddress>mailto:contact at shibboleth.net</EmailAddress><!-- good -->
+        </ContactPerson>
+        
+    </EntityDescriptor>
+
+    <EntityDescriptor entityID="https://wiki.shibboleth.net/shibboleth" cacheDuration="PT1H" validUntil="2047-01-01T00:00:00Z">
+        
+        <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:1.0:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+            
+            <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/Artifact/SOAP" index="0"/>
+
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SLO/Artifact"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SLO/POST"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SLO/Redirect"/>
+            <SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SLO/SOAP"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:artifact-01" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML/Artifact" index="0"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:1.0:profiles:browser-post" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML/POST" index="1"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML2/Artifact" index="2"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:PAOS" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML2/ECP" index="3"/>
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML2/POST" index="4"/>
+
+            <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign" Location="https://wiki.shibboleth.net/confluence/Shibboleth.sso/SAML2/POST-SimpleSign" index="5"/>
+            
+            <AttributeConsumingService index="1">
+                <ServiceName xml:lang="en">Shibboleth Federated Wiki</ServiceName>
+                <ServiceDescription xml:lang="en"> A shared Wiki service with automatic registration
+                    for users who can supply a supported identifier, such as eduPersonPrincipalName
+                    or swissEduPersonUniqueID. </ServiceDescription>
+                <RequestedAttribute FriendlyName="eduPersonPrincipalName" Name="urn:oid:1.3.6.1.4.1.5923.1.1.1.6" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" isRequired="true"/>
+                <RequestedAttribute FriendlyName="mail" Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
+                <RequestedAttribute FriendlyName="displayName" Name="urn:oid:2.16.840.1.113730.3.1.241" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri"/>
+
+            </AttributeConsumingService>
+        </SPSSODescriptor>
+        
+        <Organization>
+            <OrganizationName xml:lang="en">Shibboleth Consortium</OrganizationName>
+            <OrganizationDisplayName xml:lang="en">Shibboleth Consortium</OrganizationDisplayName>
+            <OrganizationURL xml:lang="en">http://www.shibboleth.net/</OrganizationURL>
+        </Organization>
+    </EntityDescriptor>
+    
+</EntitiesDescriptor>

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list