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 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.HTTPMetadataResolver; import org.opensaml.saml.metadata.resolver.impl.ResourceBackedMetadataResolver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ResourceLoader; 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.SchemaValidationException; import at.gv.egiz.eaaf.modules.pvp2.exception.SignatureValidationException; import at.gv.egiz.eaaf.modules.pvp2.impl.opensaml.OpenSaml3ResourceAdapter; import lombok.extern.slf4j.Slf4j; 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 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 */ @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, @Nullable final HttpClient httpClient) { 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 */ @Nullable public IPvp2MetadataProvider createMetadataProvider(@Nonnull final String metadataLocation, @Nullable final MetadataFilter filter, @Nonnull final String idForLogging, @Nullable final ParserPool pool, @Nullable final HttpClient httpClient) { ExtendedRefreshableMetadataResolver internalProvider = null; if (metadataLocation.startsWith(URI_PREFIX_HTTP) || metadataLocation.startsWith(URI_PREFIX_HTTPS)) { if (httpClient != null) { internalProvider = createNewHttpMetaDataProvider(metadataLocation, filter, idForLogging, timer, pool, httpClient); } else { log.warn("Can not load http(s) based SAML2 metadata without a HTTP client"); } } else { String absoluteMetadataLocation; try { absoluteMetadataLocation = FileUtils.makeAbsoluteUrl(metadataLocation, authConfig.getConfigurationRootDirectory()); 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: " + absoluteMetadataLocation + " not found or not exist"); } } catch (final IOException e) { log.warn("SAML2 metadata URL is invalid: " + metadataLocation, e); } } if (internalProvider != null) { return new PvpMetadataResolverAdapter(internalProvider); } else { log.warn("SAML2 metadata has an unsupported metadata location prefix: " + metadataLocation); return null; } } /** * 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 * * @return SAML2 Metadata Provider * @throws IOException */ private ExtendedRefreshableMetadataResolver createNewFileSystemMetaDataProvider(final Resource metadataFile, final MetadataFilter filter, final String idForLogging, final Timer timer, final ParserPool pool) throws IOException { ResourceBackedMetadataResolver fileSystemResolver = null; try { //fileSystemResolver = new FilesystemMetadataResolver(timer, metadataFile); fileSystemResolver = new ResourceBackedMetadataResolver(timer, metadataFile); if (pool != null) { fileSystemResolver.setParserPool(pool); } else { fileSystemResolver.setParserPool( XMLObjectProviderRegistrySupport.getParserPool()); } fileSystemResolver.setRequireValidMetadata(true); fileSystemResolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes fileSystemResolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours fileSystemResolver.setMetadataFilter(filter); fileSystemResolver.initialize(); fileSystemResolver.setId(metadataFile.getURI().toASCIIString()); fileSystemResolver.setRequireValidMetadata(true); return fileSystemResolver; } catch (final Exception e) { log.warn("Failed to load Metadata file for " + idForLogging + "[ " + "File: " + metadataFile.getURI().toASCIIString() + " Msg: " + e.getMessage() + " ]", e); log.warn("Can not initialize SAML2 metadata provider from filesystem: " + metadataFile.getURI().toASCIIString() + " Reason: " + e.getMessage(), e); if (fileSystemResolver != null) { fileSystemResolver.destroy(); } } return null; } /** * 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 * * @return SAML2 Metadata Provider */ private ExtendedRefreshableMetadataResolver createNewHttpMetaDataProvider(final String metadataUrl, final MetadataFilter filter, final String idForLogging, final Timer timer, final ParserPool pool, final HttpClient httpClient) { HTTPMetadataResolver httpMetadataResolver = null; try { httpMetadataResolver = new HTTPMetadataResolver(timer, httpClient, metadataUrl); httpMetadataResolver.setParserPool(pool); httpMetadataResolver.setRequireValidMetadata(true); httpMetadataResolver.setMinRefreshDelay(1000 * 60 * 15); // 15 minutes httpMetadataResolver.setMaxRefreshDelay(1000 * 60 * 60 * 24); // 24 hours // httpProvider.setRefreshDelayFactor(0.1F); httpMetadataResolver.setMetadataFilter(filter); httpMetadataResolver.setId(metadataUrl); httpMetadataResolver.initialize(); httpMetadataResolver.setRequireValidMetadata(true); return httpMetadataResolver; } catch (final Throwable e) { if (e.getCause() != null && e.getCause().getCause() instanceof SSLHandshakeException) { log.warn("SSL-Server certificate for metadata " + metadataUrl + " not trusted.", e); } if (e.getCause() != null && e.getCause().getCause() instanceof SignatureValidationException) { log.warn("Signature verification for metadata" + metadataUrl + " FAILED.", e); } if (e.getCause() != null && e.getCause().getCause() instanceof SchemaValidationException) { log.warn("Schema validation for metadata " + metadataUrl + " FAILED.", e); } log.warn("Failed to load Metadata file for " + idForLogging + "[ " + e.getMessage() + " ]", e); if (httpMetadataResolver != null) { log.debug("Destroy failed Metadata provider"); httpMetadataResolver.destroy(); } } return null; } @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"); } }