com.alfaariss.oa.sso.web.profile.logout.LogoutProfile.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.sso.web.profile.logout.LogoutProfile.java

Source

/*
 * Asimba Server
 * 
 * Copyright (C) 2012 Asimba
 * Copyright (C) 2007-2009 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.sso.web.profile.logout;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import com.alfaariss.oa.DetailedUserException;
import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.UserEvent;
import com.alfaariss.oa.UserException;
import com.alfaariss.oa.api.IService;
import com.alfaariss.oa.api.attribute.ISessionAttributes;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.api.logging.IAuthority;
import com.alfaariss.oa.api.persistence.PersistenceException;
import com.alfaariss.oa.api.requestor.IRequestor;
import com.alfaariss.oa.api.session.ISession;
import com.alfaariss.oa.api.session.SessionState;
import com.alfaariss.oa.api.sso.ISSOProfile;
import com.alfaariss.oa.api.sso.logout.IASLogout;
import com.alfaariss.oa.api.tgt.ITGT;
import com.alfaariss.oa.api.tgt.ITGTListener;
import com.alfaariss.oa.api.tgt.TGTEventError;
import com.alfaariss.oa.api.user.IUser;
import com.alfaariss.oa.engine.core.Engine;
import com.alfaariss.oa.engine.core.requestor.RequestorPool;
import com.alfaariss.oa.engine.core.requestor.factory.IRequestorPoolFactory;
import com.alfaariss.oa.engine.core.server.Server;
import com.alfaariss.oa.engine.core.session.factory.ISessionFactory;
import com.alfaariss.oa.engine.core.tgt.factory.ITGTAliasStore;
import com.alfaariss.oa.engine.core.tgt.factory.ITGTFactory;
import com.alfaariss.oa.sso.authentication.web.AuthenticationManager;
import com.alfaariss.oa.sso.authentication.web.IWebAuthenticationMethod;
import com.alfaariss.oa.sso.web.WebSSOServlet;
import com.alfaariss.oa.util.logging.SystemLogItem;
import com.alfaariss.oa.util.logging.UserEventLogItem;
import com.alfaariss.oa.util.validation.SessionValidator;
import com.alfaariss.oa.util.validation.TGTValidator;
import com.alfaariss.oa.util.web.CookieTool;
import com.alfaariss.oa.util.web.HttpUtils;

/**
 * Single Logout profile.
 *
 * @author MHO
 * @author Alfa & Ariss
 * @since 1.4
 */
public class LogoutProfile implements ISSOProfile, IService, IAuthority {
    /** Profile ID: logout */
    public final static String PROFILE_ID = "logout";

    private final static String AUTHORITY_NAME = "LogoutProfile";

    private final static String SESSION_CURRENT_METHOD = "CURRENT_LOGOUT_METHOD";
    private final static String JSP_LOGOUT_STATE = "logoutState";

    private final static String TARGET_LOGOUT_FORCE = "force";
    private final static String TARGET_LOGOUT_STATE = "state";

    private final static String DEFAULT_JSP_LOGOUT = "/ui/sso/logout/logout.jsp";
    private final static String DEFAULT_JSP_CONFIRM = "/ui/sso/logout/confirm.jsp";

    private final static String PROPERTY_LOGOUT_CONFIRMATION = ".confirmation";

    private static Log _logger;
    private static Log _eventLogger;

    private AuthenticationManager _authenticationManager;
    private ITGTFactory<?> _tgtFactory;
    private ISessionFactory<?> _sessionFactory;
    private CookieTool _cookieTool;
    private Map<String, IASLogout> _mapLogoutMethods;
    private String _sMyOrganizationID;
    private String _sJSPUserLogout;
    private ITGTAliasStore _aliasStoreSP;
    private IRequestorPoolFactory _requestorPoolFactory;
    private boolean _bShowConfirmation;
    private String _sJSPConfirmation;

    /**
     * Constructor.
     * @param authenticationManager The authentication manager.
     */
    public LogoutProfile(AuthenticationManager authenticationManager) {
        _logger = LogFactory.getLog(LogoutProfile.class);
        _eventLogger = LogFactory.getLog(Engine.EVENT_LOGGER);
        _mapLogoutMethods = null;
        _sJSPUserLogout = null;
        _authenticationManager = authenticationManager;
        _bShowConfirmation = false;
        _sJSPConfirmation = null;
    }

    /**
     * @see com.alfaariss.oa.api.sso.ISSOProfile#destroy()
     */
    @Override
    public void destroy() {
        if (_mapLogoutMethods != null) {
            _mapLogoutMethods.clear();
        }
        _sJSPUserLogout = null;
        _sJSPConfirmation = null;
        _bShowConfirmation = false;
    }

    /**
     * @see ISSOProfile#getID()
     */
    @Override
    public String getID() {
        return PROFILE_ID;
    }

    /**
     * @param eSpecific is always supplied as NULL
     * @see ISSOProfile#init(javax.servlet.ServletContext, 
     *  IConfigurationManager, org.w3c.dom.Element, org.w3c.dom.Element)
     */
    @Override
    public void init(ServletContext context, IConfigurationManager configurationManager, Element eParent,
            Element eSpecific) throws OAException {
        Engine engine = Engine.getInstance();
        _tgtFactory = engine.getTGTFactory();
        _sessionFactory = engine.getSessionFactory();
        _aliasStoreSP = _tgtFactory.getAliasStoreSP();
        _sMyOrganizationID = engine.getServer().getOrganization().getID();
        _requestorPoolFactory = engine.getRequestorPoolFactory();
        _cookieTool = new CookieTool(configurationManager, eParent);

        Element eLogout = configurationManager.getSection(eParent, "logout");
        if (eLogout == null) {
            _logger.warn("No optional 'logout' section available in configuration, using defaults");
            _sJSPUserLogout = DEFAULT_JSP_LOGOUT;
            _bShowConfirmation = false;
            _sJSPConfirmation = DEFAULT_JSP_CONFIRM;
        } else {
            readConfig(configurationManager, eLogout);
        }

        _mapLogoutMethods = loadLogoutMethods(_authenticationManager.getAuthenticationMethods());

        _logger.info("Started Logout Profile: " + PROFILE_ID);
    }

    /**
     * @see com.alfaariss.oa.api.IService#service(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws OAException {
        try {
            //Disable caching
            HttpUtils.setDisableCachingHttpHeaders(servletRequest, servletResponse);

            ISession session = (ISession) servletRequest.getAttribute(ISession.ID_NAME);
            if (session == null) {
                String sId = servletRequest.getParameter(ISession.ID_NAME);
                if (sId != null) {
                    if (!SessionValidator.validateDefaultSessionId(sId)) {
                        _logger.warn("Invalid session id in request: " + sId);
                        throw new UserException(UserEvent.REQUEST_INVALID);
                    }
                    session = _sessionFactory.retrieve(sId);
                } else
                    _logger.debug("No session attribute and no session id supplied in request");
            }

            String sTarget = resolveTarget(servletRequest);
            if (sTarget != null) {
                if (sTarget.equalsIgnoreCase(TARGET_LOGOUT_STATE)) {
                    _logger.debug("Performing 'logout state' request");
                    processLogoutState(servletResponse, session);
                    return;
                } else if (sTarget.equalsIgnoreCase(TARGET_LOGOUT_FORCE)) {
                    _logger.debug(
                            "Performing 'forced logout' request sent from IP: " + servletRequest.getRemoteAddr());
                    processForceLogout(servletRequest, servletResponse, session);
                    return;
                }
            }

            if (session == null) {
                _logger.debug("No valid session found");
                throw new UserException(UserEvent.REQUEST_INVALID);
            }

            _logger.debug("Performing 'logout' request sent from IP: " + servletRequest.getRemoteAddr());

            switch (session.getState()) {
            case USER_LOGOUT_IN_PROGRESS: {//logout at remote authn not finished yet
                finishFederativeLogout(servletRequest, servletResponse, session);
                break;
            }
            case USER_LOGOUT_FAILED:
            case USER_LOGOUT_PARTIAL: {//already failed, only remove what can be removed
                processForceLogout(servletRequest, servletResponse, session);
                break;
            }
            case USER_LOGOUT_SUCCESS: {//remote logout finished, do local logout
                processLocalLogout(servletRequest, servletResponse, session, null);
                break;
            }
            case USER_CANCELLED: {
                _logger.debug(new SystemLogItem(session.getId(), SystemErrors.OK, "Redirect back to Profile"));
                servletResponse.sendRedirect(session.getProfileURL());
                break;
            }
            default: {//check what needs to be done
                processDefault(servletRequest, servletResponse, session);
            }
            }
        } catch (UserException e) {
            try {
                if (!servletResponse.isCommitted())
                    servletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
            } catch (IOException e1) {
                _logger.debug("Could not respond", e1);
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not perform logout request", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * @see com.alfaariss.oa.api.logging.IAuthority#getAuthority()
     */
    @Override
    public String getAuthority() {
        return AUTHORITY_NAME;
    }

    private void processDefault(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session) throws OAException, UserException {
        try {
            _logger.debug("Start logout");

            String sTGTID = _cookieTool.getCookieValue(WebSSOServlet.TGT_COOKIE_NAME, servletRequest);

            if (sTGTID == null) {
                _logger.debug("No TGT cookie found");
                throw new UserException(UserEvent.REQUEST_INVALID);
            }

            if (sTGTID != null && !TGTValidator.validateDefaultTGTId(sTGTID)) {
                _logger.debug("Invalid TGT ID in cookie: " + sTGTID);
                throw new UserException(UserEvent.REQUEST_INVALID);
            }

            ITGT tgt = _tgtFactory.retrieve(sTGTID);
            if (tgt == null || tgt.isExpired()) {
                session.setState(SessionState.USER_LOGOUT_PARTIAL);
                //remove cookie and perform redirect to profile
                processForceLogout(servletRequest, servletResponse, session);
            } else {
                //DD remove aliasses for requestor id from TGT, so requestor logout will not be triggered for this supplied requestor.
                if (_aliasStoreSP != null)
                    _aliasStoreSP.removeAll(session.getRequestorId(), tgt.getId());

                //DD remove requestor from TGT, because the user is now logged out at this requestor.
                tgt.removeRequestorID(session.getRequestorId());

                tgt.persist();

                session.setTGTId(sTGTID);

                IUser user = tgt.getUser();
                session.setUser(user);

                if (mustShowConfirmation(servletRequest, session, tgt)) {
                    processConfirmation(servletRequest, servletResponse, tgt, session);
                    return;
                }

                IASLogout asLogout = null;
                if (_mapLogoutMethods != null) {
                    for (IASLogout method : _mapLogoutMethods.values()) {
                        if (method.canLogout(tgt)) {
                            asLogout = method;
                            break;
                        }
                    }
                }

                if (asLogout != null && !_sMyOrganizationID.equals(user.getOrganization())) {
                    session.getAttributes().put(this.getClass(), SESSION_CURRENT_METHOD, asLogout.getID());

                    session.setState(SessionState.USER_LOGOUT_IN_PROGRESS);

                    _logger.debug("do federative logout");
                    switch (asLogout.logout(servletRequest, servletResponse, tgt, session)) {
                    case USER_LOGOUT_IN_PROGRESS: {
                        //do nothing, process not ended yet.
                        break;
                    }
                    case USER_LOGGED_OUT: {
                        session.setState(SessionState.USER_LOGOUT_SUCCESS);
                        processLocalLogout(servletRequest, servletResponse, session, tgt);
                        break;
                    }
                    default: {//all errors
                        processForceLogout(servletRequest, servletResponse, session);
                    }
                    }
                } else {
                    //DD No sLogout.logout(...) when no logout method is found and user organization is not the local idp (the remote organization is responsible for the logout)
                    //No session.persist() needed when forwarding to sso servlet.
                    processLocalLogout(servletRequest, servletResponse, session, tgt);
                }
            }
        } catch (UserException e) {
            throw e;
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error while performing the default process", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private void finishFederativeLogout(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session) throws OAException, UserException {
        try {
            _logger.debug("finish federative logout");

            String sTGTID = session.getTGTId();
            if (sTGTID == null) {
                _logger.debug("No TGT ID found in session");
                throw new UserException(UserEvent.REQUEST_INVALID);
            }

            ITGT tgt = _tgtFactory.retrieve(sTGTID);
            if (tgt == null || tgt.isExpired()) {
                _logger.debug("No TGT available with id: " + sTGTID);
                session.setState(SessionState.USER_LOGOUT_PARTIAL);
                //remove cookie and perform redirect to profile
                processForceLogout(servletRequest, servletResponse, session);
            } else {
                String sLogoutMethodID = (String) session.getAttributes().get(this.getClass(),
                        SESSION_CURRENT_METHOD);
                if (sLogoutMethodID == null) {
                    _logger.debug("Required session attribute not available: " + SESSION_CURRENT_METHOD);
                    throw new UserException(UserEvent.REQUEST_INVALID);
                }

                IASLogout asLogout = _mapLogoutMethods.get(sLogoutMethodID);
                if (asLogout == null) {
                    _logger.debug("No method found: " + sLogoutMethodID);
                    throw new UserException(UserEvent.REQUEST_INVALID);
                }

                _logger.debug("proceed federative logout");
                switch (asLogout.logout(servletRequest, servletResponse, tgt, session)) {
                case USER_LOGOUT_IN_PROGRESS: {
                    //do nothing, process not ended yet.
                    break;
                }
                case USER_LOGGED_OUT: {
                    session.setState(SessionState.USER_LOGOUT_SUCCESS);
                    processLocalLogout(servletRequest, servletResponse, session, tgt);
                    break;
                }
                default: {//all errors
                    processForceLogout(servletRequest, servletResponse, session);
                }
                }
            }
        } catch (UserException e) {
            throw e;
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error while finishing federative logout", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * Starts the logout process at the current (local) IDP, by executing logout events.
     * <br>
     * Call can only be initiated by a requestor (with session).
     */
    private void processLocalLogout(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session, ITGT tgt) throws OAException {
        _logger.debug("Start Logout at local IDP (only TGT Events)");

        try {
            if (session == null) {
                //No session, so the request is not initiated by any requestor (userpage or protocol profile)
                _logger.debug("No session available");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            String sTGTID = session.getTGTId();
            if (sTGTID == null) {
                sTGTID = _cookieTool.getCookieValue(WebSSOServlet.TGT_COOKIE_NAME, servletRequest);

                if (sTGTID == null) {
                    _logger.debug("No TGT ID available");
                    throw new UserException(UserEvent.REQUEST_INVALID);
                }

                if (sTGTID != null && !TGTValidator.validateDefaultTGTId(sTGTID)) {
                    _logger.debug("Invalid TGT ID in cookie: " + sTGTID);
                    throw new UserException(UserEvent.REQUEST_INVALID);
                }

                session.setTGTId(sTGTID);
            }

            SessionState state = session.getState();
            LogoutState logoutState = null;
            if (state != SessionState.USER_LOGOUT_IN_PROGRESS) {
                session.setState(SessionState.USER_LOGOUT_IN_PROGRESS);

                if (tgt == null) {
                    tgt = _tgtFactory.retrieve(sTGTID);
                    if (tgt == null || tgt.isExpired()) //TGT valid
                    {
                        _logger.debug("TGT already expired: " + sTGTID);
                        throw new UserException(UserEvent.REQUEST_INVALID);
                    }
                }

                logoutState = startListeners(tgt, session, servletRequest);
            } else {
                //session must be persisted before showing a JSP
                session.persist();
            }

            if (logoutState != null && logoutState.isFinished())
                processForceLogout(servletRequest, servletResponse, session);
            else
                showLogoutJSP(servletRequest, servletResponse, SessionState.USER_LOGOUT_IN_PROGRESS,
                        session.getId(), null);
        } catch (UserException e) {
            session.setState(SessionState.USER_LOGOUT_FAILED);

            _eventLogger.info(new UserEventLogItem(session, servletRequest.getRemoteAddr(),
                    UserEvent.INTERNAL_ERROR, this, null));

            processForceLogout(servletRequest, servletResponse, session);
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during logout processing", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * Removes the TGT, TGT cookie and reports back to the initiator.
     * <br>
     * Call can be initiated by a user (without session) or a requestor (with session).
     */
    @SuppressWarnings("unchecked")
    private void processForceLogout(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session) throws OAException {
        try {
            _logger.debug("Perform Forced Logout (remove the TGT)");

            //backup session state, used as result no session is available
            SessionState sessionStateBackup = SessionState.USER_LOGOUT_FAILED;

            String sTGTID = null;
            if (session != null) {
                sTGTID = session.getTGTId();
            }

            String sTGTCookie = _cookieTool.getCookieValue(WebSSOServlet.TGT_COOKIE_NAME, servletRequest);
            if (sTGTCookie != null) {//remove TGT cookie
                _cookieTool.removeCookie(WebSSOServlet.TGT_COOKIE_NAME, servletRequest, servletResponse);
                _logger.debug("TGT cookie removed");

                sessionStateBackup = SessionState.USER_LOGOUT_PARTIAL;
            }

            if (sTGTID == null) {
                sTGTID = sTGTCookie;
                if (sTGTID != null && !TGTValidator.validateDefaultTGTId(sTGTID)) {
                    _logger.debug("Not using invalid TGT ID resolved from cookie: " + sTGTID);
                    sTGTID = null;
                }
            }

            if (sTGTID != null) //TGT Cookie found
            {//remove TGT
                ITGT tgt = _tgtFactory.retrieve(sTGTID);
                if (tgt != null) {
                    if (!tgt.isExpired()) //TGT valid
                        tgt.expire();

                    try {
                        if (session != null) {
                            //events are already performed by processLogout()
                            tgt.persistPassingListenerEvent();
                        } else {
                            //persist should also perform events
                            tgt.persist();

                            sessionStateBackup = SessionState.USER_LOGOUT_PARTIAL;
                        }
                    } catch (PersistenceException e) {
                        if (session != null)
                            session.setState(SessionState.USER_LOGOUT_FAILED);
                    }

                    _logger.debug("TGT removed");
                }
            }

            if (session == null) {
                showLogoutJSP(servletRequest, servletResponse, sessionStateBackup, null, null);
            } else {
                //logout was interrupted, so logout is failed
                if (session.getState() == SessionState.USER_LOGOUT_IN_PROGRESS)
                    session.setState(SessionState.USER_LOGOUT_FAILED);

                //TGT is now removed, so remove TGT ID from the session if available
                session.setTGTId(null);
                session.persist();

                if (session.getState() != SessionState.USER_LOGOUT_SUCCESS) {
                    List<TGTEventError> warnings = new Vector<TGTEventError>();
                    ISessionAttributes sessionAttributes = session.getAttributes();
                    if (sessionAttributes.contains(LogoutState.class, LogoutState.SESSION_LOGOUT_RESULTS)) {
                        List<TGTEventError> totalWarnings = (List<TGTEventError>) sessionAttributes
                                .get(LogoutState.class, LogoutState.SESSION_LOGOUT_RESULTS);

                        for (int i = 0; i < totalWarnings.size(); i++) {//DD Only add failed logout calls
                            TGTEventError eventError = totalWarnings.get(i);
                            if (eventError.getCode() != UserEvent.USER_LOGGED_OUT) {
                                warnings.add(eventError);
                            }
                        }
                    }

                    showLogoutJSP(servletRequest, servletResponse, session.getState(), session.getId(), warnings);
                } else {
                    String sRedirect = session.getProfileURL();
                    if (sRedirect == null) {
                        _logger.warn("No redirect to profile URL available in session with id: " + session.getId());
                        throw new OAException(SystemErrors.ERROR_INTERNAL);
                    }

                    _logger.debug("Logout finished for TGT with id: " + sTGTID);
                    _logger.debug("Redirecting user back to profile URL: " + sRedirect);

                    servletResponse.sendRedirect(sRedirect);
                }
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during forced logout", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private void processLogoutState(HttpServletResponse servletResponse, ISession session) throws OAException {
        _logger.debug("Logout - Perform State Request");
        try {
            servletResponse.setContentType("text/plain");

            if (session == null) {
                _logger.debug("no session");
                servletResponse.sendError(HttpServletResponse.SC_BAD_REQUEST);
            } else {
                switch (session.getState()) {
                case USER_LOGOUT_FAILED:
                case USER_LOGOUT_PARTIAL:
                case USER_LOGOUT_SUCCESS: {
                    _logger.debug("finished");
                    servletResponse.sendError(HttpServletResponse.SC_OK);
                    break;
                }
                default: {
                    _logger.debug("in progress");
                    servletResponse.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                }
                }
            }
        } catch (Exception e) {
            _logger.fatal("Internal error during logout state resolving", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private void showConfirmJSP(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ISession session, List<IRequestor> listRequestors, IRequestor requestor) throws OAException {
        try {
            servletRequest.setAttribute(ISession.ID_NAME, session.getId());
            servletRequest.setAttribute("requestors", listRequestors);
            servletRequest.setAttribute("requestor", requestor);
            servletRequest.setAttribute("user", session.getUser());

            //Set server info as attribute
            servletRequest.setAttribute(Server.SERVER_ATTRIBUTE_NAME, Engine.getInstance().getServer());
            //Forward to page                
            RequestDispatcher oDispatcher = servletRequest.getRequestDispatcher(_sJSPConfirmation);
            if (oDispatcher != null)
                oDispatcher.forward(servletRequest, servletResponse);
            else {
                _logger.fatal("Forward request not supported");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during jsp forward", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private void showLogoutJSP(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            SessionState state, String sessionID, List<TGTEventError> warnings) throws OAException {
        try {
            if (sessionID != null)
                servletRequest.setAttribute(ISession.ID_NAME, sessionID);

            if (state != null)
                servletRequest.setAttribute(JSP_LOGOUT_STATE, state);

            if (warnings != null) {
                servletRequest.setAttribute(DetailedUserException.DETAILS_NAME, warnings);
            }

            //Show user info
            //Set server info as attribute
            servletRequest.setAttribute(Server.SERVER_ATTRIBUTE_NAME, Engine.getInstance().getServer());
            //Forward to page                
            RequestDispatcher oDispatcher = servletRequest.getRequestDispatcher(_sJSPUserLogout);
            if (oDispatcher != null)
                oDispatcher.forward(servletRequest, servletResponse);
            else {
                _logger.fatal("Forward request not supported");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during jsp forward to logout page", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    private boolean mustShowConfirmation(HttpServletRequest servletRequest, ISession session, ITGT tgt)
            throws OAException {
        if (_sJSPConfirmation == null)
            return false;

        if (servletRequest.getParameter("confirm") != null)
            return false;

        if (tgt.getRequestorIDs().size() < 1) {//user will only be loggedout at the requestor that initiated the logout
            return false;
        }

        String requestorID = session.getRequestorId();
        IRequestor requestor = _requestorPoolFactory.getRequestor(requestorID);
        if (requestor != null) {
            String sConfirm = (String) requestor.getProperty(PROFILE_ID + PROPERTY_LOGOUT_CONFIRMATION);
            if (sConfirm != null) {
                if (sConfirm.equalsIgnoreCase("TRUE"))
                    return true;
                else if (sConfirm.equalsIgnoreCase("FALSE"))
                    return false;
                else {
                    StringBuffer sbDebug = new StringBuffer("Invalid property '");
                    sbDebug.append(PROFILE_ID);
                    sbDebug.append(PROPERTY_LOGOUT_CONFIRMATION);
                    sbDebug.append("' found for requestor '");
                    sbDebug.append(requestorID);
                    sbDebug.append("': ");
                    sbDebug.append(sConfirm);
                    _logger.debug(sbDebug.toString());
                }
            } else
                _logger.debug(
                        "No (optional) requestor specific logout confirmation property found for requestor with ID: "
                                + requestorID);

            RequestorPool pool = _requestorPoolFactory.getRequestorPool(requestorID);
            if (pool != null) {
                String sPoolConfirm = (String) pool.getProperty(PROFILE_ID + PROPERTY_LOGOUT_CONFIRMATION);
                if (sPoolConfirm != null) {
                    if (sPoolConfirm.equalsIgnoreCase("TRUE"))
                        return true;
                    else if (sPoolConfirm.equalsIgnoreCase("FALSE"))
                        return false;
                    else {
                        StringBuffer sbDebug = new StringBuffer("Invalid property '");
                        sbDebug.append(PROFILE_ID);
                        sbDebug.append(PROPERTY_LOGOUT_CONFIRMATION);
                        sbDebug.append("' found for requestorpool '");
                        sbDebug.append(pool.getID());
                        sbDebug.append("': ");
                        sbDebug.append(sPoolConfirm);
                        _logger.debug(sbDebug.toString());
                    }
                } else
                    _logger.debug(
                            "No (optional) requestorpool specific logout confirmation property found for requestorpool with ID: "
                                    + pool.getID());
            }
        }

        return _bShowConfirmation;
    }

    private void processConfirmation(HttpServletRequest servletRequest, HttpServletResponse servletResponse,
            ITGT tgt, ISession session) throws OAException {
        //create requestor list with latest added requestor first
        Vector<IRequestor> vRequestors = new Vector<IRequestor>();
        List<String> listRequestorIDs = tgt.getRequestorIDs();
        for (int i = (listRequestorIDs.size() - 1); i >= 0; i--) {
            String sRequestorID = listRequestorIDs.get(i);
            IRequestor oRequestor = _requestorPoolFactory.getRequestor(sRequestorID);
            if (oRequestor != null && !vRequestors.contains(oRequestor))
                vRequestors.add(oRequestor);
        }

        session.persist();

        IRequestor requestor = _requestorPoolFactory.getRequestor(session.getRequestorId());

        showConfirmJSP(servletRequest, servletResponse, session, vRequestors, requestor);
    }

    private LogoutState startListeners(ITGT tgt, ISession session, HttpServletRequest servletRequest)
            throws OAException {
        List<Thread> listThreads = new Vector<Thread>();
        //create threads
        LogoutState state = new LogoutState(_sessionFactory, session.getId());

        int iIndex = 0;
        for (ITGTListener listener : _tgtFactory.getListeners()) {
            iIndex++;

            StringBuffer sbRunnableName = new StringBuffer(session.getId());
            sbRunnableName.append("_");
            sbRunnableName.append(iIndex);
            LogoutRunnable runnable = new LogoutRunnable(listener, tgt, state, sbRunnableName.toString());
            Thread tLogout = new Thread(runnable);

            StringBuffer sbThreadname = new StringBuffer("Logout (");
            sbThreadname.append(sbRunnableName.toString());
            sbThreadname.append(") - ");
            sbThreadname.append(tLogout.getName());
            tLogout.setName(sbThreadname.toString());
            listThreads.add(tLogout);
        }

        session.persist();

        _eventLogger.info(new UserEventLogItem(session, servletRequest.getRemoteAddr(),
                UserEvent.USER_LOGOUT_IN_PROGRESS, this, null));

        //start threads
        for (Thread thread : listThreads) {
            thread.start();
            _logger.debug("Started: " + thread.getName());
        }

        _logger.debug("Logout threads started");

        return state;
    }

    private String resolveTarget(HttpServletRequest servletRequest) {
        String sRequestURI = servletRequest.getRequestURI();
        if (!sRequestURI.endsWith("/"))
            sRequestURI = sRequestURI + "/";
        sRequestURI = sRequestURI.toLowerCase();

        int iIndex = sRequestURI.indexOf(PROFILE_ID + "/");
        if (iIndex > -1) {
            int iStart = iIndex + PROFILE_ID.length() + "/".length();
            if (sRequestURI.length() > iStart) {
                String target = sRequestURI.substring(iStart, sRequestURI.length() - 1);
                if (target.length() > 0)
                    return target;
            }
        }

        return null;
    }

    private Map<String, IASLogout> loadLogoutMethods(Map<String, IWebAuthenticationMethod> mapAuthNMethods)
            throws OAException {
        Map<String, IASLogout> mapLogoutMethods = new HashMap<String, IASLogout>();
        try {
            for (IWebAuthenticationMethod method : mapAuthNMethods.values()) {
                if (method.isEnabled()) {
                    if (method instanceof IASLogout) {
                        IASLogout aslogout = (IASLogout) method;
                        if (mapLogoutMethods.containsKey(aslogout.getID())) {
                            _logger.info("Authentication method has not a unique id: " + aslogout.getID());
                        } else {
                            _logger.info("Found asynchronous logout method: " + aslogout.getID());
                            mapLogoutMethods.put(aslogout.getID(), aslogout);
                        }
                    }
                }
            }
        } catch (Exception e) {
            _logger.fatal("Could not load asynchronous logout methods", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }

        return mapLogoutMethods;
    }

    private void readConfig(IConfigurationManager configurationManager, Element config) throws OAException {
        Element eJSP = configurationManager.getSection(config, "jsp");
        if (eJSP == null) {
            _logger.warn("No optional 'jsp' parameter found in 'logout' section in configuration; using default");
            _sJSPUserLogout = DEFAULT_JSP_LOGOUT;
        } else {
            _sJSPUserLogout = configurationManager.getParam(eJSP, "path");
            if (_sJSPUserLogout == null) {
                _logger.error("No 'path' parameter found in 'jsp' section in configuration");
                throw new OAException(SystemErrors.ERROR_CONFIG_READ);
            }
        }
        _logger.info("Using logout jsp location: " + _sJSPUserLogout);

        _bShowConfirmation = false;
        _sJSPConfirmation = DEFAULT_JSP_CONFIRM;
        Element eConfirmation = configurationManager.getSection(config, "confirmation");
        if (eConfirmation == null) {
            _logger.warn(
                    "No optional 'confirmation' section found within 'logout' section in configuration; using defaults");
        } else {
            Element eConfirmationJSP = configurationManager.getSection(eConfirmation, "jsp");
            if (eConfirmationJSP == null) {
                _logger.warn(
                        "No optional 'jsp' parameter found in 'confirmation' section in logout confirmation configuration; using default");
                _sJSPConfirmation = DEFAULT_JSP_CONFIRM;
            } else {
                _sJSPConfirmation = configurationManager.getParam(eConfirmationJSP, "path");
                if (_sJSPConfirmation == null) {
                    _logger.error(
                            "No 'path' parameter found in 'jsp' section in logout confirmation configuration");
                    throw new OAException(SystemErrors.ERROR_CONFIG_READ);
                }
            }

            String sShow = configurationManager.getParam(eConfirmation, "show");
            if (sShow == null) {
                _logger.warn(
                        "No optional 'show' parameter found in 'confirmation' section in logout confirmation configuration; using default");
            } else {
                if (sShow.equalsIgnoreCase("TRUE"))
                    _bShowConfirmation = true;
                else if (!sShow.equalsIgnoreCase("FALSE")) {
                    _logger.error("Unknown value in 'show' configuration item: " + sShow);
                    throw new OAException(SystemErrors.ERROR_CONFIG_READ);
                }
            }
        }
        _logger.info("Using logout confirmation jsp location: " + _sJSPConfirmation);
        _logger.info("Default show logout confirmation: " + (_bShowConfirmation ? "enabled" : "disabled"));
    }
}