[java-metadata-aggregator] branch master updated: MDA-173 - Add DiscoFeedCollectionSerializer
Ian Young
ian at iay.org.uk
Tue Apr 14 12:45:29 EDT 2020
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=1834258f90e3f840d707d194f7323340519358f6
The following commit(s) were added to refs/heads/master by this push:
new 1834258 MDA-173 - Add DiscoFeedCollectionSerializer
1834258 is described below
commit 1834258f90e3f840d707d194f7323340519358f6
Author: Ian Young <ian at iay.org.uk>
AuthorDate: Tue Apr 14 17:45:21 2020 +0100
MDA-173 - Add DiscoFeedCollectionSerializer
https://issues.shibboleth.net/jira/browse/MDA-173
---
aggregator-pipeline/pom.xml | 10 +
.../dom/saml/DiscoFeedCollectionSerializer.java | 328 +++++++++++++++++++++
.../metadata/dom/saml/SAMLMetadataSupport.java | 12 +
.../metadata/dom/saml/mdui/MDUISupport.java | 55 ++++
.../metadata/dom/saml/mdui/package-info.java | 21 ++
.../resources/net/shibboleth/metadata/beans.xml | 3 +
.../saml/DiscoFeedCollectionSerializerTest.java | 248 ++++++++++++++++
.../saml/DiscoFeedCollectionSerializer-base.json | 34 +++
.../saml/DiscoFeedCollectionSerializer-base.xml | 121 ++++++++
.../DiscoFeedCollectionSerializer-entattr.json | 55 ++++
.../saml/DiscoFeedCollectionSerializer-entattr.xml | 129 ++++++++
.../DiscoFeedCollectionSerializer-infourl.json | 34 +++
.../saml/DiscoFeedCollectionSerializer-infourl.xml | 121 ++++++++
.../DiscoFeedCollectionSerializer-keywords.json | 34 +++
.../DiscoFeedCollectionSerializer-keywords.xml | 121 ++++++++
.../saml/DiscoFeedCollectionSerializer-legacy.json | 18 ++
.../saml/DiscoFeedCollectionSerializer-legacy.xml | 117 ++++++++
.../DiscoFeedCollectionSerializer-logolang.json | 35 +++
.../DiscoFeedCollectionSerializer-logolang.xml | 121 ++++++++
.../DiscoFeedCollectionSerializer-nolegacy.json | 12 +
.../DiscoFeedCollectionSerializer-nolegacy.xml | 117 ++++++++
.../DiscoFeedCollectionSerializer-privacyurl.json | 34 +++
.../DiscoFeedCollectionSerializer-privacyurl.xml | 121 ++++++++
23 files changed, 1901 insertions(+)
diff --git a/aggregator-pipeline/pom.xml b/aggregator-pipeline/pom.xml
index d565139..a3da99a 100644
--- a/aggregator-pipeline/pom.xml
+++ b/aggregator-pipeline/pom.xml
@@ -52,8 +52,18 @@
</dependency>
<!-- Provided Dependencies -->
+ <dependency>
+ <groupId>jakarta.json</groupId>
+ <artifactId>jakarta.json-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<!-- Runtime Dependencies -->
+ <dependency>
+ <groupId>org.glassfish</groupId>
+ <artifactId>jakarta.json</artifactId>
+ <scope>runtime</scope>
+ </dependency>
<!-- Test Dependencies -->
<dependency>
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java
new file mode 100644
index 0000000..1332275
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer.java
@@ -0,0 +1,328 @@
+/*
+ * 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 java.io.IOException;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.json.Json;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonGeneratorFactory;
+import javax.xml.namespace.QName;
+
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.ItemCollectionSerializer;
+import net.shibboleth.metadata.dom.saml.mdattr.MDAttrSupport;
+import net.shibboleth.metadata.dom.saml.mdui.MDUISupport;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements;
+import net.shibboleth.utilities.java.support.component.AbstractInitializableComponent;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.xml.ElementSupport;
+
+/**
+ * A collection serializer that generates an equivalent of the Shibboleth SP's discovery
+ * feed output from a collection of entity descriptors.
+ */
+public class DiscoFeedCollectionSerializer extends AbstractInitializableComponent
+ implements ItemCollectionSerializer<Element> {
+
+ /** Configured JSON generator factory. */
+ @NonnullAfterInit
+ private JsonGeneratorFactory factory;
+
+ /** Whether to pretty-print the resulting JSON. Default: <code>false</code> */
+ private boolean prettyPrinting;
+
+ /**
+ * Whether to include legacy display names if none are found in the
+ * <code>mdui:UIInfo</code> element.
+ *
+ * Default: <code>false</code>
+ */
+ private boolean includingLegacyDisplayNames;
+
+ /** Whether to include entity attributes. Default: <code>false</code> */
+ private boolean includingEntityAttributes;
+
+ /**
+ * Returns whether output is being pretty-printed.
+ *
+ * @return whether output is being pretty-printed
+ */
+ public boolean isPrettyPrinting() {
+ return prettyPrinting;
+ }
+
+ /**
+ * Sets whether to pretty-print the output.
+ *
+ * @param pretty whether to pretty-print the output
+ */
+ public void setPrettyPrinting(final boolean pretty) {
+ prettyPrinting = pretty;
+ }
+
+ /**
+ * Returns whether output includes legacy display names.
+ *
+ * @return whether output includes legacy display names
+ */
+ public boolean isIncludingLegacyDisplayNames() {
+ return includingLegacyDisplayNames;
+ }
+
+ /**
+ * Sets whether to include legacy display names.
+ *
+ * @param includeLegacyDisplayNames whether to include legacy display names
+ */
+ public void setIncludingLegacyDisplayNames(final boolean includeLegacyDisplayNames) {
+ includingLegacyDisplayNames = includeLegacyDisplayNames;
+ }
+
+ /**
+ * Returns whether output includes entity attributes.
+ *
+ * @return whether output includes entity attributes
+ */
+ public boolean isIncludingEntityAttributes() {
+ return includingEntityAttributes;
+ }
+
+ /**
+ * Sets whether to include entity attributes.
+ *
+ * @param includeEntityAttributes whether to include entity attributes.
+ */
+ public void setIncludingEntityAttributes(final boolean includeEntityAttributes) {
+ includingEntityAttributes = includeEntityAttributes;
+ }
+
+ /**
+ * Find the first <code>mdui:UIInfo</code> {@link Element} across a list of
+ * <code>md:IDPSSODescriptor</code>s.
+ *
+ * @param idpDescriptors list of <code>md:IDPSSODescriptor</code>s
+ *
+ * @return the first <code>mdui:UIInfo</code> element, or <code>null</code> if none are found
+ */
+ @Nullable
+ private Element findFirstUIInfo(@Nonnull @NonnullElements final List<Element> idpDescriptors) {
+ for (final var idpDescriptor : idpDescriptors) {
+ final var uiInfo = SAMLMetadataSupport.getDescriptorExtension(idpDescriptor, MDUISupport.UIINFO_NAME);
+ if (uiInfo != null) {
+ return uiInfo;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Write a list of element values and language tags to the output
+ * as a JSON array under a given key.
+ *
+ * @param gen the {@link JsonGenerator} to write the output to
+ * @param elements list of {@link Element}s to write out
+ * @param key key for the JSON array within the containing object
+ */
+ private void writeValueLangList(@Nonnull final JsonGenerator gen,
+ @Nonnull @NonnullElements final List<Element> elements,
+ @Nonnull final String key) {
+ gen.writeStartArray(key);
+ for (final var element : elements) {
+ gen.writeStartObject();
+ gen.write("value", element.getTextContent());
+ gen.write("lang", element.getAttribute("xml:lang"));
+ gen.writeEnd();
+ }
+ gen.writeEnd();
+ }
+
+ /**
+ * Write a value/language list for elements within a <code>mdui:UIInfo</code> container.
+ *
+ * @param gen the {@link JsonGenerator} to write the output to
+ * @param uiInfo the <code>mdui:UIInfo</code> element
+ * @param elementName name of the elements to collect from
+ * @param key key for the JSON array within the containing object
+ */
+ private void writeValueLangList(@Nonnull final JsonGenerator gen,
+ @Nonnull final Element uiInfo,
+ @Nonnull final QName elementName,
+ @Nonnull final String key) {
+ final var elements = ElementSupport.getChildElements(uiInfo, elementName);
+ if (!elements.isEmpty()) {
+ writeValueLangList(gen, elements, key);
+ }
+ }
+
+ /**
+ * Write out an entity's entity attributes.
+ *
+ * @param gen the {@link JsonGenerator} to write the output to
+ * @param entity the <code>md:EntityDescriptor</code> element
+ */
+ private void writeEntityAttributes(@Nonnull final JsonGenerator gen, @Nonnull final Element entity) {
+ final var ext = SAMLMetadataSupport.getDescriptorExtension(entity, MDAttrSupport.ENTITY_ATTRIBUTES_NAME);
+ if (ext != null) {
+ final var attributes = ElementSupport.getChildElements(ext, SAMLSupport.ATTRIBUTE_NAME);
+ if (!attributes.isEmpty()) {
+ gen.writeStartArray("EntityAttributes");
+ for (final var attribute : attributes) {
+ final var values = ElementSupport.getChildElements(attribute, SAMLSupport.ATTRIBUTE_VALUE_NAME);
+ if (!values.isEmpty()) {
+ gen.writeStartObject();
+ gen.write("name", attribute.getAttribute("Name"));
+ gen.writeStartArray("values");
+ for (final var value : values) {
+ gen.write(value.getTextContent());
+ }
+ gen.writeEnd();
+ gen.writeEnd();
+ }
+ }
+ gen.writeEnd();
+ }
+ }
+ }
+
+ /**
+ * Write the entity's logos to the output.
+ *
+ * @param gen the {@link JsonGenerator} to write the output to
+ * @param uiInfo the <code>mdui:UIInfo</code> element
+ */
+ private void writeLogos(@Nonnull final JsonGenerator gen, @Nonnull final Element uiInfo) {
+ final var logos = ElementSupport.getChildElements(uiInfo, MDUISupport.LOGO_NAME);
+ if (!logos.isEmpty()) {
+ gen.writeStartArray("Logos");
+ for (final var logo: logos) {
+ gen.writeStartObject();
+ gen.write("value", logo.getTextContent());
+ gen.write("height", logo.getAttribute("height"));
+ gen.write("width", logo.getAttribute("width"));
+ // xml:lang is optional on mdui:Logo elements
+ final var lang = logo.getAttributeNode("xml:lang");
+ if (lang != null) {
+ gen.write("lang", lang.getTextContent());
+ }
+ gen.writeEnd();
+ }
+ gen.writeEnd();
+ }
+ }
+
+ /**
+ * Write the list of display names to the output.
+ *
+ * If we have a <code>mdui:UIInfo</code>, we may be able to find some display names
+ * there. If we can find no <code>mdui:DisplayName</code> elements, we instead
+ * find legacy display names in the <code>md:Organization</code> element.
+ *
+ * @param gen the {@link JsonGenerator} to write the output to
+ * @param entity <code>md:EntityDescriptor</code> element
+ * @param uiInfo <code>mdui:UIInfo</code> if available, or <code>null</code>
+ */
+ private void writeDisplayNames(@Nonnull final JsonGenerator gen, @Nonnull final Element entity,
+ @Nullable final Element uiInfo) {
+ // Attempt to find display names in the mdui:UIInfo element
+ if (uiInfo != null) {
+ final var displayNames = ElementSupport.getChildElements(uiInfo, MDUISupport.DISPLAYNAME_NAME);
+ if (!displayNames.isEmpty()) {
+ writeValueLangList(gen, displayNames, "DisplayNames");
+ // We have found our display names
+ return;
+ }
+ }
+
+ // Attempt to find display names elsewhere
+ if (includingLegacyDisplayNames) {
+ final var org = ElementSupport.getFirstChildElement(entity, SAMLMetadataSupport.ORGANIZATION_NAME);
+ if (org != null) {
+ final var displayNames =
+ ElementSupport.getChildElements(org, SAMLMetadataSupport.ORGANIZATIONDISPLAYNAME_NAME);
+ if (!displayNames.isEmpty()) {
+ writeValueLangList(gen, displayNames, "DisplayNames");
+ }
+ }
+ }
+ }
+
+ @Override
+ public void serializeCollection(@Nonnull @NonnullElements final Collection<Item<Element>> items,
+ @Nonnull final OutputStream output) throws IOException {
+ ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+ ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this);
+ final JsonGenerator gen = factory.createGenerator(output);
+ gen.writeStartArray();
+ for (final Item<Element> item : items) {
+ final Element entity = item.unwrap();
+ if (SAMLMetadataSupport.isEntityDescriptor(entity)) {
+ final var idpDescriptors =
+ ElementSupport.getChildElements(entity, SAMLMetadataSupport.IDP_SSO_DESCRIPTOR_NAME);
+ if (!idpDescriptors.isEmpty()) {
+ gen.writeStartObject();
+ gen.write("entityID", entity.getAttributeNS(null, "entityID"));
+ final var uiInfo = findFirstUIInfo(idpDescriptors);
+ writeDisplayNames(gen, entity, uiInfo);
+ if (uiInfo != null) {
+ writeValueLangList(gen, uiInfo, MDUISupport.DESCRIPTION_NAME, "Descriptions");
+ writeValueLangList(gen, uiInfo, MDUISupport.KEYWORDS_NAME, "Keywords");
+ writeValueLangList(gen, uiInfo, MDUISupport.INFORMATIONURL_NAME, "InformationURLs");
+ writeValueLangList(gen, uiInfo,
+ MDUISupport.PRIVACYSTATEMENTURL_NAME, "PrivacyStatementURLs");
+ writeLogos(gen, uiInfo);
+ }
+ if (includingEntityAttributes) {
+ writeEntityAttributes(gen, entity);
+ }
+ gen.writeEnd();
+ }
+ }
+ }
+ gen.writeEnd();
+ gen.close();
+ }
+
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ final Map<String, String> generatorConfig = new HashMap<>();
+ if (prettyPrinting) {
+ generatorConfig.put(JsonGenerator.PRETTY_PRINTING, "true");
+ }
+ factory = Json.createGeneratorFactory(generatorConfig);
+ }
+
+ @Override
+ protected void doDestroy() {
+ factory = null;
+ super.doDestroy();
+ }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
index 6b2556d..3108ca9 100644
--- a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/SAMLMetadataSupport.java
@@ -76,6 +76,18 @@ public final class SAMLMetadataSupport {
/** QName of the PDPDescriptor element. */
public static final QName PDP_DESCRIPTOR_NAME = new QName(MD_NS, "PDPDescriptor");
+ /** QName of the Organization element. */
+ public static final QName ORGANIZATION_NAME = new QName(MD_NS, "Organization", MD_PREFIX);
+
+ /** QName of the OrganizationName element. */
+ public static final QName ORGANIZATIONNAME_NAME = new QName(MD_NS, "OrganizationName", MD_PREFIX);
+
+ /** QName of the OrganizationDisplayName element. */
+ public static final QName ORGANIZATIONDISPLAYNAME_NAME = new QName(MD_NS, "OrganizationDisplayName", MD_PREFIX);
+
+ /** QName of the OrganizationURL element. */
+ public static final QName ORGANIZATIONURL_NAME = new QName(MD_NS, "OrganizationURL", MD_PREFIX);
+
/** Constructor. */
private SAMLMetadataSupport() {
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/MDUISupport.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/MDUISupport.java
new file mode 100644
index 0000000..046542b
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/MDUISupport.java
@@ -0,0 +1,55 @@
+/*
+ * 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.mdui;
+
+import javax.annotation.concurrent.ThreadSafe;
+import javax.xml.namespace.QName;
+
+/** Support class for dealing with MDUI metadata. */
+ at ThreadSafe
+public final class MDUISupport {
+
+ /** MDUI namespace. */
+ public static final String MDUI_NS = "urn:oasis:names:tc:SAML:metadata:ui";
+
+ /** {@link QName} representing an <code>mdui:DisplayName</code>. */
+ public static final QName DISPLAYNAME_NAME = new QName(MDUISupport.MDUI_NS, "DisplayName");
+
+ /** {@link QName} representing an <code>mdui:UIInfo</code>. */
+ public static final QName UIINFO_NAME = new QName(MDUISupport.MDUI_NS, "UIInfo");
+
+ /** {@link QName} representing an <code>mdui:Description</code>. */
+ public static final QName DESCRIPTION_NAME = new QName(MDUISupport.MDUI_NS, "Description");
+
+ /** {@link QName} representing an <code>mdui:Keywords</code>. */
+ public static final QName KEYWORDS_NAME = new QName(MDUISupport.MDUI_NS, "Keywords");
+
+ /** {@link QName} representing an <code>mdui:Logo</code>. */
+ public static final QName LOGO_NAME = new QName(MDUISupport.MDUI_NS, "Logo");
+
+ /** {@link QName} representing an <code>mdui:InformationURL</code>. */
+ public static final QName INFORMATIONURL_NAME = new QName(MDUISupport.MDUI_NS, "InformationURL");
+
+ /** {@link QName} representing an <code>mdui:PrivacyStatementURL</code>. */
+ public static final QName PRIVACYSTATEMENTURL_NAME = new QName(MDUISupport.MDUI_NS, "PrivacyStatementURL");
+
+ /** Constructor. */
+ private MDUISupport() {
+ }
+
+}
diff --git a/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/package-info.java b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/package-info.java
new file mode 100644
index 0000000..6b84d0a
--- /dev/null
+++ b/aggregator-pipeline/src/main/java/net/shibboleth/metadata/dom/saml/mdui/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/**
+ * Aggregator classes for the MDUI specification.
+ */
+package net.shibboleth.metadata.dom.saml.mdui;
diff --git a/aggregator-pipeline/src/main/resources/net/shibboleth/metadata/beans.xml b/aggregator-pipeline/src/main/resources/net/shibboleth/metadata/beans.xml
index 0fd6d15..0a18970 100644
--- a/aggregator-pipeline/src/main/resources/net/shibboleth/metadata/beans.xml
+++ b/aggregator-pipeline/src/main/resources/net/shibboleth/metadata/beans.xml
@@ -117,6 +117,9 @@
<bean id="mda.ContactPersonFilterStage" abstract="true" parent="mda.stage_parent"
class="net.shibboleth.metadata.dom.saml.ContactPersonFilterStage"/>
+ <bean id="mda.DiscoFeedCollectionSerializer" abstract="true" parent="mda.component_parent"
+ class="net.shibboleth.metadata.dom.saml.DiscoFeedCollectionSerializer"/>
+
<bean id="mda.EntitiesDescriptorAssemblerStage" abstract="true" parent="mda.stage_parent"
class="net.shibboleth.metadata.dom.saml.EntitiesDescriptorAssemblerStage"/>
diff --git a/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializerTest.java b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializerTest.java
new file mode 100644
index 0000000..10c3ed7
--- /dev/null
+++ b/aggregator-pipeline/src/test/java/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializerTest.java
@@ -0,0 +1,248 @@
+
+package net.shibboleth.metadata.dom.saml;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.w3c.dom.Element;
+
+import net.shibboleth.metadata.Item;
+import net.shibboleth.metadata.ItemCollectionSerializer;
+import net.shibboleth.metadata.dom.BaseDOMTest;
+import net.shibboleth.utilities.java.support.xml.XMLParserException;
+
+public class DiscoFeedCollectionSerializerTest extends BaseDOMTest {
+
+ protected DiscoFeedCollectionSerializerTest() {
+ super(DiscoFeedCollectionSerializer.class);
+ }
+
+ @Test
+ public void testEmptyCollection() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final String output;
+ try (final var out = new ByteArrayOutputStream()) {
+ ser.serializeCollection(List.of(), out);
+ output = out.toString();
+ }
+ final var read = Json.createReader(new StringReader(output));
+ final var array = read.readArray();
+ Assert.assertEquals(array.size(), 0);
+ }
+
+ private JsonArray fetchJSONArray(@Nonnull final String path) throws IOException {
+ try (var in = BaseDOMTest.class.getResourceAsStream(classRelativeResource(path))) {
+ try (var reader = Json.createReader(in)) {
+ return reader.readArray();
+ }
+ }
+ }
+
+ private void checkEntity(@Nonnull final JsonObject entity) {
+ Assert.assertEquals(entity.getString("entityID"), "https://idp.example.com/idp/shibboleth");
+ }
+
+ private void checkEntityArray(@Nonnull final JsonArray array) {
+ Assert.assertEquals(array.size(), 1);
+ checkEntity((JsonObject)array.get(0));
+ }
+
+ @Test
+ public void testPreCooked() throws Exception {
+ var entity = fetchJSONArray("base.json");
+ checkEntityArray(entity);
+ }
+
+ private void compareCollections(@Nonnull final JsonArray actual, @Nonnull final JsonArray expected) {
+ // The two collections must match as to size
+ if (actual.size() != expected.size()) {
+ Assert.assertEquals(actual.size(), expected.size(), "array sizes differ");
+ }
+ // The two collections must match entity by entity
+ for (int i = 0; i < actual.size(); i++) {
+ final var actualEntity = (JsonObject)actual.get(i);
+ final var expectedEntity = (JsonObject)expected.get(i);
+
+ // Get an identifier for the entity
+ final String id = "entity " + i + " ('" + expectedEntity.getString("entityID") + "')";
+
+ // Compare the key sets
+ final var actualKeys = actualEntity.keySet();
+ final var expectedKeys = expectedEntity.keySet();
+ Assert.assertEquals(actualKeys, expectedKeys, "key sets differ for " + id);
+
+ // Key sets are the same... compare each key value
+ for (final var key : actualKeys) {
+ final var actualValue = actualEntity.get(key);
+ final var expectedValue = expectedEntity.get(key);
+ Assert.assertEquals(actualValue, expectedValue, "key " + key + " differs for " + id);
+ }
+
+ // Backstop
+ Assert.assertEquals(actualEntity, expectedEntity, id + " differs:");
+ }
+ }
+
+ /**
+ * Checks the serialization of a single entity using a given serializer
+ * configuration.
+ *
+ * @param name base name for the resources
+ * @param ser configured and initialized serializer to use
+ *
+ * @return the single {@link JsonObject} for further tests
+ *
+ * @throws IOException if there are parsing issues with the JSON file
+ * @throws XMLParserException if there are parsing issues with the XML file
+ */
+ private JsonObject checkSingle(@Nonnull final String name, @Nonnull final ItemCollectionSerializer ser)
+ throws IOException, XMLParserException {
+ final String output;
+ try (final var out = new ByteArrayOutputStream()) {
+ ser.serializeCollection(List.of(readDOMItem(name + ".xml")), out);
+ output = out.toString();
+ }
+ //System.out.println(output);
+ final var read = Json.createReader(new StringReader(output));
+ final var array = read.readArray();
+ Assert.assertEquals(array.size(), 1);
+
+ final var entity = (JsonObject)array.get(0);
+ checkEntity(entity);
+ final var expected = fetchJSONArray(name + ".json");
+ compareCollections(array, expected);
+ Assert.assertEquals(array, expected);
+
+ return entity;
+ }
+
+ // Test the base case, derived from a single standard IdP
+ // with default generator settings.
+ @Test
+ public void testBase() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final var entity = checkSingle("base", ser);
+ Assert.assertTrue(entity.containsKey("Descriptions"));
+ Assert.assertTrue(entity.containsKey("DisplayNames"));
+ Assert.assertFalse(entity.containsKey("InformationURLs"));
+ Assert.assertFalse(entity.containsKey("PrivacyStatementURLs"));
+ }
+
+ // Test the absence of an mdui:DisplayName
+ // with default generator settings.
+ @Test
+ public void testNoLegacy() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final var entity = checkSingle("nolegacy", ser);
+ Assert.assertFalse(entity.containsKey("Descriptions"));
+ Assert.assertFalse(entity.containsKey("DisplayNames"));
+ }
+
+ // Test the inclusion of a legacy DisplayName when the
+ // serializer is set to allow this.
+ @Test
+ public void testLegacy() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.setIncludingLegacyDisplayNames(true);
+ ser.setPrettyPrinting(true);
+ ser.initialize();
+ final var entity = checkSingle("legacy", ser);
+ Assert.assertFalse(entity.containsKey("Descriptions"));
+ Assert.assertTrue(entity.containsKey("DisplayNames"));
+ }
+
+ // Test an entity with a couple of InformationURLs.
+ @Test
+ public void testInformationURLs() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final var entity = checkSingle("infourl", ser);
+ Assert.assertTrue(entity.containsKey("Descriptions"));
+ Assert.assertTrue(entity.containsKey("DisplayNames"));
+ Assert.assertTrue(entity.containsKey("InformationURLs"));
+ }
+
+ // Test an entity with a couple of PrivacyStatementURLs.
+ @Test
+ public void testPrivacyStatementURLs() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final var entity = checkSingle("privacyurl", ser);
+ Assert.assertTrue(entity.containsKey("Descriptions"));
+ Assert.assertTrue(entity.containsKey("DisplayNames"));
+ Assert.assertTrue(entity.containsKey("PrivacyStatementURLs"));
+ }
+
+ // Test the optional nature of xml:lang on mdui:Logo elements.
+ @Test
+ public void testLogoLang() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+ final var entity = checkSingle("logolang", ser);
+ final var logos = entity.getJsonArray("Logos");
+ Assert.assertEquals(logos.size(), 3);
+ Assert.assertFalse(logos.getJsonObject(0).containsKey("lang"));
+ Assert.assertTrue(logos.getJsonObject(1).containsKey("lang"));
+ Assert.assertEquals(logos.getJsonObject(1).getString("lang"), "en");
+ Assert.assertFalse(logos.getJsonObject(2).containsKey("lang"));
+ }
+
+ // Test entity attributes present (base tests absent)
+ @Test
+ public void testEntityAttributes() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.setIncludingEntityAttributes(true);
+ ser.initialize();
+ final var entity = checkSingle("entattr", ser);
+ final var attrs = entity.getJsonArray("EntityAttributes");
+ Assert.assertEquals(attrs.size(), 3);
+ Assert.assertEquals(attrs.getJsonObject(2).getString("name"), "something");
+ Assert.assertEquals(attrs.getJsonObject(2).getJsonArray("values").size(), 1);
+ Assert.assertEquals(attrs.getJsonObject(2).getJsonArray("values").getString(0), "whatever");
+ }
+
+ // Test a large aggregate. This test is normally disabled, but it
+ // can be brought back to life if necessary to test a new corpus.
+ @Test(enabled=false)
+ public void testAll() throws Exception {
+ final var ser = new DiscoFeedCollectionSerializer();
+ ser.initialize();
+
+ // Build a collection to serialize.
+ final var item = readDOMItem("all.xml");
+ final var items = new ArrayList<Item<Element>>();
+ items.add(item);
+
+ // Disaggregate the collection into individual entities
+ final var disassembler = new EntitiesDescriptorDisassemblerStage();
+ disassembler.setId("disassemble");
+ disassembler.initialize();
+ disassembler.execute(items);
+
+ // Now generate the disco feed
+ final String output;
+ try (final var out = new ByteArrayOutputStream()) {
+ ser.serializeCollection(items, out);
+ output = out.toString();
+ }
+ final var read = Json.createReader(new StringReader(output));
+ final var array = read.readArray();
+
+ final var expected = fetchJSONArray("all.json");
+ compareCollections(array, expected);
+ Assert.assertEquals(array, expected);
+ }
+}
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.json
new file mode 100644
index 0000000..9a8f5ec
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.json
@@ -0,0 +1,34 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_100x43.jpg",
+ "height": "43",
+ "width": "100"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_240x104.jpg",
+ "height": "104",
+ "width": "240"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.xml
new file mode 100644
index 0000000..2c7579f
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-base.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:Logo height="43" width="100">https://idp.example.com/images/heads_100x43.jpg</mdui:Logo>
+ <mdui:Logo height="104" width="240">https://idp.example.com/images/heads_240x104.jpg</mdui:Logo>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.json
new file mode 100644
index 0000000..2fe0a6c
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.json
@@ -0,0 +1,55 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_100x43.jpg",
+ "height": "43",
+ "width": "100"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_240x104.jpg",
+ "height": "104",
+ "width": "240"
+ }
+ ],
+ "EntityAttributes": [
+ {
+ "name": "http://macedir.org/entity-category-support",
+ "values": [
+ "http://refeds.org/category/research-and-scholarship"
+ ]
+ },
+ {
+ "name": "urn:oasis:names:tc:SAML:attribute:assurance-certification",
+ "values": [
+ "https://refeds.org/sirtfi",
+ "second-assurance-value"
+ ]
+ },
+ {
+ "name": "something",
+ "values": [
+ "whatever"
+ ]
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.xml
new file mode 100644
index 0000000..3af7a0b
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-entattr.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="urn:oasis:names:tc:SAML:attribute:assurance-certification"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>https://refeds.org/sirtfi</saml:AttributeValue>
+ <saml:AttributeValue>second-assurance-value</saml:AttributeValue>
+ </saml:Attribute>
+ <saml:Attribute Name="something" NameFormat="something-else">
+ <saml:AttributeValue>whatever</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:Logo height="43" width="100">https://idp.example.com/images/heads_100x43.jpg</mdui:Logo>
+ <mdui:Logo height="104" width="240">https://idp.example.com/images/heads_240x104.jpg</mdui:Logo>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.json
new file mode 100644
index 0000000..68fa5c3
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.json
@@ -0,0 +1,34 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "InformationURLs": [
+ {
+ "value": "https://example.org/info-english",
+ "lang": "en"
+ },
+ {
+ "value": "https://example.org/info-welsh",
+ "lang": "cy"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.xml
new file mode 100644
index 0000000..5ac5c80
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-infourl.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:InformationURL xml:lang="en">https://example.org/info-english</mdui:InformationURL>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:InformationURL xml:lang="cy">https://example.org/info-welsh</mdui:InformationURL>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.json
new file mode 100644
index 0000000..8197031
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.json
@@ -0,0 +1,34 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "Keywords": [
+ {
+ "value": "the quick brown English fox",
+ "lang": "en"
+ },
+ {
+ "value": "the quick brown Welsh fox",
+ "lang": "cy"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.xml
new file mode 100644
index 0000000..151b3ed
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-keywords.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:Keywords xml:lang="en">the quick brown English fox</mdui:Keywords>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:Keywords xml:lang="cy">the quick brown Welsh fox</mdui:Keywords>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.json
new file mode 100644
index 0000000..ba93a54
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.json
@@ -0,0 +1,18 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ }
+ ],
+ "DisplayNames": [
+ {
+ "value": "The Example Organization",
+ "lang": "en"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.xml
new file mode 100644
index 0000000..584126f
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-legacy.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.json
new file mode 100644
index 0000000..4b7b8c3
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.json
@@ -0,0 +1,35 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_100x43.jpg",
+ "height": "43",
+ "width": "100",
+ "lang": "en"
+ },
+ {
+ "value": "https://idp.example.com/images/heads_240x104.jpg",
+ "height": "104",
+ "width": "240"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.xml
new file mode 100644
index 0000000..badd37e
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-logolang.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:Logo height="43" width="100" xml:lang="en">https://idp.example.com/images/heads_100x43.jpg</mdui:Logo>
+ <mdui:Logo height="104" width="240">https://idp.example.com/images/heads_240x104.jpg</mdui:Logo>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.json
new file mode 100644
index 0000000..7e36278
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.json
@@ -0,0 +1,12 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.xml
new file mode 100644
index 0000000..584126f
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-nolegacy.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.json b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.json
new file mode 100644
index 0000000..6a56293
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.json
@@ -0,0 +1,34 @@
+[
+{
+ "entityID": "https://idp.example.com/idp/shibboleth",
+ "DisplayNames": [
+ {
+ "value": "Example Organization",
+ "lang": "en"
+ }
+ ],
+ "Descriptions": [
+ {
+ "value": "This is the identity provider for The Example Organization.",
+ "lang": "en"
+ }
+ ],
+ "PrivacyStatementURLs": [
+ {
+ "value": "https://example.org/priv-english",
+ "lang": "en"
+ },
+ {
+ "value": "https://example.org/priv-welsh",
+ "lang": "cy"
+ }
+ ],
+ "Logos": [
+ {
+ "value": "https://idp.example.com/images/heads_80x80.jpg",
+ "height": "80",
+ "width": "80"
+ }
+ ]
+}
+]
diff --git a/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.xml b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.xml
new file mode 100644
index 0000000..734b902
--- /dev/null
+++ b/aggregator-pipeline/src/test/resources/net/shibboleth/metadata/dom/saml/DiscoFeedCollectionSerializer-privacyurl.xml
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata"
+ xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
+ xmlns:idpdisc="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol"
+ xmlns:init="urn:oasis:names:tc:SAML:profiles:SSO:request-init"
+ xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
+ xmlns:mdrpi="urn:oasis:names:tc:SAML:metadata:rpi"
+ xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
+ xmlns:remd="http://refeds.org/metadata"
+ xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
+ xmlns:shibmd="urn:mace:shibboleth:metadata:1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ entityID="https://idp.example.com/idp/shibboleth">
+ <Extensions>
+ <shibmd:Scope regexp="false">example.com</shibmd:Scope>
+ <mdrpi:RegistrationInfo registrationAuthority="http://ukfederation.org.uk"
+ registrationInstant="2007-03-30T16:36:00Z">
+ <mdrpi:RegistrationPolicy xml:lang="en"
+ >http://ukfederation.org.uk/doc/mdrps-20130902</mdrpi:RegistrationPolicy>
+ </mdrpi:RegistrationInfo>
+ <mdattr:EntityAttributes xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">
+ <saml:Attribute Name="http://macedir.org/entity-category-support"
+ NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
+ <saml:AttributeValue>http://refeds.org/category/research-and-scholarship</saml:AttributeValue>
+ </saml:Attribute>
+ </mdattr:EntityAttributes>
+ </Extensions>
+ <IDPSSODescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:2.0:protocol">
+ <Extensions>
+ <mdui:UIInfo>
+ <mdui:PrivacyStatementURL xml:lang="en">https://example.org/priv-english</mdui:PrivacyStatementURL>
+ <mdui:DisplayName xml:lang="en">Example Organization</mdui:DisplayName>
+ <mdui:Description xml:lang="en">This is the identity provider for The Example Organization.</mdui:Description>
+ <mdui:Logo height="80" width="80">https://idp.example.com/images/heads_80x80.jpg</mdui:Logo>
+ <mdui:PrivacyStatementURL xml:lang="cy">https://example.org/priv-welsh</mdui:PrivacyStatementURL>
+ </mdui:UIInfo>
+ </Extensions>
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/ArtifactResolution" index="1"/>
+ <ArtifactResolutionService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/ArtifactResolution" index="2"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ <SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest"
+ Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
+ Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST-SimpleSign"
+ Location="https://idp.example.com/idp/profile/SAML2/POST-SimpleSign/SSO"/>
+ <SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
+ Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
+ </IDPSSODescriptor>
+ <AttributeAuthorityDescriptor
+ protocolSupportEnumeration="urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
+ <KeyDescriptor>
+ <ds:KeyInfo>
+ <ds:X509Data>
+ <ds:X509Certificate>
+ MIIDSTCCAjGgAwIBAgIhAMSPOSGN+3UUTXSKV+2EBOuF3x/pwPX/TD9GfyEkzLp+
+ MA0GCSqGSIb3DQEBBQUAMFgxGDAWBgNVBAMMD2lkcDIuaWF5Lm9yZy51azETMBEG
+ CgmSJomT8ixkARkWA2lheTETMBEGCgmSJomT8ixkARkWA29yZzESMBAGCgmSJomT
+ 8ixkARkWAnVrMB4XDTA4MDIyNTEwMzAxNFoXDTI4MDIyNTEwMzAxNFowWDEYMBYG
+ A1UEAwwPaWRwMi5pYXkub3JnLnVrMRMwEQYKCZImiZPyLGQBGRYDaWF5MRMwEQYK
+ CZImiZPyLGQBGRYDb3JnMRIwEAYKCZImiZPyLGQBGRYCdWswggEiMA0GCSqGSIb3
+ DQEBAQUAA4IBDwAwggEKAoIBAQCb6ts48g10XHTnpy+23huzR184aahkrG0AoeUl
+ FVlomPjoFDk6czq0S3Qyd+ceF7tMRu3XzS7cMmtVH53O9d+wCs8aPQcPXxHQ5gLk
+ L7Gu6eJ+3N3jXhpt7/DDPhnzFPNW3EVMueHJ/0IzyspTvq2LPbNWXJ86NKJ+gesZ
+ QftskwXScOjpoJEIP0EA890QYd4WdYtQPqVV+LPKtnYBoGOnuRhSAM1D/EhCbeb0
+ lCmRGcdGbDFBchiPO4VLGl85sLa0EhjxMIPAOKXcj8bBlO9Ww9kkG06kQp6eLHwm
+ Jmt7VNKveCGhyF2QH/CvmdUaPv3gcp1UjrlqFN9LBVSaTIL/AgMBAAEwDQYJKoZI
+ hvcNAQEFBQADggEBAG+jDBAtlKoHaEBB+l6PpW5zuiDjyHG4zZZYqX77mZ9xP/xe
+ Kn0yJ18ZLjS3b9WztGLYyC4SJHSF2okq1K02bqsCv9YeP+UWpw2uRR8jt96lLWxZ
+ jTjoko2v8jBtzDk8LZsqw58m4vZ0AGNZjKeGIywKhxnepwREguyj3bjBpZAGgl0M
+ HQuXoO/BDC9yKyZslE5CpWp5xP4XzY2/LrorrkwOJLnFuk1sox4/gvkDQukUx/jr
+ YRbrWfOjcNBx3LE/HI6RNLINicK7yUwerDE86nix5Zc3hskVcCykW+r6HbY6bx7P
+ YmNKYMZhQAgDtXIjFHOy+WbyVTidmJvxM9UeYCY=
+ </ds:X509Certificate>
+ </ds:X509Data>
+ </ds:KeyInfo>
+ </KeyDescriptor>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:1.0:bindings:SOAP-binding"
+ Location="https://idp.example.com:8443/idp/profile/SAML1/SOAP/AttributeQuery"/>
+ <AttributeService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP"
+ Location="https://idp.example.com:8443/idp/profile/SAML2/SOAP/AttributeQuery"/>
+ <NameIDFormat>urn:mace:shibboleth:1.0:nameIdentifier</NameIDFormat>
+ <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>
+ </AttributeAuthorityDescriptor>
+ <Organization>
+ <OrganizationName xml:lang="en">Example Organization</OrganizationName>
+ <OrganizationDisplayName xml:lang="en">The Example Organization</OrganizationDisplayName>
+ <OrganizationURL xml:lang="en">http://example.com/</OrganizationURL>
+ </Organization>
+</EntityDescriptor>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list