org.asimba.util.saml2.metadata.provider.management.StandardMetadataProviderManager.java Source code

Java tutorial

Introduction

Here is the source code for org.asimba.util.saml2.metadata.provider.management.StandardMetadataProviderManager.java

Source

/*
 * Asimba Server
 * 
 * Copyright (C) 2013 Asimba
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see www.gnu.org/licenses
 * 
 * Asimba - Serious Open Source SSO - More information on www.asimba.org
 * 
 */
package org.asimba.util.saml2.metadata.provider.management;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asimba.util.saml2.metadata.provider.IMetadataProviderManager;
import org.joda.time.DateTime;
import org.opensaml.saml2.common.Extensions;
import org.opensaml.saml2.metadata.EntitiesDescriptor;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.RoleDescriptor;
import org.opensaml.saml2.metadata.provider.AbstractObservableMetadataProvider;
import org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.saml2.metadata.provider.ObservableMetadataProvider.Observer;
import org.opensaml.samlext.saml2mdui.UIInfo;
import org.opensaml.xml.XMLObject;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.api.IComponent;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.engine.core.idp.storage.IIDP;
import com.alfaariss.oa.util.saml2.idp.SAML2IDP;

/**
 * Manager for MetadataProviders
 * Designed as general purpose manager, as well as for generic and specific
 * providers, that use a specification for setting a filter on the source of
 * the metadata
 * 
 * @author mdobrinic
 *
 */
public class StandardMetadataProviderManager implements IMetadataProviderManager, IComponent, Observer {
    /** Local logger instance */
    private static final Log _oLogger = LogFactory.getLog(StandardMetadataProviderManager.class);

    /** Reference to ConfigManager for reloading state  */
    protected IConfigurationManager _oConfigManager;

    /** Local Id */
    protected String _sId;

    /**
     * MetadataProvider with some metadata for local administration
     */
    protected class StoredMetadataProvider {
        public String _sID;
        public MetadataProvider _oProvider;

        /** Timer that does background reloading */
        public Timer _oBackgroundTimer;

        /** Timestamp in ms when the provider was last used */
        public long _lastUsed;

        /** List of IDPs - intermediary result processed to Asimba Model */
        public List<IIDP> _lCachedIDPs;

        /**
         * Parameterized constructor
         * @param sID
         * @param oProvider
         * @param oTimer Timer that manages background metadata refreshes; must be specific for this oProvider
         */
        public StoredMetadataProvider(String sID, MetadataProvider oProvider, Timer oTimer) {
            _sID = sID;
            _oProvider = oProvider;
            _oBackgroundTimer = oTimer;
            _lCachedIDPs = new ArrayList<IIDP>();
            _lastUsed = System.currentTimeMillis();
        }

        /** Clean up */
        public void destroy() {
            _oProvider = null;
        }

        /** Touch access */
        public void touch() {
            _lastUsed = System.currentTimeMillis();
        }
    }

    /** Map of managed StoredMetadataProvider-instances */
    protected Map<String, StoredMetadataProvider> _hmSpecificProviders;

    /** Timeout in milliseconds after which resources of a MetadataProvider are released */
    protected int _iCacheInterval;

    /** Shared timer for all MetadataProviders */
    protected Timer _oMetadataProviderTimer = null;

    /**
     * Parameterless constructor to support generic .newInstance() instantiation
     * Must call start(..) to initialize Id!
     */
    public StandardMetadataProviderManager() {
        _hmSpecificProviders = new HashMap<String, StoredMetadataProvider>();
    }

    /**
     * Default constructor with ID of the MetadataProviderManager
     */
    public StandardMetadataProviderManager(String sId) {
        _sId = sId;
        _hmSpecificProviders = new HashMap<String, StoredMetadataProvider>();
    }

    public String getId() {
        return _sId;
    }

    /**
     * {@inheritDoc}
     */
    public void setCacheInterval(int iTimeoutMS) {
        _iCacheInterval = iTimeoutMS;
    }

    /**
     * {@inheritDoc}
     */
    public int getCacheInterval() {
        return _iCacheInterval;
    }

    /**
     * {@inheritDoc}
     */
    public void setTimer(Timer oTimer) {
        if (_oMetadataProviderTimer != null) {
            _oMetadataProviderTimer.cancel();
            _oMetadataProviderTimer = null; // and release
        }
        _oMetadataProviderTimer = oTimer;
    }

    /**
     * {@inheritDoc}
     */
    public Timer getTimer() {
        return _oMetadataProviderTimer;
    }

    /**
     * {@inheritDoc}
     */
    public boolean existsFor(String sSourceRef) {
        return _hmSpecificProviders.containsKey(sSourceRef);
    }

    /**
     * {@inheritDoc}
     */
    public MetadataProvider getProviderFor(String sSourceRef, Date dLastModified) throws OAException {
        StoredMetadataProvider oSMP = _hmSpecificProviders.get(sSourceRef);

        if (oSMP == null) {
            return null;
        }

        // Touch MetadataProvider access:
        oSMP.touch();

        MetadataProvider oMP = oSMP._oProvider;

        // Does the metadata need to be refreshed?
        if (dLastModified != null) {
            // Can the metadata be refreshed?
            if (oMP instanceof AbstractReloadingMetadataProvider) {
                AbstractReloadingMetadataProvider oARMP = (AbstractReloadingMetadataProvider) oMP;

                try {
                    DateTime dtLastRefresh = oARMP.getLastRefresh();

                    if (dtLastRefresh.isBefore(new DateTime(dLastModified))) {
                        oARMP.refresh();
                    }

                } catch (MetadataProviderException e) {
                    _oLogger.error("Error reloading metadata for provider " + sSourceRef + ":" + e.getMessage());
                    return null;
                }
            }
        }

        // And return instance
        return oSMP._oProvider;
    }

    /**
     * {@inheritDoc}
     */
    public List<IIDP> getIDPs(String sSourceRef) {
        String sRef;

        if (sSourceRef == null) {
            sRef = "unspecified"; // <-- not good
        } else {
            sRef = sSourceRef;
        }

        StoredMetadataProvider oSMP = _hmSpecificProviders.get(sRef);

        if (oSMP == null) {
            return null;
        }

        // Touch MetadataProvider access:
        oSMP.touch();

        // Return results
        return oSMP._lCachedIDPs;
    }

    /**
     * {@inheritDoc}
     */
    public void setProviderFor(String sSourceRef, MetadataProvider oProvider, Timer oTimer) throws OAException {
        if (existsFor(sSourceRef)) {
            removeProviderFor(sSourceRef);
        }

        StoredMetadataProvider oSMP = new StoredMetadataProvider(sSourceRef, oProvider, oTimer);

        // Store provider in local map
        _hmSpecificProviders.put(sSourceRef, oSMP);

        if (oProvider instanceof AbstractObservableMetadataProvider) {
            // If we can, we want to act when new metadata arrives
            ((AbstractObservableMetadataProvider) oProvider).getObservers().add(this);
        }

        // Initialize IDP cache
        oSMP._lCachedIDPs = createIDPList(oProvider);
    }

    /**
     * {@inheritDoc}
     */
    public MetadataProvider removeProviderFor(String sSourceRef) throws OAException {
        if (!existsFor(sSourceRef)) {
            return null;
        }
        StoredMetadataProvider oSMP = _hmSpecificProviders.get(sSourceRef);

        // Get reference for returning later
        MetadataProvider oMP = oSMP._oProvider;

        // Now clean up all tracks in our own state
        _hmSpecificProviders.remove(sSourceRef);

        if (oMP instanceof AbstractObservableMetadataProvider) {
            // We want to remove ourself from observers
            ((AbstractObservableMetadataProvider) oMP).getObservers().remove(this);
        }

        if (oSMP._oBackgroundTimer != null) {
            // Clear all tasks from the timer of the MetadataProvider
            oSMP._oBackgroundTimer.cancel();
            oSMP._oBackgroundTimer = null; // remove reference
        }

        oSMP.destroy();
        oSMP = null;

        // Return provider
        return oMP;
    }

    /**
     * Local helper to establish a StoredMetadataProvider instance from managed list 
     */
    protected StoredMetadataProvider getFromProvider(MetadataProvider oProvider) {
        for (StoredMetadataProvider smp : _hmSpecificProviders.values()) {
            if (smp._oProvider == oProvider) {
                return smp;
            }
        }
        return null;
    }

    /**
     * Local helper to build the cached IDPList for a MetadataProvider
     * @param oProvider
     * @return list with IDPs, or empty list when no IDPs were established
     */
    protected List<IIDP> createIDPList(MetadataProvider oProvider) {

        try {
            List<IIDP> oIDPs = new ArrayList<IIDP>();

            XMLObject x = (XMLObject) oProvider.getMetadata();

            if (!(x instanceof EntitiesDescriptor)) {
                _oLogger.debug("No EntitiesDescriptor was returned, so no IDPList to create.");
                return oIDPs;
            }

            EntitiesDescriptor oEntitiesDescriptor = (EntitiesDescriptor) x;
            List<EntityDescriptor> lEntityDescriptors = oEntitiesDescriptor.getEntityDescriptors();

            for (EntityDescriptor e : lEntityDescriptors) {
                IIDP i = idpFromEntityDescriptor(e);
                if (i != null) {
                    oIDPs.add(i);
                }
            }

            return oIDPs;

        } catch (MetadataProviderException e) {
            _oLogger.error("Exception when updating cached IDP list after metadata refresh: " + e.getMessage());
            return null;
        } catch (OAException e) {
            _oLogger.error("OAException while creating SAML2 IDP list: " + e.getMessage());
            return null;
        }
    }

    /**
     * onEvent is called when fresh metadata is initialized by the MetadataProvider
     * Use this moment to do a translation to cache to Asimba SAML2IDP list format  
     */
    public void onEvent(MetadataProvider provider) {
        StoredMetadataProvider oSMP = getFromProvider(provider);
        if (oSMP == null) {
            _oLogger.error("Inconsistent state: no instance known for provider " + provider);
            return;
        }

        List<IIDP> oIDPList = createIDPList(provider);

        synchronized (this) {
            if (oIDPList == null) {
                oSMP._lCachedIDPs.clear();
            } else {
                oSMP._lCachedIDPs = oIDPList;
            }
            oSMP.touch();
        }

        return;
    }

    /**
     * Create a SAMLIDP instance from the provided EntityDescriptor
     * 
     * @param oED EntityDescriptor of the SAML entity
     * @return SAMPIDP instance, or null when the EntityDescriptor did not 
     *    contain an IDPSSO definition
     * @throws OAException
     */
    protected IIDP idpFromEntityDescriptor(EntityDescriptor oED) throws OAException {
        /* 
        SAML2IDP(String sID, byte[] baSourceID, String sFriendlyName,
             String sMetadataFile, String sMetadataURL, 
             int iMetadataTimeout, Boolean useACSIndex, Boolean useAllowCreate, 
             Boolean useScoping, Boolean useNameIDPolicy, String forceNameIDFormat)
         */
        SAML2IDP oSAML2IDP = null;

        List<RoleDescriptor> lRoles = oED.getRoleDescriptors();

        if (lRoles.size() == 0) {
            // No roles contained in the EntityDescriptor
            return null;
        }

        boolean bIDPDescriptorProcessed = false;

        // Only use the first IDPSSODescriptor definition:
        for (RoleDescriptor r : lRoles) {

            // ::: wrong logics:  vvvv
            if (bIDPDescriptorProcessed || (!(r instanceof IDPSSODescriptor))) {
                if (bIDPDescriptorProcessed) {
                    _oLogger.info("IDP " + oED.getEntityID()
                            + " defines more than one IDPSSODescriptor roles; only using the first one.");
                }
                continue;
            }

            bIDPDescriptorProcessed = true;

            _oLogger.info("Entity '" + oED.getEntityID());
            _oLogger.info("  Supported protocols: " + r.getSupportedProtocols().toString());

            String sFriendlyname = oED.getEntityID();

            // try to work with mdui extension:
            Extensions ext = r.getExtensions();

            if (ext != null) {
                List<XMLObject> lx = ext.getOrderedChildren();
                for (XMLObject x : lx) {
                    if (x instanceof UIInfo) {
                        UIInfo u = (UIInfo) x;
                        if (u.getDisplayNames().size() > 0) {
                            sFriendlyname = u.getDisplayNames().get(0).getName().getLocalString();
                        }
                    }
                }
            }

            String sMetadataFile = null; // no file source
            String sMetadataURL = null; // no (direct) metadata source
            int iMetadataTimeout = -1; // use default
            boolean useACSIndex = false; // default: no ACS use
            boolean bAllowCreate = true; // default: yes, allow create new transit id's
            boolean bUseScoping = true; // default: true; configurable?
            boolean bUseNameIDPolicy = false; // default: do not force NameID policy in requests
            String sForceNameIDFormat = null; // default: no specific NameIDFormat is requested
            boolean bAvoidSubjectConfirmation = false; // default: don't change default SubjectConfirmation behavior
            boolean bDisableSSOForIDP = false; // default: do not disable SSO for this IDP

            // No MetadataProviderManager needed, as the SAML2IDP MetadataProvider is already managed
            oSAML2IDP = new SAML2IDP(oED.getEntityID(), null, sFriendlyname, sMetadataFile, sMetadataURL,
                    iMetadataTimeout, useACSIndex, bAllowCreate, bUseScoping, bUseNameIDPolicy, sForceNameIDFormat,
                    bAvoidSubjectConfirmation, bDisableSSOForIDP, new Date(), null);

            // Instead, set the SAML2IDP's metadata to live XML-document's format
            oSAML2IDP.setMetadataXMLObject(oED);
        }

        if (!bIDPDescriptorProcessed) {
            return null; // no IDP descriptor processed
        }

        return oSAML2IDP;
    }

    /**
     * {@inheritDoc}
     */
    public void destroy() {
        _oLogger.debug("Destroying id " + _sId);

        // We'll be removing from the hashmap, so iterate through a deep copy:
        Object[] oaProviders = _hmSpecificProviders.keySet().toArray(); // array of strings

        for (Object o : oaProviders) {
            String s = (String) o;
            try {
                removeProviderFor(s);
            } catch (OAException oae) {
                _oLogger.warn("OAException when removing MetadataProvider for " + s);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void start(IConfigurationManager oConfigManager, Element elConfig) throws OAException {
        _oConfigManager = oConfigManager;
        _hmSpecificProviders = new HashMap<String, StoredMetadataProvider>();

        _oMetadataProviderTimer = null; // new Timer(this.getClass().getName());

        _sId = oConfigManager.getParam(elConfig, "id");
        if (_sId == null) {
            _oLogger.error("No 'id' item for StandardMetadataProvider configured; stopping.");
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void restart(Element eConfig) throws OAException {
        synchronized (this) {
            stop();
            start(_oConfigManager, eConfig);
        }

    }

    /**
     * {@inheritDoc}
     */
    public void stop() {
        if (_oMetadataProviderTimer != null) {
            _oMetadataProviderTimer.cancel();
            _oMetadataProviderTimer = null; // and release
        }

        // Clean up the rest.
        destroy();
    }
}