/******************************************************************************* * Copyright 2017 Graz University of Technology * EAAF-Core Components has been developed in a cooperation between EGIZ, * A-SIT+, 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 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; import at.gv.egiz.eaaf.core.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; 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 (EAAFConfigurationException e) { log.error("Access to MOA-ID configuration FAILED.", e); } } } public void fullyDestroy() { internalDestroy(); } @Override public synchronized boolean refreshMetadataProvider(String entityID) { try { //check if metadata provider is already loaded try { if (internalProvider.getEntityDescriptor(entityID) != null) return true; } catch (MetadataProviderException e) {} //reload metadata provider String metadataURL = getMetadataURL(entityID); if (StringUtils.isNotEmpty(metadataURL)) { 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); 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 (MetadataProviderException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (IOException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (EAAFConfigurationException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } catch (CertificateException e) { log.warn("Refresh SAML2 metadata for service provider: " + entityID + " FAILED.", e); } return false; } public void internalDestroy() { if (internalProvider != null && internalProvider instanceof ChainingMetadataProvider) { log.info("Destrorying PVP-Authentication MetaDataProvider."); ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; List providers = chainProvider.getProviders(); for (MetadataProvider provider : providers) { if (provider instanceof HTTPMetadataProvider) { 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(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(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(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 (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(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 (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(String entityID, QName roleName) throws MetadataProviderException { 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(String entityID, QName roleName, String supportedProtocol) throws MetadataProviderException { 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 * @return * @throws EAAFConfigurationException */ protected abstract String getMetadataURL(String entityId) throws EAAFConfigurationException; /** * Creates a new implementation specific SAML2 metadata provider * * @param entityId * @return * @throws EAAFConfigurationException * @throws IOException * @throws CertificateException * @throws ConfigurationException */ protected abstract MetadataProvider createNewMetadataProvider(String entityId) throws EAAFConfigurationException, IOException, CertificateException; /** * Get a List of metadata URLs for all SAML2 SPs from configuration * * @throws EAAFConfigurationException */ protected abstract List getAllMetadataURLsFromConfiguration() throws EAAFConfigurationException; 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 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); } } 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.*/ Map providersinuse = new HashMap(); ChainingMetadataProvider chainProvider = (ChainingMetadataProvider) internalProvider; //get all actually loaded metadata providers 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 List allMetadataURLs = getAllMetadataURLsFromConfiguration(); if (allMetadataURLs != null) { Iterator metadataURLInterator = allMetadataURLs.iterator(); while (metadataURLInterator.hasNext()) { 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 (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 Collection notusedproviders = loadedproviders.values(); for (HTTPMetadataProvider provider : notusedproviders) { 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 (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 (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"); } }