com.alfaariss.oa.profile.aselect.logout.LogoutManager.java Source code

Java tutorial

Introduction

Here is the source code for com.alfaariss.oa.profile.aselect.logout.LogoutManager.java

Source

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

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.Vector;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Element;

import com.alfaariss.oa.OAException;
import com.alfaariss.oa.SystemErrors;
import com.alfaariss.oa.UserEvent;
import com.alfaariss.oa.api.configuration.IConfigurationManager;
import com.alfaariss.oa.api.logging.IAuthority;
import com.alfaariss.oa.api.requestor.IRequestor;
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.tgt.TGTListenerEvent;
import com.alfaariss.oa.api.tgt.TGTListenerException;
import com.alfaariss.oa.engine.core.Engine;
import com.alfaariss.oa.engine.core.crypto.CryptoManager;
import com.alfaariss.oa.engine.core.requestor.factory.IRequestorPoolFactory;
import com.alfaariss.oa.engine.core.server.Server;
import com.alfaariss.oa.engine.core.tgt.factory.ITGTAliasStore;
import com.alfaariss.oa.engine.core.tgt.factory.ITGTFactory;
import com.alfaariss.oa.profile.aselect.ASelectErrors;
import com.alfaariss.oa.profile.aselect.processor.ASelectProcessor;
import com.alfaariss.oa.profile.aselect.processor.handler.BrowserHandler;
import com.alfaariss.oa.util.logging.UserEventLogItem;

/**
 * Logout handler that sends synchronous logout calls to requestors that are in 
 * the SP role.
 * <br>
 * The logout can be triggerd by the ON_CREATE and ON_EXPIRE TGT Listener Events.
 * @author MHO
 * @author Alfa & Ariss
 * @since 1.4
 */
public class LogoutManager implements ITGTListener, IAuthority {
    private final static String AUTHORITY_NAME = "ASelectProfileLogoutManager_";

    private final static String PROPERTY_LOGOUT_TARGET = ".logout.target";
    private final static String PROPERTY_LOGOUT_SIGNING = ".logout.signing";

    private static Log _logger;
    private static Log _eventLogger;

    private ITGTAliasStore _aliasStoreSPRole;
    private IRequestorPoolFactory _requestorPoolFactory;
    private CryptoManager _cryptoManager;
    private Server _server;

    private String _sProfileID;
    private HttpClient _httpClient;
    private boolean _bEnabled;

    /**
     * Constructor. 
     * @param profileID The ID of this A-Select Profile.
     * @param configurationManager The configuration manager
     * @param config The configuration section
     * @throws OAException If config could not be read or is invalid.
     */
    public LogoutManager(String profileID, IConfigurationManager configurationManager, Element config)
            throws OAException {
        try {
            _logger = LogFactory.getLog(LogoutManager.class);
            _eventLogger = LogFactory.getLog(Engine.EVENT_LOGGER);

            _bEnabled = true;
            if (config != null) {
                String sEnabled = configurationManager.getParam(config, "enabled");
                if (sEnabled != null) {
                    if (sEnabled.equalsIgnoreCase("FALSE"))
                        _bEnabled = false;
                    else if (!sEnabled.equalsIgnoreCase("TRUE")) {
                        _logger.error("Unknown value in 'enabled' configuration item: " + sEnabled);
                        throw new OAException(SystemErrors.ERROR_CONFIG_READ);
                    }
                }
            }

            if (!_bEnabled) {
                _logger.info("Logout Manager: disabled");
            } else {
                Engine engine = Engine.getInstance();
                ITGTFactory tgtFactory = engine.getTGTFactory();
                _aliasStoreSPRole = tgtFactory.getAliasStoreSP();
                _requestorPoolFactory = engine.getRequestorPoolFactory();
                _cryptoManager = engine.getCryptoManager();
                _server = engine.getServer();
                _sProfileID = profileID;

                //Create thread safe HTTP client
                MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager();
                _httpClient = new HttpClient(connectionManager);

                if (config != null) {
                    Element eHTTP = configurationManager.getSection(config, "http");
                    if (eHTTP != null)
                        readHTTPConfig(configurationManager, eHTTP);
                    else
                        _logger.info(
                                "No optional 'http' section configured, using default http connection settings");
                }

                _logger.info("Logout Manager: enabled");
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not create logout manager", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
    }

    /**
     * @see com.alfaariss.oa.api.tgt.ITGTListener#processTGTEvent(com.alfaariss.oa.api.tgt.TGTListenerEvent, com.alfaariss.oa.api.tgt.ITGT)
     */
    public void processTGTEvent(TGTListenerEvent event, ITGT tgt) throws TGTListenerException {
        if (!_bEnabled)
            return;

        switch (event) {
        case ON_EXPIRE:
        case ON_REMOVE: {
            List<TGTEventError> listEventErrors = new Vector<TGTEventError>();
            if (_aliasStoreSPRole != null)
                listEventErrors.addAll(processRemove(tgt, event));

            if (!listEventErrors.isEmpty())
                throw new TGTListenerException(listEventErrors);

            break;
        }
        default: {
            //not process any other events
        }
        }
    }

    /**
     * @return TRUE if this logout manager is enabled
     */
    public boolean isEnabled() {
        return _bEnabled;
    }

    /**
     * @see IAuthority#getAuthority()
     */
    public String getAuthority() {
        return AUTHORITY_NAME + _sProfileID;
    }

    private List<TGTEventError> processRemove(ITGT tgt, TGTListenerEvent event) {
        List<TGTEventError> listEventErrors = new Vector<TGTEventError>();
        List<LogoutTarget> listLogoutTargets = new Vector<LogoutTarget>();

        Map<String, String> mapTargets = new HashMap<String, String>();
        for (String sRequestor : tgt.getRequestorIDs()) {
            IRequestor requestor = null;
            try {
                if (!mapTargets.containsKey(sRequestor)) {
                    String sCredentials = _aliasStoreSPRole.getAlias(BrowserHandler.ALIAS_TYPE_CREDENTIALS,
                            sRequestor, tgt.getId());
                    if (sCredentials != null) {
                        requestor = _requestorPoolFactory.getRequestor(sRequestor);
                        String sLogoutURL = (String) requestor.getProperty(_sProfileID + PROPERTY_LOGOUT_TARGET);
                        if (sLogoutURL != null) {
                            String sSigning = (String) requestor.getProperty(_sProfileID + PROPERTY_LOGOUT_SIGNING);

                            new URL(sLogoutURL);
                            String sLocation = generateSLogout(sLogoutURL, sCredentials, Boolean.valueOf(sSigning),
                                    event);

                            LogoutTarget lt = new LogoutTarget(requestor, sLocation, tgt);
                            listLogoutTargets.add(lt);
                        }
                    }
                }
            } catch (MalformedURLException e) {

                TGTEventError error = null;
                if (requestor != null)
                    error = new TGTEventError(UserEvent.INTERNAL_ERROR, requestor.getFriendlyName());
                else
                    error = new TGTEventError(UserEvent.INTERNAL_ERROR);

                listEventErrors.add(error);

                UserEventLogItem logItem = new UserEventLogItem(null, tgt.getId(), null, UserEvent.INTERNAL_ERROR,
                        tgt.getUser().getID(), tgt.getUser().getOrganization(), null, sRequestor, this, null);

                _eventLogger.info(logItem);
            } catch (OAException e) {
                TGTEventError error = null;
                if (requestor != null)
                    error = new TGTEventError(UserEvent.INTERNAL_ERROR, requestor.getFriendlyName());
                else
                    error = new TGTEventError(UserEvent.INTERNAL_ERROR);

                listEventErrors.add(error);

                UserEventLogItem logItem = new UserEventLogItem(null, tgt.getId(), null, UserEvent.INTERNAL_ERROR,
                        tgt.getUser().getID(), tgt.getUser().getOrganization(), null, sRequestor, this, null);

                _eventLogger.info(logItem);
            }
        }

        for (LogoutTarget lt : listLogoutTargets) {
            UserEvent result = sendSLogout(lt.getTargetURL());
            if (result != UserEvent.USER_LOGGED_OUT) {
                listEventErrors.add(new TGTEventError(result, lt.getRequestor().getFriendlyName()));
            }

            UserEventLogItem logItem = new UserEventLogItem(null, lt.getTGTID(), null, result, lt.getUserID(),
                    lt.getUserOrganization(), null, lt.getRequestor().getID(), this, null);

            _eventLogger.info(logItem);
        }

        return listEventErrors;
    }

    private String generateSLogout(String sLogoutURL, String sCredentials, boolean sign, TGTListenerEvent event)
            throws OAException {
        String logoutCall = null;
        try {
            Map<String, String> mapRequest = new HashMap<String, String>();
            mapRequest.put(ASelectProcessor.PARAM_LOCAL_IDP, _server.getOrganization().getID());
            mapRequest.put(ASelectProcessor.PARAM_ASELECT_CREDENTIALS, sCredentials);
            if (event == TGTListenerEvent.ON_EXPIRE)
                mapRequest.put(ASelectProcessor.PARAM_REASON, ASelectProcessor.VALUE_REASON_TIMEOUT);

            if (sign) {
                String signature = createSignature(mapRequest);
                mapRequest.put(ASelectProcessor.PARAM_SIGNATURE, signature);
            }

            mapRequest.put("request", "logout");//not part of signature

            StringBuffer sbMessage = new StringBuffer(sLogoutURL);
            if (!sLogoutURL.contains("?"))
                sbMessage.append("?");

            for (String key : mapRequest.keySet()) {
                if (!sbMessage.toString().endsWith("&") && !sbMessage.toString().endsWith("?"))
                    sbMessage.append("&");

                sbMessage.append(key);
                sbMessage.append("=");
                sbMessage.append(URLEncoder.encode(mapRequest.get(key), ASelectProcessor.CHARSET));
            }

            logoutCall = sbMessage.toString();
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.error("Could not generate logout call", e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
        return logoutCall;
    }

    private UserEvent sendSLogout(String logoutCall) {
        GetMethod method = null;
        try {
            method = new GetMethod(logoutCall);

            _logger.debug("Sending message: " + logoutCall);
            int statusCode = _httpClient.executeMethod(method);
            if (statusCode != HttpStatus.SC_OK) {
                StringBuffer sbWarn = new StringBuffer("Received invalid http status '");
                sbWarn.append(method.getStatusLine());
                sbWarn.append("' while sending: ");
                sbWarn.append(logoutCall);

                _logger.warn(sbWarn.toString());
                throw new OAException(SystemErrors.ERROR_RESOURCE_CONNECT);
            }

            // Read the response body.
            byte[] responseBody = method.getResponseBody();
            if (responseBody != null) {
                String sResponseMessage = new String(responseBody).trim();
                _logger.debug("Received response: " + sResponseMessage);

                Hashtable<String, String> htResponse = convertCGI(sResponseMessage);
                String sResultCode = htResponse.get(ASelectProcessor.PARAM_RESULT_CODE);
                if (sResultCode == null) {
                    _logger.debug("No result code in response, logout failed");
                    return UserEvent.USER_LOGOUT_FAILED;
                } else if (!sResultCode.equals(ASelectErrors.ERROR_ASELECT_SUCCESS)) {
                    if (sResultCode.equals(ASelectErrors.ERROR_LOGOUT_PARTIALLY)) {
                        _logger.debug("Logout parially in response from server");
                        return UserEvent.USER_LOGOUT_PARTIALLY;
                    }

                    _logger.debug("Logout failed, result code: " + sResultCode);
                    return UserEvent.USER_LOGOUT_FAILED;
                }
            }
        } catch (OAException e) {
            return UserEvent.USER_LOGOUT_FAILED;
        } catch (Exception e) {
            _logger.warn("Could not send synchronous logout", e);
            return UserEvent.USER_LOGOUT_FAILED;
        } finally {
            try {
                // Release the connection.
                if (method != null)
                    method.releaseConnection();
            } catch (Exception e) {
                _logger.error("Could not close the connection reader", e);
            }
        }

        return UserEvent.USER_LOGGED_OUT;
    }

    private void readHTTPConfig(IConfigurationManager configurationManager, Element config) throws OAException {
        //connection_timeout
        //The timeout until a connection is established. 
        //A value of zero means the timeout is not used.
        String sConnectionTimeout = configurationManager.getParam(config, "connection_timeout");
        if (sConnectionTimeout == null) {
            _logger.info("No 'connection_timeout' parameter found in configuration, using default");
        } else {
            try {
                int iConnectionTimeout = Integer.parseInt(sConnectionTimeout);

                _httpClient.getParams().setParameter(HttpConnectionParams.CONNECTION_TIMEOUT,
                        new Integer(iConnectionTimeout));
            } catch (NumberFormatException e) {
                _logger.error("Invalid 'connection_timeout' parameter found in configuration, not a number: "
                        + sConnectionTimeout, e);
                throw new OAException(SystemErrors.ERROR_INIT);
            }
        }

        //socket_timeout
        //The parameters below are optional parameters used by the apache <code>HttpClient</code>.
        //Whenever a parameter is left undefined (no value is explicitly set anywhere in the 
        //parameter hierarchy) <code>HttpClient</code> will use its best judgment to pick up a value. 
        //This default behavior is likely to provide the best compatibility with widely used HTTP servers. 
        //The default socket timeout (SO_TIMEOUT) in milliseconds which is 
        //the timeout for waiting for data. A timeout value of zero is interpreted
        //as an infinite timeout. This value is used when no socket timeout is 
        //set in the HTTP method parameters.  
        String sSocketTimeout = configurationManager.getParam(config, "socket_timeout");
        if (sSocketTimeout == null) {
            _logger.info("No 'socket_timeout' parameter found in configuration, using an infinite timeout");
        } else {
            try {
                int iSocketTimeout = Integer.parseInt(sSocketTimeout);

                _httpClient.getParams().setParameter(HttpConnectionParams.SO_TIMEOUT, new Integer(iSocketTimeout));
            } catch (NumberFormatException e) {
                _logger.error("Invalid 'socket_timeout' parameter found in configuration, not a number: "
                        + sSocketTimeout, e);
                throw new OAException(SystemErrors.ERROR_INIT);
            }
        }
    }

    private String createSignature(Map<String, String> mapRequest) throws OAException {
        String sSignature = null;
        try {
            if (_cryptoManager == null) {
                _logger.warn("No crypto manager available");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            Signature signature = _cryptoManager.getSignature();
            if (signature == null) {
                _logger.warn("No signature object found");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }

            StringBuffer sbSignatureData = new StringBuffer();
            TreeSet<String> sortedSet = new TreeSet<String>(mapRequest.keySet());
            for (Iterator<String> iter = sortedSet.iterator(); iter.hasNext();) {
                String sKey = iter.next();
                sbSignatureData.append(mapRequest.get(sKey));
            }

            PrivateKey keyPrivate = _cryptoManager.getPrivateKey();
            if (keyPrivate == null) {
                _logger.error("No private key available");
                throw new OAException(SystemErrors.ERROR_INTERNAL);
            }
            signature.initSign(keyPrivate);
            signature.update(sbSignatureData.toString().getBytes(ASelectProcessor.CHARSET));

            byte[] baSignature = signature.sign();

            byte[] baEncSignature = Base64.encodeBase64(baSignature);
            sSignature = new String(baEncSignature, ASelectProcessor.CHARSET);
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Could not create signature for data: " + mapRequest, e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }

        return sSignature;
    }

    private Hashtable<String, String> convertCGI(String sMessage) throws OAException {
        Hashtable<String, String> htResult = new Hashtable<String, String>();
        try {
            if (sMessage.trim().length() == 0)
                return htResult;

            String[] saMessage = sMessage.split("&");
            for (int i = 0; i < saMessage.length; i++) {
                String sPart = saMessage[i];
                int iIndex = sPart.indexOf('=');
                String sKey = sPart.substring(0, iIndex);
                sKey = sKey.trim();
                String sValue = sPart.substring(iIndex + 1);
                sValue = URLDecoder.decode(sValue.trim(), ASelectProcessor.CHARSET);

                if (htResult.containsKey(sKey)) {
                    _logger.error("Key is not unique in message: " + sKey);
                    throw new OAException(SystemErrors.ERROR_INTERNAL);
                }

                htResult.put(sKey, sValue);
            }
        } catch (OAException e) {
            throw e;
        } catch (Exception e) {
            _logger.fatal("Internal error during conversion of message: " + sMessage, e);
            throw new OAException(SystemErrors.ERROR_INTERNAL);
        }
        return htResult;
    }
}