com.tremolosecurity.proxy.SessionManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.tremolosecurity.proxy.SessionManagerImpl.java

Source

/*
Copyright 2015 Tremolo Security, Inc.
    
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    
http://www.apache.org/licenses/LICENSE-2.0
    
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package com.tremolosecurity.proxy;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPOutputStream;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.logging.log4j.Logger;

import org.joda.time.DateTime;

import com.google.gson.Gson;
import com.tremolosecurity.config.util.ConfigManager;
import com.tremolosecurity.config.util.UrlHolder;
import com.tremolosecurity.config.xml.ApplicationType;
import com.tremolosecurity.config.xml.AuthChainType;
import com.tremolosecurity.json.Token;
import com.tremolosecurity.openunison.OpenUnisonConstants;
import com.tremolosecurity.proxy.auth.AnonAuth;
import com.tremolosecurity.proxy.auth.AuthController;
import com.tremolosecurity.proxy.auth.AuthInfo;
import com.tremolosecurity.proxy.logout.LogoutHandler;
import com.tremolosecurity.proxy.logout.LogoutUtil;
import com.tremolosecurity.proxy.util.ProxyConstants;
import com.tremolosecurity.proxy.util.ProxyTools;

public class SessionManagerImpl implements SessionManager {

    static Logger logger = org.apache.logging.log4j.LogManager.getLogger(SessionManagerImpl.class);

    private static final String AUTOIDM_KEY_SESSION = "AUTOIDM_KEY_SESSION";

    public static final String TREMOLO_SESSION_LAST_ACCESSED = "TREMOLO_SESSION_LAST_ACCESSED";

    SecureRandom random;

    private ConfigManager cfg;

    private AuthChainType anonChainType;

    private AnonAuth anonMech;

    ServletContext ctx;

    private ConcurrentHashMap<String, TremoloHttpSession> sessions;

    private SessionTimeoutChecker checker;

    /*
     * (non-Javadoc)
     * 
     * @see com.tremolosecurity.proxy.SessionManager#invalidateSession(com.
     * tremolosecurity.proxy.TremoloHttpSession)
     */
    @Override
    public void invalidateSession(TremoloHttpSession tsession) {
        //we need to run the logout handlers
        ArrayList<LogoutHandler> handlers = (ArrayList<LogoutHandler>) tsession
                .getAttribute(LogoutUtil.LOGOUT_HANDLERS);
        if (handlers != null) {
            for (LogoutHandler handler : handlers) {
                try {
                    handler.handleLogout(null, null);
                } catch (ServletException e) {
                    logger.warn("Could not run logout handler", e);
                }
            }
        }

        sessions.remove(tsession.getId());
    }

    public SessionManagerImpl(ConfigManager cfg, ServletContext ctx) {
        sessions = new ConcurrentHashMap<String, TremoloHttpSession>();
        this.ctx = ctx;
        try {
            this.random = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException e) {
            logger.error("Could not load secure random", e);
        }

        this.cfg = cfg;

        for (String key : this.cfg.getAuthChains().keySet()) {
            AuthChainType act = this.cfg.getAuthChains().get(key);
            if (act.getLevel() == 0) {
                this.anonChainType = act;
                String mechName = act.getAuthMech().get(0).getName();
                this.anonMech = (AnonAuth) cfg.getAuthMech(cfg.getAuthMechs().get(mechName).getUri());
            }
        }

        if (this.anonMech == null) {
            this.anonChainType = new AuthChainType();
            this.anonChainType.setFinishOnRequiredSucess(true);
            this.anonChainType.setLevel(0);
            this.anonChainType.setName("anon");

            this.anonMech = new AnonAuth();

        }

        checker = new SessionTimeoutChecker(this.cfg, this);
        checker.start();

        if (cfg.getCfg().getApplications().getOpenSessionCookieName() == null) {
            cfg.getCfg().getApplications().setOpenSessionCookieName("TREMOLO_SESSION");
        }

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.tremolosecurity.proxy.SessionManager#getSession(com.tremolosecurity
     * .config.util.UrlHolder, javax.servlet.http.HttpServletRequest,
     * javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext)
     */
    @Override
    public HttpSession getSession(UrlHolder holder, HttpServletRequest request, HttpServletResponse response,
            ServletContext ctx) throws Exception {

        // if we are not using a secure session, use generic session
        if (holder.getApp().getCookieConfig() == null) {
            return request.getSession();
        }

        String cookieName = holder.getApp().getCookieConfig().getSessionCookieName();
        if (cookieName == null || cookieName.length() == 0) {
            return request.getSession();
        }

        HttpSession session = locateSession(holder, request, ctx, cookieName, response);
        if (session instanceof HttpSession) {
            ((TremoloHttpSession) session).refresh(ctx, this);
        }

        return session;

    }

    private HttpSession locateSession(UrlHolder holder, HttpServletRequest request, ServletContext ctx,
            String cookieName, HttpServletResponse resp) throws Exception {
        Cookie sessionCookie = null;

        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                if (cookie.getName().equalsIgnoreCase(cookieName)) {
                    sessionCookie = cookie;
                    break;
                }
            }
        }

        ConfigManager cfg = (ConfigManager) ctx.getAttribute(ProxyConstants.TREMOLO_CONFIG);

        ApplicationType app;

        if (holder != null) {
            app = holder.getApp();
        } else {
            app = null;

            String appName = null;
            if (cookies != null) {
                for (int i = 0; i < cookies.length; i++) {
                    if (cookies[i].getName().equals("autoIdmAppName")) {
                        appName = URLDecoder.decode(cookies[i].getValue(), "UTF-8");
                        break;
                    }
                }
            }

            if (appName == null) {
                // TODO create open session
                if (cookies != null) {
                    for (int i = 0; i < cookies.length; i++) {
                        if (cookies[i].getName()
                                .equals(cfg.getCfg().getApplications().getOpenSessionCookieName())) {
                            String sessionID = cookies[i].getValue();
                            TremoloHttpSession tsession = this.sessions.get(sessionID);
                            // TODO add timeouts
                            if (tsession == null) {
                                return this.createOpenSession(request, resp, ctx);
                            } else {
                                return tsession;
                            }

                        }
                    }
                }

                return createOpenSession(request, resp, ctx);
            } else {
                app = cfg.getApp(appName);

                if (app == null) {
                    throw new Exception("No application named '" + appName + "' found");
                }

            }
        }

        SecretKey encKey = cfg.getSecretKey(app.getCookieConfig().getKeyAlias());

        // TremoloHttpSession tsession = (TremoloHttpSession)
        // request.getSession().getAttribute(app.getCookieConfig().getSessionCookieName());

        if (sessionCookie == null) {
            // if (tsession != null) tsession.invalidate();
            return createSession(app, request, resp, ctx, encKey);
        } else {

            HttpSession session = null;

            try {

                try {

                    TremoloHttpSession tsession = findSessionFromCookie(sessionCookie, encKey, this);

                    if (tsession == null) {
                        return createSession(app, request, resp, ctx, encKey);
                    }

                    String fromSessionID = (String) tsession.getAttribute(OpenUnisonConstants.TREMOLO_SESSION_ID);

                    if (app.getCookieConfig().getTimeout() > 0) {
                        DateTime lastAccessed = (DateTime) tsession
                                .getAttribute(SessionManagerImpl.TREMOLO_SESSION_LAST_ACCESSED);
                        DateTime now = new DateTime();
                        if (now.minusSeconds(app.getCookieConfig().getTimeout()).isAfter(lastAccessed)) {
                            tsession.invalidate();
                            return createSession(app, request, resp, ctx, encKey);
                        } else {
                            tsession.setAttribute(SessionManagerImpl.TREMOLO_SESSION_LAST_ACCESSED, now);
                            session = tsession;
                        }
                    } else {
                        session = tsession;
                    }

                } catch (Exception e) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Exception loading session", e);
                    }
                    return createSession(app, request, resp, ctx, encKey);

                }

                // this.sessions.put(session.getSessionID(), key);
                // }

            } catch (Exception e) {
                logger.error("Error generating session", e);
            }
            if (session == null) {
                // session.invalidate();
                return createSession(app, request, resp, ctx, encKey);
            }

            // session.resetAccess();

            return session;

        }
    }

    public static TremoloHttpSession findSessionFromCookie(Cookie sessionCookie, SecretKey encKey,
            SessionManagerImpl sessionMgr) throws UnsupportedEncodingException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException,
            IllegalBlockSizeException, BadPaddingException {
        String tokenHeader = new String(
                org.bouncycastle.util.encoders.Base64.decode(sessionCookie.getValue().getBytes("UTF-8")));
        Gson gson = new Gson();
        Token token = gson.fromJson(tokenHeader, Token.class);
        byte[] iv = org.bouncycastle.util.encoders.Base64.decode(token.getIv());

        IvParameterSpec spec = new IvParameterSpec(iv);
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, encKey, spec);

        byte[] encBytes = org.bouncycastle.util.encoders.Base64.decode(token.getEncryptedRequest());
        String requestToken = new String(cipher.doFinal(encBytes));

        TremoloHttpSession tsession = sessionMgr.getSessions().get(requestToken);
        return tsession;
    }

    private HttpSession createSession(ApplicationType app, HttpServletRequest req, HttpServletResponse resp,
            ServletContext ctx, SecretKey encKey) throws Exception {

        byte[] idBytes = new byte[20];
        random.nextBytes(idBytes);

        StringBuffer b = new StringBuffer();
        b.append('f').append(Hex.encodeHexString(idBytes));
        String id = b.toString();

        // HttpSession session = req.getSession(true);
        TremoloHttpSession tsession = new TremoloHttpSession(id);
        tsession.setAppName(app.getName());
        tsession.refresh(this.ctx, this);
        tsession.setOpen(false);
        this.anonMech.createSession(tsession, this.anonChainType);

        AuthController actl = (AuthController) tsession.getAttribute(ProxyConstants.AUTH_CTL);

        AuthInfo auInfo = actl.getAuthInfo();
        auInfo.setAuthComplete(true);

        // session.setAttribute(app.getCookieConfig().getSessionCookieName(),
        // tsession);

        tsession.setAttribute(OpenUnisonConstants.TREMOLO_SESSION_ID, id);
        tsession.setMaxInactiveInterval(app.getCookieConfig().getTimeout());

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, encKey);

        byte[] encSessionKey = cipher.doFinal(id.getBytes("UTF-8"));
        String base64d = new String(org.bouncycastle.util.encoders.Base64.encode(encSessionKey));

        Token token = new Token();
        token.setEncryptedRequest(base64d);
        token.setIv(new String(org.bouncycastle.util.encoders.Base64.encode(cipher.getIV())));

        Gson gson = new Gson();

        String cookie = gson.toJson(token);

        byte[] btoken = cookie.getBytes("UTF-8");
        String encCookie = new String(org.bouncycastle.util.encoders.Base64.encode(btoken));

        Cookie sessionCookie;

        sessionCookie = new Cookie(app.getCookieConfig().getSessionCookieName(), encCookie);

        // logger.debug("session size : " +
        // org.apache.directory.shared.ldap.util.Base64.encode(encSession).length);
        String domain = ProxyTools.getInstance().getCookieDomain(app.getCookieConfig(), req);
        if (domain != null) {
            sessionCookie.setDomain(domain);
        }
        sessionCookie.setPath("/");
        sessionCookie.setSecure(false);
        sessionCookie.setMaxAge(-1);
        sessionCookie.setSecure(app.getCookieConfig().isSecure());
        sessionCookie.setHttpOnly(app.getCookieConfig().isHttpOnly() != null && app.getCookieConfig().isHttpOnly());
        resp.addCookie(sessionCookie);

        // delete the opensession if it exists
        if (cfg.getCfg().getApplications().getOpenSessionCookieName() != null
                && !cfg.getCfg().getApplications().getOpenSessionCookieName().isEmpty()) {
            Cookie openSessionCookie = new Cookie(cfg.getCfg().getApplications().getOpenSessionCookieName(), id);

            openSessionCookie.setPath("/");
            openSessionCookie.setSecure(cfg.getCfg().getApplications().isOpenSessionSecure());
            openSessionCookie.setHttpOnly(cfg.getCfg().getApplications().isOpenSessionHttpOnly());
            openSessionCookie.setMaxAge(0);
            resp.addCookie(openSessionCookie);
        }

        sessions.put(id, tsession);

        return tsession;
    }

    private HttpSession createOpenSession(HttpServletRequest req, HttpServletResponse resp, ServletContext ctx)
            throws Exception {

        byte[] idBytes = new byte[20];
        random.nextBytes(idBytes);
        StringBuffer b = new StringBuffer();
        b.append('f').append(Hex.encodeHexString(idBytes));
        String id = b.toString();

        // HttpSession session = req.getSession(true);
        TremoloHttpSession tsession = new TremoloHttpSession(id);
        tsession.setOpen(true);
        tsession.refresh(this.ctx, this);
        this.anonMech.createSession(tsession, this.anonChainType);

        AuthController actl = (AuthController) tsession.getAttribute(ProxyConstants.AUTH_CTL);

        AuthInfo auInfo = actl.getAuthInfo();
        auInfo.setAuthComplete(true);

        // session.setAttribute(app.getCookieConfig().getSessionCookieName(),
        // tsession);

        tsession.setAttribute(OpenUnisonConstants.TREMOLO_SESSION_ID, id);

        // TODO add global session timeout
        // tsession.setMaxInactiveInterval(app.getCookieConfig().getTimeout());

        // TODO add global open session name
        Cookie sessionCookie = new Cookie(cfg.getCfg().getApplications().getOpenSessionCookieName(), id);

        sessionCookie.setPath("/");
        sessionCookie.setSecure(cfg.getCfg().getApplications().isOpenSessionSecure());
        sessionCookie.setHttpOnly(cfg.getCfg().getApplications().isOpenSessionHttpOnly());
        sessionCookie.setMaxAge(-1);
        // TODO add secure?
        // sessionCookie.setSecure(app.getCookieConfig().isSecure());
        resp.addCookie(sessionCookie);

        sessions.put(id, tsession);

        return tsession;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.tremolosecurity.proxy.SessionManager#getSession(java.lang.String,
     * com.tremolosecurity.config.util.UrlHolder,
     * javax.servlet.http.HttpServletRequest,
     * javax.servlet.http.HttpServletResponse, javax.servlet.ServletContext)
     */
    @Override
    public HttpSession getSession(String sessionCookieName, UrlHolder holder, HttpServletRequest request,
            HttpServletResponse response, ServletContext ctx) throws Exception {
        return this.locateSession(holder, request, ctx, sessionCookieName, response);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.tremolosecurity.proxy.SessionManager#writeSession(com.tremolosecurity
     * .config.util.UrlHolder, com.tremolosecurity.proxy.TremoloHttpSession,
     * javax.servlet.http.HttpServletRequest,
     * javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void writeSession(UrlHolder holder, TremoloHttpSession session, HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        /*
         * Enumeration enumer = session.getAttributeNames(); while
         * (enumer.hasMoreElements()) { String name = (String)
         * enumer.nextElement(); String value =
         * session.getAttribute(name).toString(); logger.debug(name + "='" +
         * value + "'"); }
         */

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        GZIPOutputStream gzip = new GZIPOutputStream(bos);
        ObjectOutputStream oos = new ObjectOutputStream(gzip);
        oos.writeObject(session);
        oos.flush();
        oos.close();

        byte[] encSession = new byte[0];

        try {
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(Cipher.ENCRYPT_MODE,
                    holder.getConfig().getSecretKey(holder.getApp().getCookieConfig().getKeyAlias()));
            encSession = cipher.doFinal(bos.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
        }
        Cookie sessionCookie;
        sessionCookie = new Cookie(holder.getApp().getCookieConfig().getSessionCookieName(),
                new String(Base64.encodeBase64(encSession)));

        // logger.debug("session size : " +
        // org.apache.directory.shared.ldap.util.Base64.encode(encSession).length);

        String domain = ProxyTools.getInstance().getCookieDomain(holder.getApp().getCookieConfig(), request);
        if (domain != null) {
            sessionCookie.setDomain(domain);
        }
        sessionCookie.setPath("/");
        sessionCookie.setSecure(false);
        sessionCookie.setMaxAge(-1);
        response.addCookie(sessionCookie);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.tremolosecurity.proxy.SessionManager#clearSession(com.tremolosecurity
     * .config.util.UrlHolder, javax.servlet.http.HttpSession,
     * javax.servlet.http.HttpServletRequest,
     * javax.servlet.http.HttpServletResponse)
     */
    @Override
    public void clearSession(UrlHolder holder, HttpSession sharedSession, HttpServletRequest request,
            HttpServletResponse response) {
        Cookie sessionCookie;
        sessionCookie = new Cookie(holder.getApp().getCookieConfig().getSessionCookieName(), "LOGGED_OUT");
        String domain = ProxyTools.getInstance().getCookieDomain(holder.getApp().getCookieConfig(), request);
        if (domain != null) {
            sessionCookie.setDomain(domain);
        }
        sessionCookie.setPath("/");
        sessionCookie.setSecure(false);
        sessionCookie.setMaxAge(0);
        response.addCookie(sessionCookie);
        sharedSession.invalidate();

    }

    @Override
    public void resetSessionChecker(ConfigManager cfg) {
        this.cfg = cfg;
        SessionTimeoutChecker checker = new SessionTimeoutChecker(this.cfg, this);
        checker.start();
    }

    @Override
    public ConcurrentHashMap<String, TremoloHttpSession> getSessions() {
        return this.sessions;
    }

    @Override
    public void stopSessionChecker() {
        this.checker.stopChecker();

    }

}

class SessionTimeoutChecker extends Thread {

    boolean stillRun;
    SessionManager sessionMgr;
    ConfigManager cfg;

    public SessionTimeoutChecker(ConfigManager cfg, SessionManager sessionManager) {
        this.sessionMgr = sessionManager;
        this.cfg = cfg;
        this.stillRun = true;
        Runtime.getRuntime().addShutdownHook(new Thread() {

            @Override
            public void run() {
                stillRun = false;
            }
        });
    }

    public void stopChecker() {
        this.stillRun = false;
        this.interrupt();
    }

    @Override
    public void run() {
        while (stillRun) {

            try {

                ArrayList<String> toremove = new ArrayList<String>();

                Set<String> keys = new HashSet<String>();

                synchronized (this.sessionMgr.getSessions()) {
                    keys.addAll(this.sessionMgr.getSessions().keySet());
                }

                for (String key : keys) {
                    TremoloHttpSession session = this.sessionMgr.getSessions().get(key);

                    if (session == null) {
                        continue;
                    }

                    ApplicationType app = cfg.getApp(session.getAppName());

                    if (session.isOpen()) {
                        if (cfg.getCfg().getApplications().getOpenSessionTimeout() > 0) {
                            DateTime lastAccessed = (DateTime) session
                                    .getAttribute(SessionManagerImpl.TREMOLO_SESSION_LAST_ACCESSED);

                            if (lastAccessed == null) {
                                lastAccessed = new DateTime(session.getCreationTime());
                            }

                            DateTime now = new DateTime();
                            if (now.minusSeconds(cfg.getCfg().getApplications().getOpenSessionTimeout())
                                    .isAfter(lastAccessed)) {
                                session.invalidate();
                                toremove.add(key);
                            }
                        }
                    } else {
                        if (app == null) {
                            StringBuffer b = new StringBuffer();
                            b.append("Session ").append(session.getId()).append(" application ")
                                    .append(session.getAppName()).append(" does not exist, invalidating");
                            SessionManagerImpl.logger.warn(b.toString());
                            toremove.add(key);
                            session.invalidate();
                        } else {
                            if (app.getCookieConfig().getTimeout() > 0) {
                                DateTime lastAccessed = (DateTime) session
                                        .getAttribute(SessionManagerImpl.TREMOLO_SESSION_LAST_ACCESSED);

                                if (lastAccessed == null) {
                                    lastAccessed = new DateTime(session.getCreationTime());
                                }

                                DateTime now = new DateTime();
                                if (now.minusSeconds(app.getCookieConfig().getTimeout()).isAfter(lastAccessed)) {
                                    session.invalidate();
                                    toremove.add(key);
                                }
                            }
                        }
                    }

                }

                synchronized (this.sessionMgr.getSessions()) {
                    StringBuffer b = new StringBuffer();
                    b.append("Clearing ").append(toremove.size()).append(" sessions");
                    SessionManagerImpl.logger.warn(b.toString());
                    for (String key : toremove) {
                        this.sessionMgr.getSessions().remove(key);
                    }
                }

                try {
                    Thread.sleep(60000);
                } catch (InterruptedException e) {

                }
            } catch (Throwable t) {
                SessionManagerImpl.logger.warn("Exception while processing expired sessions", t);

                try {
                    Thread.sleep(60000);
                } catch (InterruptedException e) {

                }
            }
        }

    }

}