mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 10:59:16 +00:00
Add File-based Metadata Resolution
Closes gh-9028
This commit is contained in:
parent
78d5ffe60d
commit
9a11cc84ad
@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2020 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://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 org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import net.shibboleth.utilities.java.support.xml.ParserPool;
|
||||||
|
import org.opensaml.core.config.ConfigurationService;
|
||||||
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||||
|
import org.opensaml.core.xml.io.Unmarshaller;
|
||||||
|
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
|
||||||
|
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
||||||
|
import org.opensaml.security.credential.UsageType;
|
||||||
|
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
|
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||||
|
import org.springframework.security.saml2.core.Saml2X509Credential;
|
||||||
|
|
||||||
|
class OpenSamlAssertingPartyMetadataConverter {
|
||||||
|
|
||||||
|
static {
|
||||||
|
OpenSamlInitializationService.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final XMLObjectProviderRegistry registry;
|
||||||
|
|
||||||
|
private final ParserPool parserPool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a {@link OpenSamlAssertingPartyMetadataConverter}
|
||||||
|
*/
|
||||||
|
OpenSamlAssertingPartyMetadataConverter() {
|
||||||
|
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
||||||
|
this.parserPool = this.registry.getParserPool();
|
||||||
|
}
|
||||||
|
|
||||||
|
RelyingPartyRegistration.Builder convert(InputStream inputStream) {
|
||||||
|
EntityDescriptor descriptor = entityDescriptor(inputStream);
|
||||||
|
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
|
||||||
|
if (idpssoDescriptor == null) {
|
||||||
|
throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element");
|
||||||
|
}
|
||||||
|
List<Saml2X509Credential> verification = new ArrayList<>();
|
||||||
|
List<Saml2X509Credential> encryption = new ArrayList<>();
|
||||||
|
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
verification.add(Saml2X509Credential.verification(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
encryption.add(Saml2X509Credential.encryption(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) {
|
||||||
|
List<X509Certificate> certificates = certificates(keyDescriptor);
|
||||||
|
for (X509Certificate certificate : certificates) {
|
||||||
|
verification.add(Saml2X509Credential.verification(certificate));
|
||||||
|
encryption.add(Saml2X509Credential.encryption(certificate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (verification.isEmpty()) {
|
||||||
|
throw new Saml2Exception(
|
||||||
|
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
||||||
|
}
|
||||||
|
RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(descriptor.getEntityID())
|
||||||
|
.assertingPartyDetails((party) -> party.entityId(descriptor.getEntityID())
|
||||||
|
.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
|
||||||
|
.verificationX509Credentials((c) -> c.addAll(verification))
|
||||||
|
.encryptionX509Credentials((c) -> c.addAll(encryption)));
|
||||||
|
for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
|
||||||
|
Saml2MessageBinding binding;
|
||||||
|
if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.POST;
|
||||||
|
}
|
||||||
|
else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
||||||
|
binding = Saml2MessageBinding.REDIRECT;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.assertingPartyDetails(
|
||||||
|
(party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation())
|
||||||
|
.singleSignOnServiceBinding(binding));
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
throw new Saml2Exception(
|
||||||
|
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) {
|
||||||
|
try {
|
||||||
|
return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo());
|
||||||
|
}
|
||||||
|
catch (CertificateException ex) {
|
||||||
|
throw new Saml2Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private EntityDescriptor entityDescriptor(InputStream inputStream) {
|
||||||
|
Document document = document(inputStream);
|
||||||
|
Element element = document.getDocumentElement();
|
||||||
|
Unmarshaller unmarshaller = this.registry.getUnmarshallerFactory().getUnmarshaller(element);
|
||||||
|
if (unmarshaller == null) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new Saml2Exception(ex);
|
||||||
|
}
|
||||||
|
throw new Saml2Exception("Unsupported element of type " + element.getTagName());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Document document(InputStream inputStream) {
|
||||||
|
try {
|
||||||
|
return this.parserPool.parse(inputStream);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new Saml2Exception(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,38 +17,16 @@
|
|||||||
package org.springframework.security.saml2.provider.service.registration;
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
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.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import net.shibboleth.utilities.java.support.xml.ParserPool;
|
|
||||||
import org.opensaml.core.config.ConfigurationService;
|
|
||||||
import org.opensaml.core.xml.XMLObject;
|
|
||||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
|
||||||
import org.opensaml.core.xml.io.Unmarshaller;
|
|
||||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntitiesDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.KeyDescriptor;
|
|
||||||
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
|
||||||
import org.opensaml.security.credential.UsageType;
|
|
||||||
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import org.springframework.http.HttpInputMessage;
|
import org.springframework.http.HttpInputMessage;
|
||||||
import org.springframework.http.HttpOutputMessage;
|
import org.springframework.http.HttpOutputMessage;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.converter.HttpMessageConverter;
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||||
import org.springframework.http.converter.HttpMessageNotWritableException;
|
import org.springframework.http.converter.HttpMessageNotWritableException;
|
||||||
import org.springframework.security.saml2.Saml2Exception;
|
|
||||||
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
import org.springframework.security.saml2.core.OpenSamlInitializationService;
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link HttpMessageConverter} that takes an {@code IDPSSODescriptor} in an HTTP
|
* An {@link HttpMessageConverter} that takes an {@code IDPSSODescriptor} in an HTTP
|
||||||
@ -84,16 +62,13 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
|
|||||||
OpenSamlInitializationService.initialize();
|
OpenSamlInitializationService.initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final XMLObjectProviderRegistry registry;
|
private final OpenSamlAssertingPartyMetadataConverter converter;
|
||||||
|
|
||||||
private final ParserPool parserPool;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter}
|
* Creates a {@link OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter}
|
||||||
*/
|
*/
|
||||||
public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() {
|
public OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter() {
|
||||||
this.registry = ConfigurationService.get(XMLObjectProviderRegistry.class);
|
this.converter = new OpenSamlAssertingPartyMetadataConverter();
|
||||||
this.parserPool = this.registry.getParserPool();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -114,101 +89,7 @@ public class OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter
|
|||||||
@Override
|
@Override
|
||||||
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
public RelyingPartyRegistration.Builder read(Class<? extends RelyingPartyRegistration.Builder> clazz,
|
||||||
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
|
||||||
EntityDescriptor descriptor = entityDescriptor(inputMessage.getBody());
|
return this.converter.convert(inputMessage.getBody());
|
||||||
IDPSSODescriptor idpssoDescriptor = descriptor.getIDPSSODescriptor(SAMLConstants.SAML20P_NS);
|
|
||||||
if (idpssoDescriptor == null) {
|
|
||||||
throw new Saml2Exception("Metadata response is missing the necessary IDPSSODescriptor element");
|
|
||||||
}
|
|
||||||
List<Saml2X509Credential> verification = new ArrayList<>();
|
|
||||||
List<Saml2X509Credential> encryption = new ArrayList<>();
|
|
||||||
for (KeyDescriptor keyDescriptor : idpssoDescriptor.getKeyDescriptors()) {
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.SIGNING)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
verification.add(Saml2X509Credential.verification(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.ENCRYPTION)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
encryption.add(Saml2X509Credential.encryption(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (keyDescriptor.getUse().equals(UsageType.UNSPECIFIED)) {
|
|
||||||
List<X509Certificate> certificates = certificates(keyDescriptor);
|
|
||||||
for (X509Certificate certificate : certificates) {
|
|
||||||
verification.add(Saml2X509Credential.verification(certificate));
|
|
||||||
encryption.add(Saml2X509Credential.encryption(certificate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (verification.isEmpty()) {
|
|
||||||
throw new Saml2Exception(
|
|
||||||
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
|
||||||
}
|
|
||||||
RelyingPartyRegistration.Builder builder = RelyingPartyRegistration.withRegistrationId(descriptor.getEntityID())
|
|
||||||
.assertingPartyDetails((party) -> party.entityId(descriptor.getEntityID())
|
|
||||||
.wantAuthnRequestsSigned(Boolean.TRUE.equals(idpssoDescriptor.getWantAuthnRequestsSigned()))
|
|
||||||
.verificationX509Credentials((c) -> c.addAll(verification))
|
|
||||||
.encryptionX509Credentials((c) -> c.addAll(encryption)));
|
|
||||||
for (SingleSignOnService singleSignOnService : idpssoDescriptor.getSingleSignOnServices()) {
|
|
||||||
Saml2MessageBinding binding;
|
|
||||||
if (singleSignOnService.getBinding().equals(Saml2MessageBinding.POST.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.POST;
|
|
||||||
}
|
|
||||||
else if (singleSignOnService.getBinding().equals(Saml2MessageBinding.REDIRECT.getUrn())) {
|
|
||||||
binding = Saml2MessageBinding.REDIRECT;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
builder.assertingPartyDetails(
|
|
||||||
(party) -> party.singleSignOnServiceLocation(singleSignOnService.getLocation())
|
|
||||||
.singleSignOnServiceBinding(binding));
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
throw new Saml2Exception(
|
|
||||||
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<X509Certificate> certificates(KeyDescriptor keyDescriptor) {
|
|
||||||
try {
|
|
||||||
return KeyInfoSupport.getCertificates(keyDescriptor.getKeyInfo());
|
|
||||||
}
|
|
||||||
catch (CertificateException ex) {
|
|
||||||
throw new Saml2Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private EntityDescriptor entityDescriptor(InputStream inputStream) {
|
|
||||||
Document document = document(inputStream);
|
|
||||||
Element element = document.getDocumentElement();
|
|
||||||
Unmarshaller unmarshaller = this.registry.getUnmarshallerFactory().getUnmarshaller(element);
|
|
||||||
if (unmarshaller == null) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new Saml2Exception(ex);
|
|
||||||
}
|
|
||||||
throw new Saml2Exception("Unsupported element of type " + element.getTagName());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Document document(InputStream inputStream) {
|
|
||||||
try {
|
|
||||||
return this.parserPool.parse(inputStream);
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
throw new Saml2Exception(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,36 +16,48 @@
|
|||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
import org.springframework.core.io.DefaultResourceLoader;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
import org.springframework.security.saml2.Saml2Exception;
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
import org.springframework.web.client.RestClientException;
|
|
||||||
import org.springframework.web.client.RestOperations;
|
|
||||||
import org.springframework.web.client.RestTemplate;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class for constructing instances of {@link RelyingPartyRegistration}
|
* A utility class for constructing instances of {@link RelyingPartyRegistration}
|
||||||
*
|
*
|
||||||
* @author Josh Cummings
|
* @author Josh Cummings
|
||||||
|
* @author Ryan Cassar
|
||||||
* @since 5.4
|
* @since 5.4
|
||||||
*/
|
*/
|
||||||
public final class RelyingPartyRegistrations {
|
public final class RelyingPartyRegistrations {
|
||||||
|
|
||||||
private static final RestOperations rest = new RestTemplate(
|
private static final OpenSamlAssertingPartyMetadataConverter assertingPartyMetadataConverter = new OpenSamlAssertingPartyMetadataConverter();
|
||||||
Arrays.asList(new OpenSamlRelyingPartyRegistrationBuilderHttpMessageConverter()));
|
|
||||||
|
private static final ResourceLoader resourceLoader = new DefaultResourceLoader();
|
||||||
|
|
||||||
private RelyingPartyRegistrations() {
|
private RelyingPartyRegistrations() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0
|
* Return a {@link RelyingPartyRegistration.Builder} based off of the given SAML 2.0
|
||||||
* Asserting Party (IDP) metadata.
|
* 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,
|
* 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
|
* but this will most often not be sufficient. To complete the configuration, most
|
||||||
* applications will also need to provide a registrationId, like so:
|
* applications will also need to provide a registrationId, like so:
|
||||||
*
|
*
|
||||||
* <pre>
|
* <pre>
|
||||||
|
* String metadataLocation = "file:C:\\saml\\metadata.xml"
|
||||||
* RelyingPartyRegistration registration = RelyingPartyRegistrations
|
* RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||||
* .fromMetadataLocation(metadataLocation)
|
* .fromMetadataLocation(metadataLocation)
|
||||||
* .registrationId("registration-id")
|
* .registrationId("registration-id")
|
||||||
@ -56,14 +68,15 @@ public final class RelyingPartyRegistrations {
|
|||||||
* about the asserting party. Thus, you will need to remember to still populate
|
* 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
|
* anything about the relying party, like any private keys the relying party will use
|
||||||
* for signing AuthnRequests.
|
* for signing AuthnRequests.
|
||||||
* @param metadataLocation
|
* @param metadataLocation The classpath- or file-based locations or HTTP endpoints of
|
||||||
|
* the asserting party metadata file
|
||||||
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
|
* @return the {@link RelyingPartyRegistration.Builder} for further configuration
|
||||||
*/
|
*/
|
||||||
public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
|
public static RelyingPartyRegistration.Builder fromMetadataLocation(String metadataLocation) {
|
||||||
try {
|
try (InputStream source = resourceLoader.getResource(metadataLocation).getInputStream()) {
|
||||||
return rest.getForObject(metadataLocation, RelyingPartyRegistration.Builder.class);
|
return assertingPartyMetadataConverter.convert(source);
|
||||||
}
|
}
|
||||||
catch (RestClientException ex) {
|
catch (IOException ex) {
|
||||||
if (ex.getCause() instanceof Saml2Exception) {
|
if (ex.getCause() instanceof Saml2Exception) {
|
||||||
throw (Saml2Exception) ex.getCause();
|
throw (Saml2Exception) ex.getCause();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2020 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.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://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 org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import org.springframework.security.saml2.Saml2Exception;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
|
|
||||||
|
public class OpenSamlAssertingPartyMetadataConverterTests {
|
||||||
|
|
||||||
|
private static final String CERTIFICATE = "MIIEEzCCAvugAwIBAgIJAIc1qzLrv+5nMA0GCSqGSIb3DQEBCwUAMIGfMQswCQYDVQQGEwJVUzELMAkGA1UECAwCQ08xFDASBgNVBAcMC0Nhc3RsZSBSb2NrMRwwGgYDVQQKDBNTYW1sIFRlc3RpbmcgU2VydmVyMQswCQYDVQQLDAJJVDEgMB4GA1UEAwwXc2ltcGxlc2FtbHBocC5jZmFwcHMuaW8xIDAeBgkqhkiG9w0BCQEWEWZoYW5pa0BwaXZvdGFsLmlvMB4XDTE1MDIyMzIyNDUwM1oXDTI1MDIyMjIyNDUwM1owgZ8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDTzEUMBIGA1UEBwwLQ2FzdGxlIFJvY2sxHDAaBgNVBAoME1NhbWwgVGVzdGluZyBTZXJ2ZXIxCzAJBgNVBAsMAklUMSAwHgYDVQQDDBdzaW1wbGVzYW1scGhwLmNmYXBwcy5pbzEgMB4GCSqGSIb3DQEJARYRZmhhbmlrQHBpdm90YWwuaW8wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4cn62E1xLqpN34PmbrKBbkOXFjzWgJ9b+pXuaRft6A339uuIQeoeH5qeSKRVTl32L0gdz2ZivLwZXW+cqvftVW1tvEHvzJFyxeTW3fCUeCQsebLnA2qRa07RkxTo6Nf244mWWRDodcoHEfDUSbxfTZ6IExSojSIU2RnD6WllYWFdD1GFpBJOmQB8rAc8wJIBdHFdQnX8Ttl7hZ6rtgqEYMzYVMuJ2F2r1HSU1zSAvwpdYP6rRGFRJEfdA9mm3WKfNLSc5cljz0X/TXy0vVlAV95l9qcfFzPmrkNIst9FZSwpvB49LyAVke04FQPPwLgVH4gphiJH3jvZ7I+J5lS8VAgMBAAGjUDBOMB0GA1UdDgQWBBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAfBgNVHSMEGDAWgBTTyP6Cc5HlBJ5+ucVCwGc5ogKNGzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAvMS4EQeP/ipV4jOG5lO6/tYCb/iJeAduOnRhkJk0DbX329lDLZhTTL/x/w/9muCVcvLrzEp6PN+VWfw5E5FWtZN0yhGtP9R+vZnrV+oc2zGD+no1/ySFOe3EiJCO5dehxKjYEmBRv5sU/LZFKZpozKN/BMEa6CqLuxbzb7ykxVr7EVFXwltPxzE9TmL9OACNNyF5eJHWMRMllarUvkcXlh4pux4ks9e6zV9DQBy2zds9f1I3qxg0eX6JnGrXi/ZiCT+lJgVe3ZFXiejiLAiKB04sXW3ti0LW3lx13Y1YlQ4/tlpgTgfIJxKV6nyPiLoK0nywbMd+vpAirDt2Oc+hk";
|
||||||
|
|
||||||
|
private static final String ENTITIES_DESCRIPTOR_TEMPLATE = "<md:EntitiesDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\">\n%s</md:EntitiesDescriptor>";
|
||||||
|
|
||||||
|
private static final String ENTITY_DESCRIPTOR_TEMPLATE = "<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" "
|
||||||
|
+ "entityID=\"entity-id\" "
|
||||||
|
+ "ID=\"_bf133aac099b99b3d81286e1a341f2d34188043a77fe15bf4bf1487dae9b2ea3\">\n%s"
|
||||||
|
+ "</md:EntityDescriptor>";
|
||||||
|
|
||||||
|
private static final String IDP_SSO_DESCRIPTOR_TEMPLATE = "<md:IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n"
|
||||||
|
+ "%s\n" + "</md:IDPSSODescriptor>";
|
||||||
|
|
||||||
|
private static final String KEY_DESCRIPTOR_TEMPLATE = "<md:KeyDescriptor %s>\n"
|
||||||
|
+ "<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n" + "<ds:X509Data>\n"
|
||||||
|
+ "<ds:X509Certificate>" + CERTIFICATE + "</ds:X509Certificate>\n" + "</ds:X509Data>\n" + "</ds:KeyInfo>\n"
|
||||||
|
+ "</md:KeyDescriptor>";
|
||||||
|
|
||||||
|
private static final String SINGLE_SIGN_ON_SERVICE_TEMPLATE = "<md:SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" "
|
||||||
|
+ "Location=\"sso-location\"/>";
|
||||||
|
|
||||||
|
private OpenSamlAssertingPartyMetadataConverter converter;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
this.converter = new OpenSamlAssertingPartyMetadataConverter();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingIDPSSODescriptorThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, "");
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining("Metadata response is missing the necessary IDPSSODescriptor element");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingVerificationKeyThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE, String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, ""));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining(
|
||||||
|
"Metadata response is missing verification certificates, necessary for verifying SAML assertions");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenMissingSingleSignOnServiceThenException() {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE, String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")));
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessageContaining(
|
||||||
|
"Metadata response is missing a SingleSignOnService, necessary for sending AuthnRequests");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenDescriptorFullySpecifiedThenConfigures() throws Exception {
|
||||||
|
String payload = String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
||||||
|
+ 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.AssertingPartyDetails details = registration.getAssertingPartyDetails();
|
||||||
|
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
||||||
|
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
||||||
|
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||||
|
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
||||||
|
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-9051
|
||||||
|
@Test
|
||||||
|
public void readWhenEntitiesDescriptorThenConfigures() throws Exception {
|
||||||
|
String payload = String.format(ENTITIES_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(ENTITY_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(IDP_SSO_DESCRIPTOR_TEMPLATE,
|
||||||
|
String.format(KEY_DESCRIPTOR_TEMPLATE, "use=\"signing\"")
|
||||||
|
+ 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.AssertingPartyDetails details = registration.getAssertingPartyDetails();
|
||||||
|
assertThat(details.getWantAuthnRequestsSigned()).isFalse();
|
||||||
|
assertThat(details.getSingleSignOnServiceLocation()).isEqualTo("sso-location");
|
||||||
|
assertThat(details.getSingleSignOnServiceBinding()).isEqualTo(Saml2MessageBinding.REDIRECT);
|
||||||
|
assertThat(details.getEntityId()).isEqualTo("entity-id");
|
||||||
|
assertThat(details.getVerificationX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readWhenKeyDescriptorHasNoUseThenConfiguresBothKeyTypes() throws Exception {
|
||||||
|
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.AssertingPartyDetails details = registration.getAssertingPartyDetails();
|
||||||
|
assertThat(details.getVerificationX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
assertThat(details.getEncryptionX509Credentials()).hasSize(1);
|
||||||
|
assertThat(details.getEncryptionX509Credentials().iterator().next().getCertificate())
|
||||||
|
.isEqualTo(x509Certificate(CERTIFICATE));
|
||||||
|
}
|
||||||
|
|
||||||
|
X509Certificate x509Certificate(String data) {
|
||||||
|
try {
|
||||||
|
InputStream certificate = new ByteArrayInputStream(Base64.getDecoder().decode(data.getBytes()));
|
||||||
|
return (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(certificate);
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
throw new IllegalArgumentException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gh-9051
|
||||||
|
@Test
|
||||||
|
public void readWhenUnsupportedElementThenSaml2Exception() {
|
||||||
|
String payload = "<saml2:Assertion xmlns:saml2=\"https://some.endpoint\"/>";
|
||||||
|
InputStream inputStream = new ByteArrayInputStream(payload.getBytes());
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class).isThrownBy(() -> this.converter.convert(inputStream))
|
||||||
|
.withMessage("Unsupported element of type saml2:Assertion");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package org.springframework.security.saml2.provider.service.registration;
|
package org.springframework.security.saml2.provider.service.registration;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
import okhttp3.mockwebserver.MockResponse;
|
import okhttp3.mockwebserver.MockResponse;
|
||||||
import okhttp3.mockwebserver.MockWebServer;
|
import okhttp3.mockwebserver.MockWebServer;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -85,7 +87,7 @@ public class RelyingPartyRegistrationsTests {
|
|||||||
+ " </md:ContactPerson>\n" + " \n" + "</md:EntityDescriptor>";
|
+ " </md:ContactPerson>\n" + " \n" + "</md:EntityDescriptor>";
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void fromMetadataLocationWhenResolvableThenPopulatesBuilder() throws Exception {
|
public void fromMetadataUrlLocationWhenResolvableThenPopulatesBuilder() throws Exception {
|
||||||
try (MockWebServer server = new MockWebServer()) {
|
try (MockWebServer server = new MockWebServer()) {
|
||||||
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
|
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
|
||||||
RelyingPartyRegistration registration = RelyingPartyRegistrations
|
RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||||
@ -101,7 +103,7 @@ public class RelyingPartyRegistrationsTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void fromMetadataLocationWhenUnresolvableThenSaml2Exception() throws Exception {
|
public void fromMetadataUrlLocationWhenUnresolvableThenSaml2Exception() throws Exception {
|
||||||
try (MockWebServer server = new MockWebServer()) {
|
try (MockWebServer server = new MockWebServer()) {
|
||||||
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
|
server.enqueue(new MockResponse().setBody(IDP_SSO_DESCRIPTOR_PAYLOAD).setResponseCode(200));
|
||||||
String url = server.url("/").toString();
|
String url = server.url("/").toString();
|
||||||
@ -112,7 +114,7 @@ public class RelyingPartyRegistrationsTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void fromMetadataLocationWhenMalformedResponseThenSaml2Exception() throws Exception {
|
public void fromMetadataUrlLocationWhenMalformedResponseThenSaml2Exception() throws Exception {
|
||||||
try (MockWebServer server = new MockWebServer()) {
|
try (MockWebServer server = new MockWebServer()) {
|
||||||
server.enqueue(new MockResponse().setBody("malformed").setResponseCode(200));
|
server.enqueue(new MockResponse().setBody("malformed").setResponseCode(200));
|
||||||
String url = server.url("/").toString();
|
String url = server.url("/").toString();
|
||||||
@ -121,4 +123,24 @@ public class RelyingPartyRegistrationsTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fromMetadataFileLocationWhenResolvableThenPopulatesBuilder() {
|
||||||
|
File file = new File("src/test/resources/test-metadata.xml");
|
||||||
|
RelyingPartyRegistration registration = RelyingPartyRegistrations
|
||||||
|
.fromMetadataLocation("file:" + file.getAbsolutePath()).entityId("rp").build();
|
||||||
|
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 fromMetadataFileLocationWhenNotFoundThenSaml2Exception() {
|
||||||
|
assertThatExceptionOfType(Saml2Exception.class)
|
||||||
|
.isThrownBy(() -> RelyingPartyRegistrations.fromMetadataLocation("filePath"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
<md:EntityDescriptor entityID="https://idp.example.com/idp/shibboleth"
|
||||||
|
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: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>
|
Loading…
x
Reference in New Issue
Block a user