package org.apereo.cas.support.saml.mdui; import com.google.common.base.Throwables; import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import org.apereo.cas.support.saml.OpenSamlConfigBean; import org.opensaml.core.criterion.EntityIdCriterion; import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.opensaml.saml.metadata.resolver.filter.MetadataFilterChain; import org.opensaml.saml.metadata.resolver.impl.DOMMetadataResolver; import org.opensaml.saml.saml2.metadata.EntityDescriptor; 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 java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * This is {@link AbstractMetadataResolverAdapter} that encapsulates * commons between static and dynamic resolvers. * * @author Misagh Moayyed * @since 4.1.0 */ public abstract class AbstractMetadataResolverAdapter implements MetadataResolverAdapter { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMetadataResolverAdapter.class); /** * Metadata resources along with filters to perform validation. */ protected Map<Resource, MetadataFilterChain> metadataResources; /** * Whether the metadata resolver should require valid metadata. Default is true. */ protected boolean requireValidMetadata = true; /** * The openSAML config bean. **/ protected OpenSamlConfigBean configBean; private ChainingMetadataResolver metadataResolver; private final Object lock = new Object(); /** * Instantiates a new abstract metadata resolver adapter. */ public AbstractMetadataResolverAdapter() { this.metadataResources = new HashMap<>(); } /** * Instantiates a new static metadata resolver adapter. * * @param metadataResources the metadata resources */ public AbstractMetadataResolverAdapter(final Map<Resource, MetadataFilterChain> metadataResources) { this.metadataResources = metadataResources; } public void setRequireValidMetadata(final boolean requireValidMetadata) { this.requireValidMetadata = requireValidMetadata; } /** * Retrieve the remote source's input stream to parse data. * * @param resource the resource * @param entityId the entity id * @return the input stream * @throws IOException if stream cannot be read */ protected InputStream getResourceInputStream(final Resource resource, final String entityId) throws IOException { LOGGER.debug("Locating metadata resource from input stream."); if (!resource.exists() || !resource.isReadable()) { throw new FileNotFoundException("Resource does not exist or is unreadable"); } return resource.getInputStream(); } @Override public EntityDescriptor getEntityDescriptorForEntityId(final String entityId) { try { final CriteriaSet criterions = new CriteriaSet(new EntityIdCriterion(entityId)); if (this.metadataResolver != null) { return this.metadataResolver.resolveSingle(criterions); } } catch (final Exception ex) { throw Throwables.propagate(ex); } return null; } /** * Build metadata resolver aggregate. */ public void buildMetadataResolverAggregate() { buildMetadataResolverAggregate(null); } /** * Build metadata resolver aggregate. Loops through metadata resources * and attempts to resolve the metadata. * * @param entityId the entity id */ public void buildMetadataResolverAggregate(final String entityId) { try { LOGGER.debug("Building metadata resolver aggregate"); this.metadataResolver = new ChainingMetadataResolver(); final List<MetadataResolver> resolvers = new ArrayList<>(); final Set<Map.Entry<Resource, MetadataFilterChain>> entries = this.metadataResources.entrySet(); entries.forEach(entry -> { final Resource resource = entry.getKey(); LOGGER.debug("Loading [{}]", resource.getFilename()); resolvers.addAll(loadMetadataFromResource(entry.getValue(), resource, entityId)); }); synchronized (this.lock) { this.metadataResolver.setId(ChainingMetadataResolver.class.getCanonicalName()); this.metadataResolver.setResolvers(resolvers); LOGGER.info("Collected metadata from [{}] resolvers(s). Initializing aggregate resolver...", resolvers.size()); this.metadataResolver.initialize(); LOGGER.info("Metadata aggregate initialized successfully."); } } catch (final Exception ex) { throw Throwables.propagate(ex); } } /** * Load metadata from resource. * * @param metadataFilter the metadata filter * @param resource the resource * @param entityId the entity id * @return the list */ private List<MetadataResolver> loadMetadataFromResource(final MetadataFilter metadataFilter, final Resource resource, final String entityId) { LOGGER.debug("Evaluating metadata resource [{}]", resource.getFilename()); try (InputStream in = getResourceInputStream(resource, entityId)) { if (in.available() > 0) { LOGGER.debug("Parsing [{}]", resource.getFilename()); final Document document = this.configBean.getParserPool().parse(in); return buildSingleMetadataResolver(metadataFilter, resource, document); } LOGGER.warn("Input stream from resource [{}] appears empty. Moving on...", resource.getFilename()); } catch (final Exception e) { LOGGER.warn("Could not retrieve input stream from resource. Moving on...", e); } return new ArrayList<>(); } /** * Build single metadata resolver. * * @param metadataFilterChain the metadata filters chained together * @param resource the resource * @param document the xml document to parse * @return list of resolved metadata from resources. * @throws IOException the iO exception */ private List<MetadataResolver> buildSingleMetadataResolver(final MetadataFilter metadataFilterChain, final Resource resource, final Document document) throws IOException { try { final Element metadataRoot = document.getDocumentElement(); final DOMMetadataResolver metadataProvider = new DOMMetadataResolver(metadataRoot); metadataProvider.setParserPool(this.configBean.getParserPool()); metadataProvider.setFailFastInitialization(true); metadataProvider.setRequireValidMetadata(this.requireValidMetadata); metadataProvider.setId(metadataProvider.getClass().getCanonicalName()); if (metadataFilterChain != null) { metadataProvider.setMetadataFilter(metadataFilterChain); } LOGGER.debug("Initializing metadata resolver for [{}]", resource); metadataProvider.initialize(); final List<MetadataResolver> resolvers = new ArrayList<>(); resolvers.add(metadataProvider); return resolvers; } catch (final Exception ex) { LOGGER.warn("Could not initialize metadata resolver. Resource will be ignored", ex); } return new ArrayList<>(); } public void setMetadataResources(final Map<Resource, MetadataFilterChain> metadataResources) { this.metadataResources = metadataResources; } public void setConfigBean(final OpenSamlConfigBean configBean) { this.configBean = configBean; } }