From 965e689461b4d4b96914f715df7fcffc08b56f22 Mon Sep 17 00:00:00 2001 From: Josh Cummings <josh.cummings@gmail.com> Date: Thu, 27 Jan 2022 14:25:00 -0700 Subject: [PATCH] Add EntitiesDescriptor Support Closes gh-10782 --- ...enSamlAssertingPartyMetadataConverter.java | 35 ++-- ...gistrationBuilderHttpMessageConverter.java | 4 +- .../RelyingPartyRegistrations.java | 93 +++++++++- ...lAssertingPartyMetadataConverterTests.java | 11 +- .../RelyingPartyRegistrationsTests.java | 116 +++++++++++- .../resources/test-entitiesdescriptor.xml | 168 ++++++++++++++++++ 6 files changed, 407 insertions(+), 20 deletions(-) create mode 100644 saml2/saml2-service-provider/src/test/resources/test-entitiesdescriptor.xml diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java index 1b0eb0e35a..c642140e7e 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.io.InputStream; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import net.shibboleth.utilities.java.support.xml.ParserPool; @@ -63,8 +65,24 @@ class OpenSamlAssertingPartyMetadataConverter { this.parserPool = this.registry.getParserPool(); } - RelyingPartyRegistration.Builder convert(InputStream inputStream) { - EntityDescriptor descriptor = entityDescriptor(inputStream); + Collection<RelyingPartyRegistration.Builder> convert(InputStream inputStream) { + List<RelyingPartyRegistration.Builder> builders = new ArrayList<>(); + XMLObject xmlObject = xmlObject(inputStream); + if (xmlObject instanceof EntitiesDescriptor) { + EntitiesDescriptor descriptors = (EntitiesDescriptor) xmlObject; + for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) { + builders.add(convert(descriptor)); + } + return builders; + } + if (xmlObject instanceof EntityDescriptor) { + EntityDescriptor descriptor = (EntityDescriptor) xmlObject; + return Arrays.asList(convert(descriptor)); + } + throw new Saml2Exception("Unsupported element of type " + xmlObject.getClass()); + } + + RelyingPartyRegistration.Builder convert(EntityDescriptor descriptor) { IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS); if (idpssoDescriptor == null) { throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element"); @@ -167,7 +185,7 @@ class OpenSamlAssertingPartyMetadataConverter { return signingMethods(extensions); } - private EntityDescriptor entityDescriptor(InputStream inputStream) { + private XMLObject xmlObject(InputStream inputStream) { Document document = document(inputStream); Element element = document.getDocumentElement(); Unmarshaller unmarshaller = this.registry.getUnmarshallerFactory().getUnmarshaller(element); @@ -175,18 +193,11 @@ class OpenSamlAssertingPartyMetadataConverter { throw new Saml2Exception("Unsupported element of type " + element.getTagName()); } try { - XMLObject object = unmarshaller.unmarshall(element); - if (object instanceof EntitiesDescriptor) { - return ((EntitiesDescriptor) object).getEntityDescriptors().get(0); - } - if (object instanceof EntityDescriptor) { - return (EntityDescriptor) object; - } + return unmarshaller.unmarshall(element); } catch (Exception ex) { throw new Saml2Exception(ex); } - throw new Saml2Exception("Unsupported element of type " + element.getTagName()); } private Document document(InputStream inputStream) { diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java index 6e5284a941..e7c908f742 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,7 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter @Override public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException { - return this.converter.convert(inputMessage.getBody()); + return this.converter.convert(inputMessage.getBody()).iterator().next(); } @Override diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java index 2ed5246f64..2f3913f5b4 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrations.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.security.saml2.provider.service.registration; import java.io.IOException; import java.io.InputStream; +import java.util.Collection; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.ResourceLoader; @@ -122,6 +123,96 @@ public final class RelyingPartyRegistrations { * @since 5.6 */ public static RelyingPartyRegistration.Builder fromMetadata(InputStream source) { + return assertingPartyMetadataConverter.convert(source).iterator().next(); + } + + /** + * Return a {@link Collection} of {@link RelyingPartyRegistration.Builder}s based off + * of the given SAML 2.0 Asserting Party (IDP) metadata location. + * + * Valid locations can be classpath- or file-based or they can be HTTP endpoints. Some + * valid endpoints might include: + * + * <pre> + * metadataLocation = "classpath:asserting-party-metadata.xml"; + * metadataLocation = "file:asserting-party-metadata.xml"; + * metadataLocation = "https://ap.example.org/metadata"; + * </pre> + * + * Note that by default the registrationId is set to be the given metadata location, + * but this will most often not be sufficient. To complete the configuration, most + * applications will also need to provide a registrationId, like so: + * + * <pre> + * Iterable<RelyingPartyRegistration> registrations = RelyingPartyRegistrations + * .collectionFromMetadataLocation(location).iterator(); + * RelyingPartyRegistration one = registrations.next().registrationId("one").build(); + * RelyingPartyRegistration two = registrations.next().registrationId("two").build(); + * return new InMemoryRelyingPartyRegistrationRepository(one, two); + * </pre> + * + * Also note that an {@code IDPSSODescriptor} typically only contains information + * about the asserting party. Thus, you will need to remember to still populate + * anything about the relying party, like any private keys the relying party will use + * for signing AuthnRequests. + * @param location The classpath- or file-based locations or HTTP endpoints of the + * asserting party metadata file + * @return the {@link Collection} of {@link RelyingPartyRegistration.Builder}s for + * further configuration + * @since 5.7 + */ + public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadataLocation(String location) { + try (InputStream source = resourceLoader.getResource(location).getInputStream()) { + return collectionFromMetadata(source); + } + catch (IOException ex) { + if (ex.getCause() instanceof Saml2Exception) { + throw (Saml2Exception) ex.getCause(); + } + throw new Saml2Exception(ex); + } + } + + /** + * Return a {@link Collection} of {@link RelyingPartyRegistration.Builder}s based off + * of the given SAML 2.0 Asserting Party (IDP) metadata. + * + * <p> + * This method is intended for scenarios when the metadata is looked up by a separate + * mechanism. One such example is when the metadata is stored in a database. + * </p> + * + * <p> + * <strong>The callers of this method are accountable for closing the + * {@code InputStream} source.</strong> + * </p> + * + * Note that by default the registrationId is set to be the given metadata location, + * but this will most often not be sufficient. To complete the configuration, most + * applications will also need to provide a registrationId, like so: + * + * <pre> + * String xml = fromDatabase(); + * try (InputStream source = new ByteArrayInputStream(xml.getBytes())) { + * Iterator<RelyingPartyRegistration> registrations = RelyingPartyRegistrations + * .collectionFromMetadata(source).iterator(); + * RelyingPartyRegistration one = registrations.next().registrationId("one").build(); + * RelyingPartyRegistration two = registrations.next().registrationId("two").build(); + * return new InMemoryRelyingPartyRegistrationRepository(one, two); + * } + * </pre> + * + * Also note that an {@code IDPSSODescriptor} typically only contains information + * about the asserting party. Thus, you will need to remember to still populate + * anything about the relying party, like any private keys the relying party will use + * for signing AuthnRequests. + * @param source the {@link InputStream} source containing the asserting party + * metadata + * @return the {@link Collection} of {@link RelyingPartyRegistration.Builder}s for + * further configuration + * @since 5.7 + */ + public static Collection<RelyingPartyRegistration.Builder> collectionFromMetadata(InputStream source) { return assertingPartyMetadataConverter.convert(source); } diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java index 1083fb1629..ac81eba426 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/OpenSamlAssertingPartyMetadataConverterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -98,7 +98,8 @@ public class OpenSamlAssertingPartyMetadataConverterTests { + String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") + EXTENSIONS_TEMPLATE + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))); InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration registration = this.converter.convert(inputStream).registrationId("one").build(); + RelyingPartyRegistration registration = this.converter.convert(inputStream).iterator().next() + .registrationId("one").build(); RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); assertThat(details.getWantAuthnRequestsSigned()).isFalse(); assertThat(details.getSigningAlgorithms()).containsExactly(SignatureConstants.ALGO_ID_DIGEST_SHA512); @@ -123,7 +124,8 @@ public class OpenSamlAssertingPartyMetadataConverterTests { + String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"encryption\"") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE)))); InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration registration = this.converter.convert(inputStream).registrationId("one").build(); + RelyingPartyRegistration registration = this.converter.convert(inputStream).iterator().next() + .registrationId("one").build(); RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); assertThat(details.getWantAuthnRequestsSigned()).isFalse(); assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location"); @@ -142,7 +144,8 @@ public class OpenSamlAssertingPartyMetadataConverterTests { String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "") + String.format(SINGLE_SIGN_ON_SERVICE_TEMPLATE))); InputStream inputStream = new ByteArrayInputStream(payload.getBytes()); - RelyingPartyRegistration registration = this.converter.convert(inputStream).registrationId("one").build(); + RelyingPartyRegistration registration = this.converter.convert(inputStream).iterator().next() + .registrationId("one").build(); RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate()) .isEqualTo(x509Certificate(CERTIFICATE)); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java index 0aadfbb532..9360475d68 100644 --- a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/registration/RelyingPartyRegistrationsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2021 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.List; import java.util.stream.Collectors; import okhttp3.mockwebserver.MockResponse; @@ -41,12 +42,18 @@ public class RelyingPartyRegistrationsTests { private String metadata; + private String entitiesDescriptor; + @BeforeEach public void setup() throws Exception { ClassPathResource resource = new ClassPathResource("test-metadata.xml"); try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { this.metadata = reader.lines().collect(Collectors.joining()); } + resource = new ClassPathResource("test-entitiesdescriptor.xml"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { + this.entitiesDescriptor = reader.lines().collect(Collectors.joining()); + } } @Test @@ -129,4 +136,111 @@ public class RelyingPartyRegistrationsTests { } } + @Test + public void collectionFromMetadataLocationWhenResolvableThenPopulatesBuilder() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.enqueue(new MockResponse().setBody(this.entitiesDescriptor).setResponseCode(200)); + List<RelyingPartyRegistration> registrations = RelyingPartyRegistrations + .collectionFromMetadataLocation(server.url("/").toString()).stream() + .map((r) -> r.entityId("rp").build()).collect(Collectors.toList()); + assertThat(registrations).hasSize(2); + RelyingPartyRegistration first = registrations.get(0); + RelyingPartyRegistration.AssertingPartyDetails details = first.getAssertingPartyDetails(); + assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); + assertThat(details.getSingleSignOnServiceLocation()) + .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); + assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(details.getVerificationX509Credentials()).hasSize(1); + assertThat(details.getEncryptionX509Credentials()).hasSize(1); + RelyingPartyRegistration second = registrations.get(1); + details = second.getAssertingPartyDetails(); + assertThat(details.getEntityId()).isEqualTo("https://ap.example.org/idp/shibboleth"); + assertThat(details.getSingleSignOnServiceLocation()) + .isEqualTo("https://ap.example.org/idp/profile/SAML2/POST/SSO"); + assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(details.getVerificationX509Credentials()).hasSize(1); + assertThat(details.getEncryptionX509Credentials()).hasSize(1); + } + } + + @Test + public void collectionFromMetadataLocationWhenUnresolvableThenSaml2Exception() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.enqueue(new MockResponse().setBody(this.metadata).setResponseCode(200)); + String url = server.url("/").toString(); + server.shutdown(); + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> RelyingPartyRegistrations.collectionFromMetadataLocation(url)); + } + } + + @Test + public void collectionFromMetadataLocationWhenMalformedResponseThenSaml2Exception() throws Exception { + try (MockWebServer server = new MockWebServer()) { + server.enqueue(new MockResponse().setBody("malformed").setResponseCode(200)); + String url = server.url("/").toString(); + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> RelyingPartyRegistrations.collectionFromMetadataLocation(url)); + } + } + + @Test + public void collectionFromMetadataFileWhenResolvableThenPopulatesBuilder() { + File file = new File("src/test/resources/test-entitiesdescriptor.xml"); + RelyingPartyRegistration registration = RelyingPartyRegistrations + .collectionFromMetadataLocation("file:" + file.getAbsolutePath()).stream() + .map((r) -> r.entityId("rp").build()).findFirst().get(); + RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); + assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); + assertThat(details.getSingleSignOnServiceLocation()) + .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); + assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(details.getVerificationX509Credentials()).hasSize(1); + assertThat(details.getEncryptionX509Credentials()).hasSize(1); + } + + @Test + public void collectionFromMetadataFileWhenContainsOnlyEntityDescriptorThenPopulatesBuilder() { + File file = new File("src/test/resources/test-metadata.xml"); + RelyingPartyRegistration registration = RelyingPartyRegistrations + .collectionFromMetadataLocation("file:" + file.getAbsolutePath()).stream() + .map((r) -> r.entityId("rp").build()).findFirst().get(); + RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); + assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); + assertThat(details.getSingleSignOnServiceLocation()) + .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); + assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(details.getVerificationX509Credentials()).hasSize(1); + assertThat(details.getEncryptionX509Credentials()).hasSize(1); + } + + @Test + public void collectionFromMetadataFileWhenNotFoundThenSaml2Exception() { + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> RelyingPartyRegistrations.collectionFromMetadataLocation("filePath")); + } + + @Test + public void collectionFromMetadataInputStreamWhenResolvableThenPopulatesBuilder() throws Exception { + try (InputStream source = new ByteArrayInputStream(this.entitiesDescriptor.getBytes())) { + RelyingPartyRegistration registration = RelyingPartyRegistrations.collectionFromMetadata(source).stream() + .map((r) -> r.entityId("rp").build()).findFirst().get(); + RelyingPartyRegistration.AssertingPartyDetails details = registration.getAssertingPartyDetails(); + assertThat(details.getEntityId()).isEqualTo("https://idp.example.com/idp/shibboleth"); + assertThat(details.getSingleSignOnServiceLocation()) + .isEqualTo("https://idp.example.com/idp/profile/SAML2/POST/SSO"); + assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.POST); + assertThat(details.getVerificationX509Credentials()).hasSize(1); + assertThat(details.getEncryptionX509Credentials()).hasSize(1); + } + } + + @Test + public void collectionFromMetadataInputStreamWhenEmptyThenSaml2Exception() throws Exception { + try (InputStream source = new ByteArrayInputStream("".getBytes())) { + assertThatExceptionOfType(Saml2Exception.class) + .isThrownBy(() -> RelyingPartyRegistrations.collectionFromMetadata(source)); + } + } + } diff --git a/saml2/saml2-service-provider/src/test/resources/test-entitiesdescriptor.xml b/saml2/saml2-service-provider/src/test/resources/test-entitiesdescriptor.xml new file mode 100644 index 0000000000..605c197307 --- /dev/null +++ b/saml2/saml2-service-provider/src/test/resources/test-entitiesdescriptor.xml @@ -0,0 +1,168 @@ +<md:EntitiesDescriptor xmlns:ds="http://www.w3.org/2000/09/xmldsig#" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns:shibmd="urn:mace:shibboleth:metadata:1.0" + xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" + xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"> + <md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"> + + <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <md:Extensions> + <shibmd:Scope regexp="false">example.com</shibmd:Scope> + + <mdui:UIInfo> + <mdui:DisplayName xml:lang="en"> + Consortium GARR IdP + </mdui:DisplayName> + <mdui:DisplayName xml:lang="it"> + Consortium GARR IdP + </mdui:DisplayName> + + <mdui:Description xml:lang="en"> + This Identity Provider gives support for the Consortium GARR's user community + </mdui:Description> + <mdui:Description xml:lang="it"> + Questo Identity Provider di test fornisce supporto alla comunita' utenti GARR + </mdui:Description> + </mdui:UIInfo> + </md:Extensions> + + <md:KeyDescriptor> + <ds:KeyInfo> + <ds:X509Data> + <ds:X509Certificate> + MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB + BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe + Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t + cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP + ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS + v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN + iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece + byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz + cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v + dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX + gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w + dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW + BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu + 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL + qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU + duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU + yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p + V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e + Cq53OZt9ISjHEw== + </ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </md:KeyDescriptor> + + <md:SingleSignOnService + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/> + </md:IDPSSODescriptor> + + <md:Organization> + <md:OrganizationName xml:lang="en"> + Consortium GARR + </md:OrganizationName> + <md:OrganizationName xml:lang="it"> + Consortium GARR + </md:OrganizationName> + + <md:OrganizationDisplayName xml:lang="en"> + Consortium GARR + </md:OrganizationDisplayName> + <md:OrganizationDisplayName xml:lang="it"> + Consortium GARR + </md:OrganizationDisplayName> + + <md:OrganizationURL xml:lang="it"> + https://example.org + </md:OrganizationURL> + </md:Organization> + + <md:ContactPerson contactType="technical"> + <md:EmailAddress>mailto:technical.contact@example.com</md:EmailAddress> + </md:ContactPerson> + + </md:EntityDescriptor> + <md:EntityDescriptor entityID="https://ap.example.org/idp/shibboleth"> + + <md:IDPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <md:Extensions> + <shibmd:Scope regexp="false">example.org</shibmd:Scope> + + <mdui:UIInfo> + <mdui:DisplayName xml:lang="en"> + Consortium GARR IdP + </mdui:DisplayName> + <mdui:DisplayName xml:lang="it"> + Consortium GARR IdP + </mdui:DisplayName> + + <mdui:Description xml:lang="en"> + This Identity Provider gives support for the Consortium GARR's user community + </mdui:Description> + <mdui:Description xml:lang="it"> + Questo Identity Provider di test fornisce supporto alla comunita' utenti GARR + </mdui:Description> + </mdui:UIInfo> + </md:Extensions> + + <md:KeyDescriptor> + <ds:KeyInfo> + <ds:X509Data> + <ds:X509Certificate> + MIIDZjCCAk6gAwIBAgIVAL9O+PA7SXtlwZZY8MVSE9On1cVWMA0GCSqGSIb3DQEB + BQUAMCkxJzAlBgNVBAMTHmlkZW0tcHVwYWdlbnQuZG16LWludC51bmltby5pdDAe + Fw0xMzA3MjQwMDQ0MTRaFw0zMzA3MjQwMDQ0MTRaMCkxJzAlBgNVBAMTHmlkZW0t + cHVwYWdlbnQuZG16LWludC51bmltby5pdDCCASIwDQYJKoZIhvcNAMIIDQADggEP + ADCCAQoCggEBAIAcp/VyzZGXUF99kwj4NvL/Rwv4YvBgLWzpCuoxqHZ/hmBwJtqS + v0y9METBPFbgsF3hCISnxbcmNVxf/D0MoeKtw1YPbsUmow/bFe+r72hZ+IVAcejN + iDJ7t5oTjsRN1t1SqvVVk6Ryk5AZhpFW+W9pE9N6c7kJ16Rp2/mbtax9OCzxpece + byi1eiLfIBmkcRawL/vCc2v6VLI18i6HsNVO3l2yGosKCbuSoGDx2fCdAOk/rgdz + cWOvFsIZSKuD+FVbSS/J9GVs7yotsS4PRl4iX9UMnfDnOMfO7bcBgbXtDl4SCU1v + dJrRw7IL/pLz34Rv9a8nYitrzrxtLOp3nYUCAwEAAaOBhDCBgTBgBgMIIDEEWTBX + gh5pZGVtLXB1cGFnZW50LmRtei1pbnQudW5pbW8uaXSGNWh0dHBzOi8vaWRlbS1w + dXBhZ2VudC5kbXotaW50LnVuaW1vLml0L2lkcC9zaGliYm9sZXRoMB0GA1UdDgQW + BBT8PANzz+adGnTRe8ldcyxAwe4VnzANBgkqhkiG9w0BAQUFAAOCAQEAOEnO8Clu + 9z/Lf/8XOOsTdxJbV29DIF3G8KoQsB3dBsLwPZVEAQIP6ceS32Xaxrl6FMTDDNkL + qUvvInUisw0+I5zZwYHybJQCletUWTnz58SC4C9G7FpuXHFZnOGtRcgGD1NOX4UU + duus/4nVcGSLhDjszZ70Xtj0gw2Sn46oQPHTJ81QZ3Y9ih+Aj1c9OtUSBwtWZFkU + yooAKoR8li68Yb21zN2N65AqV+ndL98M8xUYMKLONuAXStDeoVCipH6PJ09Z5U2p + V5p4IQRV6QBsNw9CISJFuHzkVYTH5ZxzN80Ru46vh4y2M0Nu8GQ9I085KoZkrf5e + Cq53OZt9ISjHEw== + </ds:X509Certificate> + </ds:X509Data> + </ds:KeyInfo> + </md:KeyDescriptor> + + <md:SingleSignOnService + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="https://ap.example.org/idp/profile/SAML2/POST/SSO"/> + </md:IDPSSODescriptor> + + <md:Organization> + <md:OrganizationName xml:lang="en"> + Consortium GARR + </md:OrganizationName> + <md:OrganizationName xml:lang="it"> + Consortium GARR + </md:OrganizationName> + + <md:OrganizationDisplayName xml:lang="en"> + Consortium GARR + </md:OrganizationDisplayName> + <md:OrganizationDisplayName xml:lang="it"> + Consortium GARR + </md:OrganizationDisplayName> + + <md:OrganizationURL xml:lang="it"> + https://example.org + </md:OrganizationURL> + </md:Organization> + + <md:ContactPerson contactType="technical"> + <md:EmailAddress>mailto:technical.contact@example.org</md:EmailAddress> + </md:ContactPerson> + + </md:EntityDescriptor> +</md:EntitiesDescriptor>