diff options
Diffstat (limited to 'eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java')
-rw-r--r-- | eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java new file mode 100644 index 00000000..0b505e56 --- /dev/null +++ b/eaaf_modules/eaaf_module_pvp2_core/src/main/java/at/gv/egiz/eaaf/modules/pvp2/impl/metadata/PvpMetadataResolverFactory.java @@ -0,0 +1,278 @@ +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); + + } + + } + +} |