package at.gv.egovernment.moa.id.auth.modules.eidas.engine; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Timer; import javax.xml.namespace.QName; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.MOAHttpClient; import org.apache.commons.httpclient.params.HttpClientParams; import org.opensaml.saml2.metadata.EntitiesDescriptor; import org.opensaml.saml2.metadata.EntityDescriptor; import org.opensaml.saml2.metadata.RoleDescriptor; import org.opensaml.saml2.metadata.provider.ChainingMetadataProvider; import org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider; import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataFilter; import org.opensaml.saml2.metadata.provider.MetadataProvider; import org.opensaml.saml2.metadata.provider.MetadataProviderException; import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider; import org.opensaml.xml.XMLObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import at.gv.egiz.eaaf.core.api.IDestroyableObject; import at.gv.egiz.eaaf.core.api.IGarbageCollectorProcessing; import at.gv.egiz.eaaf.core.api.IPostStartupInitializable; 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.IRefreshableMetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.MetadataFilterChain; import at.gv.egiz.eaaf.modules.pvp2.impl.metadata.SimpleMetadataProvider; import at.gv.egovernment.moa.id.auth.modules.eidas.Constants; import at.gv.egovernment.moa.id.commons.api.AuthConfiguration; import at.gv.egovernment.moa.id.commons.api.exceptions.ConfigurationException; import at.gv.egovernment.moa.id.commons.ex.MOAHttpProtocolSocketFactoryException; import at.gv.egovernment.moa.id.commons.utils.MOAHttpProtocolSocketFactory; import at.gv.egovernment.moa.id.protocols.pvp2x.PVPConstants; import at.gv.egovernment.moa.id.protocols.pvp2x.verification.metadata.MOASPMetadataSignatureFilter; import at.gv.egovernment.moa.logging.Logger; import at.gv.egovernment.moa.util.MiscUtil; import eu.eidas.auth.engine.AbstractProtocolEngine; @Service("eIDASMetadataProvider") public class MOAeIDASChainingMetadataProvider extends SimpleMetadataProvider implements ObservableMetadataProvider, IGarbageCollectorProcessing, IDestroyableObject, IRefreshableMetadataProvider, IPostStartupInitializable{ @Autowired(required=true) IConfiguration basicConfig; private Timer timer = null; private MetadataProvider internalProvider; private Map lastAccess = null; public MOAeIDASChainingMetadataProvider() { internalProvider = new ChainingMetadataProvider(); lastAccess = new HashMap(); } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.auth.IPostStartupInitializable#executeAfterStartup() */ @Override public void executeAfterStartup() { try { initializeEidasMetadataFromFileSystem(); } catch (ConfigurationException e) { Logger.error("Post start-up initialization of eIDAS Metadata-Provider FAILED.", e); } } protected void initializeEidasMetadataFromFileSystem() throws ConfigurationException { try { Map metadataToLoad = authConfig.getBasicMOAIDConfigurationWithPrefix(Constants.CONIG_PROPS_EIDAS_METADATA_URLS_LIST_PREFIX); if (!metadataToLoad.isEmpty()) { Logger.info("Load static configurated eIDAS metadata ... "); for (String metaatalocation : metadataToLoad.values()) { String absMetadataLocation = FileUtils.makeAbsoluteURL(metaatalocation, authConfig.getConfigurationRootDirectory()); Logger.info(" Load eIDAS metadata from: " + absMetadataLocation); refreshMetadataProvider(absMetadataLocation); } Logger.info("Load static configurated eIDAS metadata finished "); } } catch (MalformedURLException e) { Logger.warn("MOA-ID configuration error." , e); throw new ConfigurationException("MOA-ID configuration error.", null, e); } } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.auth.IDestroyableObject#fullyDestroy() */ @Override public void fullyDestroy() { if (timer != null) timer.cancel(); Map loadedproviders = getAllActuallyLoadedProviders(); if (loadedproviders != null) { for (Entry el : loadedproviders.entrySet()) { try { el.getValue().destroy(); Logger.debug("Destroy eIDAS Matadataprovider: " + el.getKey() + " finished"); } catch (Exception e) { Logger.warn("Destroy eIDAS Matadataprovider: " + el.getKey() + " FAILED"); } } } } /* (non-Javadoc) * @see at.gv.egovernment.moa.id.config.auth.IGarbageCollectorProcessing#runGarbageCollector() */ @Override public void runGarbageCollector() { if (!lastAccess.isEmpty()) { Date now = new Date(); Date expioredate = new Date(now.getTime() - Constants.CONFIG_PROPS_METADATA_GARBAGE_TIMEOUT); Logger.debug("Starting eIDAS Metadata garbag collection (Expioredate:" + expioredate + ")"); List expiredEntities = new ArrayList(); Iterator> lastAccessInterator = lastAccess.entrySet().iterator(); while(lastAccessInterator.hasNext()) { Entry element = lastAccessInterator.next(); if (element.getValue().before(expioredate)) { Logger.debug("Remove unused eIDAS Metadate: " + element.getKey()); expiredEntities.add(element.getKey()); } } ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; boolean isUpdateRequired = false; //get all actually loaded metadata providers Map loadedproviders = getAllActuallyLoadedProviders(); if (!expiredEntities.isEmpty()) { for (String expired : expiredEntities) { if (loadedproviders.containsKey(expired)) { HTTPMetadataProvider provider = loadedproviders.get(expired); //destroy metadata provider provider.destroy(); //remove from map loadedproviders.remove(expired); isUpdateRequired = true; /*OpenSAML ChainingMetadataProvider can not remove a MetadataProvider (UnsupportedOperationException) *The ChainingMetadataProvider use internal a unmodifiableList to hold all registrated MetadataProviders.*/ //chainProvider.removeMetadataProvider(provider); Logger.info("Remove not used eIDAS MetadataProvider " + expired + " after timeout."); } else Logger.info("eIDAS metadata for EntityID: " + expired + " is marked as expired, but no currently loaded HTTPMetadataProvider metadata provider is found."); } } //check signature of all metadata which are actually loaded List nonValidMetadataProvider = new ArrayList(); for (HTTPMetadataProvider provider : loadedproviders.values()) { try { provider.refresh(); //provider.getMetadataFilter().doFilter(provider.getMetadata()); } catch (MetadataProviderException e) { Logger.info("eIDAS MetadataProvider: " + provider.getMetadataURI() + " is not valid any more. Reason:" + e.getMessage()); if (Logger.isDebugEnabled()) Logger.warn("Reason", e); nonValidMetadataProvider.add(provider.getMetadataURI()); } } for (String el : nonValidMetadataProvider) { HTTPMetadataProvider provider = loadedproviders.get(el); //destroy metadata provider if (provider != null) { provider.destroy(); loadedproviders.remove(el); isUpdateRequired = true; } else { Logger.error("Can not destroy eIDAS metadata for: " + el + " Reason: !!!!!NOT FOUND ANY MORE!!!!!!"); } } //update chaining metadata-provider if it is required if (isUpdateRequired) { try { synchronized (chainProvider) { chainProvider.setProviders(new ArrayList(loadedproviders.values())); emitChangeEvent(); } } catch (MetadataProviderException e) { Logger.warn("ReInitalize eIDASA MetaDataProvider is not possible! MOA-ID Instance has to be restarted manualy", e); } } } } private MetadataProvider createNewHTTPMetaDataProvider(String metadataURL) { if (timer == null) timer = new Timer(true); //add Metadata filters MetadataFilterChain filter = new MetadataFilterChain(); filter.addFilter(new MOASPMetadataSignatureFilter( authConfig.getBasicConfiguration(Constants.CONIG_PROPS_EIDAS_METADATA_VALIDATION_TRUSTSTORE))); return createNewSimpleMetadataProvider(metadataURL, filter, "eIDAS metadata-provider", timer, AbstractProtocolEngine.getSecuredParserPool(), createHttpClient(metadataURL)); } private Map getAllActuallyLoadedProviders() { Map loadedproviders = new HashMap(); ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; //make a Map of all actually loaded HTTPMetadataProvider List providers = chainProvider.getProviders(); for (MetadataProvider provider : providers) { if (provider instanceof HTTPMetadataProvider) { HTTPMetadataProvider httpprovider = (HTTPMetadataProvider) provider; loadedproviders.put(httpprovider.getMetadataURI(), httpprovider); } else if (provider instanceof FilesystemMetadataProvider) { String entityID = "'!!NO-ENTITYID!!'"; try { if (provider.getMetadata() instanceof EntityDescriptor) entityID = ((EntityDescriptor)provider.getMetadata()).getEntityID(); Logger.debug("Skip eIDAS metadata: " + entityID + " because it is loaded from local Filesystem"); } catch (MetadataProviderException e) { Logger.info("Collect currently loaded eIDAS metadata provider has an internel process error: " + e.getMessage()); } } else Logger.info("Skip " + provider.getClass().getName() + " from list of currently loaded " + "eIDAS metadata provider"); } Logger.debug("Find #" + loadedproviders.size() + " eIDAS metadata provider"); return loadedproviders; } public boolean refreshMetadataProvider(String metadataURL) { try { if (MiscUtil.isNotEmpty(metadataURL)) { Map actuallyLoadedProviders = getAllActuallyLoadedProviders(); // check if MetadataProvider is actually loaded if (actuallyLoadedProviders.containsKey(metadataURL)) { actuallyLoadedProviders.get(metadataURL).refresh(); Logger.info("eIDAS metadata for " + metadataURL + " is refreshed."); return true; } else { //load new Metadata Provider ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; MetadataProvider newMetadataProvider = createNewHTTPMetaDataProvider(metadataURL); if (newMetadataProvider != null) { chainProvider.addMetadataProvider(newMetadataProvider); emitChangeEvent(); Logger.info("eIDAS metadata for " + metadataURL + " is added."); return true; } else Logger.warn("Can not load eIDAS metadata from URL: " + metadataURL); } } else Logger.debug("Can not refresh eIDAS metadata: NO eIDAS metadata URL."); } catch (MetadataProviderException e) { Logger.warn("Refresh eIDAS metadata for " + metadataURL + " FAILED.", e); } return false; } public boolean requireValidMetadata() { return internalProvider.requireValidMetadata(); } public void setRequireValidMetadata(boolean requireValidMetadata) { internalProvider.setRequireValidMetadata(requireValidMetadata); } public MetadataFilter getMetadataFilter() { return internalProvider.getMetadataFilter(); } public void setMetadataFilter(MetadataFilter newFilter) throws MetadataProviderException { internalProvider.setMetadataFilter(newFilter); } public XMLObject getMetadata() throws MetadataProviderException { return internalProvider.getMetadata(); } public EntitiesDescriptor getEntitiesDescriptor(String entitiesID) throws MetadataProviderException { Logger.warn("eIDAS metadata not support 'EntitiesDescriptor' elements!"); return null; } public EntityDescriptor getEntityDescriptor(String entityID) throws MetadataProviderException { EntityDescriptor entityDesc = null; try { entityDesc = internalProvider.getEntityDescriptor(entityID); if (entityDesc == null) { Logger.debug("Can not find eIDAS metadata for entityID: " + entityID + " Start refreshing process ..."); if (refreshMetadataProvider(entityID)) entityDesc = internalProvider.getEntityDescriptor(entityID); } else { if (!entityDesc.isValid()) if (refreshMetadataProvider(entityID)) entityDesc = internalProvider.getEntityDescriptor(entityID); } } catch (MetadataProviderException e) { Logger.debug("Can not find eIDAS metadata for entityID: " + entityID + " Start refreshing process ..."); if (refreshMetadataProvider(entityID)) entityDesc = internalProvider.getEntityDescriptor(entityID); } if (entityDesc != null) lastAccess.put(entityID, new Date()); return entityDesc; } public List getRole(String entityID, QName roleName) throws MetadataProviderException { EntityDescriptor entityDesc = getEntityDescriptor(entityID); if (entityDesc != null) return entityDesc.getRoleDescriptors(roleName); else return null; } public RoleDescriptor getRole(String entityID, QName roleName, String supportedProtocol) throws MetadataProviderException { EntityDescriptor entityDesc = getEntityDescriptor(entityID); if (entityDesc != null) return internalProvider.getRole(entityID, roleName, supportedProtocol); else return null; } /* (non-Javadoc) * @see org.opensaml.saml2.metadata.provider.ObservableMetadataProvider#getObservers() */ @Override public List getObservers() { return ((ChainingMetadataProvider) internalProvider).getObservers(); } protected void emitChangeEvent() { if ((getObservers() == null) || (getObservers().size() == 0)) { return; } List tempObserverList = new ArrayList(getObservers()); for (ObservableMetadataProvider.Observer observer : tempObserverList) if (observer != null) observer.onEvent(this); } private HttpClient createHttpClient(String metadataURL) { MOAHttpClient httpClient = new MOAHttpClient(); HttpClientParams httpClientParams = new HttpClientParams(); httpClientParams.setSoTimeout(AuthConfiguration.CONFIG_PROPS_METADATA_SOCKED_TIMEOUT); httpClient.setParams(httpClientParams); if (metadataURL.startsWith("https:")) { try { if (basicConfig instanceof AuthConfiguration) { AuthConfiguration moaAuthConfig = (AuthConfiguration) basicConfig; //FIX: change hostname validation default flag to true when httpClient is updated to > 4.4 MOAHttpProtocolSocketFactory protoSocketFactory = new MOAHttpProtocolSocketFactory( PVPConstants.SSLSOCKETFACTORYNAME, basicConfig.getBasicMOAIDConfigurationBoolean( AuthConfiguration.PROP_KEY_SSL_USE_JVM_TRUSTSTORE, false), moaAuthConfig.getTrustedCACertificates(), null, AuthConfiguration.DEFAULT_X509_CHAININGMODE, moaAuthConfig.isTrustmanagerrevoationchecking(), moaAuthConfig.getRevocationMethodOrder(), moaAuthConfig.getBasicMOAIDConfigurationBoolean( AuthConfiguration.PROP_KEY_SSL_HOSTNAME_VALIDATION, false)); httpClient.setCustomSSLTrustStore(metadataURL, protoSocketFactory); } } catch (MOAHttpProtocolSocketFactoryException | MalformedURLException e) { Logger.warn("MOA SSL-TrustStore can not initialized. Use default Java TrustStore.", e); } } return httpClient; } }