/* * Copyright 2017 Graz University of Technology EAAF-Core Components has been developed in a * cooperation between EGIZ, A-SIT Plus, A-SIT, and Graz University of Technology. * * Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European * Commission - subsequent versions of the EUPL (the "Licence"); You may not use this work except in * compliance with the Licence. You may obtain a copy of the Licence at: * https://joinup.ec.europa.eu/news/understanding-eupl-v12 * * Unless required by applicable law or agreed to in writing, software distributed under the Licence * is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the Licence for the specific language governing permissions and limitations under * the Licence. * * This product combines work with different licenses. See the "NOTICE" text file for details on the * various modules and licenses. The "NOTICE" text file is part of the distribution. Any derivative * works that you distribute must include a readable copy of the "NOTICE" text file. */ package at.gv.egiz.eaaf.modules.pvp2.impl.metadata; import java.io.IOException; import java.security.cert.CertificateException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Timer; import javax.xml.namespace.QName; import at.gv.egiz.components.spring.api.IDestroyableObject; import at.gv.egiz.eaaf.core.api.IGarbageCollectorProcessing; import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IPvpMetadataProvider; import at.gv.egiz.eaaf.modules.pvp2.api.metadata.IRefreshableMetadataProvider; import org.apache.commons.lang3.StringUtils; 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.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.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractChainingMetadataProvider extends SimpleMetadataProvider implements ObservableMetadataProvider, IGarbageCollectorProcessing, IRefreshableMetadataProvider, IDestroyableObject, IPvpMetadataProvider { private static final Logger log = LoggerFactory.getLogger(AbstractChainingMetadataProvider.class); private MetadataProvider internalProvider = null; private static Object mutex = new Object(); private Timer timer = null; public AbstractChainingMetadataProvider() { internalProvider = new ChainingMetadataProvider(); } public final Timer getTimer() { return this.timer; } /* * (non-Javadoc) * * @see at.gv.egovernment.moa.id.config.auth.IGarbageCollectorProcessing#runGarbageCollector() */ @Override public void runGarbageCollector() { synchronized (mutex) { /* add new Metadataprovider or remove Metadataprovider which are not in use any more. */ try { log.trace("Check consistence of PVP2X metadata"); addAndRemoveMetadataProvider(); } catch (final EaafConfigurationException e) { log.error("Access to MOA-ID configuration FAILED.", e); } } } @Override public void fullyDestroy() { internalDestroy(); } @Override public synchronized boolean refreshMetadataProvider(final String entityID) { try { // check if metadata provider is already loaded try { if (internalProvider.getEntityDescriptor(entityID) != null) { return true; } } catch (final MetadataProviderException e) { log.debug("Metadata for EntityId: {} is not valid. Starting refresh ... ", entityID); } // reload metadata provider final String metadataUrl = getMetadataUrl(entityID); if (StringUtils.isNotEmpty(metadataUrl)) { final Map actuallyLoadedProviders = getAllActuallyLoadedProviders(); // check if MetadataProvider is actually loaded if (actuallyLoadedProviders.containsKey(metadataUrl)) { actuallyLoadedProviders.get(metadataUrl).refresh(); log.info("SAML2 metadata for service provider: " + entityID + " is refreshed."); return true; } else { // load new Metadata Provider if (timer == null) { timer = new Timer(true); } final ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; chainProvider.addMetadataProvider(createNewMetadataProvider(entityID)); emitChangeEvent(); log.info("SAML2 metadata for service provider: " + entityID + " is added."); return true; } } else { log.debug( "Can not refresh SAML2 metadata: NO SAML2 metadata URL for SP with Id: " + entityID); } } catch (final MetadataProviderException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (final IOException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (final EaafConfigurationException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (final CertificateException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } return false; } /** * Close metadata provider and remove all loaded metadata. * */ public void internalDestroy() { if (internalProvider != null && internalProvider instanceof ChainingMetadataProvider) { log.info("Destrorying PVP-Authentication MetaDataProvider."); final ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; final List providers = chainProvider.getProviders(); for (final MetadataProvider provider : providers) { if (provider instanceof HTTPMetadataProvider) { final HTTPMetadataProvider httpprovider = (HTTPMetadataProvider) provider; log.debug("Destroy HTTPMetadataProvider +" + httpprovider.getMetadataURI()); httpprovider.destroy(); } else { log.warn("MetadataProvider can not be destroyed."); } } internalProvider = new ChainingMetadataProvider(); if (timer != null) { timer.cancel(); } } else { log.warn( "ReInitalize MOAMetaDataProvider is not possible! MOA-ID Instance has to be restarted manualy"); } } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#requireValidMetadata() */ @Override public boolean requireValidMetadata() { return internalProvider.requireValidMetadata(); } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#setRequireValidMetadata * (boolean) */ @Override public void setRequireValidMetadata(final boolean requireValidMetadata) { internalProvider.setRequireValidMetadata(requireValidMetadata); } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getMetadataFilter() */ @Override public MetadataFilter getMetadataFilter() { return internalProvider.getMetadataFilter(); } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#setMetadataFilter(org. * opensaml.saml2.metadata.provider.MetadataFilter) */ @Override public void setMetadataFilter(final MetadataFilter newFilter) throws MetadataProviderException { internalProvider.setMetadataFilter(newFilter); } /* * (non-Javadoc) * * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getMetadata() */ @Override public XMLObject getMetadata() throws MetadataProviderException { return internalProvider.getMetadata(); } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getEntitiesDescriptor( * java.lang.String) */ @Override public EntitiesDescriptor getEntitiesDescriptor(final String entitiesID) throws MetadataProviderException { EntitiesDescriptor entitiesDesc = null; try { entitiesDesc = internalProvider.getEntitiesDescriptor(entitiesID); if (entitiesDesc == null) { log.debug("Can not find PVP metadata for entityID: " + entitiesID + " Start refreshing process ..."); if (refreshMetadataProvider(entitiesID)) { return internalProvider.getEntitiesDescriptor(entitiesID); } } } catch (final MetadataProviderException e) { log.debug("Can not find PVP metadata for entityID: " + entitiesID + " Start refreshing process ..."); if (refreshMetadataProvider(entitiesID)) { return internalProvider.getEntitiesDescriptor(entitiesID); } } return entitiesDesc; } /* * (non-Javadoc) * * @see * at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getEntityDescriptor( * java.lang.String) */ @Override public EntityDescriptor getEntityDescriptor(final String entityID) throws MetadataProviderException { EntityDescriptor entityDesc = null; try { entityDesc = internalProvider.getEntityDescriptor(entityID); if (entityDesc == null) { log.debug("Can not find PVP metadata for entityID: " + entityID + " Start refreshing process ..."); if (refreshMetadataProvider(entityID)) { return internalProvider.getEntityDescriptor(entityID); } } } catch (final MetadataProviderException e) { log.debug( "Can not find PVP metadata for entityID: " + entityID + " Start refreshing process ..."); if (refreshMetadataProvider(entityID)) { return internalProvider.getEntityDescriptor(entityID); } } // if (entityDesc != null) // lastAccess.put(entityID, new Date()); return entityDesc; } /* * (non-Javadoc) * * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getRole(java.lang. * String, javax.xml.namespace.QName) */ @Override public List getRole(final String entityID, final QName roleName) throws MetadataProviderException { final List result = internalProvider.getRole(entityID, roleName); // if (result != null) // lastAccess.put(entityID, new Date()); return result; } /* * (non-Javadoc) * * @see at.gv.egovernment.moa.id.protocols.pvp2x.metadata.IEAAFMetadataProvider#getRole(java.lang. * String, javax.xml.namespace.QName, java.lang.String) */ @Override public RoleDescriptor getRole(final String entityID, final QName roleName, final String supportedProtocol) throws MetadataProviderException { final RoleDescriptor result = internalProvider.getRole(entityID, roleName, supportedProtocol); // if (result != null) // lastAccess.put(entityID, new Date()); return result; } /* * (non-Javadoc) * * @see org.opensaml.saml2.metadata.provider.ObservableMetadataProvider#getObservers() */ @Override public List getObservers() { return ((ChainingMetadataProvider) internalProvider).getObservers(); } /** * Get the URL to metadata for a specific entityID. * * @param entityId EntityId * @return URL to metadata * @throws EaafConfigurationException In case of an error */ protected abstract String getMetadataUrl(String entityId) throws EaafConfigurationException; /** * Creates a new implementation specific SAML2 metadata provider. * * @param entityId EntityId * @return MetadataProvider * @throws EaafConfigurationException In case of an error * @throws IOException In case of an error * @throws CertificateException In case of an error * @throws ConfigurationException In case of an error */ protected abstract MetadataProvider createNewMetadataProvider(String entityId) throws EaafConfigurationException, IOException, CertificateException; /** * Get a List of metadata URLs for all SAML2 SPs from configuration. * * @throws EaafConfigurationException In case of an error */ protected abstract List getAllMetadataUrlsFromConfiguration() throws EaafConfigurationException; protected void emitChangeEvent() { if ((getObservers() == null) || (getObservers().size() == 0)) { return; } final List tempObserverList = new ArrayList<>(getObservers()); for (final ObservableMetadataProvider.Observer observer : tempObserverList) { if (observer != null) { observer.onEvent(this); } } } private Map getAllActuallyLoadedProviders() { final Map loadedproviders = new HashMap<>(); final ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; // make a Map of all actually loaded HTTPMetadataProvider final List providers = chainProvider.getProviders(); for (final MetadataProvider provider : providers) { if (provider instanceof HTTPMetadataProvider) { final HTTPMetadataProvider httpprovider = (HTTPMetadataProvider) provider; loadedproviders.put(httpprovider.getMetadataURI(), httpprovider); } } return loadedproviders; } private void addAndRemoveMetadataProvider() throws EaafConfigurationException { if (internalProvider != null && internalProvider instanceof ChainingMetadataProvider) { log.info("Reload MOAMetaDataProvider."); /* * OpenSAML ChainingMetadataProvider can not remove a MetadataProvider * (UnsupportedOperationException) The ChainingMetadataProvider use internal a * unmodifiableList to hold all registrated MetadataProviders. */ final Map providersinuse = new HashMap<>(); final ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; // get all actually loaded metadata providers final Map loadedproviders = getAllActuallyLoadedProviders(); /* * TODO: maybe add metadata provider destroy after timeout. But could be a problem if one * Metadataprovider load an EntitiesDescriptor with more the multiple EntityDescriptors. If * one of this EntityDesciptors are expired the full EntitiesDescriptor is removed. * * Timeout requires a better solution in this case! */ // load all SAML2 SPs form configuration and // compare actually loaded Providers with configured SAML2 SPs final List allMetadataUrls = getAllMetadataUrlsFromConfiguration(); if (allMetadataUrls != null) { final Iterator metadataUrlInterator = allMetadataUrls.iterator(); while (metadataUrlInterator.hasNext()) { final String metadataurl = metadataUrlInterator.next(); try { if (StringUtils.isNotEmpty(metadataurl)) { if (loadedproviders.containsKey(metadataurl)) { // SAML2 SP is actually loaded, to nothing providersinuse.put(metadataurl, loadedproviders.get(metadataurl)); loadedproviders.remove(metadataurl); } } } catch (final Throwable e) { log.error("Failed to add Metadata (unhandled reason: " + e.getMessage(), e); } } } // remove all actually loaded MetadataProviders with are not in ConfigurationDB any more final Collection notusedproviders = loadedproviders.values(); for (final HTTPMetadataProvider provider : notusedproviders) { final String metadataurl = provider.getMetadataURI(); try { provider.destroy(); /* * OpenSAML ChainingMetadataProvider can not remove a MetadataProvider * (UnsupportedOperationException) The ChainingMetadataProvider use internal a * unmodifiableList to hold all registrated MetadataProviders. */ // chainProvider.removeMetadataProvider(provider); log.info("Remove not used MetadataProvider with MetadataURL " + metadataurl); } catch (final Throwable e) { log.error("HTTPMetadataProvider with URL " + metadataurl + " can not be removed from the list of actually loaded Providers.", e); } } try { chainProvider.setProviders(new ArrayList<>(providersinuse.values())); emitChangeEvent(); } catch (final MetadataProviderException e) { log.warn( "ReInitalize AbstractMetaDataProvider is not possible! Service has to be restarted manualy", e); } } else { log.warn( "ReInitalize AbstractMetaDataProvider is not possible! Service has to be restarted manualy"); } } }