package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; import java.io.IOException; import java.util.Timer; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.net.ssl.SSLHandshakeException; import at.gv.egiz.components.spring.api.IDestroyableObject; import at.gv.egiz.eaaf.core.api.idp.IConfiguration; import at.gv.egiz.eaaf.core.impl.utils.FileUtils; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvp2MetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.exception.Pvp2MetadataException; import at.gv.egiz.eaaf.modules.pvp2.exception.SchemaValidationException; import at.gv.egiz.eaaf.modules.pvp2.exception.SignatureValidationException; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.OpenSaml3ResourceAdapter; import org.apache.http.client.HttpClient; import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport; import org.opensaml.saml.metadata.resolver.ExtendedRefreshableMetadataResolver; import org.opensaml.saml.metadata.resolver.filter.MetadataFilter; import org.opensaml.saml.metadata.resolver.impl.AbstractReloadingMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; import com.google.common.base.Predicates; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; import lombok.extern.slf4j.Slf4j; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; import net.shibboleth.utilities.java.support.resolver.ResolverException; import net.shibboleth.utilities.java.support.resource.Resource; import net.shibboleth.utilities.java.support.xml.ParserPool; @Slf4j public class PvpMetadataResolverFactory implements IDestroyableObject { private static final String URI_PREFIX_HTTP = "http:"; private static final String URI_PREFIX_HTTPS = "https:"; private static final String NOT_SUCCESS = "Maybe metadata was expired"; private Timer timer = null; @Autowired private IConfiguration authConfig; @Autowired private ResourceLoader resourceLoader; /** * Create a single SAML2 metadata provider by using the default OpenSAML3 * parser-pool. * * @param metadataLocation where the metadata should be loaded, but never null. * If the location starts with http(s):, than a http * based metadata provider is used. If the location * starts with file:, than a filesystem based metadata * provider is used * @param filter Filters, which should be used to validate the * metadata * @param idForLogging Id, which is used for Logging * @param httpClient Apache commons 4.x http client * * @return SAML2 Metadata Provider, or null if the metadata provider can not * initialized * @throws Pvp2MetadataException In case of an initialization error */ @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, @Nullable final HttpClient httpClient) throws Pvp2MetadataException { return createMetadataProvider(metadataLocation, filter, idForLogging, XMLObjectProviderRegistrySupport.getParserPool(), httpClient); } /** * Create a single SAML2 metadata provider. * * @param metadataLocation where the metadata should be loaded, but never null. * If the location starts with http(s):, than a http * based metadata provider is used. If the location * starts with file:, than a filesystem based metadata * provider is used * @param filter Filters, which should be used to validate the * metadata * @param idForLogging Id, which is used for Logging * @param httpClient Apache commons 4.x http client * * @return SAML2 Metadata Provider, or null if the metadata provider can not * initialized * @throws Pvp2MetadataException In case of an initialization error */ @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, @Nullable final ParserPool pool, @Nullable final HttpClient httpClient) throws Pvp2MetadataException { ExtendedRefreshableMetadataResolver internalProvider = null; try { if (metadataLocation.startsWith(URI_PREFIX_HTTP) || metadataLocation.startsWith(URI_PREFIX_HTTPS)) { internalProvider = createNewHttpMetaDataProvider(metadataLocation, filter, idForLogging, timer, pool, httpClient); } else { final String absoluteMetadataLocation = FileUtils.makeAbsoluteUrl(metadataLocation, authConfig.getConfigurationRootDirectory()); final org.springframework.core.io.Resource resource = resourceLoader.getResource(absoluteMetadataLocation); if (resource.exists()) { internalProvider = createNewFileSystemMetaDataProvider( new OpenSaml3ResourceAdapter(resource), filter, idForLogging, timer, pool); } else { log.warn( "SAML2 metadata file: {} not found or not exist", absoluteMetadataLocation); throw new Pvp2MetadataException("internal.pvp.05", new Object[] { absoluteMetadataLocation, "File NOT found or exist." }); } } } catch (final ComponentInitializationException e) { log.warn("Failed to load Metadata file for {} [ {} ]", idForLogging, e.getMessage()); checkResolverInitializationError(e, metadataLocation); } catch (final Exception e) { throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, e.getMessage() }); } if (!internalProvider.wasLastRefreshSuccess()) { log.info("Metadata loading from source: {} failed. {}", metadataLocation, NOT_SUCCESS); throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, NOT_SUCCESS }); } return new PvpMetadataResolverAdapter(internalProvider); } @Override public void fullyDestroy() { if (timer != null) { log.info("Stopping timer-thread for PVP metadata resolver ... "); timer.cancel(); } } @PostConstruct private void initialize() { log.info("Initializing timer-thread for PVP metadata resolver ... "); timer = new Timer("PVP metadata-resolver refresh"); } /** * Create a single SAML2 filesystem based metadata provider. * * @param metadataFile File, where the metadata should be loaded * @param filter Filters, which should be used to validate the metadata * @param idForLogging Id, which is used for Logging * @param timer {@link Timer} which is used to schedule metadata refresh * operations * @param pool SAML2 parser pool that should be used * * @return SAML2 Metadata Provider * @throws IOException In case of a metadata resource error * @throws ComponentInitializationException In case of a metadata resolver * initialization error */ private ExtendedRefreshableMetadataResolver createNewFileSystemMetaDataProvider(final Resource metadataFile, final MetadataFilter filter, final String idForLogging, final Timer timer, final ParserPool pool) throws IOException, ComponentInitializationException { ResourceBackedMetadataResolver fileSystemResolver = null; fileSystemResolver = new ResourceBackedMetadataResolver(timer, metadataFile); injectMetadataResolverConfiguration(fileSystemResolver, filter, pool); fileSystemResolver.setId(metadataFile.getURI().toASCIIString()); fileSystemResolver.initialize(); log.trace("Set-up metadata-resolver with ID: {} as: {}", idForLogging, fileSystemResolver.getClass().getSimpleName()); return fileSystemResolver; } /** * Create a single SAML2 HTTP metadata provider. * * @param metadataUrl URL, where the metadata should be loaded * @param filter Filters, which should be used to validate the metadata * @param idForLogging Id, which is used for Logging * @param timer {@link Timer} which is used to schedule metadata refresh * operations * @param pool SAML2 parser pool that should be used * @return SAML2 Metadata Provider * @throws ComponentInitializationException In case of a metadata resolver * initialization error * @throws ResolverException In case of an internal OpenSAML * resolver error */ private ExtendedRefreshableMetadataResolver createNewHttpMetaDataProvider(final String metadataUrl, final MetadataFilter filter, final String idForLogging, final Timer timer, final ParserPool pool, final HttpClient httpClient) throws ComponentInitializationException, ResolverException { HTTPMetadataResolver httpMetadataResolver = null; httpMetadataResolver = new HTTPMetadataResolver(timer, httpClient, metadataUrl); injectMetadataResolverConfiguration(httpMetadataResolver, filter, pool); httpMetadataResolver.setId(metadataUrl); httpMetadataResolver.initialize(); log.trace("Set-up metadata-resolver with ID: {} as: {}", idForLogging, httpMetadataResolver.getClass().getSimpleName()); return httpMetadataResolver; } private void injectMetadataResolverConfiguration(AbstractReloadingMetadataResolver resolver, final MetadataFilter filter, final ParserPool pool) { if (pool != null) { resolver.setParserPool(pool); } else { resolver.setParserPool( XMLObjectProviderRegistrySupport.getParserPool()); } resolver.setRequireValidMetadata(true); resolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes resolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours resolver.setMetadataFilter(filter); } private void checkResolverInitializationError(ComponentInitializationException e, String metadataLocation) throws Pvp2MetadataException { if (FluentIterable.from(Throwables.getCausalChain(e)).filter( Predicates.instanceOf(SSLHandshakeException.class)).first().isPresent()) { log.info("SSL-Server certificate for metadata: {} not trusted.", metadataLocation, null, e); throw new Pvp2MetadataException("internal.pvp.06", new Object[] { metadataLocation, e.getMessage() }, e); } else if (FluentIterable.from(Throwables.getCausalChain(e)).filter( Predicates.instanceOf(SignatureValidationException.class)).first().isPresent()) { log.info("Signature verification for metadata: {} FAILED.", metadataLocation, null, e); throw new Pvp2MetadataException("internal.pvp.07", new Object[] { metadataLocation, e.getMessage() }, e); } else if (FluentIterable.from(Throwables.getCausalChain(e)).filter( Predicates.instanceOf(SchemaValidationException.class)).first().isPresent()) { log.info("Schema validation for metadata: {} FAILED.", metadataLocation, null, e); throw new Pvp2MetadataException("internal.pvp.08", new Object[] { metadataLocation, e.getMessage() }, e); } else { log.info("Generic initialization error for metadata: {}", metadataLocation, null, e); throw new Pvp2MetadataException("internal.pvp.09", new Object[] { metadataLocation, e.getMessage() }, e); } } }