com.alfaariss.oa.authentication.remote.saml2.profile.AbstractAuthNMethodSAML2Profile.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.authentication.remote.saml2.profile.AbstractAuthNMethodSAML2Profile.java

Source

/*
 * Asimba - Serious Open Source SSO
 * 
 * Copyright (C) 2012 Asimba
 * Copyright (C) 2007-2011 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.authentication.remote.saml2.profile;

import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.namespace.QName;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.asimba.authentication.remote.provisioning.saml2.AssertionUserStorage;
import org.asimba.util.saml2.assertion.SAML2TimestampWindow;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.binding.BasicSAMLMessageContext;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.impl.SAMLObjectContentReference;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.Assertion;
import org.opensaml.saml2.core.Attribute;
import org.opensaml.saml2.core.AttributeStatement;
import org.opensaml.saml2.core.Audience;
import org.opensaml.saml2.core.AudienceRestriction;
import org.opensaml.saml2.core.AuthnContext;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Conditions;
import org.opensaml.saml2.core.GetComplete;
import org.opensaml.saml2.core.IDPEntry;
import org.opensaml.saml2.core.IDPList;
import org.opensaml.saml2.core.Issuer;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.NameIDPolicy;
import org.opensaml.saml2.core.NameIDType;
import org.opensaml.saml2.core.ProxyRestriction;
import org.opensaml.saml2.core.RequesterID;
import org.opensaml.saml2.core.Scoping;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.saml2.core.impl.GetCompleteBuilder;
import org.opensaml.saml2.core.impl.IDPEntryBuilder;
import org.opensaml.saml2.core.impl.IDPListBuilder;
import org.opensaml.saml2.core.impl.IssuerBuilder;
import org.opensaml.saml2.core.impl.NameIDBuilder;
import org.opensaml.saml2.core.impl.NameIDPolicyBuilder;
import org.opensaml.saml2.core.impl.RequesterIDBuilder;
import org.opensaml.saml2.core.impl.ScopingBuilder;
import org.opensaml.saml2.core.impl.SubjectBuilder;
import org.opensaml.saml2.core.impl.SubjectConfirmationBuilder;
import org.opensaml.saml2.core.impl.SubjectConfirmationDataBuilder;
import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.NameIDFormat;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.transport.http.HTTPInTransport;
import org.opensaml.ws.transport.http.HTTPOutTransport;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.ws.transport.http.HttpServletResponseAdapter;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.XMLObjectBuilder;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.schema.XSAny;
import org.opensaml.xml.schema.XSString;
import org.opensaml.xml.security.SecurityHelper;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.Signer;
import org.opensaml.xml.signature.impl.SignatureBuilder;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.UserEvent;
import com.alfaariss.oa.api.attribute.IAttributes;
import com.alfaariss.oa.api.attribute.ISessionAttributes;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.api.idmapper.IIDMapper;
import com.alfaariss.oa.api.session.ISession;
import com.alfaariss.oa.authentication.remote.saml2.SAML2AuthNConstants;
import com.alfaariss.oa.authentication.remote.saml2.beans.SAMLRemoteUser;
import com.alfaariss.oa.engine.core.Engine;
import com.alfaariss.oa.engine.core.attribute.UserAttributes;
import com.alfaariss.oa.engine.core.crypto.CryptoManager;
import com.alfaariss.oa.engine.core.idp.storage.IIDPStorage;
import com.alfaariss.oa.engine.core.requestor.factory.IRequestorPoolFactory;
import com.alfaariss.oa.engine.user.provisioning.ProvisioningUser;
import com.alfaariss.oa.engine.user.provisioning.translator.standard.StandardProfile;
import com.alfaariss.oa.util.saml2.SAML2ConditionsWindow;
import com.alfaariss.oa.util.saml2.crypto.SAML2CryptoUtils;
import com.alfaariss.oa.util.saml2.idp.SAML2IDP;
import com.alfaariss.oa.util.saml2.proxy.ProxyAttributes;
import com.alfaariss.oa.util.saml2.proxy.SAML2IDPEntry;
import java.util.ArrayList;
import org.opensaml.saml2.core.AuthenticatingAuthority;

/**
 * Basics for SAML2 Profile implementations.
 *
 * @author jre
 * @author Alfa & Ariss
 */
public abstract class AbstractAuthNMethodSAML2Profile implements IAuthNMethodSAML2Profile {
    /** System logger */
    private Log _logger = LogFactory.getLog(AbstractAuthNMethodSAML2Profile.class);

    /**
     * Authentication method ID
     */
    protected String _sMethodID;

    /** Linked SAML2 IDP Profile that receives the profile's Response messages */
    protected String _sLinkedIDPProfile;

    /**
     * The Configuration manager instance
     */
    protected IConfigurationManager _oConfigManager = null;

    /**
     * The cryptoManager
     */
    protected CryptoManager _crypto = null;

    /**
     * Metadata store.
     */
    protected EntityDescriptor _entityDescriptor = null;

    /**
     * SAML XML object builder factory.
     */
    protected XMLObjectBuilderFactory _builderFactory = Configuration.getBuilderFactory();

    /** Signing is enabled in OA */
    protected boolean _signingEnabled;

    /**
     * UID mapper
     */
    protected IIDMapper _idMapper = null;

    /**
     * The organization storage
     */
    protected IIDPStorage _organizationStorage;

    /** SAML2 Conditions acceptance Window */
    protected SAML2ConditionsWindow _conditionsWindow;

    /**
     * Acceptance window for AuthnStatement/AuthnInstant values
     */
    protected SAML2TimestampWindow _oAuthnInstantWindow;

    /** Organization ID of this OpenASelect Server */
    protected String _sMyOrganizationID;

    /** IRequestorPoolFactory */
    protected IRequestorPoolFactory _requestorPoolFactory;

    /** Is OA Server 1.5 compaible */
    protected boolean _bCompatible;

    /** StandardProfile how a Remote User is provisioned */
    protected StandardProfile _oRemoteSAMLUserProvisioningProfile;

    /**
     * @see com.alfaariss.oa.authentication.remote.saml2.profile.IAuthNMethodSAML2Profile#init(com.alfaariss.oa.api.configuration.IConfigurationManager, org.w3c.dom.Element, org.opensaml.saml2.metadata.EntityDescriptor, com.alfaariss.oa.api.idmapper.IIDMapper, com.alfaariss.oa.engine.core.idp.storage.IIDPStorage, java.lang.String, com.alfaariss.oa.util.saml2.SAML2ConditionsWindow)
     * @see 
     */
    @Override
    public void init(IConfigurationManager configurationManager, Element config, EntityDescriptor entityDescriptor,
            IIDMapper mapper, IIDPStorage orgStorage, String sMethodID, String sLinkedIDPProfile,
            SAML2ConditionsWindow conditionsWindow, SAML2TimestampWindow oAuthnInstantWindow,
            StandardProfile oRemoteSAMLUserProvisioningProfile) throws OAException {
        _oConfigManager = configurationManager;

        Engine oaEngine = Engine.getInstance();
        _crypto = oaEngine.getCryptoManager();
        _entityDescriptor = entityDescriptor;
        _idMapper = mapper;
        _organizationStorage = orgStorage;
        _sMethodID = sMethodID;
        _sLinkedIDPProfile = sLinkedIDPProfile;
        _conditionsWindow = conditionsWindow;
        _oAuthnInstantWindow = oAuthnInstantWindow;
        _sMyOrganizationID = oaEngine.getServer().getOrganization().getID();
        _requestorPoolFactory = oaEngine.getRequestorPoolFactory();
        _oRemoteSAMLUserProvisioningProfile = oRemoteSAMLUserProvisioningProfile;

        try {
            _signingEnabled = false;
            SAML2CryptoUtils.retrieveMySigningCredentials(_crypto, _entityDescriptor.getEntityID());
            SAML2CryptoUtils.getXMLSignatureURI(_crypto);
            SAML2CryptoUtils.getXMLDigestMethodURI(_crypto.getMessageDigest());
            _signingEnabled = true;
            _logger.info("Signing enabled");
        } catch (OAException e) {
            //Logged in SAML2CryptoUtils
            _logger.info("Signing disabled");
        }
    }

    /**
     * @see com.alfaariss.oa.authentication.remote.saml2.profile.IAuthNMethodSAML2Profile#destroy()
     */
    @Override
    public void destroy() {
        //does nothing
    }

    /**
     * Check authentication statements' subject confirmations.
     *
     * @param subjectConfirmations The authentication statements' subject confirmations.
     * @return true if confirmations are correct.
     */
    protected boolean checkConfirmations(List<SubjectConfirmation> subjectConfirmations) {
        if (subjectConfirmations != null) {
            for (SubjectConfirmation conf : subjectConfirmations) {
                SubjectConfirmationData subjectConfirmationData = conf.getSubjectConfirmationData();
                if (subjectConfirmationData != null) {
                    boolean dateOK = subjectConfirmationData.getNotBefore() == null
                            || subjectConfirmationData.getNotBefore().isBeforeNow();
                    dateOK = dateOK && (subjectConfirmationData.getNotOnOrAfter() == null
                            || subjectConfirmationData.getNotOnOrAfter().isAfterNow());
                    dateOK = dateOK && (subjectConfirmationData.getNotOnOrAfter() == null
                            || !subjectConfirmationData.getNotOnOrAfter().isEqualNow());
                    if (!dateOK)
                        return false;
                }
            }
        }
        return true;
    }

    /**
     * Build a user object from an Assertion
     *
     * TODO -FO: determine what to do with NameQualifier/SPNameQualifier?
     *
     * @param oAssertion The Assertion object (must contain a subject child!)
     * @param sMethodId The Method ID calling this method.
     * @param sIDPId The EntityId of the organization that is used as organizationId 
     *    when no NameQualifier is specified.
     * @return The SAMLUser.
     */
    protected SAMLRemoteUser createUserFromAssertion(Assertion oAssertion, String sMethodId, String sIDPId)
            throws OAException {
        Subject subj = oAssertion.getSubject();
        NameID nid = subj.getNameID();

        if (nid == null) {
            _logger.warn("No NameID in Subject when trying to establish User from Assertion");
            return null;
        }

        if (nid != null) {
            String sUserID = getUID(nid);
            if (sUserID != null) {
                String sNameIDFormat = nid.getFormat();
                if (sNameIDFormat == null) { //DD use unspecified if no format is available saml-core-2.0-os r455
                    sNameIDFormat = NameIDType.UNSPECIFIED;
                }

                String sNameQualifier = nid.getNameQualifier();
                String sSPNameQualifier = nid.getSPNameQualifier();

                String sUserOrganization = sNameQualifier;
                if (sUserOrganization == null) {
                    String sLocalEntityId = _entityDescriptor.getEntityID();

                    // If provided SPNameQualifier is not the same as our SP EntityId:
                    if (sSPNameQualifier != null && !sLocalEntityId.equals(sSPNameQualifier)) {
                        //..then the UserOrg can be assumed to be scoped within provided SPNameQualif
                        sUserOrganization = sSPNameQualifier;
                    } else {
                        //..otherwise UserOrg is not explicitly provided; make it to be the RemoteIDP-ID
                        sUserOrganization = sIDPId;
                    }
                }

                List<SubjectConfirmation> subjectConfirmations = subj.getSubjectConfirmations();
                //DD: Multiple confirmations are not yet supported.

                if (!checkConfirmations(subjectConfirmations)) {
                    _logger.debug("Subject Confirmation data time stamp(s) incorrect");
                    return null;
                }

                // Do provisioning?
                if (_oRemoteSAMLUserProvisioningProfile == null) {
                    // Nope, just instantiate without provisioning properties and do not
                    // add more available AuthMethods
                    return new SAMLRemoteUser(sUserOrganization, sUserID, sMethodId, sNameIDFormat, sNameQualifier,
                            sSPNameQualifier, sIDPId);
                } else {
                    // Yes, do provisioning, use the assertion as External Storage
                    AssertionUserStorage oAUS = new AssertionUserStorage(oAssertion);
                    ProvisioningUser oProvisioningUser = _oRemoteSAMLUserProvisioningProfile.getUser(oAUS,
                            sUserOrganization, sUserID);

                    return new SAMLRemoteUser(oProvisioningUser, sMethodId, sNameIDFormat, sNameQualifier,
                            sSPNameQualifier, sIDPId);
                }
            }
        }

        return null;
    }

    /**
     * Checks validity of authentication statement
     *
     * @param stmt The AuthnStatement SAML object
     * @return true if correct
     */
    protected boolean checkAuthNStatement(AuthnStatement stmt) {
        //TODO check authN context?
        //does not have to do anything yet? AuthN context type check?
        if (!_oAuthnInstantWindow.canAccept(stmt.getAuthnInstant())) {
            _logger.debug("AuthN statement check failed: issue instant not in acceptable window.");
            return false;
        }

        // check AuthnContext
        AuthnContext authnContext = stmt.getAuthnContext();
        if (authnContext == null) {
            _logger.debug("AuthN statement check failed: empty AuthnContext section.");
            return false;
        }

        for (AuthenticatingAuthority authority : authnContext.getAuthenticatingAuthorities()) {
            if (authority.getURI() == null && authority.getURI().isEmpty()) {
                _logger.debug("AuthN statement check failed: empty AuthenticatingAuthority URI.");
                return false;
            }
        }

        return true;
    }

    /**
     * Check status.
     *
     * @param status The Status object.
     * @param org The organization.
     * @return true if status is "Success"
     */
    protected UserEvent getStatus(Status status, SAML2IDP org) {
        StatusCode statusCode = (status == null ? null : status.getStatusCode());
        String sStatus = (statusCode == null ? null : statusCode.getValue());

        if (StatusCode.SUCCESS_URI.equals(sStatus)) {
            return UserEvent.AUTHN_METHOD_SUCCESSFUL;
        }

        StringBuffer sbDebug = new StringBuffer("Status code isn't '");
        sbDebug.append(StatusCode.SUCCESS_URI);
        sbDebug.append("' but is: ");
        sbDebug.append(sStatus);
        _logger.debug(sbDebug.toString());

        return UserEvent.AUTHN_METHOD_FAILED;
    }

    /**
     * Checks conditions and sets proxy restrictions if available.
     *
     * @param con The Conditions SAML object
     * @return true if conditions are met.
     */
    protected boolean doConditions(Conditions con) {
        if (!_conditionsWindow.canAccept(con.getNotBefore(), con.getNotOnOrAfter()))
            return false;

        //check audience restrictions
        boolean arOK = false;
        List<AudienceRestriction> ars = con.getAudienceRestrictions();
        if (ars == null || ars.size() == 0)
            arOK = true;
        else {
            for (AudienceRestriction ar : ars) {
                List<Audience> as = ar.getAudiences();
                if (as != null) {
                    for (Audience a : as) {
                        String sAURI = a.getAudienceURI();
                        if (!sAURI.endsWith("/"))
                            sAURI = sAURI + "/";
                        if (sAURI.startsWith(_entityDescriptor.getEntityID())) {
                            arOK = true;
                            break;
                        }
                    }
                }
                if (arOK)
                    break;
            }
        }

        //OneTimeUse is ignored.

        //ProxyRestriction never invalidates a message (saml-core-2.0-os, r.994)
        //only sets new values in proxy session attributes
        //DD ProxyRestriction is not supported in the profile, so ignore it here as well (for now)

        if (!arOK) {
            _logger.debug("Message error: Audience restriction prohibited use of assertion");
        }

        return arOK;
    }

    /**
     * Sets proxy attributes according to ProxyAttributes element.
     * 
     * @param pr The ProxyRestriction element.
     */
    protected void setProxyRestrictions(ProxyRestriction pr) {
    }

    /**
     * Get attributes from attribute statement.
     * 
     * @param stmts List of attribute statement SAML objects.
     * @return The IAttributes object.
     */
    protected IAttributes getAttributeMap(List<AttributeStatement> stmts) {
        if (stmts == null || stmts.isEmpty())
            return null;

        IAttributes returnAtts = new UserAttributes();

        for (AttributeStatement stmt : stmts) {
            List<Attribute> atts = stmt.getAttributes();
            for (Attribute att : atts) {
                Object content = null;

                if (att.getName() == null || att.getName().isEmpty()) {
                    _logger.warn("Empty attribute name (skipped)");
                    continue;
                } else if (att.getAttributeValues() == null || att.getAttributeValues().isEmpty()) {
                    _logger.warn("Empty attribute value: " + att.getName());
                    content = "";
                } else {
                    List<XMLObject> attrValuesXML = att.getAttributeValues();

                    List<String> attrValues = new ArrayList<>();

                    for (XMLObject obj : attrValuesXML) {
                        // We only support XSString (if OpenSAML does) and XSAny
                        if (obj instanceof XSString) {
                            //ok
                            XSString str = (XSString) obj;
                            attrValues.add(str.getValue());

                        }
                        //OpenSAML reads type=xs:string attributes as any-typed?
                        else if (obj instanceof XSAny) {
                            //ok
                            XSAny str = (XSAny) obj;
                            attrValues.add(str.getTextContent());
                        } else {
                            _logger.debug("Unrecognized type of attribute (skipped): " + obj.getClass().getName());
                        }
                    }

                    content = attrValues;

                    //                    XMLObject obj = att.getAttributeValues().get(0);
                    //                    // We only support XSString (if OpenSAML does) and XSAny
                    //                    if (obj instanceof XSString) {
                    //                        //ok
                    //                        XSString str = (XSString)obj;
                    //                        content = str.getValue();
                    //                    }
                    //                    //OpenSAML reads type=xs:string attributes as any-typed?
                    //                    else if (obj instanceof XSAny) {
                    //                        //ok
                    //                        XSAny str = (XSAny)obj;
                    //                        content = str.getTextContent();
                    //                    } else {
                    //                        _logger.debug("Unrecognized type of attribute (skipped): " + obj.getClass().getName());
                    //                    }
                }

                if (returnAtts.contains(att.getName())) {
                    _logger.debug("Duplicate name for attribute (skipped): " + att.getName());
                } else if (content == null) {
                    // Workaround to not crash on null-content (i.e. when AttributeValue is not text-content)
                    _logger.debug("No content for the value of " + att.getName() + " (" + content + "), ignoring.");
                } else {
                    _logger.debug("Adding attribute to map: " + att.getName());

                    if (_bCompatible) {
                        //DD Unspecified name format will be ignored
                        String sAttributeNameFormat = att.getNameFormat();
                        if (sAttributeNameFormat != null && sAttributeNameFormat.equals(Attribute.UNSPECIFIED))
                            sAttributeNameFormat = null;

                        returnAtts.put(att.getName(), sAttributeNameFormat, content);
                    } else {
                        returnAtts.put(att.getName(), content);
                    }
                }
            }
        }

        return returnAtts;
    }

    /**
     * Maps attributes using the configured mapping (if exists).
     *
     * @param source The source attributes, probably from the SAML response.
     * @param target The mapped attributes, to be used by OA.
     * @param mapping The mapping hashtable.
     * @return The mapped attributes.
     */
    protected IAttributes mapAttributes(IAttributes source, IAttributes target, Hashtable<String, String> mapping) {
        Enumeration enumNames = source.getNames();
        while (enumNames.hasMoreElements()) {
            String sName = (String) enumNames.nextElement();
            Object oValue = source.get(sName);
            String sMappedName = mapping.get(sName);
            if (sMappedName != null)
                sName = sMappedName;

            if (_bCompatible) {
                //DD Unspecified name format will be ignored
                String sAttributeNameFormat = source.getFormat(sName);
                if (sAttributeNameFormat != null && sAttributeNameFormat.equals(Attribute.UNSPECIFIED))
                    sAttributeNameFormat = null;

                target.put(sName, sAttributeNameFormat, oValue);
            } else {// not using unsupported attribute format
                target.put(sName, oValue);
            }
        }
        return target;
    }

    /**
     * Sign the suplied saml object.
     * @param samlObject saml object to be signed
     * @throws OAException If the crypto configuration in OA is unsupported
     */
    protected void signSAMLObject(SignableSAMLObject samlObject) throws OAException {
        try {
            XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
            //Build signature
            SignatureBuilder builder = (SignatureBuilder) builderFactory.getBuilder(Signature.DEFAULT_ELEMENT_NAME);
            Signature signature = builder.buildObject(Signature.DEFAULT_ELEMENT_NAME);

            signature.setSignatureAlgorithm(SAML2CryptoUtils.getXMLSignatureURI(_crypto));

            //Get signing credentials
            X509Credential signingX509Cred = SAML2CryptoUtils.retrieveMySigningCredentials(_crypto,
                    _entityDescriptor.getEntityID());
            signature.setSigningCredential(signingX509Cred);

            SecurityHelper.prepareSignatureParams(signature, signingX509Cred, null, null);

            samlObject.setSignature(signature);

            //update digest algorithm
            SAMLObjectContentReference contentReference = ((SAMLObjectContentReference) signature
                    .getContentReferences().get(0));
            contentReference.setDigestAlgorithm(SAML2CryptoUtils.getXMLDigestMethodURI(_crypto.getMessageDigest()));

            //Marshall 
            Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(samlObject);
            if (marshaller == null) {
                _logger.error("No marshaller registered for " + samlObject.getElementQName()
                        + ", unable to marshall assertion");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
            if (samlObject.getDOM() == null)
                marshaller.marshall(samlObject);

            Signer.signObject(signature);
        } catch (OAException e) {
            throw e;
        } catch (MarshallingException e) {
            _logger.warn("Marshalling error while signing object", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        } catch (Exception e) {
            _logger.error("Could not sign object", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * Creates a default empty SAML context object.
     *  
     * TODO -MG: merge with AbstractSAML2Profile.createEncodingContext
     *  
     * @param request Servlet request.
     * @param response Servlet response.
     * @return Default SAML context object.
     */
    protected SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> createEncodingContext(
            HttpServletRequest request, HttpServletResponse response) {
        HTTPInTransport inTransport = new HttpServletRequestAdapter(request);

        HTTPOutTransport outTransport = new HttpServletResponseAdapter(response, request.isSecure());

        SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context = new BasicSAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject>();

        context.setInboundMessageTransport(inTransport);
        context.setOutboundMessageTransport(outTransport);

        return context;
    }

    /**
     * Build an endpoint.
     * 
     * TODO -MG: merge with AbstractSAML2Profile.buildMetadataEndpoint
     * 
     * @param elementName The type of service for the endpoint.
     * @param sBinding The binding to be used.
     * @param sLocation The endpoint location.
     * @param sResponseLocation The optional response location. 
     * @return The constructed endpoint.
     */
    protected Endpoint buildMetadataEndpoint(QName elementName, String sBinding, String sLocation,
            String sResponseLocation) {
        XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();

        XMLObjectBuilder endpointBuilder = builderFactory.getBuilder(elementName);

        Endpoint samlEndpoint = (Endpoint) endpointBuilder.buildObject(elementName);
        samlEndpoint.setLocation(sLocation);
        samlEndpoint.setBinding(sBinding);

        if (sResponseLocation != null)
            samlEndpoint.setResponseLocation(sResponseLocation);

        return samlEndpoint;
    }

    /**
     * Builds <code>Issuer</code> object.
     *
     * @return The <code>Issuer</code> object.
     */
    protected Issuer buildIssuer() {
        IssuerBuilder issuerBuilder = (IssuerBuilder) Configuration.getBuilderFactory()
                .getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
        Issuer issuer = issuerBuilder.buildObject();
        issuer.setValue(_entityDescriptor.getEntityID());

        return issuer;
    }

    /**
     * Builds <code>NameIDPolicy</code> object.
     * 
     * NameID format is determined using the following sources (in this order):
     * 
     * 1. Proxy attribute
     * 2. Requestor metadata
     * 3. Configured value
     * 
     * @param session The Authentication session (for proxy attribute).
     * @param descriptor The current idp sso descriptor from the organization metadata.
     * @param useAllowCreate The explicit value of AllowCreate that must be used unless a proxied value is available.
     * @param forcedNameIDFormat The explicit value of the NameIDFormat that must be used.
     *
     * @return The <code>NameIDPolicy</code> object.
     */
    protected NameIDPolicy buildNameIDPolicy(ISession session, IDPSSODescriptor descriptor, Boolean useAllowCreate,
            String forcedNameIDFormat) {
        NameIDPolicy nameIDPolicy = null;

        String nameIDFormat = null;
        if (forcedNameIDFormat != null) {
            nameIDFormat = forcedNameIDFormat;
        } else if (descriptor != null) {//try metadata
            List<NameIDFormat> metadataNIFs = descriptor.getNameIDFormats();

            if (metadataNIFs != null && !metadataNIFs.isEmpty()) {//DD Always using the first NameIDFormat that is available in the metadata of the organization
                nameIDFormat = metadataNIFs.get(0).getFormat();

                _logger.debug("Using first NameIDFormat from IdP metadata: " + nameIDFormat);
            }
        }

        if (nameIDFormat != null) {
            NameIDPolicyBuilder nidpBuilder = (NameIDPolicyBuilder) Configuration.getBuilderFactory()
                    .getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME);
            nameIDPolicy = nidpBuilder.buildObject();
            nameIDPolicy.setFormat(nameIDFormat);

            Boolean boolAllowCreate = (Boolean) session.getAttributes().get(ProxyAttributes.class,
                    ProxyAttributes.ALLOW_CREATE);
            if (boolAllowCreate != null) {//allow create has been proxied
                nameIDPolicy.setAllowCreate(boolAllowCreate);
            } else if (useAllowCreate != null) {//the allow create is explicitly configured
                nameIDPolicy.setAllowCreate(useAllowCreate);
            }
        }

        return nameIDPolicy;
    }

    /**
     * Builds NameID.
     *
     * @param sNameID UserID
     * @param sFormat The format of the NameID or NULL if none.
     * @param sNameQualifier The NameQualifier of the NameID or NULL if none.
     * @return The NameID object.
     */
    protected NameID buildNameID(String sNameID, String sFormat, String sNameQualifier) {
        NameIDBuilder nameidBuilder = (NameIDBuilder) Configuration.getBuilderFactory()
                .getBuilder(NameID.DEFAULT_ELEMENT_NAME);

        if (_idMapper != null) {
            try {
                String mappedUID = _idMapper.map(sNameID);
                if (mappedUID != null) {
                    sNameID = mappedUID;
                }
            } catch (OAException e) {
                _logger.debug("Could not map OA UID to ext. ID");
                return null;
            }
        }

        NameID nid = nameidBuilder.buildObject();

        nid.setValue(sNameID);

        //DD currently the SPProvidedID is not supported
        //nid.setSPProvidedID();

        if (sFormat != null)
            nid.setFormat(sFormat);

        if (sNameQualifier != null)
            nid.setNameQualifier(sNameQualifier);

        return nid;
    }

    /**
     * Builds a subject SAML object based on a User ID.
     *
     * @param sNameID The UID.
     * @param sNameIDFormat The nameid format or NULL if none.
     * @param sNameQualifier The NameQualifier of the NameID or NULL if none.
     * @param bAvoidSubjectConfirmation When true, do not include any SubjectConfirmation elements
     *       in the Subject
     * @return The <code>Subject</code> object.
     */
    protected Subject buildSubject(String sNameID, String sNameIDFormat, String sNameQualifier,
            boolean bAvoidSubjectConfirmation) {
        SubjectBuilder subjBuilder = (SubjectBuilder) Configuration.getBuilderFactory()
                .getBuilder(Subject.DEFAULT_ELEMENT_NAME);

        Subject subj = subjBuilder.buildObject();

        NameID nid = buildNameID(sNameID, sNameIDFormat, sNameQualifier);

        subj.setNameID(nid);

        if (!bAvoidSubjectConfirmation) {
            SubjectConfirmationBuilder subjConfBuilder = (SubjectConfirmationBuilder) Configuration
                    .getBuilderFactory().getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);

            SubjectConfirmation subjConf = subjConfBuilder.buildObject();

            SubjectConfirmationDataBuilder subjConfDataBuilder = (SubjectConfirmationDataBuilder) Configuration
                    .getBuilderFactory().getBuilder(SubjectConfirmationData.DEFAULT_ELEMENT_NAME);

            SubjectConfirmationData scData = subjConfDataBuilder.buildObject();

            subjConf.setSubjectConfirmationData(scData);

            subjConf.setMethod(AuthnContext.UNSPECIFIED_AUTHN_CTX);

            subj.getSubjectConfirmations().add(subjConf);
        } else {
            _logger.debug("Skipping '" + SubjectConfirmation.DEFAULT_ELEMENT_NAME.getLocalPart() + "' in "
                    + subj.DEFAULT_ELEMENT_NAME.getLocalPart());
        }

        return subj;
    }

    /**
     * Builds Scoping based on the proxy attributes.
     *
     * @param atts The session attributes to extract the proxy attributes from.
     * @param requestorID The ID of the requestor that initiated authentication.
     * @return The Scoping.
     */
    @SuppressWarnings("unchecked")
    protected Scoping buildScoping(ISessionAttributes atts, String requestorID) {
        ScopingBuilder scopBuilder = (ScopingBuilder) Configuration.getBuilderFactory()
                .getBuilder(Scoping.DEFAULT_ELEMENT_NAME);

        Scoping scop = scopBuilder.buildObject();

        //Set ProxyCount
        Integer cnt = (Integer) atts.get(ProxyAttributes.class, ProxyAttributes.PROXYCOUNT);
        if (cnt != null) {
            scop.setProxyCount(cnt - 1);
        }

        //Set IDPList
        List<SAML2IDPEntry> entries = (List<SAML2IDPEntry>) atts.get(ProxyAttributes.class,
                ProxyAttributes.IDPLIST);
        String sGetComplete = (String) atts.get(ProxyAttributes.class, ProxyAttributes.IDPLIST_GETCOMPLETE);
        if (entries != null || sGetComplete != null) {
            scop.setIDPList(buildIDPList(entries, sGetComplete));
        }

        RequesterIDBuilder ridBuilder = (RequesterIDBuilder) Configuration.getBuilderFactory()
                .getBuilder(RequesterID.DEFAULT_ELEMENT_NAME);

        //Add RequesterIDs
        List<String> listRequesterIDs = (List<String>) atts.get(ProxyAttributes.class,
                ProxyAttributes.REQUESTORIDS);
        if (listRequesterIDs != null) {
            for (String requesterID : listRequesterIDs) {
                RequesterID rid = ridBuilder.buildObject();
                rid.setRequesterID(requesterID);
                scop.getRequesterIDs().add(rid);
            }
        }

        //DD When in proxy mode the RequesterID must always be proxied
        RequesterID requestorRID = ridBuilder.buildObject();
        requestorRID.setRequesterID(requestorID);
        scop.getRequesterIDs().add(requestorRID);

        return scop;
    }

    /**
     * Build an IDP list based on the entries from the proxy attributes.
     *
     * @param entries The list of SAML2 provider entries.
     * @param sGetComplete The URL where a full list can be found.
     * @return The IDPList object.
     */
    protected IDPList buildIDPList(List<SAML2IDPEntry> entries, String sGetComplete) {
        IDPListBuilder idplBuilder = (IDPListBuilder) Configuration.getBuilderFactory()
                .getBuilder(IDPList.DEFAULT_ELEMENT_NAME);

        IDPList idpl = idplBuilder.buildObject();

        if (entries != null) {
            for (SAML2IDPEntry entry : entries) {
                IDPEntry idpEntry = buildIDPEntry(entry);
                idpl.getIDPEntrys().add(idpEntry);
            }
        }

        if (sGetComplete != null) {
            GetCompleteBuilder gcBuilder = (GetCompleteBuilder) Configuration.getBuilderFactory()
                    .getBuilder(GetComplete.DEFAULT_ELEMENT_NAME);

            GetComplete gc = gcBuilder.buildObject();
            gc.setGetComplete(sGetComplete);
            idpl.setGetComplete(gc);
        }

        return idpl;
    }

    /**
     * TODO -MG: merge with AbstractSAML2Profile.logXML
     * 
     * Caller should perform debug enabled check
     * Logs the XML object as debug logging. 
     * @param xmlObject The XML object that must be logged.
     */
    protected void logXML(XMLObject xmlObject) {
        assert _logger.isDebugEnabled() : "Logger debug state not checked";
        Element eDOM = xmlObject.getDOM();
        if (eDOM == null) {
            Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(xmlObject);
            if (marshaller != null) {
                try {
                    eDOM = marshaller.marshall(xmlObject);
                } catch (MarshallingException e) {
                    _logger.debug("Could not prettyPrint XML object", e);
                }
            }
        }

        if (eDOM != null) {
            String sXML = XMLHelper.prettyPrintXML(eDOM);
            _logger.debug(sXML);
        }

    }

    /**
     * Returns the IdP descriptor of the organization. 
     * 
     * @param organization The SAML2Organization
     * @return The IdP description object or <code>null</code> if no IdP descriptor can be found.
     */
    protected IDPSSODescriptor getIdPDescriptor(SAML2IDP organization) {
        IDPSSODescriptor idpSSODescriptor = null;
        try {
            MetadataProvider metadataProvider = organization.getMetadataProvider();

            if (metadataProvider != null) {
                idpSSODescriptor = (IDPSSODescriptor) metadataProvider.getRole(organization.getID(),
                        IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS);
            }
        } catch (Exception mpe) {
            _logger.debug("Could not retrieve metadata for requestor " + organization.getID(), mpe);
        }

        if (idpSSODescriptor == null)
            _logger.debug("Could not retrieve metadata (IDP Role) for IdP with ID: " + organization.getID());

        return idpSSODescriptor;
    }

    /**
     * Generate a requestor ID.
     * 
     * @param sSessionID The Session ID that will be used as postfix for the ID
     * @param sessionAttributes The session attributes where the generated 
     *  prefix in will be added. 
     * @return The Requestor ID prefix
     * @throws MessageEncodingException If secure random generator cannot be 
     *  created.
     * @since 1.2
     */
    protected String generateRequestID(String sSessionID, ISessionAttributes sessionAttributes)
            throws MessageEncodingException {
        SecureRandomIdentifierGenerator idGenerator = null;
        try {
            idGenerator = new SecureRandomIdentifierGenerator();
        } catch (NoSuchAlgorithmException e) {
            String msg = "Could not generate ID for logout request";
            _logger.error(msg);
            throw new MessageEncodingException(msg, e);
        }

        String requestIDPrefix = idGenerator.generateIdentifier(SAML2AuthNConstants.REQUEST_ID_BYTE_SIZE);

        if (sessionAttributes != null)
            sessionAttributes.put(SAML2AuthNConstants.class, SAML2AuthNConstants.AUTHNREQUEST_ID_PREFIX,
                    requestIDPrefix);

        return requestIDPrefix + sSessionID;
    }

    private IDPEntry buildIDPEntry(SAML2IDPEntry entry) {
        IDPEntryBuilder idpeBuilder = (IDPEntryBuilder) Configuration.getBuilderFactory()
                .getBuilder(IDPEntry.DEFAULT_ELEMENT_NAME);

        IDPEntry idpe = idpeBuilder.buildObject();

        idpe.setLoc(entry.getLoc());
        idpe.setName(entry.getName());
        idpe.setProviderID(entry.getProviderID());

        return idpe;
    }

    private String getUID(NameID nid) {
        String sUID = null;

        if (nid == null) {
            _logger.debug("Message error: Subject NameID not found");
            return null;
        }

        //TODO -FO: determine what to do with NameQualifier and SPNameQualifier
        //        else if(!nid.getNameQualifier().equals(issuer))
        //        {
        //            _logger.debug("Message error: Subject NameID NameQualifier (" 
        //                + nid.getNameQualifier() 
        //                + ") does not match issuer ("
        //                + issuer + ").");
        //            return null;
        //        }

        sUID = nid.getValue();

        if (sUID == null) {
            //try SPProvided
            sUID = nid.getSPProvidedID();
        }

        if (_idMapper != null) {
            try {
                String remappedUID = _idMapper.remap(sUID);
                if (remappedUID != null) {
                    sUID = remappedUID;
                }
            } catch (OAException e) {
                _logger.debug("Could not remap ext. ID to OA UID");
                return null;
            }
        }

        return sUID;
    }
}