package org.pac4j.saml.metadata; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.resolver.ResolverException; import net.shibboleth.utilities.java.support.xml.SerializeSupport; import net.shibboleth.utilities.java.support.xml.XMLParserException; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.core.xml.XMLObject; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.impl.DOMMetadataResolver; import org.opensaml.saml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.pac4j.core.exception.TechnicalException; import org.pac4j.core.util.CommonHelper; import org.pac4j.saml.client.SAML2ClientConfiguration; import org.pac4j.saml.exceptions.SAMLException; import org.pac4j.saml.util.Configuration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.Resource; import org.w3c.dom.Document; import org.w3c.dom.Element; import javax.annotation.Nullable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; /** * @author Misagh Moayyed * @since 1.7 */ public class SAML2IdentityProviderMetadataResolver implements SAML2MetadataResolver { protected final Logger logger = LoggerFactory.getLogger(getClass()); private final Resource idpMetadataResource; private String idpEntityId; private DOMMetadataResolver idpMetadataProvider; public SAML2IdentityProviderMetadataResolver(final SAML2ClientConfiguration configuration) { this(configuration.getIdentityProviderMetadataResource(), configuration.getIdentityProviderEntityId()); } public SAML2IdentityProviderMetadataResolver(final Resource idpMetadataResource, @Nullable final String idpEntityId) { CommonHelper.assertNotNull("idpMetadataResource", idpMetadataResource); this.idpMetadataResource = idpMetadataResource; this.idpEntityId = idpEntityId; } @Override public final MetadataResolver resolve() { // No locks are used since saml2client's init does in turn invoke resolve and idpMetadataProvider is set. // idpMetadataProvider is initialized by Saml2Client::internalInit->MetadataResolver::initIdentityProviderMetadataResolve->resolve // Usage of locks will adversly impact performance. if(idpMetadataProvider != null) { return idpMetadataProvider; } try { if (this.idpMetadataResource == null) { throw new XMLParserException("idp metadata cannot be resolved from " + this.idpMetadataResource); } try (final InputStream in = this.idpMetadataResource.getInputStream()) { final Document inCommonMDDoc = Configuration.getParserPool().parse(in); final Element metadataRoot = inCommonMDDoc.getDocumentElement(); idpMetadataProvider = new DOMMetadataResolver(metadataRoot); idpMetadataProvider.setParserPool(Configuration.getParserPool()); idpMetadataProvider.setFailFastInitialization(true); idpMetadataProvider.setRequireValidMetadata(true); idpMetadataProvider.setId(idpMetadataProvider.getClass().getCanonicalName()); idpMetadataProvider.initialize(); } catch (final FileNotFoundException e) { throw new TechnicalException("Error loading idp Metadata"); } // If no idpEntityId declared, select first EntityDescriptor entityId as our IDP entityId if (this.idpEntityId == null) { final Iterator<EntityDescriptor> it = idpMetadataProvider.iterator(); while (it.hasNext()) { final EntityDescriptor entityDescriptor = it.next(); if (SAML2IdentityProviderMetadataResolver.this.idpEntityId == null) { SAML2IdentityProviderMetadataResolver.this.idpEntityId = entityDescriptor.getEntityID(); } } } if (this.idpEntityId == null) { throw new SAMLException("No idp entityId found"); } } catch (final ComponentInitializationException e) { throw new SAMLException("Error initializing idpMetadataProvider", e); } catch (final XMLParserException e) { throw new TechnicalException("Error parsing idp Metadata", e); } catch (final IOException e) { throw new TechnicalException("Error getting idp Metadata resource", e); } return idpMetadataProvider; } @Override public String getEntityId() { final XMLObject md = getEntityDescriptorElement(); if (md instanceof EntitiesDescriptor) { return ((EntitiesDescriptor) md).getEntityDescriptors().get(0).getEntityID(); } else if (md instanceof EntityDescriptor) { return ((EntityDescriptor) md).getEntityID(); } throw new SAMLException("No idp entityId found"); } @Override public String getMetadataPath() { return idpMetadataResource.getFilename(); } @Override public String getMetadata() { if (getEntityDescriptorElement() != null && getEntityDescriptorElement().getDOM() != null) { return SerializeSupport.nodeToString(getEntityDescriptorElement().getDOM()); } throw new TechnicalException("Metadata cannot be retrieved because entity descriptor is null"); } @Override public final XMLObject getEntityDescriptorElement() { try { return resolve().resolveSingle(new CriteriaSet(new EntityIdCriterion(this.idpEntityId))); } catch (final ResolverException e) { throw new SAMLException("Error initializing idpMetadataProvider", e); } } }