[java-metadata-aggregator] 02/02: MDA-56 - add stage to add entity attribute values

Ian Young ian at iay.org.uk
Wed Jun 28 11:55:04 EDT 2017


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

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

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

commit 32540df0c35c4cffd43a8cabc4981ffdfb413d22
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Wed Jun 28 16:54:51 2017 +0100

    MDA-56 - add stage to add entity attribute values
---
 .../metadata/dom/saml/AttributeElementMaker.java   |  63 +++++
 .../metadata/dom/saml/AttributeElementMatcher.java |  63 +++++
 .../dom/saml/AttributeValueElementMaker.java       |  56 +++++
 .../dom/saml/AttributeValueElementMatcher.java     |  57 +++++
 .../shibboleth/metadata/dom/saml/SAMLSupport.java  |  15 ++
 .../saml/mdattr/EntityAttributeAddingStage.java    | 264 +++++++++++++++++++++
 .../metadata/dom/saml/mdattr/MDAttrSupport.java    |  17 ++
 .../dom/saml/AttributeElementMakerTest.java        |  49 ++++
 .../dom/saml/AttributeElementMatcherTest.java      |  56 +++++
 .../dom/saml/AttributeValueElementMakerTest.java   |  35 +++
 .../dom/saml/AttributeValueElementMatcherTest.java |  50 ++++
 .../mdattr/EntityAttributeAddingStageTest.java     | 165 +++++++++++++
 .../mdattr/EntityAttributeAddingStage-added1.xml   |  53 +++++
 .../mdattr/EntityAttributeAddingStage-added2.xml   |  65 +++++
 .../mdattr/EntityAttributeAddingStage-added3.xml   |  65 +++++
 .../EntityAttributeAddingStage-extensions.xml      |  52 ++++
 .../EntityAttributeAddingStage-noExtensions.xml    |  45 ++++
 .../mdattr/EntityAttributeAddingStage-some.xml     |  60 +++++
 18 files changed, 1230 insertions(+)

diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java
new file mode 100644
index 0000000..0371db3
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMaker.java
@@ -0,0 +1,63 @@
+/*
+ * 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.saml;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.dom.Container;
+import net.shibboleth.metadata.dom.ElementMaker;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A class for constructing SAML <code>Attribute</code> elements
+ * for use with the {@link Container} system.
+ */
+ at ThreadSafe
+public class AttributeElementMaker extends ElementMaker {
+
+    /** Value for the <code>Name</code> XML attribute. */
+    @Nonnull
+    private final String attributeName;
+
+    /** Value for the <code>NameFormat</code> XML attribute. */
+    @Nonnull
+    private final String attributeNameFormat;
+
+    /**
+     * Constructor.
+     * 
+     * @param name value for the <code>Name</code> XML attribute
+     * @param nameFormat value for the <code>NameFormat</code> XML attribute
+     */
+    public AttributeElementMaker(@Nonnull final String name, @Nonnull final String nameFormat) {
+        super(SAMLSupport.ATTRIBUTE_NAME);
+        attributeName = Constraint.isNotNull(name, "attribute name must not be null");
+        attributeNameFormat = Constraint.isNotNull(nameFormat, "attribute name format must not be null");
+    }
+
+    @Override
+    public Element apply(@Nonnull final Container container) {
+        final Element newElement = super.apply(container);
+        newElement.setAttributeNS(null, "Name", attributeName);
+        newElement.setAttributeNS(null, "NameFormat", attributeNameFormat);
+        return newElement;
+    }
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java
new file mode 100644
index 0000000..4a7345f
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcher.java
@@ -0,0 +1,63 @@
+/*
+ * 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.saml;
+
+import javax.annotation.Nonnull;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.dom.ElementMatcher;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Match {@link com.google.common.base.Predicate} for SAML <code>Attribute</code> elements with specific
+ * <code>Name</code> and <code>NameFormat</code> attributes,
+ * for use with the {@link net.shibboleth.metadata.dom.Container} system.
+ */
+public class AttributeElementMatcher extends ElementMatcher {
+
+    /** <code>NameFormat</code> attribute value to match. */
+    @Nonnull private final String matchFormat;
+
+    /** <code>Name</code> attribute value to match. */
+    @Nonnull private final String matchName;
+
+    /**
+     * Constructor.
+     * 
+     * @param name <code>Name</code> attribute value to match
+     * @param format <code>NameFormat</code> attribute value to match
+     */
+    public AttributeElementMatcher(@Nonnull final String name, @Nonnull final String format) {
+        super(SAMLSupport.ATTRIBUTE_NAME);
+        matchName = Constraint.isNotNull(name, "attribute name must not be null");
+        matchFormat = Constraint.isNotNull(format, "attribute name format must not be null");
+    }
+
+    @Override
+    public boolean apply(@Nonnull final Element element) {
+        // check for element name
+        if (!super.apply(element)) {
+            return false;
+        }
+
+        // now check attributes
+        return matchFormat.equals(SAMLSupport.extractAttributeNameFormat(element)) &&
+                matchName.equals(element.getAttribute("Name"));
+    }
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java
new file mode 100644
index 0000000..9439f26
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMaker.java
@@ -0,0 +1,56 @@
+/*
+ * 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.saml;
+
+import javax.annotation.Nonnull;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.dom.Container;
+import net.shibboleth.metadata.dom.ElementMaker;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * A class for constructing SAML <code>AttributeValue</code> elements
+ * for use with the {@link Container} system.
+ */
+ at ThreadSafe
+public class AttributeValueElementMaker extends ElementMaker {
+
+    /** Value for the attribute. */
+    @Nonnull
+    private final String attributeValue;
+
+    /**
+     * Constructor.
+     * 
+     * @param value value for the attribute
+     */
+    public AttributeValueElementMaker(@Nonnull final String value) {
+        super(SAMLSupport.ATTRIBUTE_VALUE_NAME);
+        attributeValue = Constraint.isNotNull(value, "attribute value must not be null");
+    }
+
+    @Override
+    public Element apply(@Nonnull final Container container) {
+        final Element newElement = super.apply(container);
+        newElement.setTextContent(attributeValue);
+        return newElement;
+    }
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java
new file mode 100644
index 0000000..386cd84
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcher.java
@@ -0,0 +1,57 @@
+/*
+ * 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.saml;
+
+import javax.annotation.Nonnull;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.dom.ElementMatcher;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Match {@link com.google.common.base.Predicate} for SAML <code>AttributeValue</code> elements with specific
+ * text values,
+ * for use with the {@link net.shibboleth.metadata.dom.Container} system.
+ */
+public class AttributeValueElementMatcher extends ElementMatcher {
+
+    /** <code>Attribute</code> value to match. */
+    @Nonnull private final String matchValue;
+
+    /**
+     * Constructor.
+     * 
+     * @param value <code>Attribute</code> value to match
+     */
+    public AttributeValueElementMatcher(@Nonnull final String value) {
+        super(SAMLSupport.ATTRIBUTE_VALUE_NAME);
+        matchValue = Constraint.isNotNull(value, "attribute value must not be null");
+    }
+
+    @Override
+    public boolean apply(@Nonnull final Element element) {
+        // check for element name
+        if (!super.apply(element)) {
+            return false;
+        }
+
+        // now check attribute value
+        return matchValue.equals(element.getTextContent());
+    }
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
index 7a5a7ae..fe09aa0 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLSupport.java
@@ -24,6 +24,13 @@ import javax.xml.namespace.QName;
 import org.w3c.dom.Attr;
 import org.w3c.dom.Element;
 
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import net.shibboleth.metadata.dom.Container;
+import net.shibboleth.metadata.dom.ElementMaker;
+import net.shibboleth.metadata.dom.ElementMatcher;
+
 /** Helper class for dealing with SAML documents. */
 @ThreadSafe
 public final class SAMLSupport {
@@ -43,6 +50,14 @@ public final class SAMLSupport {
     /** saml:AttributeValue element. */
     public static final QName ATTRIBUTE_VALUE_NAME = new QName(SAML_NS, "AttributeValue", SAML_PREFIX);
 
+    /** Matcher for the <code>Extensions</code> element, for use with the {@link Container} system. */
+    public static final Predicate<Element> EXTENSIONS_MATCHER =
+            new ElementMatcher(SAMLMetadataSupport.EXTENSIONS_NAME);
+
+    /** Maker for the <code>Extensions</code> element, for use with the {@link Container} system. */
+    public static final Function<Container, Element> EXTENSIONS_MAKER =
+            new ElementMaker(SAMLMetadataSupport.EXTENSIONS_NAME);
+
     /** Constructor. */
     private SAMLSupport() {
     }
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage.java
new file mode 100644
index 0000000..5812383
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage.java
@@ -0,0 +1,264 @@
+/*
+ * 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.saml.mdattr;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.Container;
+import net.shibboleth.metadata.dom.saml.AttributeElementMaker;
+import net.shibboleth.metadata.dom.saml.AttributeElementMatcher;
+import net.shibboleth.metadata.dom.saml.AttributeValueElementMaker;
+import net.shibboleth.metadata.dom.saml.AttributeValueElementMatcher;
+import net.shibboleth.metadata.dom.saml.SAMLMetadataSupport;
+import net.shibboleth.metadata.dom.saml.SAMLSupport;
+import net.shibboleth.metadata.pipeline.BaseStage;
+import net.shibboleth.metadata.pipeline.StageProcessingException;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+/**
+ * A stage which adds entity attribute values to entity definitions.
+ */
+public class EntityAttributeAddingStage extends BaseStage<Element> {
+
+    /** Class logger. */
+    private final Logger log = LoggerFactory.getLogger(EntityAttributeAddingStage.class);
+
+    /**
+     * The <code>Name</code> of the attribute to be added.
+     * 
+     * The default value is for an entity attribute specifying an entity category.
+     */
+    @Nonnull
+    private String attributeName = EntityCategorySupport.EC_CATEGORY_ATTR_NAME;
+
+    /**
+     * The <code>NameFormat</code> of the attribute to be added.
+     * 
+     * The default value is suitable for most entity attributes.
+     */
+    @Nonnull
+    private String attributeNameFormat = EntityCategorySupport.EC_ATTR_NAME_FORMAT;
+
+    /** The value of the attribute to be added. */
+    @Nonnull
+    private String attributeValue;
+
+    /**
+     * Whether we add <code>mdattr:EntityAttributes</code> as the first child
+     * of <code>md:Extensions</code> if not already present.
+     * 
+     * Default: <code>false</code> (add as last child).
+     */
+    private boolean addingFirstChild;
+
+    /** {@link Predicate} used to match existing Attribute elements. */
+    @NonnullAfterInit
+    private Predicate<Element> attributeMatcher;
+
+    /** {@link Function} used to create new Attribute elements. */
+    @NonnullAfterInit
+    private Function<Container, Element> attributeMaker;
+
+    /** {@link Predicate} used to match existing AttributeValue elements. */
+    @NonnullAfterInit
+    private Predicate<Element> attributeValueMatcher;
+
+    /** {@link Function} used to create new AttributeValue elements. */
+    @NonnullAfterInit
+    private Function<Container, Element> attributeValueMaker;
+
+    /**
+     * Returns the attribute name.
+     * 
+     * @return the attributeName
+     */
+    @Nonnull
+    public String getAttributeName() {
+        return attributeName;
+    }
+
+    /**
+     * Sets the attribute name.
+     * 
+     * @param name the attributeName to set
+     */
+    public void setAttributeName(@Nonnull final String name) {
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        attributeName = Constraint.isNotNull(name, "attributeName must not be null");
+    }
+
+    /**
+     * Gets the attribute name format.
+     * 
+     * @return the attributeNameFormat
+     */
+    @Nonnull
+    public String getAttributeNameFormat() {
+        return attributeNameFormat;
+    }
+
+    /**
+     * Sets the attribute name format.
+     * 
+     * @param nameFormat the attributeNameFormat to set
+     */
+    public void setAttributeNameFormat(@Nonnull final String nameFormat) {
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        attributeNameFormat = Constraint.isNotNull(nameFormat, "attributeNameFormat must not be null");
+    }
+
+    /**
+     * Gets the attribute value.
+     * 
+     * @return the attributeValue
+     */
+    @Nonnull
+    public String getAttributeValue() {
+        return attributeValue;
+    }
+
+    /**
+     * Sets the attribute value.
+     * 
+     * @param value the attributeValue to set
+     */
+    public void setAttributeValue(@Nonnull final String value) {
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        attributeValue = Constraint.isNotNull(value, "attributeValue must not be null");
+    }
+
+    /**
+     * Get whether we are adding <code>mdattr:EntityAttributes</code> as the first child
+     * of <code>md:Extensions</code> if not already present.
+     * 
+     * @return <code>true</code> if adding as the first child, <code>false</code> if the last
+     */
+    public boolean isAddingFirstChild() {
+        return addingFirstChild;
+    }
+
+    /**
+     * Sets whether to add <code>mdattr:EntityAttributes</code> as the first child
+     * of <code>md:Extensions</code> if not already present.
+     * 
+     * @param addFirst <code>true</code> to add as the first child, <code>false</code> as the last
+     */
+    public void setAddingFirstChild(final boolean addFirst) {
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        addingFirstChild = addFirst;
+    }
+
+    /**
+     * Looks for the attribute value we want to add within the contents of a list of
+     * Attribute container elements.
+     * 
+     * @param attributes {@link List} of Attribute {@link Container}s
+     * @return true iff the value appears somewhere in the list of containers
+     */
+    private boolean attributeValuePresent(@Nonnull final List<Container> attributes) {
+        for (final Container attribute : attributes) {
+            if (attribute.findChild(attributeValueMatcher) != null) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void doExecute(@Nonnull final Collection<Item<Element>> itemCollection) throws StageProcessingException {
+        for (final Item<Element> item : itemCollection) {
+            final Element entity = item.unwrap();
+            if (SAMLMetadataSupport.isEntityDescriptor(entity)) {
+                // Start from the entity
+                final Container entityContainer = new Container(entity);
+
+                // Dig down to <Extensions>
+                final Container extensionsContainer =
+                        entityContainer.locateChild(SAMLSupport.EXTENSIONS_MATCHER,
+                                SAMLSupport.EXTENSIONS_MAKER, Container.FIRST_CHILD);
+
+                // Dig down to <EntityAttributes>
+                final Container attributesContainer =
+                        extensionsContainer.locateChild(MDAttrSupport.ENTITY_ATTRIBUTES_MATCHER,
+                                MDAttrSupport.ENTITY_ATTRIBUTES_MAKER,
+                                addingFirstChild ? Container.FIRST_CHILD : Container.LAST_CHILD);
+
+                // Collect all matching <Attribute> containers
+                final List<Container> attributes =
+                        attributesContainer.findChildren(attributeMatcher);
+
+                // If any of the existing attribute values match our value, we're done
+                if (attributeValuePresent(attributes)) {
+                    log.debug("attribute value '{}' already present", attributeValue);
+                    continue;
+                }
+
+                // If not already present, re-locate an <Attribute> and add it in there.
+                final Container attribute =
+                        attributesContainer.locateChild(attributeMatcher, attributeMaker, Container.LAST_CHILD);
+                attribute.addChild(attributeValueMaker, Container.LAST_CHILD);
+            }
+        }
+    }
+
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (attributeValue == null) {
+            throw new ComponentInitializationException("attributeValue property must be supplied");
+        }
+
+        attributeMatcher = new AttributeElementMatcher(attributeName, attributeNameFormat);
+        attributeMaker = new AttributeElementMaker(attributeName, attributeNameFormat);
+        attributeValueMatcher = new AttributeValueElementMatcher(attributeValue);
+        attributeValueMaker = new AttributeValueElementMaker(attributeValue);
+    }
+
+    @Override
+    protected void doDestroy() {
+        attributeMatcher = null;
+        attributeMaker = null;
+        attributeValueMatcher = null;
+        attributeValueMaker = null;
+        super.doDestroy();
+    }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/MDAttrSupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/MDAttrSupport.java
index 97c38b5..93aec6d 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/MDAttrSupport.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdattr/MDAttrSupport.java
@@ -20,6 +20,15 @@ package net.shibboleth.metadata.dom.saml.mdattr;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.xml.namespace.QName;
 
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+
+import net.shibboleth.metadata.dom.Container;
+import net.shibboleth.metadata.dom.ElementMaker;
+import net.shibboleth.metadata.dom.ElementMatcher;
+
 /** Helper class for dealing with MDAttr metadata. */
 @ThreadSafe
 public final class MDAttrSupport {
@@ -33,6 +42,14 @@ public final class MDAttrSupport {
     /** mdattr:EntityAttributes element. */
     public static final QName ENTITY_ATTRIBUTES_NAME = new QName(MDATTR_NS, "EntityAttributes", MDATTR_PREFIX);
 
+    /** Matcher for the <code>EntityAttributes</code> element, for use with the {@link Container} system. */
+    public static final Predicate<Element> ENTITY_ATTRIBUTES_MATCHER =
+            new ElementMatcher(ENTITY_ATTRIBUTES_NAME);
+
+    /** Maker for the <code>EntityAttributes</code> element, for use with the {@link Container} system. */
+    public static final Function<Container, Element> ENTITY_ATTRIBUTES_MAKER =
+            new ElementMaker(ENTITY_ATTRIBUTES_NAME);
+
     /** Constructor. */
     private MDAttrSupport() {
     }
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMakerTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMakerTest.java
new file mode 100644
index 0000000..0de81a0
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMakerTest.java
@@ -0,0 +1,49 @@
+
+package net.shibboleth.metadata.dom.saml;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+import com.google.common.base.Function;
+
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.metadata.dom.Container;
+
+public class AttributeElementMakerTest extends BaseDOMTest {
+
+    private final Document doc;
+
+    protected AttributeElementMakerTest() throws Exception {
+        super(AttributeElementMaker.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @Test
+    public void apply() {
+        final Function<Container, Element> maker = new AttributeElementMaker("name", "nameFormat");
+        final Element root = doc.createElementNS("ns", "root");
+        final Container rootContainer = new Container(root);
+        final Element newElement = maker.apply(rootContainer);
+        Assert.assertNotNull(newElement);
+        Assert.assertEquals(newElement.getLocalName(), "Attribute");
+        Assert.assertEquals(newElement.getNamespaceURI(), SAMLSupport.SAML_NS);
+        Assert.assertEquals(newElement.getAttribute("Name"), "name");
+        Assert.assertEquals(newElement.getAttribute("NameFormat"), "nameFormat");
+
+        // Check that the attributes have been created in a namespace-aware way
+        // to avoid problems with schema checkers.
+        final NamedNodeMap attrs = newElement.getAttributes();
+        for (int i = 0; i<attrs.getLength(); i++) {
+            final Node node = attrs.item(i);
+            Assert.assertNotNull(node.getLocalName());
+        }
+
+        // check that the prefix is declared properly
+        Assert.assertEquals(newElement.lookupNamespaceURI(SAMLSupport.SAML_PREFIX), SAMLSupport.SAML_NS);
+    }
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcherTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcherTest.java
new file mode 100644
index 0000000..55af283
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeElementMatcherTest.java
@@ -0,0 +1,56 @@
+
+package net.shibboleth.metadata.dom.saml;
+
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Predicate;
+
+public class AttributeElementMatcherTest extends BaseDOMTest {
+
+    private final Document doc;
+    private Element attr;
+
+    protected AttributeElementMatcherTest() throws Exception {
+        super(AttributeElementMatcher.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @BeforeTest
+    private void beforeTest() throws Exception {
+        attr = ElementSupport.constructElement(doc, SAMLSupport.ATTRIBUTE_NAME);
+    }
+
+    @Test
+    public void matchNormal() throws Exception {
+        final Predicate<Element> matcher1 = new AttributeElementMatcher("name", "name-format");
+        attr.setAttribute("Name", "name");
+        attr.setAttribute("NameFormat", "name-format");
+        Assert.assertTrue(matcher1.apply(attr));
+
+        final Predicate<Element> matcher2 = new AttributeElementMatcher("name2", "name-format");
+        Assert.assertFalse(matcher2.apply(attr));
+
+        final Predicate<Element> matcher3 = new AttributeElementMatcher("name", "name-format2");
+        Assert.assertFalse(matcher3.apply(attr));
+    }
+
+    @Test
+    public void matchDefaultFormat() throws Exception {
+        attr.setAttribute("Name", "name");
+
+        final Predicate<Element> matcher1 = new AttributeElementMatcher("name", SAMLSupport.ATTRNAME_FORMAT_UNSPECIFIED);
+        Assert.assertTrue(matcher1.apply(attr));
+
+        final Predicate<Element> matcher2 = new AttributeElementMatcher("name2", SAMLSupport.ATTRNAME_FORMAT_UNSPECIFIED);
+        Assert.assertFalse(matcher2.apply(attr));
+    }
+
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMakerTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMakerTest.java
new file mode 100644
index 0000000..b9f3ad0
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMakerTest.java
@@ -0,0 +1,35 @@
+
+package net.shibboleth.metadata.dom.saml;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Function;
+
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.metadata.dom.Container;
+
+public class AttributeValueElementMakerTest extends BaseDOMTest {
+
+    private final Document doc;
+
+    protected AttributeValueElementMakerTest() throws Exception {
+        super(AttributeValueElementMaker.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @Test
+    public void apply() {
+        final Function<Container, Element> maker = new AttributeValueElementMaker("value text");
+        final Element root = doc.createElementNS("ns", "root");
+        final Container rootContainer = new Container(root);
+        final Element newElement = maker.apply(rootContainer);
+        Assert.assertNotNull(newElement);
+        Assert.assertEquals(newElement.getLocalName(), "AttributeValue");
+        Assert.assertEquals(newElement.getNamespaceURI(), SAMLSupport.SAML_NS);
+        Assert.assertEquals(newElement.getTextContent(), "value text");
+    }
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcherTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcherTest.java
new file mode 100644
index 0000000..9dfd16c
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/AttributeValueElementMatcherTest.java
@@ -0,0 +1,50 @@
+
+package net.shibboleth.metadata.dom.saml;
+
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+import org.testng.Assert;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import com.google.common.base.Predicate;
+
+public class AttributeValueElementMatcherTest extends BaseDOMTest {
+
+    private final Document doc;
+    private Element value;
+
+    protected AttributeValueElementMatcherTest() throws Exception {
+        super(AttributeValueElementMatcher.class);
+        setUp();
+        doc = getParserPool().newDocument();
+    }
+
+    @BeforeTest
+    private void beforeTest() throws Exception {
+        value = ElementSupport.constructElement(doc, SAMLSupport.ATTRIBUTE_VALUE_NAME);
+    }
+
+    @Test
+    public void apply() throws Exception {
+        final Predicate<Element> matcher1 = new AttributeValueElementMatcher("value");
+
+        value.setTextContent("value");
+        Assert.assertTrue(matcher1.apply(value));
+
+        value.setTextContent("other");
+        Assert.assertFalse(matcher1.apply(value));
+
+        final Predicate<Element> matcher2 = new AttributeValueElementMatcher("other");
+
+        value.setTextContent("value");
+        Assert.assertFalse(matcher2.apply(value));
+
+        value.setTextContent("other");
+        Assert.assertTrue(matcher2.apply(value));
+    }
+
+}
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStageTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStageTest.java
new file mode 100644
index 0000000..03fced0
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStageTest.java
@@ -0,0 +1,165 @@
+
+package net.shibboleth.metadata.dom.saml.mdattr;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.metadata.dom.DOMElementItem;
+import net.shibboleth.metadata.pipeline.Pipeline;
+import net.shibboleth.metadata.pipeline.SimplePipeline;
+import net.shibboleth.metadata.pipeline.Stage;
+
+import org.testng.annotations.Test;
+import org.w3c.dom.Element;
+
+public class EntityAttributeAddingStageTest extends BaseDOMTest {
+
+    protected EntityAttributeAddingStageTest() throws Exception {
+        super(EntityAttributeAddingStage.class);
+        setUp();
+    }
+
+    private List<Item<Element>> makeItems(final String inputFile) throws Exception {
+        final Element startElement = readXMLData(inputFile);
+        final List<Item<Element>> items = new ArrayList<>();
+        items.add(new DOMElementItem(startElement));
+        return items;
+    }
+
+    private Stage<Element> makeStage(final String value) throws Exception {
+        final EntityAttributeAddingStage stage = new EntityAttributeAddingStage();
+        stage.setId("test");
+        stage.setAttributeValue(value);
+        stage.initialize();
+        return stage;
+    }
+
+    private Stage<Element> makeStage(final String value, final String name) throws Exception {
+        final EntityAttributeAddingStage stage = new EntityAttributeAddingStage();
+        stage.setId("test");
+        stage.setAttributeValue(value);
+        stage.setAttributeName(name);
+        stage.initialize();
+        return stage;
+    }
+
+    private Stage<Element> makeStage(final String value, final String name, final String nameFormat) throws Exception {
+        final EntityAttributeAddingStage stage = new EntityAttributeAddingStage();
+        stage.setId("test");
+        stage.setAttributeValue(value);
+        stage.setAttributeName(name);
+        stage.setAttributeNameFormat(nameFormat);
+        stage.initialize();
+        return stage;
+    }
+
+    private Pipeline<Element> makePipeline(final List<Stage<Element>> stages) throws Exception {
+        final SimplePipeline<Element> pipeline = new SimplePipeline<>();
+        pipeline.setId("test");
+        pipeline.setStages(stages);
+        pipeline.initialize();
+        return pipeline;
+    }
+
+    /*
+     * Checks creation of Extensions if none is there already.
+     */
+    @Test
+    public void addNoExtensions() throws Exception {
+        final List<Item<Element>> itemCollection = makeItems("noExtensions.xml");
+        final List<Stage<Element>> stages = new ArrayList<>();
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1"));
+        stages.add(makeStage("another"));
+        // add the same attributes twice, just to make sure duplicates are ignored
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1"));
+        stages.add(makeStage("another"));
+        final Pipeline<Element> pipeline = makePipeline(stages);
+        pipeline.execute(itemCollection);
+        final Element result = itemCollection.get(0).unwrap();
+        final Element expected = readXMLData("added1.xml");
+        assertXMLEqual(expected, result);
+    }
+
+    /*
+     * Input has Extensions but no EntityAttributes.
+     */
+    @Test
+    public void addToExtensions() throws Exception {
+        final List<Item<Element>> itemCollection = makeItems("extensions.xml");
+        final List<Stage<Element>> stages = new ArrayList<>();
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1"));
+        stages.add(makeStage("http://example.org/category2"));
+        stages.add(makeStage("http://example.org/category2support", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("anotherValue", "anotherAttributeName", "anotherNameFormat"));
+        final Pipeline<Element> pipeline = makePipeline(stages);
+        pipeline.execute(itemCollection);
+        final Element result = itemCollection.get(0).unwrap();
+        final Element expected = readXMLData("added2.xml");
+        assertXMLEqual(expected, result);
+    }
+
+    /*
+     * Input has Extensions but no EntityAttributes; adding at the front.
+     */
+    @Test
+    public void addToExtensionsFirst() throws Exception {
+        final List<Item<Element>> itemCollection = makeItems("extensions.xml");
+        final List<Stage<Element>> stages = new ArrayList<>();
+        final EntityAttributeAddingStage stage = new EntityAttributeAddingStage();
+        stage.setId("test");
+        stage.setAttributeValue("http://www.geant.net/uri/dataprotection-code-of-conduct/v1");
+        stage.setAddingFirstChild(true);
+        stage.initialize();
+        stages.add(stage);
+        stages.add(makeStage("http://example.org/category2"));
+        stages.add(makeStage("http://example.org/category2support", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("anotherValue", "anotherAttributeName", "anotherNameFormat"));
+        final Pipeline<Element> pipeline = makePipeline(stages);
+        pipeline.execute(itemCollection);
+        final Element result = itemCollection.get(0).unwrap();
+        final Element expected = readXMLData("added3.xml");
+        assertXMLEqual(expected, result);
+    }
+
+    /*
+     * Check that adding something has no effect if it's already there.
+     */
+    @Test
+    public void addDuplicates() throws Exception {
+        final List<Item<Element>> itemCollection = makeItems("added2.xml");
+        final List<Stage<Element>> stages = new ArrayList<>();
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1"));
+        stages.add(makeStage("http://example.org/category2"));
+        stages.add(makeStage("http://example.org/category2support", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("anotherValue", "anotherAttributeName", "anotherNameFormat"));
+        final Pipeline<Element> pipeline = makePipeline(stages);
+        pipeline.execute(itemCollection);
+        final Element result = itemCollection.get(0).unwrap();
+        final Element expected = readXMLData("added2.xml");
+        assertXMLEqual(expected, result);
+    }
+
+    /*
+     * Add some missing values to an existing EntityAttributes collection.
+     */
+    @Test
+    public void addToExisting() throws Exception {
+        final List<Item<Element>> itemCollection = makeItems("some.xml");
+        final List<Stage<Element>> stages = new ArrayList<>();
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1"));
+        stages.add(makeStage("http://example.org/category2"));
+        stages.add(makeStage("http://example.org/category2support", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("http://www.geant.net/uri/dataprotection-code-of-conduct/v1", "http://macedir.org/entity-category-support"));
+        stages.add(makeStage("anotherValue", "anotherAttributeName", "anotherNameFormat"));
+        final Pipeline<Element> pipeline = makePipeline(stages);
+        pipeline.execute(itemCollection);
+        final Element result = itemCollection.get(0).unwrap();
+        final Element expected = readXMLData("added2.xml");
+        assertXMLEqual(expected, result);
+    }
+}
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added1.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added1.xml
new file mode 100644
index 0000000..df601f8
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added1.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <md:Extensions xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
+        <mdattr:EntityAttributes>
+            <saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+                <saml:AttributeValue>another</saml:AttributeValue>
+            </saml:Attribute>
+        </mdattr:EntityAttributes>
+    </md:Extensions>
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added2.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added2.xml
new file mode 100644
index 0000000..dab899a
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added2.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <Extensions>
+        <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+            registrationInstant="2014-03-18T15:23:31Z">
+            <mdrpi:RegistrationPolicy xml:lang="en"
+                >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+        </mdrpi:RegistrationInfo>
+        <mdattr:EntityAttributes>
+            <saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+                <saml:AttributeValue>http://example.org/category2</saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="http://macedir.org/entity-category-support" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://example.org/category2support</saml:AttributeValue>
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="anotherAttributeName" NameFormat="anotherNameFormat">
+                <saml:AttributeValue>anotherValue</saml:AttributeValue>
+            </saml:Attribute>
+        </mdattr:EntityAttributes>
+    </Extensions>
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added3.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added3.xml
new file mode 100644
index 0000000..7b1d4a7
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-added3.xml
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <Extensions>
+        <mdattr:EntityAttributes>
+            <saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+                <saml:AttributeValue>http://example.org/category2</saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="http://macedir.org/entity-category-support" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://example.org/category2support</saml:AttributeValue>
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="anotherAttributeName" NameFormat="anotherNameFormat">
+                <saml:AttributeValue>anotherValue</saml:AttributeValue>
+            </saml:Attribute>
+        </mdattr:EntityAttributes>
+        <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+            registrationInstant="2014-03-18T15:23:31Z">
+            <mdrpi:RegistrationPolicy xml:lang="en"
+                >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+        </mdrpi:RegistrationInfo>
+    </Extensions>
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-extensions.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-extensions.xml
new file mode 100644
index 0000000..599ef4d
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-extensions.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <Extensions>
+        <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+            registrationInstant="2014-03-18T15:23:31Z">
+            <mdrpi:RegistrationPolicy xml:lang="en"
+                >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+        </mdrpi:RegistrationInfo>
+    </Extensions>
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-noExtensions.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-noExtensions.xml
new file mode 100644
index 0000000..52bdfac
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-noExtensions.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-some.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-some.xml
new file mode 100644
index 0000000..fdd1f35
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/mdattr/EntityAttributeAddingStage-some.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+    xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+    xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+    entityID="http://example.org/entity">
+    <Extensions>
+        <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+            registrationInstant="2014-03-18T15:23:31Z">
+            <mdrpi:RegistrationPolicy xml:lang="en"
+                >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+        </mdrpi:RegistrationInfo>
+        <mdattr:EntityAttributes>
+            <saml:Attribute Name="http://macedir.org/entity-category" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://www.geant.net/uri/dataprotection-code-of-conduct/v1</saml:AttributeValue>
+            </saml:Attribute>
+            <saml:Attribute Name="http://macedir.org/entity-category-support" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+                <saml:AttributeValue>http://example.org/category2support</saml:AttributeValue>
+            </saml:Attribute>
+        </mdattr:EntityAttributes>
+    </Extensions>
+    <SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
+        <KeyDescriptor>
+            <ds:KeyInfo>
+                <ds:X509Data>
+                    <ds:X509Certificate>
+                        MIIEfzCCA2egAwIBAgIQQSSnV5Mk/EXZxgrsbnU7ajANBgkqhkiG9w0BAQUFADA2
+                        MQswCQYDVQQGEwJOTDEPMA0GA1UEChMGVEVSRU5BMRYwFAYDVQQDEw1URVJFTkEg
+                        U1NMIENBMB4XDTEzMDMwNjAwMDAwMFoXDTE2MDMwNTIzNTk1OVowRzEhMB8GA1UE
+                        CxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMSIwIAYDVQQDExlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
+                        qzeOIPfgr/SneQUtjqDRGLbG4YKxmo2zfAIF+wjaYXnDTq/yWt852nImchvCgjWu
+                        eXnHJ6oZZGdaN4kMgPh1oxqJ4UE6dXY9HEoru/Awp2P/CGiYOQ4Q5u5+AyFznGRx
+                        y2uNYrD85C6uTIgiKytH6Lai8f6dFxHKO/u8o+kbrl2Z1CkAf6wePu2H5a44tGnF
+                        SP/s7lW4ScSthNz1OF8BoPFKpWUmJk/584rZZjR+fqB9fQBwxRoRBCFZTduXP0G9
+                        861BS6Nt6Dfuli9jdCBC0L8ai4fH43a94EQVmMfcjImrkYBg7v6lVS4IY75ONiOt
+                        sObamvbT+yNEuqw0jBjmKwIDAQABo4IBdjCCAXIwHwYDVR0jBBgwFoAUDL2TaAzz
+                        3qujSWsrN1dH6pDjue0wHQYDVR0OBBYEFBQXWZZ8RdkPMtC2o0RF+T+gH7VsMA4G
+                        A1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB0GA1UdJQQWMBQGCCsGAQUFBwMB
+                        BggrBgEFBQcDAjAiBgNVHSAEGzAZMA0GCysGAQQBsjEBAgIdMAgGBmeBDAECATA6
+                        BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3JsLnRjcy50ZXJlbmEub3JnL1RFUkVO
+                        QVNTTENBLmNybDBtBggrBgEFBQcBAQRhMF8wNQYIKwYBBQUHMAKGKWh0dHA6Ly9j
+                        cnQudGNzLnRlcmVuYS5vcmcvVEVSRU5BU1NMQ0EuY3J0MCYGCCsGAQUFBzABhhpo
+                        dHRwOi8vb2NzcC50Y3MudGVyZW5hLm9yZzAkBgNVHREEHTAbghlzcDIwMTMtaGEt
+                        YWRmczIuZ2VhbnQubmV0MA0GCSqGSIb3DQEBBQUAA4IBAQCRJqxXjeInIqlPLH5+
+                        iFqL92oLPKiJDqlDWpzR9P0xF7IS4oBApWZ3f2Rgcx1dtwUf7TakMpAuxIxzMjfP
+                        xaik/AuInMjxKU9AJS/lRlLogT3YXZ2aAGZbt2P/hYn4mdn5ryB9WF/w7mnyrpBb
+                        o7vllsYXYmRX/c0MEPYfSMoKxomRAY2ViIh83m4sYk8+Nkm+3I9t0O3wBqaQYqpc
+                        Mg48AG2JwLeuKUIC+faPOckeMXdrknL0Ra7Vb+eRoEKCqw3L9ka5/rloFNEN+hMZ
+                        zubyfY3feeIV+kq3YNmjCwhDqckv5kXAdnkFprjB3X/xNfML2Jum02hKkff4AYfP
+                        2IOi
+                    </ds:X509Certificate>
+                </ds:X509Data>
+            </ds:KeyInfo>
+        </KeyDescriptor>
+        <AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+            Location="https://example.org/whatever" index="0" isDefault="true"/>
+    </SPSSODescriptor>
+</EntityDescriptor>

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


More information about the commits mailing list