com.alfaariss.oa.util.saml2.idp.SAML2IDP.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.util.saml2.idp.SAML2IDP.java

Source

/*
 * Asimba Server
 * 
 * Copyright (C) 2012 Asimba
 * Copyright (C) 2007-2010 Alfa & Ariss B.V.
 * 
 * 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 com.alfaariss.oa.util.saml2.idp;

import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asimba.util.saml2.metadata.provider.IMetadataProviderManager;
import org.asimba.util.saml2.metadata.provider.MetadataProviderConfiguration;
import org.asimba.util.saml2.metadata.provider.MetadataProviderUtil;
import org.asimba.util.saml2.metadata.provider.XMLObjectMetadataProvider;
import org.asimba.util.saml2.metadata.provider.management.MdMgrManager;
import org.joda.time.DateTime;
import org.opensaml.saml2.metadata.provider.AbstractReloadingMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.util.XMLObjectHelper;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.engine.core.idp.storage.AbstractIDP;
import org.gluu.asimba.util.ldap.idp.IDPEntry;

/**
 * SAML2 remote organization object.
 *
 * @author MHO
 * @author Alfa & Ariss
 * @since 1.1
 */
public class SAML2IDP extends AbstractIDP implements Serializable {

    /**
     * Local logger instance
     */
    private static final Log _oLogger = LogFactory.getLog(SAML2IDP.class);;

    /** Type: id */
    public final static String TYPE_ID = "id";
    /**
     * Type: sourceid
     */
    public final static String TYPE_SOURCEID = "sourceid";

    private static final long serialVersionUID = -3291910972515606397L;

    private static final int HTTP_METADATA_REQUEST_TIMEOUT = 5000;

    /**
     * SourceID is a 20-byte sequence used by the artifact receiver to determine
     * artifact issuer identity and the set of possible resolution endpoints.
     * <br/>
     * The issuer constructs the SourceID component of the artifact by taking
     * the SHA-1 hash of the identification URL. The hash value is NOT encoded
     * into hexadecimal.
     */
    private byte[] _baSourceID;
    private String _sMetadataFile;
    private String _sMetadataURL;
    private int _iMetadataTimeout;
    private Boolean _boolACSIndex;
    private Boolean _boolScoping;
    private Boolean _boolNameIDPolicy;
    private Boolean _boolAllowCreate;
    /**
     * _boolAvoidSubjectConfirmations indicates whether avoid including
     * SubjectConfirmation in an AuthnRequest to this IDP; used for
     * compatibility with Microsoft ADFS Default should be false
     */
    protected Boolean _boolAvoidSubjectConfirmations;

    /**
     * _boolDisableSSOForIDP indicates whether SSO should be disabled when
     * authentication is performed by this IDP. Default should be false
     */
    protected Boolean _boolDisableSSOForIDP = false;

    private String _sNameIDFormat;

    /**
     * The name of the MetadataProviderManager that manages this SAML2IDP
     */
    protected String _sMPMId = null;

    /**
     * Element containing the parsed XMLObject of the metadata document
     *
     * Does not serialize, so marshall to _sMetadata upon serialization
     */
    transient protected XMLObject _oMetadataXMLObject = null;

    /**
     * Keep reference to MetadataProvider for this IDP
     *
     * Does not serialize, so it is lost whenever it has been resuscitated. This
     * should not present any problem, as the MetadataProviderManager can
     * re-deliver the MetadataProvider, or when it can not, the SAML2IDP can
     * re-create one
     */
    transient protected MetadataProvider _oMetadataProvider = null;

    /**
     * Contains the string version of the XMLObject's metadata Only used for
     * transit, so object instance can be serialized, so will be set before
     * serializing, and will be set when un-serialized from instance that was
     * serialized before
     */
    protected String _sMetadata = null;

    /**
     * Creates an organization object.
     *
     * @param sID The id of the organization
     * @param baSourceID the SourceID of the organization
     * @param sFriendlyName the organization friendly name
     * @param sMetadataFile The location of the metadata file or NULL if none
     * @param sMetadataURL The url of the metadata or NULL if none
     * @param iMetadataTimeout The timeout to be used in connecting the the url
     * metadata or -1 when default must be used
     * @param useACSIndex TRUE if ACS should be set as Index
     * @param useAllowCreate AllowCreate value or NULL if disabled
     * @param useScoping TRUE if Scoping element must be send
     * @param useNameIDPolicy TRUE if NameIDPolicy element must be send
     * @param forceNameIDFormat The NameIDFormat to be set in the NameIDPolicy
     * or NULL if resolved from metadata
     * @param avoidSubjectConfirmations TRUE if ConfirmationData must not be
     * included in an AuthnRequest to this IDP
     * @param disableSSOForIDP Configure whether the SSO should be disabled for
     * this IDP
     * @param dLastModified Timestamp when SAML2IDP was last modified, or null
     * when unknown
     * @param sMPMId Id of the MetadataProviderManager that manages
     * MetadataProvider for this IDP i.e. the name of the IDPStorage
     * @throws OAException if invalid data supplied
     */
    public SAML2IDP(String sID, byte[] baSourceID, String sFriendlyName, String sMetadataFile, String sMetadataURL,
            int iMetadataTimeout, Boolean useACSIndex, Boolean useAllowCreate, Boolean useScoping,
            Boolean useNameIDPolicy, String forceNameIDFormat, Boolean avoidSubjectConfirmations,
            Boolean disableSSOForIDP, Date dLastModified, String sMPMId) throws OAException {
        super(sID, sFriendlyName, dLastModified);

        init(sID, baSourceID, sFriendlyName, sMetadataFile, sMetadataURL, iMetadataTimeout, useACSIndex,
                useAllowCreate, useScoping, useNameIDPolicy, forceNameIDFormat, avoidSubjectConfirmations,
                disableSSOForIDP, dLastModified, sMPMId);
    }

    /**
     * Creates an organization object from LDAP entry object..
     */
    public SAML2IDP(IDPEntry entry, byte[] baSourceID, String _sMPMId) throws OAException {
        super(entry.getId(), entry.getFriendlyName(), entry.getLastModified());

        init(entry.getId(), baSourceID, entry.getFriendlyName(), entry.getMetadataFile(), entry.getMetadataUrl(),
                entry.getMetadataTimeout(), entry.isAcsIndex(), entry.isAllowCreate(), entry.isScoping(),
                entry.isNameIdPolicy(), entry.getNameIdFormat(), entry.isAvoidSubjectConfirmations(),
                entry.isDisableSSOForIDP(), entry.getLastModified(), _sMPMId);

        // IDPEntry can keep metadata as text
        if (entry.getMetadataXMLText() != null && !"".equals(entry.getMetadataXMLText())) {
            _sMetadata = entry.getMetadataXMLText();
        }
    }

    /**
     * Creates an organization object.
     *
     * @param sID The id of the organization
     * @param baSourceID the SourceID of the organization
     * @param sFriendlyName the organization friendly name
     * @param sMetadataFile The location of the metadata file or NULL if none
     * @param sMetadataURL The url of the metadata or NULL if none
     * @param iMetadataTimeout The timeout to be used in connecting the the url
     * metadata or -1 when default must be used
     * @param useACSIndex TRUE if ACS should be set as Index
     * @param useAllowCreate AllowCreate value or NULL if disabled
     * @param useScoping TRUE if Scoping element must be send
     * @param useNameIDPolicy TRUE if NameIDPolicy element must be send
     * @param forceNameIDFormat The NameIDFormat to be set in the NameIDPolicy
     * or NULL if resolved from metadata
     * @param avoidConfirmationData TRUE if ConfirmationData must not be
     * included in an AuthnRequest to this IDP
     * @param disableSSOForIDP Configure whether the SSO should be disabled for
     * this IDP
     * @param dLastModified Timestamp when SAML2IDP was last modified, or null
     * when unknown
     * @param sMPMId Id of the MetadataProviderManager that manages
     * MetadataProvider for this IDP i.e. the name of the IDPStorage
     * @throws OAException if invalid data supplied
     */
    private void init(String sID, byte[] baSourceID, String sFriendlyName, String sMetadataFile,
            String sMetadataURL, int iMetadataTimeout, Boolean useACSIndex, Boolean useAllowCreate,
            Boolean useScoping, Boolean useNameIDPolicy, String forceNameIDFormat,
            Boolean avoidSubjectConfirmations, Boolean disableSSOForIDP, Date dLastModified, String sMPMId)
            throws OAException {
        _baSourceID = baSourceID;

        _sMetadataFile = sMetadataFile;
        if (_sMetadataFile != null && !"".equals(_sMetadataFile)) {
            File fMetadata = new File(_sMetadataFile);
            if (!fMetadata.exists()) {
                StringBuffer sbError = new StringBuffer("Supplied metadata file for organization '");
                sbError.append(_sID);
                sbError.append("' doesn't exist: ");
                sbError.append(_sMetadataFile);
                _oLogger.error(sbError.toString());
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        } else {
            // set null to prevent empty string
            _sMetadataFile = null;
        }

        _sMetadataURL = sMetadataURL;
        if (_sMetadataURL != null && !"".equals(_sMetadataURL)) {
            try {
                new URL(_sMetadataURL);
            } catch (MalformedURLException e) {
                StringBuffer sbError = new StringBuffer("Invalid metadata URL supplied for organization '");
                sbError.append(_sID);
                sbError.append("': ");
                sbError.append(_sMetadataURL);
                _oLogger.error(sbError.toString(), e);
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        } else {
            // set null to prevent empty string
            _sMetadataURL = null;
        }

        _iMetadataTimeout = iMetadataTimeout;
        if (_iMetadataTimeout <= 0) {
            _iMetadataTimeout = HTTP_METADATA_REQUEST_TIMEOUT;

            StringBuffer sbDebug = new StringBuffer("Supplied HTTP metadata timeout for organization '");
            sbDebug.append(_sID);
            sbDebug.append("' is equal or smaller then zero, using default: ");
            sbDebug.append(_iMetadataTimeout);
            _oLogger.debug(sbDebug.toString());
        }

        _boolACSIndex = useACSIndex;
        _boolScoping = useScoping;
        _boolNameIDPolicy = useNameIDPolicy;
        _boolAllowCreate = useAllowCreate;
        _boolAvoidSubjectConfirmations = avoidSubjectConfirmations;
        _boolDisableSSOForIDP = disableSSOForIDP;
        _sNameIDFormat = forceNameIDFormat;

        // Initialize the name of the MetadataProviderManager
        _sMPMId = sMPMId;
    }

    /**
     * Returns the SourceID of the organization.
     *
     * @return the source id
     */
    public byte[] getSourceID() {
        return _baSourceID;
    }

    /**
     * Return whether the SAML2IDP is initialized with a MetadataProvider
     *
     * @return
     */
    public boolean isMetadataProviderSet() {
        return (_oMetadataProvider != null);
    }

    /**
     * Returns a metadata provider with the metadata of the organization.
     * <br>
     * If the provider was set externally, this provider is returned. <br/>
     * When the SAML2IDP has been serialized/deserialized, a MetadataProvider
     * based on the (static) metadata is returned. Otherwise, a new
     * MetadataProvider is constructed that retrieves its metadata from the
     * configured file- and/or url-source.
     *
     * @return The initialized MetadataProvider with the metadata for this
     * organization or NULL when no metadata is available.
     * @throws OAException If metadata is invalid or could not be accessed
     */
    public MetadataProvider getMetadataProvider() throws OAException {
        if (_oMetadataProvider != null) {
            _oLogger.debug("Returning existing MetadataProvider for SAML2 IDP '" + _sID + "'");
            return _oMetadataProvider;
        }

        // If there is a local metadata document available, return the
        // MetadataProvider that is based on this document
        if (_oMetadataXMLObject != null) {
            _oLogger.debug("Creating new XMLObject MetadataProvider for SAML2 IDP '" + _sID + "'");

            XMLObjectMetadataProvider oMP = new XMLObjectMetadataProvider(_oMetadataXMLObject);
            oMP.initialize();
            _oMetadataProvider = oMP;
            return oMP;
        }
        if (_sMetadata != null) {
            _oLogger.debug("Creating new XML-String MetadataProvider for SAML2 IDP '" + _sID + "'");

            // This is the case after de-serialization (i.e. when session resumes)
            // Re-instantiate XMLProvider from retrieved metadata
            // No cache re-evaluation, but this performs better
            try {
                BasicParserPool parserPool = new BasicParserPool();
                parserPool.setNamespaceAware(true);

                StringReader oSR = new StringReader(_sMetadata);

                _oMetadataXMLObject = XMLObjectHelper.unmarshallFromReader(parserPool, oSR);

                XMLObjectMetadataProvider oMP = new XMLObjectMetadataProvider(_oMetadataXMLObject);
                oMP.initialize();

                _oMetadataProvider = oMP;
                return oMP;

            } catch (XMLParserException e) {
                _oLogger.warn("XMLParser exception with establishing metadata for SAML2IDP, trying file/url: "
                        + e.getMessage());
            } catch (UnmarshallingException e) {
                _oLogger.warn("Unmarshalling exception with establishing metadata for SAML2IDP, trying file/url: "
                        + e.getMessage());
            }
        }

        _oLogger.debug("Creating new MetadataProvider from configured source for SAML2 IDP '" + _sID + "'");

        // First time a MetadataProvider request is being handled for this SAML2IDP instance:
        MetadataProviderConfiguration oMPC = new MetadataProviderConfiguration(_sMetadataURL, _iMetadataTimeout,
                _sMetadataFile, _sMetadata);
        String sConfiguredProviderFingerprint = oMPC.getFingerprint();

        IMetadataProviderManager oMPM = null;
        MetadataProvider oMP = null;

        if (_sMPMId != null) {
            oMPM = MdMgrManager.getInstance().getMetadataProviderManager(_sMPMId);
        }

        // Can we get a managed MetadataProvider?
        if (oMPM != null) {
            oMP = oMPM.getProviderFor(_sID, _dLastModified);
        }

        if (oMP != null) {
            // Is it still valid?
            String sCachedProviderFingerprint = MetadataProviderUtil.getMetadataProviderFingerprint(oMP);

            if (!sCachedProviderFingerprint.equals(sConfiguredProviderFingerprint)) {
                _oLogger.info("Metadata configuration changed; re-initializing metadata for IDP " + _sID);
                // No longer valid; invalidate the version from cache
                oMPM.removeProviderFor(_sID);
                oMP = null;
            } else // For the purpose of logging:
            if (_oLogger.isDebugEnabled()) {
                String sNextRefresh = null;

                if (oMP instanceof AbstractReloadingMetadataProvider) {
                    DateTime oNextRefresh = ((AbstractReloadingMetadataProvider) oMP).getNextRefresh();
                    sNextRefresh = oNextRefresh.toString();
                }
                _oLogger.debug("Using cached MetadataProvider for IDP " + _sID
                        + (sNextRefresh == null ? "" : " (next refresh: " + sNextRefresh + ")"));
            }
        }

        if (oMP == null) {
            oMP = MetadataProviderUtil.createMetadataProvider(_sID, oMPC, oMPM);
        }

        _oMetadataProvider = oMP;

        return _oMetadataProvider;
    }

    /**
     * Indicates whether the ACS location in the AuthnRequest must be an Index.
     *
     * Values are:
     * <ul>
     * <li>TRUE - AssertionConsumerServiceIndex must be set
     * <b>(default)</b></li>
     * <li>FALSE - AssertionConsumerServiceURL and ProtocolBinding must be
     * set</li>
     * </ul>
     *
     * @return TRUE if the ACS location must be an index.
     * @since 1.2
     */
    public Boolean useACSIndex() {
        return _boolACSIndex;
    }

    /**
     * Indicates what the value of AllowCreate in the NameIDPolicy of the
     * AuthnRequest must be.
     *
     * Values are:
     * <ul>
     * <li>NULL - AllowCreate is not send in the AuthnRequest <b>(default unless
     * it's proxied)</b></li>
     * <li>TRUE - AllowCreate=true</li>
     * <li>FALSE - AllowCreate=false</li>
     * </ul>
     *
     * @return the preferred AllowCreate value.
     * @since 1.2
     */
    public Boolean useAllowCreate() {
        return _boolAllowCreate;
    }

    /**
     * Indicates what the value of Scoping in the AuthnRequest must be.
     *
     * Values are:
     * <ul>
     * <li>TRUE - Scoping element will be send <b>(default)</b></li>
     * <li>FALSE - Scoping element will not be send </li>
     * </ul>
     *
     * @return TRUE if the Scoping element must be send.
     * @since 1.2
     */
    public Boolean useScoping() {
        return _boolScoping;
    }

    /**
     * Indicates what the value of NameIDPolicy in the AuthnRequest must be.
     *
     * Values are:
     * <ul>
     * <li>TRUE - NameIDPolicy element will be sent <b>(default)</b></li>
     * <li>FALSE - NameIDPolicy element will not be sent </li>
     * </ul>
     *
     * @return TRUE if the NameIDPolicy element must be send.
     * @since 1.2
     */
    public Boolean useNameIDPolicy() {
        return _boolNameIDPolicy;
    }

    /**
     * Return indication whether to avoid including SubjectConfirmation in an
     * AuthnRequest to this IDP; used for compatibility with Microsoft ADFS
     *
     * @return TRUE to avoid this element
     */
    public Boolean avoidSubjectConfirmations() {
        return _boolAvoidSubjectConfirmations;
    }

    /**
     * Set DisableSSOForIDP for this instance
     *
     * @param bDisableSSOForIDP
     */
    public void setDisableSSOForIDP(boolean bDisableSSOForIDP) {
        _boolDisableSSOForIDP = bDisableSSOForIDP;
    }

    @Override
    public boolean disableSSO() {
        return _boolDisableSSOForIDP;
    }

    /**
     * Indicates what the value of Format in the NameIDPolicy of the
     * AuthnRequest must be.
     *
     * Values are:
     * <ul>
     * <li>NULL - The first NameIDFormat in the IdP Metadata should be used OR
     * no format when a NameIDFormat is not available in that
     * metadata<b>(default)</b></li>
     * <li>NOT NULL - The Format should be overrulen with the configured
     * format</li>
     * </ul>
     * This functionality will only be used when the NameIDPolicy is used.
     *
     * @return the preferred NameIDFormat value.
     * @since 1.2
     */
    public String getNameIDFormat() {
        return _sNameIDFormat;
    }

    /**
     * Set Metadata of the IDP to be the provided (OpenSAML2) parsed XML
     * document
     *
     * @param elMetadataDocument
     */
    public void setMetadataXMLObject(XMLObject oMetadataXMLObject) {
        _oMetadataXMLObject = oMetadataXMLObject;
    }

    /**
     * Deal with internally stored metadata stuff
     *
     * @param oOutputStream
     */
    private void writeObject(ObjectOutputStream oOutputStream) throws java.io.IOException {
        try {
            if (_sMetadata == null) {
                // Create the MetadataXMLObject so we can extract the XML-string from it:
                if (_oMetadataXMLObject == null && _oMetadataProvider != null) {
                    _oMetadataXMLObject = _oMetadataProvider.getMetadata();
                }

                if (_oMetadataXMLObject != null) {
                    StringWriter oSW = new StringWriter();
                    XMLObjectHelper.marshallToWriter(_oMetadataXMLObject, oSW);
                    _sMetadata = oSW.toString();
                }
            }
        } catch (MarshallingException e) {
            _oLogger.error("Exception when marshalling XMLObject to Writer for SAML2IDP, dropping metadata: "
                    + e.getMessage());
            return;
        } catch (MetadataProviderException e) {
            _oLogger.error("Exception when serializing and retrieving Metadata for SAML2IDP '" + _sID + "':"
                    + e.getMessage());
            throw new IOException(e);
        }

        // Do its thing:
        oOutputStream.defaultWriteObject();
    }
}