diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java index d09ab1d..ae0a49e 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java @@ -8,8 +8,6 @@ package org.jasig.cas.client.session; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jasig.cas.client.util.AbstractConfigurationFilter; -import org.jasig.cas.client.util.CommonUtils; -import org.jasig.cas.client.util.XmlUtils; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; @@ -17,7 +15,6 @@ import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.io.IOException; /** @@ -28,97 +25,51 @@ import java.io.IOException; * @since 3.1 */ public final class SingleSignOutFilter extends AbstractConfigurationFilter { - - /** - * The name of the artifact parameter. This is used to capture the session identifier. - */ - private String artifactParameterName = "ticket"; - - private static SessionMappingStorage SESSION_MAPPING_STORAGE = new HashMapBackedSessionMappingStorage(); private static Log log = LogFactory.getLog(SingleSignOutFilter.class); + private static final SingleSignOutHandler handler = new SingleSignOutHandler(); + public void init(final FilterConfig filterConfig) throws ServletException { if (!isIgnoreInitConfiguration()) { - setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); + handler.setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); + handler.setLogoutParameterName(getPropertyFromInitParams(filterConfig, "logoutParameterName", "logoutRequest")); } - init(); + handler.init(); } - public void init() { - CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null."); - CommonUtils.assertNotNull(SESSION_MAPPING_STORAGE, "sessionMappingStorage cannote be null."); + public void setArtifactParameterName(final String name) { + handler.setArtifactParameterName(name); + } + + public void setLogoutParameterName(final String name) { + handler.setLogoutParameterName(name); } - public void setArtifactParameterName(final String artifactParameterName) { - this.artifactParameterName = artifactParameterName; + public void setSessionMappingStorage(final SessionMappingStorage storage) { + handler.setSessionMappingStorage(storage); } - + public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; - if ("POST".equals(request.getMethod())) { - final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest"); - - if (CommonUtils.isNotBlank(logoutRequest)) { - - if (log.isTraceEnabled()) { - log.trace ("Logout request=[" + logoutRequest + "]"); - } - - final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex"); - - if (CommonUtils.isNotBlank(sessionIdentifier)) { - final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier); - - if (session != null) { - String sessionID = session.getId(); - - if (log.isDebugEnabled()) { - log.debug ("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]"); - } - - try { - session.invalidate(); - } catch (final IllegalStateException e) { - log.debug(e,e); - } - } - return; - } - } + if (handler.isTokenRequest(request)) { + handler.recordSession(request); + } else if (handler.isLogoutRequest(request)) { + handler.destroySession(request); + // Do not continue up filter chain + return; } else { - final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName); - - if (CommonUtils.isNotBlank(artifact)) { - final HttpSession session = request.getSession(true); - - if (log.isDebugEnabled()) { - log.debug("Storing session identifier for " + session.getId()); - } - - try { - SESSION_MAPPING_STORAGE.removeBySessionById(session.getId()); - } catch (final Exception e) { - // ignore if the session is already marked as invalid. Nothing we can do! - } - SESSION_MAPPING_STORAGE.addSessionById(artifact, session); - } else { - log.debug("No Artifact Provided; no action taking place."); - } + log.trace("Ignoring URI " + request.getRequestURI()); } filterChain.doFilter(servletRequest, servletResponse); } - public void setSessionMappingStorage(final SessionMappingStorage storage) { - SESSION_MAPPING_STORAGE = storage; - } - - public static SessionMappingStorage getSessionMappingStorage() { - return SESSION_MAPPING_STORAGE; - } - public void destroy() { // nothing to do } + + protected static SingleSignOutHandler getSingleSignOutHandler() { + return handler; + } } diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java new file mode 100644 index 0000000..bf924e4 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHandler.java @@ -0,0 +1,157 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.session; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.util.XmlUtils; + +/** + * Performs CAS single sign-out operations in an API-agnostic fashion. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class SingleSignOutHandler { + /** Logger instance */ + private final Log log = LogFactory.getLog(getClass()); + + /** Mapping of token IDs and session IDs to HTTP sessions */ + private SessionMappingStorage sessionMappingStorage = new HashMapBackedSessionMappingStorage(); + + /** The name of the artifact parameter. This is used to capture the session identifier. */ + private String artifactParameterName = "ticket"; + + /** Parameter name that stores logout request */ + private String logoutParameterName = "logoutRequest"; + + + public void setSessionMappingStorage(final SessionMappingStorage storage) { + this.sessionMappingStorage = storage; + } + + public SessionMappingStorage getSessionMappingStorage() { + return this.sessionMappingStorage; + } + + /** + * @return Name of the parameter containing the authentication token. + */ + public String getArtifactParameterName() { + return artifactParameterName; + } + + /** + * @param name Name of the authentication token parameter. + */ + public void setArtifactParameterName(final String name) { + this.artifactParameterName = name; + } + + /** + * @return Name of parameter containing CAS logout request message. + */ + public String getLogoutParameterName() { + return logoutParameterName; + } + + /** + * @param name Name of parameter containing CAS logout request message. + */ + public void setLogoutParameterName(final String name) { + this.logoutParameterName = name; + } + + /** + * Initializes the component for use. + */ + public void init() { + CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null."); + CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null."); + CommonUtils.assertNotNull(this.sessionMappingStorage, "sessionMappingStorage cannote be null."); + } + + /** + * Determines whether the given request contains an authentication token. + * + * @param request HTTP reqest. + * + * @return True if request contains authentication token, false otherwise. + */ + public boolean isTokenRequest(final HttpServletRequest request) { + return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName)); + } + + /** + * Determines whether the given request is a CAS logout request. + * + * @param request HTTP request. + * + * @return True if request is logout request, false otherwise. + */ + public boolean isLogoutRequest(final HttpServletRequest request) { + return "POST".equals(request.getMethod()) && + CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName)); + } + + /** + * Associates a token request with the current HTTP session by recording the mapping + * in the the configured {@link SessionMappingStorage} container. + * + * @param request HTTP request containing an authentication token. + */ + public void recordSession(final HttpServletRequest request) { + final HttpSession session = request.getSession(true); + + final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName); + if (log.isDebugEnabled()) { + log.debug("Recording session for token " + token); + } + + try { + this.sessionMappingStorage.removeBySessionById(session.getId()); + } catch (final Exception e) { + // ignore if the session is already marked as invalid. Nothing we can do! + } + sessionMappingStorage.addSessionById( + CommonUtils.safeGetParameter(request, this.artifactParameterName), session); + } + + /** + * Destroys the current HTTP session for the given CAS logout request. + * + * @param request HTTP request containing a CAS logout message. + */ + public void destroySession(final HttpServletRequest request) { + final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName); + if (log.isTraceEnabled()) { + log.trace ("Logout request:\n" + logoutMessage); + } + + final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex"); + if (CommonUtils.isNotBlank(token)) { + final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token); + + if (session != null) { + String sessionID = session.getId(); + + if (log.isDebugEnabled()) { + log.debug ("Invalidating session [" + sessionID + "] for token [" + token + "]"); + } + try { + session.invalidate(); + } catch (final IllegalStateException e) { + log.debug("Error invalidating session.", e); + } + } + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java index cb1f83c..648682a 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java @@ -9,9 +9,6 @@ import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - /** * Listener to detect when an HTTP session is destroyed and remove it from the map of * managed sessions. Also allows for the programmatic removal of sessions. @@ -24,19 +21,18 @@ import org.apache.commons.logging.LogFactory; */ public final class SingleSignOutHttpSessionListener implements HttpSessionListener { - private SessionMappingStorage SESSION_MAPPING_STORAGE; + private SessionMappingStorage sessionMappingStorage; public void sessionCreated(final HttpSessionEvent event) { // nothing to do at the moment } public void sessionDestroyed(final HttpSessionEvent event) { - if (SESSION_MAPPING_STORAGE == null) { - SESSION_MAPPING_STORAGE = getSessionMappingStorage(); + if (sessionMappingStorage == null) { + sessionMappingStorage = getSessionMappingStorage(); } final HttpSession session = event.getSession(); - - SESSION_MAPPING_STORAGE.removeBySessionById(session.getId()); + sessionMappingStorage.removeBySessionById(session.getId()); } /** @@ -46,6 +42,6 @@ public final class SingleSignOutHttpSessionListener implements HttpSessionListen * @return the SessionMappingStorage */ protected static SessionMappingStorage getSessionMappingStorage() { - return SingleSignOutFilter.getSessionMappingStorage(); + return SingleSignOutFilter.getSingleSignOutHandler().getSessionMappingStorage(); } } diff --git a/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java new file mode 100644 index 0000000..5b36904 --- /dev/null +++ b/cas-client-integration-tomcat-v6/src/main/java/org/jasig/cas/client/tomcat/v6/SingleSignOutValve.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v6; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.LifecycleException; +import org.apache.catalina.Session; +import org.apache.catalina.SessionEvent; +import org.apache.catalina.SessionListener; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; + +import org.jasig.cas.client.session.SessionMappingStorage; +import org.jasig.cas.client.session.SingleSignOutHandler; + +/** + * Handles logout request messages sent from the CAS server by ending the current + * HTTP session. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class SingleSignOutValve extends AbstractLifecycleValve implements SessionListener { + private static final String NAME = SingleSignOutValve.class.getName(); + + private final SingleSignOutHandler handler = new SingleSignOutHandler(); + + public void setArtifactParameterName(final String name) { + handler.setArtifactParameterName(name); + } + + public void setLogoutParameterName(final String name) { + handler.setLogoutParameterName(name); + } + + public void setSessionMappingStorage(final SessionMappingStorage storage) { + handler.setSessionMappingStorage(storage); + } + + /** {@inheritDoc} */ + public void start() throws LifecycleException { + super.start(); + handler.init(); + this.log.info("Startup completed."); + } + + /** {@inheritDoc} */ + public void invoke(final Request request, final Response response) throws IOException, ServletException { + if (this.handler.isTokenRequest(request)) { + this.handler.recordSession(request); + request.getSessionInternal(true).addSessionListener(this); + } + else if (this.handler.isLogoutRequest(request)) { + this.handler.destroySession(request); + // Do not proceed up valve chain + return; + } else { + this.log.debug("Ignoring URI " + request.getRequestURI()); + } + getNext().invoke(request, response); + } + + + /** {@inheritDoc} */ + public void sessionEvent(final SessionEvent event) { + if (Session.SESSION_DESTROYED_EVENT.equals(event.getType())) { + this.log.debug("Cleaning up SessionMappingStorage on destroySession event"); + this.handler.getSessionMappingStorage().removeBySessionById(event.getSession().getId()); + } + } + + /** {@inheritDoc} */ + protected String getName() { + return NAME; + } + +} diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java new file mode 100644 index 0000000..f2ce712 --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/SingleSignOutValve.java @@ -0,0 +1,84 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import java.io.IOException; + +import javax.servlet.ServletException; + +import org.apache.catalina.LifecycleException; +import org.apache.catalina.Session; +import org.apache.catalina.SessionEvent; +import org.apache.catalina.SessionListener; +import org.apache.catalina.connector.Request; +import org.apache.catalina.connector.Response; +import org.apache.catalina.valves.ValveBase; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.jasig.cas.client.session.SessionMappingStorage; +import org.jasig.cas.client.session.SingleSignOutHandler; + +/** + * Handles logout request messages sent from the CAS server by ending the current + * HTTP session. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class SingleSignOutValve extends ValveBase implements SessionListener { + /** Logger instance */ + private final Log log = LogFactory.getLog(getClass()); + + private final SingleSignOutHandler handler = new SingleSignOutHandler(); + + public void setArtifactParameterName(final String name) { + handler.setArtifactParameterName(name); + } + + public void setLogoutParameterName(final String name) { + handler.setLogoutParameterName(name); + } + + public void setSessionMappingStorage(final SessionMappingStorage storage) { + handler.setSessionMappingStorage(storage); + } + + + /** {@inheritDoc} */ + public void invoke(final Request request, final Response response) throws IOException, ServletException { + if (this.handler.isTokenRequest(request)) { + this.handler.recordSession(request); + request.getSessionInternal(true).addSessionListener(this); + } + else if (this.handler.isLogoutRequest(request)) { + this.handler.destroySession(request); + // Do not proceed up valve chain + return; + } else { + this.log.debug("Ignoring URI " + request.getRequestURI()); + } + getNext().invoke(request, response); + } + + + /** {@inheritDoc} */ + public void sessionEvent(final SessionEvent event) { + if (Session.SESSION_DESTROYED_EVENT.equals(event.getType())) { + this.log.debug("Cleaning up SessionMappingStorage on destroySession event"); + this.handler.getSessionMappingStorage().removeBySessionById(event.getSession().getId()); + } + } + + /** {@inheritDoc} */ + protected void startInternal() throws LifecycleException { + super.startInternal(); + this.log.info("Starting..."); + handler.init(); + this.log.info("Startup completed."); + } +}