com.alfaariss.oa.authentication.remote.saml2.profile.sp.sso.SPSingleLogout.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.authentication.remote.saml2.profile.sp.sso.SPSingleLogout.java

Source

/*
 * Asimba - Serious Open Source SSO
 * 
 * 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.authentication.remote.saml2.profile.sp.sso;

import java.io.IOException;
import java.util.Arrays;
import java.util.Hashtable;
import java.util.List;

import javax.servlet.RequestDispatcher;
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.opensaml.common.SAMLObject;
import org.opensaml.common.SignableSAMLObject;
import org.opensaml.common.binding.SAMLMessageContext;
import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.binding.artifact.SAML2ArtifactType0004;
import org.opensaml.saml2.binding.artifact.SAML2ArtifactType0004Builder;
import org.opensaml.saml2.binding.security.SAML2HTTPPostSimpleSignRule;
import org.opensaml.saml2.binding.security.SAML2HTTPRedirectDeflateSignatureRule;
import org.opensaml.saml2.core.LogoutRequest;
import org.opensaml.saml2.core.LogoutResponse;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.metadata.EntityDescriptor;
import org.opensaml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml2.metadata.SingleLogoutService;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.security.MetadataCredentialResolver;
import org.opensaml.security.MetadataCriteria;
import org.opensaml.ws.message.decoder.MessageDecodingException;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.ws.security.SecurityPolicyException;
import org.opensaml.ws.transport.http.HTTPInTransport;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.SecurityException;
import org.opensaml.xml.security.credential.ChainingCredentialResolver;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.credential.StaticCredentialResolver;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.signature.Signature;
import org.opensaml.xml.signature.SignatureTrustEngine;
import org.opensaml.xml.signature.impl.ExplicitKeySignatureTrustEngine;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.DatatypeHelper;
import org.opensaml.xml.validation.ValidationException;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.RequestorEvent;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.UserEvent;
import com.alfaariss.oa.UserException;
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.api.tgt.ITGT;
import com.alfaariss.oa.authentication.remote.saml2.SAML2AuthNConstants;
import com.alfaariss.oa.authentication.remote.saml2.profile.sp.sso.protocol.SingleLogoutProtocol;
import com.alfaariss.oa.engine.core.Engine;
import com.alfaariss.oa.engine.core.crypto.CryptoException;
import com.alfaariss.oa.engine.core.idp.IDPStorageManager;
import com.alfaariss.oa.engine.core.idp.storage.IIDP;
import com.alfaariss.oa.engine.core.tgt.factory.ITGTAliasStore;
import com.alfaariss.oa.util.logging.RequestorEventLogItem;
import com.alfaariss.oa.util.logging.UserEventLogItem;
import com.alfaariss.oa.util.saml2.NameIDFormatter;
import com.alfaariss.oa.util.saml2.SAML2IssueInstantWindow;
import com.alfaariss.oa.util.saml2.ISAML2Requestors;
import com.alfaariss.oa.util.saml2.SAML2SecurityException;
import com.alfaariss.oa.util.saml2.StatusException;
import com.alfaariss.oa.util.saml2.binding.AbstractDecodingFactory;
import com.alfaariss.oa.util.saml2.binding.AbstractEncodingFactory;
import com.alfaariss.oa.util.saml2.binding.BindingProperties;
import com.alfaariss.oa.util.saml2.binding.soap11.SOAP11Utils;
import com.alfaariss.oa.util.saml2.crypto.SAML2CryptoUtils;
import com.alfaariss.oa.util.saml2.idp.SAML2IDP;
import com.alfaariss.oa.util.saml2.metadata.role.sso.SPSSODescriptorBuilder;
import com.alfaariss.oa.util.saml2.profile.AbstractSAML2Profile;
import com.alfaariss.oa.util.validation.SessionValidator;

/**
 * SLO Profile for use in SAML2 Profile.
 *
 * @author MHO
 * @author Alfa & Ariss
 * @since 1.1
 */
public class SPSingleLogout extends AbstractSAML2Profile {
    /** Sesstion attribute: ProtocolBinding */
    public final static String SESSION_REQUEST_PROTOCOLBINDING = "ProtocolBinding";
    /** Sesstion attribute: ID */
    public final static String SESSION_REQUEST_ID = "ID";
    /** Sesstion attribute: RelayState */
    public final static String SESSION_REQUEST_RELAYSTATE = "RelayState";

    private final static String SSO_LOGOUT_URI = "logout";

    private Log _logger;
    private BindingProperties _bindingProperties;
    private SingleLogoutProtocol _protocol;
    private IIDMapper _idMapper;
    private IDPStorageManager _idpStorageManager;
    private SPSSODescriptor _spSSODescriptor;
    private Hashtable<String, Boolean> _htLogoutReasonActions;

    /**
     * Constructor.
     */
    public SPSingleLogout() {
        _logger = LogFactory.getLog(this.getClass());
        _htLogoutReasonActions = new Hashtable<String, Boolean>();
    }

    /**
     * @see com.alfaariss.oa.util.saml2.profile.AbstractSAML2Profile#init(com.alfaariss.oa.api.configuration.IConfigurationManager, org.w3c.dom.Element, org.opensaml.saml2.metadata.EntityDescriptor, java.lang.String, java.lang.String, com.alfaariss.oa.util.saml2.SAML2Requestors, com.alfaariss.oa.util.saml2.SAML2IssueInstantWindow, java.lang.String)
     */
    public void init(IConfigurationManager oConfigurationManager, Element eConfig,
            EntityDescriptor entityDescriptor, String sBaseUrl, String sWebSSOPath, ISAML2Requestors requestors,
            SAML2IssueInstantWindow issueInstantWindow, String sProfileID) throws OAException {
        super.init(oConfigurationManager, eConfig, entityDescriptor, sBaseUrl, sWebSSOPath, requestors,
                issueInstantWindow, sProfileID);

        //read bindings config
        Element eBindings = oConfigurationManager.getSection(eConfig, "bindings");
        if (eBindings == null) {
            _logger.error("No 'bindings' section found in 'profile' section in configuration");
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
        }

        _bindingProperties = new BindingProperties(oConfigurationManager, eBindings);

        ITGTAliasStore idpAliasStore = _tgtFactory.getAliasStoreIDP();
        if (idpAliasStore == null) {
            _logger.error("TGT Factory has no IdP Role alias support");
            throw new OAException(SystemErrors.ERROR_INIT);
        }

        //read NameID config
        Element eNameID = oConfigurationManager.getSection(eConfig, "nameid");
        if (eNameID == null) {
            _logger.error(
                    "No 'nameid' section found in 'profile' section in configuration with profile id: " + _sID);
            throw new OAException(SystemErrors.ERROR_CONFIG_READ);
        }

        Element eIDMapper = oConfigurationManager.getSection(eConfig, "idmapper");
        if (eIDMapper != null)
            _idMapper = createIDMapper(oConfigurationManager, eIDMapper);

        NameIDFormatter nameIDFormatter = new NameIDFormatter(oConfigurationManager, eNameID, _cryptoManager,
                idpAliasStore);

        //Create protocol
        _protocol = new SingleLogoutProtocol(_cryptoManager.getSecureRandom(), _sProfileURL, _tgtFactory,
                nameIDFormatter, _issueInstantWindow, _idMapper);

        updateEntityDescriptor(oConfigurationManager, eConfig);

        _idpStorageManager = Engine.getInstance().getIDPStorageManager();

        Element eReasons = oConfigurationManager.getSection(eConfig, "reasons");
        if (eReasons == null) {
            _logger.info("No optional 'reasons' section found in configuration, using defaults");
        } else {
            readReasonConfig(oConfigurationManager, eReasons);
        }
    }

    /**
     * @see com.alfaariss.oa.util.saml2.profile.ISAML2Profile#process(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    public void process(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws OAException {
        ISession session = null;
        try {
            String sSessionId = servletRequest.getParameter(ISession.ID_NAME);
            if (sSessionId != null) {
                if (!SessionValidator.validateDefaultSessionId(sSessionId)) {
                    _logger.warn("Invalid session id in request: " + sSessionId);
                    throw new UserException(UserEvent.REQUEST_INVALID);
                }
                session = _sessionFactory.retrieve(sSessionId);

                processResponse(servletRequest, servletResponse, session);
            } else
                processSAMLRequest(servletRequest, servletResponse);
        } catch (UserException e) //User error
        {
            UserEventLogItem logItem = null;
            if (session != null)
                logItem = new UserEventLogItem(session, servletRequest.getRemoteAddr(), e.getEvent(), this, null);
            else
                logItem = new UserEventLogItem(null, null, null, e.getEvent(), null, servletRequest.getRemoteAddr(),
                        null, this, null);
            _eventLogger.info(logItem);

            if (!servletResponse.isCommitted()) {
                try {
                    servletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (IOException e1) {
                    _logger.warn("Could not send response", e1);
                }
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not process request", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * @see com.alfaariss.oa.util.saml2.profile.AbstractSAML2Profile#destroy()
     */
    public void destroy() {
        if (_idMapper != null)
            _idMapper.stop();

        super.destroy();
    }

    /**
     * Validate a inbound message signature and/or simple signature.
     * @param context The message context.
     * @param saml2IDP The IDP. 
     * @param issuer The inbound message issuer
     * @return <code>true</code> if signature is valid, otherwise <code>false</code>. 
     * @throws OAException If validation fails due to internal error.
     */
    protected boolean validateSignature(
            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context, SAML2IDP saml2IDP,
            String issuer) throws OAException {
        boolean bValid = false;
        try {
            // requestMessage == null is checked
            SignableSAMLObject message = context.getInboundSAMLMessage();

            Signature signature = message.getSignature();
            if (message.isSigned()) {
                //Validate against profile in order to prevent certain types 
                //of denial-of-service attacks associated with signature verification
                _profileValidator.validate(signature);
            }

            //Create ChainingCredentialResolver
            ChainingCredentialResolver chainingCredentialResolver = new ChainingCredentialResolver();

            //TODO EVB, JRE, RDV: define order of credential resolvers and test

            //Metadata credentials
            if (saml2IDP != null) {
                MetadataProvider mdProvider = saml2IDP.getMetadataProvider();
                if (mdProvider != null) //Metadata provider available
                {
                    _logger.debug("Metadata provider found for issuer: " + issuer);
                    MetadataCredentialResolver mdCredResolver = new MetadataCredentialResolver(mdProvider);
                    chainingCredentialResolver.getResolverChain().add(mdCredResolver);
                }
            }

            //OA Engine credentials
            try {
                if (_signingEnabled) //OA Signing enabled
                {
                    Credential signingCred = SAML2CryptoUtils.retrieveSigningCredentials(_cryptoManager, issuer);
                    StaticCredentialResolver oaResolver = new StaticCredentialResolver(signingCred);
                    chainingCredentialResolver.getResolverChain().add(oaResolver);
                }
            } catch (CryptoException e) //No certificate found
            {
                _logger.debug("No trusted certificate found for issuer: " + issuer);
                //Ignore
            }

            //TODO EVB, JRE, RDV: define order of credential resolvers and test                        
            if (chainingCredentialResolver.getResolverChain().isEmpty()) {
                _logger.warn("No trusted certificate or metadata found for issuer: " + issuer);
                //bValid = false already    
            } else {
                //Create trust engine                
                //TODO EVB: trust engine and resolver creation can be placed in one-time init code (e.g. SAML2Requestor)
                SignatureTrustEngine sigTrustEngine = new ExplicitKeySignatureTrustEngine(
                        chainingCredentialResolver, _keyInfoCredResolver);

                if (message.isSigned()) //Validate XML signature (if applicable)
                {
                    //create criteria set            
                    CriteriaSet criteriaSet = new CriteriaSet();
                    criteriaSet.add(new EntityIDCriteria(issuer));
                    MetadataCriteria mdCriteria = new MetadataCriteria(context.getPeerEntityRole(),
                            context.getInboundSAMLProtocol());
                    criteriaSet.add(mdCriteria);
                    criteriaSet.add(new UsageCriteria(UsageType.SIGNING));
                    bValid = sigTrustEngine.validate(signature, criteriaSet);
                } else
                    bValid = true; //Message itself not signed

                if (bValid) //Message not signed or valid signature
                {
                    //Validate simple signature for GET (if applicable)
                    SAML2HTTPRedirectDeflateSignatureRule ruleGET = new SAML2HTTPRedirectDeflateSignatureRule(
                            sigTrustEngine);
                    ruleGET.evaluate(context);
                    //Validate simple signature for POST (if applicable)
                    SAML2HTTPPostSimpleSignRule rulePOST = new SAML2HTTPPostSimpleSignRule(sigTrustEngine, _pool,
                            _keyInfoCredResolver);
                    rulePOST.evaluate(context);
                }
            }
        } catch (SecurityPolicyException e) {
            // Indicates signature was not cryptographically valid, or possibly a processing error
            _logger.debug("Invalid signature", e);
            bValid = false;
        } catch (ValidationException e) {
            // Indicates signature was not cryptographically valid, or possibly a processing error
            _logger.debug("Invalid signature", e);
            bValid = false;
        } catch (SecurityException e) //Internal processing error
        {
            _logger.error("Processing error evaluating the signature", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
        return bValid;
    }

    private void readReasonConfig(IConfigurationManager configurationManager, Element config) throws OAException {
        _htLogoutReasonActions.clear();

        Element eReason = configurationManager.getSection(config, "reason");
        while (eReason != null) {
            String sURI = configurationManager.getParam(eReason, "uri");
            if (sURI == null) {
                _logger.error("No 'uri' parameter in 'reason' section found in configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }

            if (_htLogoutReasonActions.containsKey(sURI)) {
                _logger.error(
                        "Invalid 'uri' parameter in 'reason' section found in configuration; not unique: " + sURI);
                throw new OAException(SystemErrors.ERROR_INIT);
            }

            String sPartial = configurationManager.getParam(eReason, "partial");
            if (sPartial == null) {
                _logger.error("No 'partial' parameter in 'reason' section found in configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }

            Boolean boolPartial = new Boolean(Boolean.TRUE);
            if (sPartial.equalsIgnoreCase("FALSE"))
                boolPartial = new Boolean(Boolean.FALSE);
            else if (!sPartial.equalsIgnoreCase("TRUE")) {
                _logger.error("Unknown value in 'partial' configuration item: " + sPartial);
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }

            _htLogoutReasonActions.put(sURI, boolPartial);

            StringBuffer sbInfo = new StringBuffer("Using logout action for reason is '");
            sbInfo.append(sURI);
            sbInfo.append("': ");
            _logger.info(sbInfo.toString() + (boolPartial.booleanValue() ? "partial" : "full"));

            eReason = configurationManager.getNextSection(eReason);
        }
    }

    private void processSAMLRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
            throws OAException {
        SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context = null;
        String sBindingURI = null;
        try {
            //Decode message
            AbstractDecodingFactory decFactory = AbstractDecodingFactory.resolveInstance(servletRequest,
                    servletResponse, _bindingProperties);
            if (decFactory == null) {
                _logger.debug("Decoding factory not created: Invalid request");
                throw new MessageDecodingException("Could not determine binding");
            }

            SAMLMessageDecoder decoder = decFactory.getDecoder();
            sBindingURI = decoder.getBindingURI();

            if (!_bindingProperties.isSupported(sBindingURI)) {
                _logger.error("The binding is not supported by this protocol: " + sBindingURI);
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            _logger.debug("Binding URI: " + sBindingURI);

            context = decFactory.getContext();
            context.setLocalEntityId(_sEntityID);
            context.setLocalEntityMetadata(_entityDescriptor);
            context.setLocalEntityRoleMetadata(_spSSODescriptor);

            String val = servletRequest.getParameter("SAMLart");
            if (val != null) {
                //SAML artifact received, requestor metadata and IssuerID must be added
                //in order to enable the decoder to decode artifact

                byte[] bb = Base64.decode(val);
                SAML2ArtifactType0004 b = null;
                SAML2ArtifactType0004Builder bf = new SAML2ArtifactType0004Builder();
                b = bf.buildArtifact(bb);

                IIDP org = _idpStorageManager.getIDP(b.getSourceID(), SAML2IDP.TYPE_SOURCEID);
                if (org != null && org instanceof SAML2IDP) {
                    SAML2IDP saml2IDP = (SAML2IDP) org;
                    context.setMetadataProvider(saml2IDP.getMetadataProvider());
                    context.setInboundMessageIssuer(saml2IDP.getID());
                    context.setOutboundMessageIssuer(_sEntityID);
                } else {
                    StringBuffer sbDebug = new StringBuffer("Unknown organization specified with with SourceID '");
                    sbDebug.append(Arrays.toString(b.getSourceID()));
                    sbDebug.append("' in artifact: ");
                    sbDebug.append(val);
                    _logger.debug(sbDebug.toString());
                    throw new MessageDecodingException("Could not find metadata for decoding artifact");
                }
            }

            //Decode request
            try {
                decoder.decode(context);
            } catch (SecurityException e) {
                _logger.debug("Could not decode inbound message due to security exception", e);
                throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
            }

            //verify saml message in request
            SignableSAMLObject requestMessage = context.getInboundSAMLMessage();

            if (_logger.isDebugEnabled()) {
                if (requestMessage != null)
                    logXML(requestMessage);
            }

            if (requestMessage instanceof LogoutResponse) {
                processLogoutResponse(servletRequest, servletResponse, context, (LogoutResponse) requestMessage);
            } else if (requestMessage instanceof LogoutRequest) {
                //DD <LogoutRequest> signing is forced by code for HTTP POST or Redirect binding [saml-profiles-2.0-os r1223].
                boolean bMandatorySinging = sBindingURI.equals(SAMLConstants.SAML2_POST_BINDING_URI)
                        || sBindingURI.equals(SAMLConstants.SAML2_REDIRECT_BINDING_URI);

                HTTPInTransport inTransport = (HTTPInTransport) context.getInboundMessageTransport();
                String sigParam = inTransport.getParameterValue("Signature");
                boolean bSigned = !DatatypeHelper.isEmpty(sigParam) || requestMessage.isSigned();

                if (bMandatorySinging && !bSigned) {
                    _logger.debug("LogoutRequest MUST be signed if the HTTP POST or Redirect binding is used");
                    throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
                }

                LogoutRequest lr = (LogoutRequest) requestMessage;
                String sReason = lr.getReason();

                processLogoutRequest(servletRequest, servletResponse, context, sBindingURI, sReason);
            } else {
                _logger.debug(
                        "Unsupported SAML message in request from issuer: " + context.getInboundMessageIssuer());
                throw new MessageDecodingException("Unsupported SAML message");
            }
        } catch (StatusException e) //SAML processing error
        {
            _eventLogger.info(new RequestorEventLogItem(null, null, null, e.getEvent(), null,
                    servletRequest.getRemoteAddr(), e.getRequestorID(), this, e.getMessage()));

            sendResponse(context, servletRequest, servletResponse, sBindingURI);
        } catch (MessageDecodingException e) //Binding processing error  
        {
            _logger.debug("Decoding error", e);
            _eventLogger.info(new RequestorEventLogItem(null, null, null, RequestorEvent.REQUEST_INVALID, null,
                    servletRequest.getRemoteAddr(), null, this, null));
            if (sBindingURI != null && sBindingURI.equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
                SOAP11Utils.sendSOAPFault(context, RequestorEvent.REQUEST_INVALID);
            } else {
                try {
                    if (!servletResponse.isCommitted())
                        servletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
                } catch (IOException e1) {
                    _logger.warn("Could not send response", e1);
                }
            }
        } catch (SAML2SecurityException e) {//The message does not meet the required security constraints
            _logger.debug("Security error", e);
            _eventLogger.info(new RequestorEventLogItem(null, null, null, e.getEvent(), null,
                    servletRequest.getRemoteAddr(), null, this, "Security Fault"));

            //DD Security error -> Return a "403 Forbidden" response
            try {
                if (!servletResponse.isCommitted())
                    servletResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
            } catch (IOException e1) {
                _logger.warn("Could not send response", e1);
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not process SAML request message", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    //Handle logout request 
    private void processLogoutRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context, String sBinding,
            String sReason) throws OAException, SAML2SecurityException, StatusException {
        //Validate requestor and signature
        SAML2IDP saml2IDP = validateRequestMessage(context, IDPSSODescriptor.DEFAULT_ELEMENT_NAME);

        //Process request
        ITGT tgt = _protocol.processRequest(context);

        String sInReponseTo = context.getInboundSAMLMessageId();

        boolean bIsPartiallyLogout = false;
        if (sReason != null) {
            if (_htLogoutReasonActions.containsKey(sReason)) {//The reason logout action can be optionally configured
                bIsPartiallyLogout = _htLogoutReasonActions.get(sReason).booleanValue();
            } else {
                if (sReason.equals(LogoutResponse.GLOBAL_TIMEOUT_URI)) {//More info about this reason: saml-core-2.0-o.s.pdf r2662
                    bIsPartiallyLogout = true;
                } else if (sReason.equals(LogoutResponse.SP_TIMEOUT_URI)) {//More info about this reason: saml-core-2.0-o.s.pdf r2665
                                                                           //it is agreed with the requestor that we must do a full logout
                    bIsPartiallyLogout = false;
                } else if (sReason.equals(LogoutResponse.USER_LOGOUT_URI)) {//More info about this reason: saml-core-2.0-o.s.pdf r2580
                    bIsPartiallyLogout = false;
                } else if (sReason.equals(LogoutResponse.ADMIN_LOGOUT_URI)) {//More info about this reason: saml-core-2.0-o.s.pdf r2583
                    bIsPartiallyLogout = false;
                }
            }
        }

        if (bIsPartiallyLogout || sBinding.equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
            //Process response
            _protocol.processResponse(tgt, sInReponseTo, context, bIsPartiallyLogout);

            context.setOutboundMessageIssuer(saml2IDP.getID());
            context.setMetadataProvider(saml2IDP.getMetadataProvider());

            //Send response      
            sendResponse(context, servletRequest, servletResponse, sBinding);

            _eventLogger.info(new UserEventLogItem(null, tgt.getId(), null, UserEvent.USER_LOGGED_OUT,
                    tgt.getUser().getID(), servletRequest.getRemoteAddr(), saml2IDP.getID(), this,
                    context.getOutboundSAMLMessageId()));
        } else {//process a-synchronous logout

            //DD Creating a session with an OpenASelect IdP instead of an OpenASelect Requestor has the consequence that any SP Aliasses for a requestor with the same ID will be removed 
            ISession session = _sessionFactory.createSession(saml2IDP.getID());

            ISessionAttributes sessionAttributes = session.getAttributes();
            sessionAttributes.put(this.getClass(), SESSION_REQUEST_ID, sInReponseTo);
            sessionAttributes.put(this.getClass(), SESSION_REQUEST_PROTOCOLBINDING, sBinding);

            String sRelayState = context.getRelayState();
            if (sRelayState != null)
                sessionAttributes.put(this.getClass(), SESSION_REQUEST_RELAYSTATE, sRelayState);

            session.persist();//this generates the session ID

            StringBuffer sbProfileURL = new StringBuffer();
            sbProfileURL.append(_sProfileURL);
            sbProfileURL.append("?");
            sbProfileURL.append(ISession.ID_NAME);
            sbProfileURL.append("=");
            sbProfileURL.append(session.getId());
            session.setProfileURL(sbProfileURL.toString());

            servletRequest.setAttribute(ISession.ID_NAME, session);

            StringBuffer sbForward = new StringBuffer(_sWebSSOPath);
            if (!_sWebSSOPath.endsWith("/"))
                sbForward.append("/");
            sbForward.append(SSO_LOGOUT_URI);

            _logger.debug("Forwarding user to: " + sbForward.toString());

            RequestDispatcher oDispatcher = servletRequest.getRequestDispatcher(sbForward.toString());
            if (oDispatcher == null) {
                _logger.warn("There is no requestor dispatcher supported with name: " + sbForward.toString());
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            _eventLogger.info(new UserEventLogItem(session, servletRequest.getRemoteAddr(),
                    UserEvent.USER_LOGOUT_IN_PROGRESS, this, null));
            try {
                oDispatcher.forward(servletRequest, servletResponse);
            } catch (Exception e) {
                _logger.fatal("Could not forward user", e);
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        }
    }

    private void processResponse(HttpServletRequest request, HttpServletResponse response, ISession session)
            throws OAException, UserException {
        try {
            String sIssuer = session.getRequestorId();
            if (_idpStorageManager.existStorage(sIssuer)) {
                _logger.debug("No IDP found with for issuer: " + sIssuer);
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            SAML2IDP saml2IDP = (SAML2IDP) _idpStorageManager.getIDP(sIssuer);

            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context = createEncodingContext(
                    request, response);
            context.setInboundMessageIssuer(_sEntityID);
            context.setOutboundMessageIssuer(saml2IDP.getID());
            context.setMetadataProvider(saml2IDP.getMetadataProvider());

            if (session.isExpired()) {
                StringBuffer sbError = new StringBuffer("Expired session with id '");
                sbError.append(session.getId());
                sbError.append("' found in request sent from IP: ");
                sbError.append(request.getRemoteAddr());
                _logger.debug(sbError.toString());
                throw new UserException(UserEvent.SESSION_EXPIRED);
            }

            ISessionAttributes sessionAttributes = session.getAttributes();

            String sInResponseTo = (String) sessionAttributes.get(this.getClass(), SESSION_REQUEST_ID);
            if (sInResponseTo == null) {
                StringBuffer sbDebug = new StringBuffer("No session attribute available with name '");
                sbDebug.append(SESSION_REQUEST_ID);
                sbDebug.append("' in session with ID: ");
                sbDebug.append(session.getId());
                _logger.debug(sbDebug.toString());
                throw new UserException(UserEvent.SESSION_INVALID);
            }

            String sRequestBinding = (String) sessionAttributes.get(this.getClass(),
                    SESSION_REQUEST_PROTOCOLBINDING);
            if (sRequestBinding == null) {
                StringBuffer sbDebug = new StringBuffer("No session attribute available with name '");
                sbDebug.append(SESSION_REQUEST_PROTOCOLBINDING);
                sbDebug.append("' in session with ID: ");
                sbDebug.append(session.getId());
                _logger.debug(sbDebug.toString());
                throw new UserException(UserEvent.SESSION_INVALID);
            }

            String sRelayState = (String) sessionAttributes.get(this.getClass(), SESSION_REQUEST_RELAYSTATE);
            if (sRelayState != null)
                context.setRelayState(sRelayState);

            context.setLocalEntityId(_sEntityID);//needed by processResponse()

            UserEvent userEvent = UserEvent.INTERNAL_ERROR;
            switch (session.getState()) {
            case USER_LOGOUT_SUCCESS: {
                _protocol.processResponse(null, sInResponseTo, context);
                userEvent = UserEvent.USER_LOGGED_OUT;
                break;
            }
            case USER_LOGOUT_PARTIAL: {
                _protocol.buildErrorResponse(context, StatusCode.SUCCESS_URI, StatusCode.PARTIAL_LOGOUT_URI,
                        sInResponseTo);

                userEvent = UserEvent.USER_LOGOUT_PARTIALLY;
                break;
            }
            case USER_LOGOUT_IN_PROGRESS:
            case USER_LOGOUT_FAILED: {
                _protocol.buildErrorResponse(context, StatusCode.RESPONDER_URI, null, sInResponseTo);
                userEvent = UserEvent.USER_LOGOUT_FAILED;
                break;
            }
            default: {
                StringBuffer sbError = new StringBuffer("Unsupported session state '");
                sbError.append(session.getState());
                sbError.append("' for session with id: ");
                sbError.append(session.getId());
                _logger.debug(sbError.toString());
                throw new UserException(UserEvent.REQUEST_INVALID);
            }
            }

            sendASynchronousResponse(context, request, response, sRequestBinding, saml2IDP);

            _eventLogger.info(new UserEventLogItem(session, request.getRemoteAddr(), userEvent, this,
                    context.getOutboundSAMLMessageId()));
        } catch (UserException e) {
            throw e;
        } finally {
            //Always remove logout session; even if an error ocurred
            if (session != null) {
                session.expire();
                session.persist();
            }
        }
    }

    private void processLogoutResponse(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context, LogoutResponse message)
            throws OAException, SAML2SecurityException {
        String sInResponseTo = message.getInResponseTo();
        if (sInResponseTo == null) {
            _logger.debug("Incoming SAML object is missing InResponseTo attribute");
            throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
        }

        //DD: Session ID is extracted from InResponseTo. If null, relaystate or target are used for Unsolicited Response.
        String sSessionID = null;
        String sRequestIDPrefix = null;
        if (sInResponseTo.length() <= SAML2AuthNConstants.REQUEST_ID_LENGTH) {
            StringBuffer sbWarn = new StringBuffer("Invalid InResponseTo ID supplied (");
            sbWarn.append(sInResponseTo);
            sbWarn.append(") is must have a length that is at least bigger then: ");
            sbWarn.append(SAML2AuthNConstants.REQUEST_ID_LENGTH);
            _logger.warn(sbWarn.toString());

            throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
        }

        sRequestIDPrefix = sInResponseTo.substring(0, SAML2AuthNConstants.REQUEST_ID_LENGTH);
        sSessionID = sInResponseTo.substring(SAML2AuthNConstants.REQUEST_ID_LENGTH);

        if (!SessionValidator.validateDefaultSessionId(sSessionID)) {
            StringBuffer sbError = new StringBuffer("Invalid '");
            sbError.append(ISession.ID_NAME);
            sbError.append("' in request: ");
            sbError.append(sSessionID);
            _logger.debug(sbError.toString());
            throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
        }

        ISession session = _sessionFactory.retrieve(sSessionID);

        if (session == null || session.isExpired()) {
            _logger.debug("Could not process SAML response; Session expired");
            throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
        }

        if (session.getAttributes().contains(SAML2AuthNConstants.class,
                SAML2AuthNConstants.AUTHNREQUEST_ID_PREFIX)) {
            String sSessionRequestIDPrefix = (String) session.getAttributes().get(SAML2AuthNConstants.class,
                    SAML2AuthNConstants.AUTHNREQUEST_ID_PREFIX);

            if (sSessionRequestIDPrefix != null && sRequestIDPrefix != null
                    && !sSessionRequestIDPrefix.equals(sRequestIDPrefix)) {
                StringBuffer sbError = new StringBuffer(
                        "Invalid InResponseTo session ID prefix in request: expected '");
                sbError.append(sSessionRequestIDPrefix);
                sbError.append("' but recieved: ");
                sbError.append(sRequestIDPrefix);
                _logger.debug(sbError.toString());
                throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
            }
        }

        //forward user to /sso to finish logout (in authn.saml2 where result will be checked)

        servletRequest.setAttribute(SAML2AuthNConstants.SESSION_ATTRIBUTE_NAME, context);
        servletRequest.setAttribute(ISession.ID_NAME, session);

        StringBuffer sbForward = new StringBuffer(_sWebSSOPath);
        if (!_sWebSSOPath.endsWith("/"))
            sbForward.append("/");
        sbForward.append(SSO_LOGOUT_URI);

        _logger.debug("Forwarding user to: " + sbForward.toString());

        RequestDispatcher oDispatcher = servletRequest.getRequestDispatcher(sbForward.toString());
        if (oDispatcher == null) {
            _logger.warn("There is no requestor dispatcher supported with name: " + sbForward.toString());
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }

        try {
            oDispatcher.forward(servletRequest, servletResponse);
        } catch (Exception e) {
            _logger.fatal("Could not forward user", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private void updateEntityDescriptor(IConfigurationManager configurationManager, Element config) {
        _spSSODescriptor = _entityDescriptor.getSPSSODescriptor(SAMLConstants.SAML20P_NS);
        if (_spSSODescriptor == null)
            throw new IllegalArgumentException("No SPSSODescriptor available");

        SPSSODescriptorBuilder builder = new SPSSODescriptorBuilder(configurationManager, config, _spSSODescriptor);

        builder.buildSingleLogoutService(_sProfileURL, _bindingProperties);
    }

    //Send SAML response message using given binding
    private void sendResponse(SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context,
            HttpServletRequest servletRequest, HttpServletResponse servletResponse, String sRequestBinding)
            throws OAException {
        try {
            LogoutResponse logoutResponse = (LogoutResponse) context.getOutboundSAMLMessage();
            LogoutRequest logoutRequest = (LogoutRequest) context.getInboundSAMLMessage();

            //Prepare the response signing
            if (_signingEnabled) {
                Credential credentials = SAML2CryptoUtils.retrieveMySigningCredentials(_cryptoManager, _sEntityID);
                context.setOutboundSAMLMessageSigningCredential(credentials);
            }

            String sBindingURI = null;

            if (sRequestBinding.equals(SAMLConstants.SAML2_SOAP11_BINDING_URI)) {
                sBindingURI = SAMLConstants.SAML2_SOAP11_BINDING_URI;
            } else //ASynchronous
            {
                //Response must be signed if POST, Redirect binding is used
                if (!_signingEnabled) {
                    _logger.warn(
                            "No outbound signing credential found: responses must be signed, make sure server signing is enabled");
                    throw new OAException(SystemErrors.ERROR_INTERNAL);
                }

                String sIssuer = context.getInboundMessageIssuer();

                if (_idpStorageManager.existStorage(sIssuer)) {
                    _logger.debug("No IDP found with for issuer: " + sIssuer);
                    throw new OAException(SystemErrors.ERROR_INTERNAL);
                }

                SAML2IDP saml2IDP = (SAML2IDP) _idpStorageManager.getIDP(sIssuer);

                //resolve LogoutService destination using metadata               
                SingleLogoutService singleLogoutService = resolveSingleLogoutServiceEndpoint(saml2IDP,
                        sRequestBinding);

                if (singleLogoutService == null) {
                    _logger.warn(
                            "No SingleLogoutService with supported binding for response available. Request ID: "
                                    + logoutRequest.getID());
                    throw new OAException(SystemErrors.ERROR_INTERNAL);
                }

                sBindingURI = singleLogoutService.getBinding();

                String sDestination = singleLogoutService.getResponseLocation();
                if (sDestination == null) //No response location
                {
                    _logger.debug(
                            "No SingleLogoutService response location for response available, using 'location'. Request ID: "
                                    + logoutRequest.getID());
                    //Try location
                    sDestination = singleLogoutService.getLocation();
                } else {
                    //DD Copy response location to location: OpenSAML encoders only support response location for Response objects, LogoutResponse is not a Response
                    singleLogoutService.setLocation(sDestination);
                }

                if (sDestination == null) {
                    _logger.warn("No SingleLogoutService location for response available. Request ID: "
                            + logoutRequest.getID());
                    throw new OAException(SystemErrors.ERROR_INTERNAL);
                }

                logoutResponse.setDestination(sDestination);
                context.setPeerEntityEndpoint(singleLogoutService);
            }

            AbstractEncodingFactory encodingFactory = AbstractEncodingFactory.createInstance(servletRequest,
                    servletResponse, sBindingURI, _bindingProperties);
            SAMLMessageEncoder encoder = encodingFactory.getEncoder();
            encoder.encode(context);

            if (_logger.isDebugEnabled()) {
                XMLObject xmlObject = context.getOutboundSAMLMessage();
                if (xmlObject != null)
                    logXML(xmlObject);
            }
        } catch (MessageEncodingException e) {
            _logger.error("Could not send reponse", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        } catch (Exception e) {
            _logger.error("Internal error when sending reponse", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    //Send SAML response message using given binding
    private void sendASynchronousResponse(
            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context,
            HttpServletRequest servletRequest, HttpServletResponse servletResponse, String sRequestBinding,
            SAML2IDP saml2IDP) throws OAException {
        try {
            LogoutResponse logoutResponse = (LogoutResponse) context.getOutboundSAMLMessage();

            //Response must be signed if POST, Redirect binding is used
            if (!_signingEnabled) {
                _logger.warn(
                        "No outbound signing credential found: responses must be signed, make sure server signing is enabled");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            //resolve LogoutService destination using metadata               
            SingleLogoutService singleLogoutService = resolveSingleLogoutServiceEndpoint(saml2IDP, sRequestBinding);

            if (singleLogoutService == null) {
                StringBuffer sbWarning = new StringBuffer(
                        "No SingleLogoutService with supported binding for response available (");
                sbWarning.append(sRequestBinding);
                sbWarning.append(") for SAML2 IdP with ID: ");
                sbWarning.append(saml2IDP.getID());
                _logger.warn(sbWarning.toString());
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            String sBindingURI = singleLogoutService.getBinding();

            String sDestination = singleLogoutService.getResponseLocation();
            if (sDestination == null) //No response location
            {
                _logger.debug("No SingleLogoutService response location for response available, using 'location'");
                //Try location
                sDestination = singleLogoutService.getLocation();
            } else {
                //DD Copy response location to location: OpenSAML encoders only support response location for Response objects, LogoutResponse is not a Response
                singleLogoutService.setLocation(sDestination);
            }

            if (sDestination == null) {
                _logger.warn("No SingleLogoutService location for response available");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            logoutResponse.setDestination(sDestination);

            //Add my metadata
            context.setLocalEntityMetadata(_entityDescriptor);
            context.setLocalEntityRoleMetadata(_spSSODescriptor);

            context.setPeerEntityEndpoint(singleLogoutService);

            //Prepare the response signing
            if (_signingEnabled) {
                Credential credentials = SAML2CryptoUtils.retrieveMySigningCredentials(_cryptoManager,
                        _entityDescriptor.getEntityID());
                context.setOutboundSAMLMessageSigningCredential(credentials);
            }

            AbstractEncodingFactory encodingFactory = AbstractEncodingFactory.createInstance(servletRequest,
                    servletResponse, sBindingURI, _bindingProperties);

            SAMLMessageEncoder encoder = encodingFactory.getEncoder();

            encoder.encode(context);

            if (_logger.isDebugEnabled()) {
                XMLObject xmlObject = context.getOutboundSAMLMessage();
                if (xmlObject != null)
                    logXML(xmlObject);
            }
        } catch (MessageEncodingException e) {
            _logger.error("Could not send reponse", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private SingleLogoutService resolveSingleLogoutServiceEndpoint(SAML2IDP saml2IDP, String sRequestBinding)
            throws OAException {
        assert saml2IDP != null : "Empty SAML2 IDP supplied";
        try {
            //DD Metadata is mandatory for asynchronous logout
            MetadataProvider metadataProvider = saml2IDP.getMetadataProvider();
            if (metadataProvider == null) {
                _logger.warn("No MetadataProvider found for IDP: " + saml2IDP.getID());
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            IDPSSODescriptor idpSSODescriptor = (IDPSSODescriptor) metadataProvider.getRole(saml2IDP.getID(),
                    IDPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS);
            if (idpSSODescriptor == null) {
                _logger.warn("No IDPSSODescriptor in metadata: Can't resolve response target for IDP: "
                        + saml2IDP.getID());
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            SingleLogoutService service = null;
            List<SingleLogoutService> singleLogoutServices = idpSSODescriptor.getSingleLogoutServices();

            String sDefault = _bindingProperties.getDefault();
            SingleLogoutService defaultService = null;
            for (SingleLogoutService tempService : singleLogoutServices) {
                String sBinding = tempService.getBinding();
                if (sBinding != null && _bindingProperties.isSupported(sBinding)) {
                    if (sBinding.equals(sRequestBinding)) {
                        service = tempService;
                        break;
                    } else if (defaultService == null && sBinding.equals(sDefault)) {
                        defaultService = tempService;
                    }
                }
            }

            if (service == null)
                service = defaultService;

            return service;
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not resolve SingleLogoutService for: " + saml2IDP.getID(), e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private IIDMapper createIDMapper(IConfigurationManager configManager, Element eConfig) throws OAException {
        IIDMapper oMapper = null;
        try {
            String sClass = configManager.getParam(eConfig, "class");
            if (sClass == null) {
                _logger.error("No 'class' parameter found in 'idmapper' section in configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }

            Class cMapper = null;
            try {
                cMapper = Class.forName(sClass);
            } catch (Exception e) {
                _logger.error("No 'class' found with name: " + sClass, e);
                throw new OAException(SystemErrors.ERROR_INIT);
            }

            try {
                oMapper = (IIDMapper) cMapper.newInstance();
            } catch (Exception e) {
                _logger.error("Could not create an 'IIDMapper' instance of the configured 'class' found with name: "
                        + sClass, e);
                throw new OAException(SystemErrors.ERROR_INIT);
            }

            oMapper.start(configManager, eConfig);
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during creation of id mapper", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
        return oMapper;
    }

    private SAML2IDP validateRequestMessage(
            SAMLMessageContext<SignableSAMLObject, SignableSAMLObject, SAMLObject> context, QName role)
            throws SAML2SecurityException, OAException {
        context.setPeerEntityRole(role);
        String sIssuer = context.getInboundMessageIssuer();

        // requestMessage == null is checked
        SignableSAMLObject message = context.getInboundSAMLMessage();

        if (_idpStorageManager.existStorage(sIssuer)) {
            _logger.debug("No IDP found with for issuer: " + sIssuer);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }

        SAML2IDP saml2IDP = (SAML2IDP) _idpStorageManager.getIDP(sIssuer);

        //Validate signature  
        HTTPInTransport inTransport = (HTTPInTransport) context.getInboundMessageTransport();
        String sigParam = inTransport.getParameterValue("Signature");
        boolean bSignatureParam = !DatatypeHelper.isEmpty(sigParam);
        if (bSignatureParam || message.isSigned()) {
            if (!validateSignature(context, saml2IDP, sIssuer)) {
                _logger.debug("Invalid XML signature received for message from issuer: " + sIssuer);
                throw new SAML2SecurityException(RequestorEvent.REQUEST_INVALID);
            }

            _logger.debug("XML signature validation okay");

        }

        return saml2IDP;
    }
}