diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml new file mode 100644 index 0000000..656096f --- /dev/null +++ b/cas-client-core/pom.xml @@ -0,0 +1,104 @@ + + + org.jasig.cas + 3.1-SNAPSHOT + cas-client + + 4.0.0 + org.jasig.cas + cas-client-core + jar + JA-SIG CAS Client for Java - Core + + src/main/java + src/test/java + + + src/test/resources + false + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.4 + 1.4 + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*Tests* + + + + + org.apache.maven.plugins + maven-clover-plugin + + ${basedir}/src/test/clover/clover.license + + + + pre-site + + instrument + + + + + + + + + + org.springframework + spring-core + 2.0.6 + test + + + + org.springframework + spring-mock + 2.0.6 + test + + + org.springframework + spring-dao + + + + org.springframework + spring-jdbc + + + + org.springframework + spring-jpa + + + + + + xml-security + xmlsec + 1.3.0 + runtime + + + + org.opensaml + opensaml + 1.1b + jar + compile + + + diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipal.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipal.java new file mode 100644 index 0000000..39bb100 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipal.java @@ -0,0 +1,33 @@ +package org.jasig.cas.client.authentication; + +import java.security.Principal; +import java.util.Map; + +/** + * Extension to the standard Java Principal that includes a way to retrieve proxy tickets for a particular user + * and attributes. + *

+ * Developer's who don't want their code tied to CAS merely need to work with the Java Principal then. Working with + * the CAS-specific features requires knowledge of the AttributePrincipal class. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public interface AttributePrincipal extends Principal { + + /** + * Retrieves a CAS proxy ticket for this specific principal. + * + * @param service the service we wish to proxy this user to. + * @return a String representing the proxy ticket. + */ + String getProxyTicketFor(String service); + + /** + * The Map of key/value pairs associated with this principal. + * @return the map of key/value pairs associated with this principal. + */ + Map getAttributes(); + +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipalImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipalImpl.java new file mode 100644 index 0000000..375df24 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AttributePrincipalImpl.java @@ -0,0 +1,88 @@ +package org.jasig.cas.client.authentication; + +import org.jasig.cas.client.proxy.ProxyRetriever; + +import java.util.Collections; +import java.util.Map; + +/** + * Concrete implementation of the AttributePrincipal interface. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class AttributePrincipalImpl implements AttributePrincipal{ + + /** The unique identifier for this principal. */ + private final String name; + + /** Map of key/value pairs about this principal. */ + private final Map attributes; + + /** The CAS 2 ticket used to retrieve a proxy ticket. */ + private final String proxyGrantingTicket; + + /** The method to retrieve a proxy ticket from a CAS server. */ + private final ProxyRetriever proxyRetriever; + + /** + * Constructs a new principal with an empty map of attributes. + * + * @param name the unique identifier for the principal. + */ + public AttributePrincipalImpl(final String name) { + this(name, Collections.EMPTY_MAP); + } + + /** + * Constructs a new principal with the supplied name and attributes. + * + * @param name the unique identifier for the principal. + * @param attributes the key/value pairs for this principal. + */ + public AttributePrincipalImpl(final String name, final Map attributes) { + this(name, attributes, null, null); + } + + /** + * Constructs a new principal with the supplied name and the proxying capabilities. + * + * @param name the unique identifier for the principal. + * @param proxyGrantingTicket the ticket associated with this principal. + * @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server. + */ + public AttributePrincipalImpl(final String name, final String proxyGrantingTicket, final ProxyRetriever proxyRetriever) { + this(name, Collections.EMPTY_MAP, proxyGrantingTicket, proxyRetriever); + } + + /** + * Constructs a new principal witht he supplied name, attributes, and proxying capabilities. + * + * @param name the unique identifier for the principal. + * @param attributes the key/value pairs for this principal. + * @param proxyGrantingTicket the ticket associated with this principal. + * @param proxyRetriever the ProxyRetriever implementation to call back to the CAS server. + */ + public AttributePrincipalImpl(final String name, final Map attributes, final String proxyGrantingTicket, final ProxyRetriever proxyRetriever) { + this.name = name; + this.attributes = attributes; + this.proxyGrantingTicket = proxyGrantingTicket; + this.proxyRetriever = proxyRetriever; + } + + public Map getAttributes() { + return this.attributes; + } + + public String getProxyTicketFor(String service) { + if (proxyGrantingTicket != null) { + return this.proxyRetriever.getProxyTicketIdFor(this.proxyGrantingTicket, service); + } + return null; + } + + public String getName() { + return this.name; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java new file mode 100644 index 0000000..67ea407 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/authentication/AuthenticationFilter.java @@ -0,0 +1,112 @@ +/* + * Copyright 2006 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.authentication; + +import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.validation.Assertion; +import org.jasig.cas.client.util.AbstractCasFilter; + +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.net.URLEncoder; + +/** + * Filter implementation to intercept all requests and attempt to authenticate + * the user by redirecting them to CAS (unless the user has a ticket). + *

+ * This filter allows you to specify the following parameters (at either the context-level or the filter-level): + *

+ * + *

Please see AbstractCasFilter for additional properties.

+ * + * @author Scott Battaglia + * @version $Revision: 11768 $ $Date: 2007-02-07 15:44:16 -0500 (Wed, 07 Feb 2007) $ + * @since 3.0 + */ +public class AuthenticationFilter extends AbstractCasFilter { + + protected static final String CONST_CAS_GATEWAY = "_const_cas_gateway_"; + + /** + * The URL to the CAS Server login. + */ + private String casServerLoginUrl; + + /** + * Whether to send the renew request or not. + */ + private boolean renew = false; + + /** + * Whether to send the gateway request or not. + */ + private boolean gateway = false; + + public void init(final FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "casServerLoginUrl", null)); + setRenew(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false"))); + setGateway(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "gateway", "false"))); + } + + public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpServletResponse response = (HttpServletResponse) servletResponse; + final HttpSession session = request.getSession(false); + final String ticket = request.getParameter(getArtifactParameterName()); + final Assertion assertion = session != null ? (Assertion) session + .getAttribute(CONST_CAS_ASSERTION) : null; + final boolean wasGatewayed = session != null + && session.getAttribute(CONST_CAS_GATEWAY) != null; + + if (CommonUtils.isBlank(ticket) && assertion == null && !wasGatewayed) { + log.debug("no ticket and no assertion found"); + if (this.gateway) { + log.debug("setting gateway attribute in session"); + request.getSession(true).setAttribute(CONST_CAS_GATEWAY, "yes"); + } + + final String serviceUrl = constructServiceUrl(request, response); + final String urlToRedirectTo = this.casServerLoginUrl + "?" + getServiceParameterName() + "=" + + URLEncoder.encode(serviceUrl, "UTF-8") + + (this.renew ? "&renew=true" : "") + + (this.gateway ? "&gateway=true" : ""); + + if (log.isDebugEnabled()) { + log.debug("redirecting to \"" + urlToRedirectTo + "\""); + } + + response.sendRedirect(urlToRedirectTo); + return; + } + + if (session != null) { + log.debug("removing gateway attribute from session"); + session.setAttribute(CONST_CAS_GATEWAY, null); + } + + filterChain.doFilter(request, response); + } + + public void setRenew(final boolean renew) { + this.renew = renew; + } + + public void setGateway(final boolean gateway) { + this.gateway = gateway; + } + + public void setCasServerLoginUrl(final String casServerLoginUrl) { + this.casServerLoginUrl = casServerLoginUrl; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java new file mode 100644 index 0000000..badee2c --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/Cas20ProxyRetriever.java @@ -0,0 +1,106 @@ +/* + * Copyright 2006 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.proxy; + +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; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; + +/** + * Implementation of a ProxyRetriever that follows the CAS 2.0 specification. + * For more information on the CAS 2.0 specification, please see the specification + * document. + *

+ * In general, this class will make a call to the CAS server with some specified + * parameters and receive an XML response to parse. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class Cas20ProxyRetriever implements ProxyRetriever { + + /** + * Instance of Commons Logging. + */ + protected final Log log = LogFactory.getLog(this.getClass()); + + /** + * Url to CAS server. + */ + private final String casServerUrl; + + /** + * Main Constructor. + * + * @param casServerUrl the URL to the CAS server (i.e. http://localhost/cas/) + */ + public Cas20ProxyRetriever(final String casServerUrl) { + CommonUtils.assertNotNull(casServerUrl, + "casServerUrl cannot be null."); + this.casServerUrl = casServerUrl; + } + + public String getProxyTicketIdFor(final String proxyGrantingTicketId, + final String targetService) { + + final String url = constructUrl(proxyGrantingTicketId, targetService); + HttpURLConnection conn = null; + try { + final URL constructedUrl = new URL(url); + conn = (HttpURLConnection) constructedUrl.openConnection(); + + final BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line; + final StringBuffer stringBuffer = new StringBuffer(255); + final String response; + + synchronized (stringBuffer) { + while ((line = in.readLine()) != null) { + stringBuffer.append(line); + } + response = stringBuffer.toString(); + } + + final String error = XmlUtils.getTextForElement(response, + "proxyFailure"); + + if (CommonUtils.isNotEmpty(error)) { + log.debug(error); + return null; + } + + return XmlUtils.getTextForElement(response, "proxyTicket"); + } catch (final Exception e) { + throw new RuntimeException(e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + private String constructUrl(final String proxyGrantingTicketId, + final String targetService) { + try { + return this.casServerUrl + "proxy" + "?pgt=" + + proxyGrantingTicketId + "&targetService=" + + URLEncoder.encode(targetService, "UTF-8"); + } catch (final UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java new file mode 100644 index 0000000..482b227 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorage.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006 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.proxy; + +/** + * Interface for the storage and retrieval of ProxyGrantingTicketIds by mapping + * them to a specific ProxyGrantingTicketIou. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public interface ProxyGrantingTicketStorage { + + /** + * Method to save the ProxyGrantingTicket to the backing storage facility. + * + * @param proxyGrantingTicketIou used as the key + * @param proxyGrantingTicket used as the value + */ + public void save(String proxyGrantingTicketIou, String proxyGrantingTicket); + + /** + * Method to retrieve a ProxyGrantingTicket based on the + * ProxyGrantingTicketIou. Note that implementations are not guaranteed to + * return the same result if retrieve is called twice with the same + * proxyGrantingTicketIou. + * + * @param proxyGrantingTicketIou used as the key + * @return the ProxyGrantingTicket Id or null if it can't be found + */ + public String retrieve(String proxyGrantingTicketIou); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java new file mode 100644 index 0000000..b0cb891 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyGrantingTicketStorageImpl.java @@ -0,0 +1,142 @@ +/* + * Copyright 2006 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.proxy; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * Implementation of {@link ProxyGrantingTicketStorage} that is backed by a + * HashMap that keeps a ProxyGrantingTicket for a specified amount of time. + *

+ * A cleanup thread is run periodically to clean out the HashMap. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class ProxyGrantingTicketStorageImpl implements + ProxyGrantingTicketStorage { + + /** + * Default timeout in milliseconds. + */ + private static final long DEFAULT_TIMEOUT = 60000; + + private final Map cache = new HashMap(); + + /** + * Constructor set the timeout to the default value. + */ + public ProxyGrantingTicketStorageImpl() { + this(DEFAULT_TIMEOUT); + } + + /** + * Sets the amount of time to hold on to a ProxyGrantingTicket if its never + * been retrieved. + * + * @param timeout the time to hold on to the ProxyGrantingTicket + */ + public ProxyGrantingTicketStorageImpl(final long timeout) { + final Thread thread = new ProxyGrantingTicketCleanupThread( + timeout, this.cache); + thread.setDaemon(true); + thread.start(); + } + + /** + * NOTE: you can only retrieve a ProxyGrantingTicket once with this method. + * Its removed after retrieval. + */ + public String retrieve(final String proxyGrantingTicketIou) { + final ProxyGrantingTicketHolder holder = (ProxyGrantingTicketHolder) this.cache + .get(proxyGrantingTicketIou); + + if (holder == null) { + return null; + } + + this.cache.remove(holder); + + return holder.getProxyGrantingTicket(); + } + + public void save(final String proxyGrantingTicketIou, + final String proxyGrantingTicket) { + final ProxyGrantingTicketHolder holder = new ProxyGrantingTicketHolder( + proxyGrantingTicket); + + this.cache.put(proxyGrantingTicketIou, holder); + } + + private final class ProxyGrantingTicketHolder { + + private final String proxyGrantingTicket; + + private final long timeInserted; + + protected ProxyGrantingTicketHolder(final String proxyGrantingTicket) { + this.proxyGrantingTicket = proxyGrantingTicket; + this.timeInserted = System.currentTimeMillis(); + } + + public String getProxyGrantingTicket() { + return this.proxyGrantingTicket; + } + + final boolean isExpired(final long timeout) { + return System.currentTimeMillis() - this.timeInserted > timeout; + } + } + + private final class ProxyGrantingTicketCleanupThread extends Thread { + + private final long timeout; + + private final Map cache; + + public ProxyGrantingTicketCleanupThread(final long timeout, + final Map cache) { + this.timeout = timeout; + this.cache = cache; + } + + public void run() { + + while (true) { + try { + Thread.sleep(this.timeout); + } catch (final InterruptedException e) { + // nothing to do + } + + final List itemsToRemove = new ArrayList(); + + synchronized (this.cache) { + for (final Iterator iter = this.cache.keySet().iterator(); iter + .hasNext();) { + final Object key = iter.next(); + final ProxyGrantingTicketHolder holder = (ProxyGrantingTicketHolder) this.cache + .get(key); + + if (holder.isExpired(this.timeout)) { + itemsToRemove.add(key); + } + } + + for (final Iterator iter = itemsToRemove.iterator(); iter + .hasNext();) { + this.cache.remove(iter.next()); + } + } + } + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java new file mode 100644 index 0000000..997f461 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/ProxyRetriever.java @@ -0,0 +1,27 @@ +/* + * Copyright 2006 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.proxy; + +/** + * Interface to abstract the retrieval of a proxy ticket to make the + * implementation a black box to the client. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public interface ProxyRetriever { + + /** + * Retrieves a proxy ticket for a specific targetService. + * + * @param proxyGrantingTicketId the ProxyGrantingTicketId + * @param targetService the service we want to proxy. + * @return the ProxyTicket Id if Granted, null otherwise. + */ + String getProxyTicketIdFor(String proxyGrantingTicketId, + String targetService); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html new file mode 100644 index 0000000..c3cbfc5 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/proxy/package.html @@ -0,0 +1,7 @@ + + +

The proxy package includes a servlet to act as a proxy receptor, + an interface for ProxyGrantingTicketStorage and an abstraction for + retrieving proxy tickets.

+ + \ No newline at end of file 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 new file mode 100644 index 0000000..d28f668 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutFilter.java @@ -0,0 +1,63 @@ +package org.jasig.cas.client.session; + +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; +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; + +/** + * Implements the Single Sign Out protocol. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class SingleSignOutFilter extends AbstractConfigurationFilter { + + private String artifactParameterName = "ticket"; + + public void init(final FilterConfig filterConfig) throws ServletException { + setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); + } + + public void setArtifactParameterName(final String artifactParameterName) { + this.artifactParameterName = artifactParameterName; + } + + 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 = request.getParameter("logoutRequest"); + + if (CommonUtils.isNotBlank(logoutRequest)) { + final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex"); + + if (CommonUtils.isNotBlank(sessionIdentifier)) { + SingleSignOutHttpSessionListener.removeSession(sessionIdentifier); + return; + } + } + } else { + final String artifact = request.getParameter(this.artifactParameterName); + final HttpSession session = request.getSession(); + if (CommonUtils.isNotBlank(artifact)) { + SingleSignOutHttpSessionListener.addSession(artifact, session); + } + } + + filterChain.doFilter(servletRequest, servletResponse); + } + + public void destroy() { + // nothing to do + } +} 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 new file mode 100644 index 0000000..8895d05 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/session/SingleSignOutHttpSessionListener.java @@ -0,0 +1,56 @@ +package org.jasig.cas.client.session; + +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionEvent; +import javax.servlet.http.HttpSessionListener; +import java.util.HashMap; +import java.util.Map; + +/** + * 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. + *

+ * Enables the CAS Single Sign out feature. + *

+ * Note that this class does not scale to multiple machines. + * + * Scott Battaglia + * @version $Revision$ Date$ + * @since 3.1 + */ +public class SingleSignOutHttpSessionListener implements HttpSessionListener { + + private static final Map MANAGED_SESSIONS = new HashMap(); + + private static final Map ID_TO_SESSION_KEY_MAPPING = new HashMap(); + + public void sessionCreated(final HttpSessionEvent event) { + // nothing to do at the moment + } + + public void sessionDestroyed(final HttpSessionEvent event) { + final HttpSession session = event.getSession(); + final String key = (String) ID_TO_SESSION_KEY_MAPPING.get(session.getId()); + MANAGED_SESSIONS.remove(key); + ID_TO_SESSION_KEY_MAPPING.remove(session.getId()); + } + + public static void addSession(final String key, final HttpSession value) { + ID_TO_SESSION_KEY_MAPPING.put(value.getId(), key); + MANAGED_SESSIONS.put(key, value); + } + + public static void removeSession(final String key) { + final HttpSession session = (HttpSession) MANAGED_SESSIONS.get(key); + + if (session == null) { + return; + } + + final String id = session.getId(); + MANAGED_SESSIONS.remove(key); + ID_TO_SESSION_KEY_MAPPING.remove(id); + + session.invalidate(); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java new file mode 100644 index 0000000..1da6921 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractCasFilter.java @@ -0,0 +1,142 @@ +package org.jasig.cas.client.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Abstract filter that contains code that is common to all CAS filters. + *

+ * The following filter options can be configured (either at the context-level or filter-level). + *

+ *

Please note that one of the two above parameters must be set.

+ * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractCasFilter extends AbstractConfigurationFilter { + + /** Represents the constant for where the assertion will be located in memory. */ + public static final String CONST_CAS_ASSERTION = "_const_cas_assertion_"; + + /** Instance of commons logging for logging purposes. */ + protected final Log log = LogFactory.getLog(getClass()); + + private String artifactParameterName = "ticket"; + + private String serviceParameterName = "service"; + + /** + * The name of the server. Should be in the following format: {protocol}:{hostName}:{port}. + * Standard ports can be excluded. */ + private String serverName; + + /** The exact url of the service. */ + private String service; + + public void init(final FilterConfig filterConfig) throws ServletException { + setServerName(getPropertyFromInitParams(filterConfig, "serverName", null)); + setService(getPropertyFromInitParams(filterConfig, "service", null)); + setArtifactParameterName(getPropertyFromInitParams(filterConfig, "artifactParameterName", "ticket")); + setServiceParameterName(getPropertyFromInitParams(filterConfig, "serviceParameterName", "service")); + } + + public final void destroy() { + // nothing to do + } + + /** + * Constructs a service url from the HttpServletRequest or from the given + * serviceUrl. Prefers the serviceUrl provided if both a serviceUrl and a + * serviceName. + * + * @param request the HttpServletRequest + * @param response the HttpServletResponse + * @return the service url to use. + */ + protected final String constructServiceUrl(final HttpServletRequest request, + final HttpServletResponse response) { + if (CommonUtils.isNotBlank(this.service)) { + return response.encodeURL(this.service); + } + + final StringBuffer buffer = new StringBuffer(); + + synchronized (buffer) { + if (!this.serverName.startsWith("https://") && !this.serverName.startsWith("http://")) { + buffer.append(request.isSecure() ? "https://" : "http://"); + } + + buffer.append(this.serverName); + buffer.append(request.getRequestURI()); + + if (CommonUtils.isNotBlank(request.getQueryString())) { + final int location = request.getQueryString().indexOf( + this.artifactParameterName + "="); + + if (location == 0) { + final String returnValue = response.encodeURL(buffer + .toString()); + if (log.isDebugEnabled()) { + log.debug("serviceUrl generated: " + returnValue); + } + return returnValue; + } + + buffer.append("?"); + + if (location == -1) { + buffer.append(request.getQueryString()); + } else if (location > 0) { + final int actualLocation = request.getQueryString() + .indexOf("&" + this.artifactParameterName + "="); + + if (actualLocation == -1) { + buffer.append(request.getQueryString()); + } else if (actualLocation > 0) { + buffer.append(request.getQueryString().substring(0, + actualLocation)); + } + } + } + } + + final String returnValue = response.encodeURL(buffer.toString()); + if (log.isDebugEnabled()) { + log.debug("serviceUrl generated: " + returnValue); + } + return returnValue; + } + + public final void setServerName(final String serverName) { + this.serverName = serverName; + } + + public final void setService(final String service) { + this.service = service; + } + + public final void setArtifactParameterName(final String artifactParameterName) { + this.artifactParameterName = artifactParameterName; + } + + public final void setServiceParameterName(final String serviceParameterName) { + this.serviceParameterName = serviceParameterName; + } + + public final String getArtifactParameterName() { + return this.artifactParameterName; + } + + public final String getServiceParameterName() { + return this.serviceParameterName; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java new file mode 100644 index 0000000..4b24931 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AbstractConfigurationFilter.java @@ -0,0 +1,41 @@ +package org.jasig.cas.client.util; + +import javax.servlet.Filter; +import javax.servlet.FilterConfig; + +/** + * Abstracts out the ability to configure the filters from the initial properties provided. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractConfigurationFilter implements Filter { + + /** + * Retrieves the property from the FilterConfig. First it checks the FilterConfig's initParameters to see if it + * has a value. + * If it does, it returns that, otherwise it trieves the ServletContext's initParameters and returns that value if any. + * + * @param filterConfig the Filter Configuration. + * @param propertyName the property to retrieve. + * @return the property value, following the above conventions. It will always return the more specific value (i.e. + * filter vs. context). + */ + protected final String getPropertyFromInitParams(final FilterConfig filterConfig, final String propertyName, final String defaultValue) { + final String value = filterConfig.getInitParameter(propertyName); + + if (CommonUtils.isNotBlank(value)) { + return value; + } + + final String value2 = filterConfig.getServletContext().getInitParameter(propertyName); + + if (CommonUtils.isNotBlank(value2)) { + return value2; + } + + return defaultValue; + } + +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java new file mode 100644 index 0000000..eb52e4c --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionHolder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2006 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.util; + +import org.jasig.cas.client.validation.Assertion; + +/** + * Static holder that places Assertion in a threadlocal. + * + * @author Scott Battaglia + * @version $Revision: 11728 $ $Date: 2006-09-26 14:20:43 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public class AssertionHolder { + + /** + * ThreadLocal to hold the Assertion for Threads to access. + */ + private static final ThreadLocal threadLocal = new ThreadLocal(); + + + /** + * Retrieve the assertion from the ThreadLocal. + */ + public static Assertion getAssertion() { + return (Assertion) threadLocal.get(); + } + + /** + * Add the Assertion to the ThreadLocal. + */ + public static void setAssertion(final Assertion assertion) { + threadLocal.set(assertion); + } + + /** + * Clear the ThreadLocal. + */ + public static void clear() { + threadLocal.set(null); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java new file mode 100644 index 0000000..6ae3442 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/AssertionThreadLocalFilter.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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.util; + +import org.jasig.cas.client.validation.Assertion; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +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; + +/** + * Places the assertion in a ThreadLocal such that other resources can access it that do not have access to the web tier session. + * + * @author Scott Battaglia + * @version $Revision: 11728 $ $Date: 2006-09-26 14:20:43 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class AssertionThreadLocalFilter implements Filter { + + public void init(final FilterConfig filterConfig) throws ServletException { + // nothing to do here + } + + public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpSession session = request.getSession(false); + final Assertion assertion = (Assertion) (session != null ? session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : null); + + try { + AssertionHolder.setAssertion(assertion); + filterChain.doFilter(servletRequest, servletResponse); + } finally { + AssertionHolder.clear(); + } + } + + public void destroy() { + // nothing to do + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java new file mode 100644 index 0000000..2efe6ee --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java @@ -0,0 +1,106 @@ +/* + * Copyright 2006 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.util; + +import java.util.Collection; + +/** + * Common utilities so that we don't need to include Commons Lang. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class CommonUtils { + + private CommonUtils() { + // nothing to do + } + + /** + * Check whether the object is null or not. If it is, throw an exception and + * display the message. + * + * @param object the object to check. + * @param message the message to display if the object is null. + */ + public static void assertNotNull(final Object object, final String message) { + if (object == null) { + throw new IllegalArgumentException(message); + } + } + + /** + * Check whether the collection is null or empty. If it is, throw an + * exception and display the message. + * + * @param c the collecion to check. + * @param message the message to display if the object is null. + */ + public static void assertNotEmpty(final Collection c, final String message) { + assertNotNull(c, message); + if (c.isEmpty()) { + throw new IllegalArgumentException(message); + } + } + + /** + * Assert that the statement is true, otherwise throw an exception with the + * provided message. + * + * @param cond the codition to assert is true. + * @param message the message to display if the condition is not true. + */ + public static void assertTrue(final boolean cond, final String message) { + if (!cond) { + throw new IllegalArgumentException(message); + } + } + + /** + * Determines whether the String is null or of length 0. + * + * @param string the string to check + * @return true if its null or length of 0, false otherwise. + */ + public static boolean isEmpty(final String string) { + return string == null || string.length() == 0; + } + + /** + * Determines if the String is not empty. A string is not empty if it is not + * null and has a length > 0. + * + * @param string the string to check + * @return true if it is not empty, false otherwise. + */ + public static boolean isNotEmpty(final String string) { + return !isEmpty(string); + } + + /** + * Determines if a String is blank or not. A String is blank if its empty or + * if it only contains spaces. + * + * @param string the string to check + * @return true if its blank, false otherwise. + */ + public static boolean isBlank(final String string) { + return isEmpty(string) || string.trim().length() == 0; + } + + /** + * Determines if a string is not blank. A string is not blank if it contains + * at least one non-whitespace character. + * + * @param string the string to check. + * @return true if its not blank, false otherwise. + */ + public static boolean isNotBlank(final String string) { + return !isBlank(string); + } + +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java new file mode 100644 index 0000000..da671bb --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilter.java @@ -0,0 +1,83 @@ +/* + * Copyright 2006 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.util; + +import org.jasig.cas.client.validation.Assertion; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpSession; +import java.io.IOException; +import java.security.Principal; + +/** + * Implementation of a filter that wraps the normal HttpServletRequest with a + * wrapper that overrides the getRemoteUser method to retrieve the user from the + * CAS Assertion. + *

+ * This filter needs to be configured in the chain so that it executes after + * both the authentication and the validation filters. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class HttpServletRequestWrapperFilter implements Filter { + + public void destroy() { + // nothing to do + } + + /** + * Wraps the HttpServletRequest in a wrapper class that delegates + * request.getRemoteUser to the underlying Assertion object + * stored in the user session. + */ + public void doFilter(final ServletRequest servletRequest, + final ServletResponse servletResponse, final FilterChain filterChain) + throws IOException, ServletException { + final Principal principal = retrievePrincipalFromSessionOrRequest(servletRequest); + + filterChain.doFilter(new CasHttpServletRequestWrapper( + (HttpServletRequest) servletRequest, principal), servletResponse); + } + + protected Principal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) { + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpSession session = request.getSession(false); + final Assertion assertion = (Assertion) (session == null ? request.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION)); + + return assertion == null ? null : assertion.getPrincipal(); + } + + public void init(final FilterConfig filterConfig) throws ServletException { + // nothing to do + } + + final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper { + + private final Principal principal; + + CasHttpServletRequestWrapper(final HttpServletRequest request, final Principal principal) { + super(request); + this.principal = principal; + } + + public Principal getUserPrincipal() { + return this.principal; + } + + public String getRemoteUser() { + return getUserPrincipal().getName(); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java new file mode 100644 index 0000000..eca4d49 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java @@ -0,0 +1,158 @@ +/* + * Copyright 2006 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.util; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.DefaultHandler; +import org.xml.sax.helpers.XMLReaderFactory; + +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Common utilities for easily parsing XML without duplicating logic. + * + * @author Scott Battaglia + * @version $Revision: 11729 $ $Date: 2006-09-26 14:22:30 -0400 (Tue, 26 Sep 2006) $ + * @since 3.0 + */ +public final class XmlUtils { + + /** + * Static instance of Commons Logging. + */ + private final static Log LOG = LogFactory.getLog(XmlUtils.class); + + /** + * Get an instance of an XML reader from the XMLReaderFactory. + * + * @return the XMLReader. + */ + public static XMLReader getXmlReader() { + try { + return XMLReaderFactory.createXMLReader(); + } catch (final SAXException e) { + throw new RuntimeException("Unable to create XMLReader", e); + } + } + + /** + * Retrieve the text for a group of elements. Each text element is an entry + * in a list. + *

This method is currently optimized for the use case of two elements in a list. + * + * @param xmlAsString the xml response + * @param element the element to look for + * @return the list of text from the elements. + */ + public static List getTextForElements(final String xmlAsString, + final String element) { + final List elements = new ArrayList(2); + final XMLReader reader = getXmlReader(); + + final DefaultHandler handler = new DefaultHandler() { + + private boolean foundElement = false; + + private StringBuffer buffer = new StringBuffer(); + + public void startElement(final String uri, final String localName, + final String qName, final Attributes attributes) + throws SAXException { + if (localName.equals(element)) { + this.foundElement = true; + } + } + + public void endElement(final String uri, final String localName, + final String qName) throws SAXException { + if (localName.equals(element)) { + this.foundElement = false; + elements.add(this.buffer.toString()); + this.buffer = new StringBuffer(); + } + } + + public void characters(char[] ch, int start, int length) + throws SAXException { + if (this.foundElement) { + this.buffer.append(ch, start, length); + } + } + }; + + reader.setContentHandler(handler); + reader.setErrorHandler(handler); + + try { + reader.parse(new InputSource(new StringReader(xmlAsString))); + } catch (final Exception e) { + LOG.error(e, e); + return null; + } + + return elements; + } + + /** + * Retrieve the text for a specific element (when we know there is only + * one). + * + * @param xmlAsString the xml response + * @param element the element to look for + * @return the text value of the element. + */ + public static String getTextForElement(final String xmlAsString, + final String element) { + final XMLReader reader = getXmlReader(); + final StringBuffer buffer = new StringBuffer(); + + final DefaultHandler handler = new DefaultHandler() { + + private boolean foundElement = false; + + public void startElement(final String uri, final String localName, + final String qName, final Attributes attributes) + throws SAXException { + if (localName.equals(element)) { + this.foundElement = true; + } + } + + public void endElement(final String uri, final String localName, + final String qName) throws SAXException { + if (localName.equals(element)) { + this.foundElement = false; + } + } + + public void characters(char[] ch, int start, int length) + throws SAXException { + if (this.foundElement) { + buffer.append(ch, start, length); + } + } + }; + + reader.setContentHandler(handler); + reader.setErrorHandler(handler); + + try { + reader.parse(new InputSource(new StringReader(xmlAsString))); + } catch (final Exception e) { + LOG.error(e, e); + return null; + } + + return buffer.toString(); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html b/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html new file mode 100644 index 0000000..d6d6b49 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/package.html @@ -0,0 +1,5 @@ + + +

The validation package includes interfaces for validating Tickets, as well as the common implementations.

+ + diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java new file mode 100644 index 0000000..6d84f8f --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java @@ -0,0 +1,51 @@ +package org.jasig.cas.client.validation; + +import java.net.URL; +import java.net.HttpURLConnection; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; + +/** + * Abstract class that knows the protocol for validating a CAS ticket. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractCasProtocolUrlBasedTicketValidator extends AbstractUrlBasedTicketValidator { + + protected AbstractCasProtocolUrlBasedTicketValidator(final String casServerUrlPrefix) { + super(casServerUrlPrefix); + } + + /** + * Retrieves the response from the server by opening a connection and merely reading the response. + */ + protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) { + HttpURLConnection connection = null; + + try { + connection = (HttpURLConnection) validationUrl.openConnection(); + final BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + + String line; + final StringBuffer stringBuffer = new StringBuffer(255); + + synchronized (stringBuffer) { + while ((line = in.readLine()) != null) { + stringBuffer.append(line); + stringBuffer.append("\n"); + } + return stringBuffer.toString(); + } + + } catch (final IOException e) { + return null; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java new file mode 100644 index 0000000..d1a983f --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractTicketValidationFilter.java @@ -0,0 +1,145 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.util.AbstractCasFilter; +import org.jasig.cas.client.util.CommonUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * The filter that handles all the work of validating ticket requests. + *

+ * This filter can be configured with the following values: + *

+ * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractTicketValidationFilter extends AbstractCasFilter { + + /** The TicketValidator we will use to validate tickets. */ + private TicketValidator ticketValidator; + + /** + * Specify whether the filter should redirect the user agent after a + * successful validation to remove the ticket parameter from the query + * string. + */ + private boolean redirectAfterValidation = false; + + /** Determines whether an exception is thrown when there is a ticket validation failure. */ + private boolean exceptionOnValidationFailure = true; + + private boolean useSession = true; + + /** + * Template method to return the appropriate validator. + * + * @param filterConfig the FilterConfiguration that may be needed to construct a validator. + * @return the ticket validator. + */ + protected TicketValidator getTicketValidator(FilterConfig filterConfig) { + return this.ticketValidator; + } + + public void init(final FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + setExceptionOnValidationFailure(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "exceptionOnValidationFailure", "true"))); + setRedirectAfterValidation(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "redirectAfterValidation", "false"))); + setUseSession(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "useSession", "true"))); + setTicketValidator(getTicketValidator(filterConfig)); + } + + /** + * Pre-process the request before the normal filter process starts. This could be useful for pre-empting code. + * + * @param servletRequest The servlet request. + * @param servletResponse The servlet response. + * @param filterChain the filter chain. + * @return true if processing should continue, false otherwise. + * @throws IOException if there is an I/O problem + * @throws ServletException if there is a servlet problem. + */ + protected boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + return true; + } + + public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + + if (!preFilter(servletRequest, servletResponse, filterChain)) { + return; + } + + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpServletResponse response = (HttpServletResponse) servletResponse; + final String ticket = request.getParameter(getArtifactParameterName()); + + if (CommonUtils.isNotBlank(ticket)) { + if (log.isDebugEnabled()) { + log.debug("Attempting to validate ticket: " + ticket); + } + + try { + final Assertion assertion = this.ticketValidator.validate( + ticket, constructServiceUrl(request, + response)); + + if (log.isDebugEnabled()) { + log.debug("Successfully authenticated user: " + + assertion.getPrincipal().getName()); + } + + request.setAttribute(CONST_CAS_ASSERTION, assertion); + + if (this.useSession) { + request.getSession().setAttribute(CONST_CAS_ASSERTION, + assertion); + } + } catch (final TicketValidationException e) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + log.warn(e, e); + + if (this.exceptionOnValidationFailure) { + throw new ServletException(e); + } + } + + if (this.redirectAfterValidation) { + response.sendRedirect(response + .encodeRedirectURL(constructServiceUrl(request, response))); + return; + } + } + + filterChain.doFilter(request, response); + + } + + public final void setTicketValidator(final TicketValidator ticketValidator) { + this.ticketValidator = ticketValidator; +} + + public final void setRedirectAfterValidation(final boolean redirectAfterValidation) { + this.redirectAfterValidation = redirectAfterValidation; + } + + public final void setExceptionOnValidationFailure(final boolean exceptionOnValidationFailure) { + this.exceptionOnValidationFailure = exceptionOnValidationFailure; + } + + public final void setUseSession(final boolean useSession) { + this.useSession = useSession; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java new file mode 100644 index 0000000..f1db47f --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java @@ -0,0 +1,161 @@ +package org.jasig.cas.client.validation; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * Abstract validator implementation for tickets that must be validated against a server. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public abstract class AbstractUrlBasedTicketValidator implements TicketValidator { + + /** + * Commons Logging instance. + */ + protected final Log log = LogFactory.getLog(getClass()); + + /** + * Prefix for the CAS server. Should be everything up to the url endpoint, including the /. + * + * i.e. https://cas.rutgers.edu/ + */ + private final String casServerUrlPrefix; + + /** + * Whether the request include a renew or not. + */ + private boolean renew; + + /** + * Constructs a new TicketValidator with the casServerUrlPrefix. + * + * @param casServerUrlPrefix the location of the CAS server. + */ + protected AbstractUrlBasedTicketValidator(final String casServerUrlPrefix) { + this.casServerUrlPrefix = casServerUrlPrefix; + } + + /** + * Template method for ticket validators that need to provide additional parameters to the validation url. + * + * @param urlParameters the map containing the parameters. + */ + protected void populateUrlAttributeMap(Map urlParameters) { + // nothing to do + } + + /** + * The endpoint of the validation URL. Should be relative (i.e. not start with a "/"). I.e. validate or serviceValidate. + * @return the endpoint of the validation URL. + */ + protected abstract String getUrlSuffix(); + + /** + * Constructs the URL to send the validation request to. + * + * @param ticket the ticket to be validated. + * @param serviceUrl the service identifier. + * @return the fully constructed URL. + */ + protected final String constructValidationUrl(final String ticket, final String serviceUrl) { + final Map urlParameters = new HashMap(); + + urlParameters.put("ticket", ticket); + urlParameters.put("service", encodeUrl(serviceUrl)); + + if (this.renew) { + urlParameters.put("renew", "true"); + } + + populateUrlAttributeMap(urlParameters); + + final String suffix = getUrlSuffix(); + final StringBuffer buffer = new StringBuffer(urlParameters.size()*10 + this.casServerUrlPrefix.length() + suffix.length() +1); + + int i = 0; + synchronized (buffer) { + buffer.append(this.casServerUrlPrefix); + buffer.append("/"); + buffer.append(suffix); + + for (final Iterator iter = urlParameters.entrySet().iterator(); iter.hasNext();) { + buffer.append(i++ == 0 ? "?" : "&"); + final Map.Entry entry = (Map.Entry) iter.next(); + final String key = (String) entry.getKey(); + final String value = (String) entry.getValue(); + + buffer.append(key); + buffer.append("="); + buffer.append(value); + } + + return buffer.toString(); + } + } + + /** + * Encodes a URL using the URLEncoder format. + * + * @param url the url to encode. + * @return the encoded url, or the original url if "UTF-8" character encoding could not be found. + */ + protected final String encodeUrl(final String url) { + try { + return URLEncoder.encode(url, "UTF-8"); + } catch (final UnsupportedEncodingException e) { + return url; + } + } + + /** + * Parses the response from the server into a CAS Assertion. + * + * @param response the response from the server, in any format. + * @return the CAS assertion if one could be parsed from the response. + * @throws TicketValidationException if an Assertion could not be created. + * + */ + protected abstract Assertion parseResponseFromServer(final String response) throws TicketValidationException; + + /** + * Contacts the CAS Server to retrieve the response for the ticket validation. + * + * @param validationUrl the url to send the validation request to. + * @param ticket the ticket to validate. + * @return the response from the CAS server. + */ + + protected abstract String retrieveResponseFromServer(URL validationUrl, String ticket); + + public Assertion validate(final String ticket, final String service) throws TicketValidationException { + + final String validationUrl = constructValidationUrl(ticket, service); + + try { + final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket); + + if (serverResponse == null) { + throw new TicketValidationException("The CAS server returned no response."); + } + + return parseResponseFromServer(serverResponse); + } catch (final MalformedURLException e) { + throw new TicketValidationException(e); + } + } + + public void setRenew(final boolean renew) { + this.renew = renew; + } +} \ No newline at end of file diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java new file mode 100644 index 0000000..04047d0 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Assertion.java @@ -0,0 +1,44 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.authentication.AttributePrincipal; + +import java.util.Date; +import java.util.Map; + +/** + * Represents a response to a validation request. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public interface Assertion { + + /** + * The date from which the assertion is valid from. + * + * @return the valid from date. + */ + Date getValidFromDate(); + + /** + * The date which the assertion is valid until. + * + * @return the valid until date. + */ + Date getValidUntilDate(); + + /** + * The key/value pairs associated with this assertion. + * + * @return the map of attributes. + */ + Map getAttributes(); + + /** + * The principal for which this assertion is valid. + * + * @return the principal. + */ + AttributePrincipal getPrincipal(); +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java new file mode 100644 index 0000000..7e61875 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AssertionImpl.java @@ -0,0 +1,89 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; + +import java.util.Collections; +import java.util.Date; +import java.util.Map; + +/** + * Concrete Implementation of the {@link Assertion}. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + * + */ +public class AssertionImpl implements Assertion { + + /** The date from which the assertion is valid. */ + private final Date validFromDate; + + /** The date the assertion is valid until. */ + private final Date validUntilDate; + + /** Map of key/value pairs associated with this assertion. I.e. authentication type. */ + private final Map attributes; + + /** The principal for which this assertion is valid for. */ + private final AttributePrincipal principal; + + /** + * Constructs a new Assertion with a Principal of the supplied name, a valid from date of now, no valid until date, and no attributes. + * + * @param name the name of the principal for which this assertion is valid. + */ + public AssertionImpl(final String name) { + this(new AttributePrincipalImpl(name)); + } + + /** + * Creates a new Assrtion with the supplied Principal. + * + * @param principal the Principal to associate with the Assertion. + */ + public AssertionImpl(final AttributePrincipal principal) { + this(principal, Collections.EMPTY_MAP); + } + + /** + * Create a new Assertion with the supplied principal and Assertion attributes. + * + * @param principal the Principal to associate with the Assertion. + * @param attributes the key/value pairs for this attribute. + */ + public AssertionImpl(final AttributePrincipal principal, final Map attributes) { + this(principal, new Date(), null, attributes); + } + + /** + * Creats a new Assertion with the supplied principal, Assertion attributes, and start and valid until dates. + * + * @param principal the Principal to associate with the Assertion. + * @param validFromDate when the assertion is valid from. + * @param validUntilDate when the assertion is valid to. + * @param attributes the key/value pairs for this attribute. + */ + public AssertionImpl(final AttributePrincipal principal, final Date validFromDate, final Date validUntilDate, final Map attributes) { + this.principal = principal; + this.validFromDate = validFromDate; + this.validUntilDate = validUntilDate; + this.attributes = attributes; + } + public Date getValidFromDate() { + return this.validFromDate; + } + + public Date getValidUntilDate() { + return this.validUntilDate; + } + + public Map getAttributes() { + return this.attributes; + } + + public AttributePrincipal getPrincipal() { + return this.principal; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidationFilter.java new file mode 100644 index 0000000..ab98906 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidationFilter.java @@ -0,0 +1,23 @@ +package org.jasig.cas.client.validation; + +import javax.servlet.FilterConfig; + +/** + * Implementation of AbstractTicketValidatorFilter that instanciates a Cas10TicketValidator. + *

Deployers can provide the "casServerPrefix" and the "renew" attributes via the standard context or filter init + * parameters. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class Cas10TicketValidationFilter extends AbstractTicketValidationFilter { + + protected TicketValidator getTicketValidator(final FilterConfig filterConfig) { + final String casUrlServerPrefix = getPropertyFromInitParams(filterConfig, "casUrlServerPrefix", null); + final Cas10TicketValidator validator = new Cas10TicketValidator(casUrlServerPrefix); + validator.setRenew(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false"))); + + return validator; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidator.java new file mode 100644 index 0000000..2e6676e --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas10TicketValidator.java @@ -0,0 +1,40 @@ +package org.jasig.cas.client.validation; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +/** + * Implementation of a Ticket Validator that can validate tickets conforming to the CAS 1.0 specification. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class Cas10TicketValidator extends AbstractCasProtocolUrlBasedTicketValidator { + + public Cas10TicketValidator(final String casServerUrlPrefix) { + super(casServerUrlPrefix); + } + + protected String getUrlSuffix() { + return "validate"; + } + + protected Assertion parseResponseFromServer(final String response) throws TicketValidationException { + if (!response.startsWith("yes")) { + throw new TicketValidationException("CAS Server could not validate ticket."); + } + + try { + final BufferedReader reader = new BufferedReader(new StringReader( + response)); + reader.readLine(); + final String name = reader.readLine(); + + return new AssertionImpl(name); + } catch (final IOException e) { + throw new TicketValidationException("Unable to parse response.", e); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java new file mode 100644 index 0000000..2f488a7 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java @@ -0,0 +1,143 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.proxy.Cas20ProxyRetriever; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl; +import org.jasig.cas.client.util.CommonUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Creates either a CAS20ProxyTicketValidator or a CAS20ServiceTicketValidator depending on whether any of the + * proxy parameters are set. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + * + */ +public final class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketValidationFilter { + + /** + * Constant representing the ProxyGrantingTicket IOU Request Parameter. + */ + private static final String PARAM_PROXY_GRANTING_TICKET_IOU = "pgtIou"; + + /** + * Constant representing the ProxyGrantingTicket Request Parameter. + */ + private static final String PARAM_PROXY_GRANTING_TICKET = "pgtId"; + + /** + * The URL to send to the CAS server as the URL that will process proxying requests on the CAS client. + */ + private String proxyReceptorUrl; + + /** + * Storage location of ProxyGrantingTickets and Proxy Ticket IOUs. + */ + private ProxyGrantingTicketStorage proxyGrantingTicketStorage = new ProxyGrantingTicketStorageImpl(); + + public void init(final FilterConfig filterConfig) throws ServletException { + super.init(filterConfig); + setProxyReceptorUrl(getPropertyFromInitParams(filterConfig, "proxyReceptorUrl", null)); + } + + /** + * Constructs a Cas20ServiceTicketValidator or a Cas20ProxyTicketValidator based on supplied parameters. + * + * @param filterConfig the Filter Configuration object. + * @return a fully constructed TicketValidator. + */ + protected TicketValidator getTicketValidator(final FilterConfig filterConfig) { + final String allowAnyProxy = getPropertyFromInitParams(filterConfig, "acceptAnyProxy", null); + final String allowedProxyChains = getPropertyFromInitParams(filterConfig, "allowedProxyChains", null); + final String casServerUrlPrefix = getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null); + final Cas20ServiceTicketValidator validator; + + if (CommonUtils.isNotBlank(allowAnyProxy) || CommonUtils.isNotBlank(allowedProxyChains)) { + final Cas20ProxyTicketValidator v = new Cas20ProxyTicketValidator(casServerUrlPrefix); + v.setAcceptAnyProxy(Boolean.parseBoolean(allowAnyProxy)); + v.setAllowedProxyChains(constructListOfProxies(allowedProxyChains)); + validator = v; + } else { + validator = new Cas20ServiceTicketValidator(casServerUrlPrefix); + } + validator.setProxyCallbackUrl(getPropertyFromInitParams(filterConfig, "proxyCallbackUrl", null)); + validator.setProxyGrantingTicketStorage(this.proxyGrantingTicketStorage); + validator.setProxyRetriever(new Cas20ProxyRetriever(casServerUrlPrefix)); + validator.setRenew(Boolean.parseBoolean(getPropertyFromInitParams(filterConfig, "renew", "false"))); + return validator; + } + + protected List constructListOfProxies(final String proxies) { + if (CommonUtils.isBlank(proxies)) { + return new ArrayList(); + } + + final String[] splitProxies = proxies.split(" "); + final List items = Arrays.asList(splitProxies); + final ProxyListPropertyEditor editor = new ProxyListPropertyEditor(); + editor.setValue(items); + return (List) editor.getValue(); + } + + /** + * This processes the ProxyReceptor request before the ticket validation code executes. + */ + protected final boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { + final HttpServletRequest request = (HttpServletRequest) servletRequest; + final HttpServletResponse response = (HttpServletResponse) servletResponse; + final String requestUri = request.getRequestURI(); + + if (!requestUri.endsWith(proxyReceptorUrl)) { + return true; + } + + final String proxyGrantingTicketIou = request + .getParameter(PARAM_PROXY_GRANTING_TICKET_IOU); + + final String proxyGrantingTicket = request + .getParameter(PARAM_PROXY_GRANTING_TICKET); + + if (CommonUtils.isBlank(proxyGrantingTicket) + || CommonUtils.isBlank(proxyGrantingTicketIou)) { + response.getWriter().write(""); + return false; + } + + if (log.isDebugEnabled()) { + log.debug("Received proxyGrantingTicketId [" + + proxyGrantingTicket + "] for proxyGrantingTicketIou [" + + proxyGrantingTicketIou + "]"); + } + + this.proxyGrantingTicketStorage.save(proxyGrantingTicketIou, + proxyGrantingTicket); + + response.getWriter().write(""); + response + .getWriter() + .write( + ""); + return false; + } + + public final void setProxyReceptorUrl(final String proxyReceptorUrl) { + this.proxyReceptorUrl = proxyReceptorUrl; + } + + public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) { + this.proxyGrantingTicketStorage = proxyGrantingTicketStorage; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidator.java new file mode 100644 index 0000000..cae4a5e --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidator.java @@ -0,0 +1,56 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.util.XmlUtils; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * Extension to the traditional Service Ticket validation that will validate service tickets and proxy tickets. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class Cas20ProxyTicketValidator extends Cas20ServiceTicketValidator { + + private boolean acceptAnyProxy; + + /** This should be a list of an array of Strings */ + private List allowedProxyChains; + + public Cas20ProxyTicketValidator(final String casServerUrlPrefix) { + super(casServerUrlPrefix); + } + + protected String getUrlSuffix() { + return "proxyValidate"; + } + + protected void customParseResponse(final String response, final Assertion assertion) throws TicketValidationException { + final List proxies = XmlUtils.getTextForElements(response, "proxy"); + final String[] proxiedList = (String[]) proxies.toArray(new String[proxies.size()]); + + // this means there was nothing in the proxy chain, which is okay + if (proxies.isEmpty() || this.acceptAnyProxy) { + return; + } + + for (Iterator iter = this.allowedProxyChains.iterator(); iter.hasNext();) { + if (Arrays.equals(proxiedList, (Object[]) iter.next())) { + return; + } + } + + throw new InvalidProxyChainTicketValidationException("Invalid proxy chain: " + proxies.toString()); + } + + public void setAcceptAnyProxy(final boolean acceptAnyProxy) { + this.acceptAnyProxy = acceptAnyProxy; + } + + public void setAllowedProxyChains(final List allowedProxyChains) { + this.allowedProxyChains = allowedProxyChains; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorBeanInfo.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorBeanInfo.java new file mode 100644 index 0000000..bb8b07d --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorBeanInfo.java @@ -0,0 +1,32 @@ +package org.jasig.cas.client.validation; + +import java.beans.IntrospectionException; +import java.beans.PropertyDescriptor; +import java.beans.PropertyEditor; +import java.beans.SimpleBeanInfo; +import java.util.List; + +/** + * BeanInfo support for using this class with Spring. Configures a ProxyListPropertyEditor to be used with the + * Cas20ProxyTicketValidator when Spring is used to configure the CAS client. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class Cas20ProxyTicketValidatorBeanInfo extends SimpleBeanInfo { + + public PropertyDescriptor[] getPropertyDescriptors() { + try { + final PropertyEditor propertyEditor = new ProxyListPropertyEditor(); + final PropertyDescriptor descriptor = new PropertyDescriptor("allowedProxyChains", List.class) { + public PropertyEditor createPropertyEditor(final Object bean) { + return propertyEditor; + } + }; + return new PropertyDescriptor[] {descriptor}; + } catch (final IntrospectionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java new file mode 100644 index 0000000..af7b027 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidator.java @@ -0,0 +1,107 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; +import org.jasig.cas.client.proxy.Cas20ProxyRetriever; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; +import org.jasig.cas.client.proxy.ProxyRetriever; +import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.util.XmlUtils; + +import java.util.Map; + +/** + * Implementation of the TicketValidator that will validate Service Tickets in compliance with the CAS 2. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTicketValidator { + + /** The CAS 2.0 protocol proxy callback url. */ + private String proxyCallbackUrl; + + /** The storage location of the proxy granting tickets. */ + private ProxyGrantingTicketStorage proxyGrantingTicketStorage; + + /** Implementation of the proxy retriever. */ + private ProxyRetriever proxyRetriever; + + /** + * Constructs an instance of the CAS 2.0 Service Ticket Validator with the supplied + * CAS server url prefix. + * + * @param casServerUrlPrefix the CAS Server URL prefix. + */ + public Cas20ServiceTicketValidator(final String casServerUrlPrefix) { + super(casServerUrlPrefix); + this.proxyRetriever = new Cas20ProxyRetriever(casServerUrlPrefix); + } + + /** + * Adds the pgtUrl to the list of parameters to pass to the CAS server. + * + * @param urlParameters the Map containing the existing parameters to send to the server. + */ + protected final void populateUrlAttributeMap(final Map urlParameters) { + urlParameters.put("pgtUrl", encodeUrl(this.proxyCallbackUrl)); + } + + protected String getUrlSuffix() { + return "serviceValidate"; + } + + protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException { + final String error = XmlUtils.getTextForElement(response, + "authenticationFailure"); + + if (CommonUtils.isNotBlank(error)) { + throw new TicketValidationException(error); + } + + final String principal = XmlUtils.getTextForElement(response, "user"); + final String proxyGrantingTicketIou = XmlUtils.getTextForElement( + response, "proxyGrantingTicket"); + final String proxyGrantingTicket = this.proxyGrantingTicketStorage.retrieve(proxyGrantingTicketIou); + + if (CommonUtils.isEmpty(principal)) { + throw new TicketValidationException("No principal was found in the response from the CAS server."); + } + + final Assertion assertion; + if (CommonUtils.isNotBlank(proxyGrantingTicket)) { + final AttributePrincipal attributePrincipal = new AttributePrincipalImpl(principal, proxyGrantingTicket, this.proxyRetriever); + assertion = new AssertionImpl(attributePrincipal); + } else { + assertion = new AssertionImpl(principal); + } + + customParseResponse(response, assertion); + + return assertion; + } + + /** + * Template method if additional custom parsing (such as Proxying) needs to be done. + * + * @param response the original response from the CAS server. + * @param assertion the partially constructed assertion. + * @throws TicketValidationException if there is a problem constructing the Assertion. + */ + protected void customParseResponse(final String response, final Assertion assertion) throws TicketValidationException { + // nothing to do + } + + public final void setProxyCallbackUrl(final String proxyCallbackUrl) { + this.proxyCallbackUrl = proxyCallbackUrl; + } + + public final void setProxyGrantingTicketStorage(final ProxyGrantingTicketStorage proxyGrantingTicketStorage) { + this.proxyGrantingTicketStorage = proxyGrantingTicketStorage; + } + + public final void setProxyRetriever(final ProxyRetriever proxyRetriever) { + this.proxyRetriever = proxyRetriever; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/InvalidProxyChainTicketValidationException.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/InvalidProxyChainTicketValidationException.java new file mode 100644 index 0000000..108adfb --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/InvalidProxyChainTicketValidationException.java @@ -0,0 +1,37 @@ +package org.jasig.cas.client.validation; + +/** + * Exception denotes that an invalid proxy chain was sent from the CAS server to the local application. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class InvalidProxyChainTicketValidationException extends TicketValidationException { + + /** + * Constructs an exception with the supplied message. + * @param string the supplied message. + */ + + public InvalidProxyChainTicketValidationException(final String string) { + super(string); + } + + /** + * Constructs an exception with the supplied message and chained throwable. + * @param string the message. + * @param throwable the root exception. + */ + public InvalidProxyChainTicketValidationException(final String string, final Throwable throwable) { + super(string, throwable); + } + + /** + * Constructs an exception with the chained throwable. + * @param throwable the root exception. + */ + public InvalidProxyChainTicketValidationException(final Throwable throwable) { + super(throwable); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/ProxyListPropertyEditor.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/ProxyListPropertyEditor.java new file mode 100644 index 0000000..98d2565 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/ProxyListPropertyEditor.java @@ -0,0 +1,35 @@ +package org.jasig.cas.client.validation; + +import java.beans.PropertyEditorSupport; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Convert a String-formatted list of acceptable proxies to an array. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + * + */ +public class ProxyListPropertyEditor extends PropertyEditorSupport { + + /** + * The new list of proxies to create. Its a list of String arrays. + */ + private List proxyChains = new ArrayList(); + + public Object getValue() { + return this.proxyChains; + } + + /** Converts the List of Strings into a list of arrays. */ + public void setValue(final Object o) { + final List values = (List) o; + + for (final Iterator iter = values.iterator(); iter.hasNext();) { + proxyChains.add(((String) iter.next()).split(" ")); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java new file mode 100644 index 0000000..a904ff3 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java @@ -0,0 +1,28 @@ +package org.jasig.cas.client.validation; + +import javax.servlet.FilterConfig; + +/** + * Implementation of TicketValidationFilter that can instanciate a SAML 1.1 Ticket Validator. + *

+ * Deployers can provide the "casServerUrlPrefix" and "tolerance" properties of the Saml11TicketValidator via the + * context or filter init parameters. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class Saml11TicketValidationFilter extends AbstractTicketValidationFilter { + + public Saml11TicketValidationFilter() { + setArtifactParameterName("SAMLart"); + setServiceParameterName("TARGET"); + } + + protected TicketValidator getTicketValidator(final FilterConfig filterConfig) { + final Saml11TicketValidator validator = new Saml11TicketValidator(getPropertyFromInitParams(filterConfig, "casServerUrlPrefix", null)); + final String tolerance = getPropertyFromInitParams(filterConfig, "tolerance", "1000"); + validator.setTolerance(Long.parseLong(tolerance)); + return validator; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java new file mode 100644 index 0000000..38211e2 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java @@ -0,0 +1,199 @@ +package org.jasig.cas.client.validation; + +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; +import org.opensaml.*; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.*; + +/** + * TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator { + + /** Time tolerance to allow for time drifting. */ + private long tolerance = 1000L; + + public Saml11TicketValidator(final String casServerUrlPrefix) { + super(casServerUrlPrefix); + } + + protected String getUrlSuffix() { + return "samlValidate"; + } + + protected void populateUrlAttributeMap(final Map urlParameters) { + final String service = (String) urlParameters.get("service"); + urlParameters.remove("service"); + urlParameters.remove("ticket"); + urlParameters.put("TARGET", service); + } + + protected Assertion parseResponseFromServer(final String response) throws TicketValidationException { + try { + final SAMLResponse samlResponse = new SAMLResponse(new ByteArrayInputStream(response.getBytes())); + + if (!samlResponse.getAssertions().hasNext()) { + throw new TicketValidationException("No assertions found."); + } + + for (final Iterator iter = samlResponse.getAssertions(); iter.hasNext();) { + final SAMLAssertion assertion = (SAMLAssertion) iter.next(); + + if (!isValidAssertion(assertion)) { + continue; + } + + final SAMLAuthenticationStatement authenticationStatement = getSAMLAuthenticationStatement(assertion); + + if (authenticationStatement == null) { + throw new TicketValidationException("No AuthentiationStatement found in SAML Assertion."); + } + final SAMLSubject subject = authenticationStatement.getSubject(); + + if (subject == null) { + throw new TicketValidationException("No Subject found in SAML Assertion."); + } + + final SAMLAttribute[] attributes = getAttributesFor(assertion, subject); + + final Map personAttributes = new HashMap(); + + for (int i = 0; i < attributes.length; i++) { + final SAMLAttribute samlAttribute = attributes[i]; + final List values = getValuesFrom(samlAttribute); + + personAttributes.put(samlAttribute.getName(), values.size() == 1 ? values.get(0) : values); + } + + final AttributePrincipal principal = new AttributePrincipalImpl(subject.getNameIdentifier().getName(), personAttributes); + + + final Map authenticationAttributes = new HashMap(); + authenticationAttributes.put("samlAuthenticationStatement::authMethod", authenticationStatement.getAuthMethod()); + + final Assertion casAssertion = new AssertionImpl(principal, authenticationAttributes); + return casAssertion; + } + } catch (final SAMLException e) { + throw new TicketValidationException(e); + } + + throw new TicketValidationException("No valid assertions from the SAML response found."); + } + + private boolean isValidAssertion(final SAMLAssertion assertion) { + final Date notBefore = assertion.getNotBefore(); + final Date notOnOrAfter = assertion.getNotOnOrAfter(); + + if (assertion.getNotBefore() == null || assertion.getNotOnOrAfter() == null) { + log.debug("Assertion has no bounding dates. Will not process."); + return false; + } + + final long currentTime = new Date().getTime(); + + if (currentTime + tolerance < notBefore.getTime()) { + log.debug("skipping assertion that's not yet valid..."); + return false; + } + + if (notOnOrAfter.getTime() <= currentTime - tolerance) { + log.debug("skipping expired assertion..."); + return false; + } + + return true; + } + + private SAMLAuthenticationStatement getSAMLAuthenticationStatement(final SAMLAssertion assertion) { + for (final Iterator iter = assertion.getStatements(); iter.hasNext();) { + final SAMLStatement statement = (SAMLStatement) iter.next(); + + if (statement instanceof SAMLAuthenticationStatement) { + return (SAMLAuthenticationStatement) statement; + } + } + + return null; + } + + private SAMLAttribute[] getAttributesFor(final SAMLAssertion assertion, final SAMLSubject subject) { + final List attributes = new ArrayList(); + for (final Iterator iter = assertion.getStatements(); iter.hasNext();) { + final SAMLStatement statement = (SAMLStatement) iter.next(); + + if (statement instanceof SAMLAttributeStatement) { + final SAMLAttributeStatement attributeStatement = (SAMLAttributeStatement) statement; + // used because SAMLSubject does not implement equals + if (subject.getNameIdentifier().getName().equals(attributeStatement.getSubject().getNameIdentifier().getName())) { + for (final Iterator iter2 = attributeStatement.getAttributes(); iter2.hasNext();) + attributes.add(iter2.next()); + } + } + } + + return (SAMLAttribute[]) attributes.toArray(new SAMLAttribute[attributes.size()]); + } + + private List getValuesFrom(final SAMLAttribute attribute) { + final List list = new ArrayList(); + for (final Iterator iter = attribute.getValues(); iter.hasNext();) { + list.add(iter.next()); + } + + return list; + } + + protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) { + final String MESSAGE_TO_SEND = "" + + "" + ticket + + ""; + + HttpURLConnection conn = null; + + try { + conn = (HttpURLConnection) validationUrl.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Length", Integer.toString(MESSAGE_TO_SEND.length())); + conn.setRequestProperty("SOAPAction", "http://www.oasis-open.org/committees/security"); + conn.setUseCaches(false); + conn.setDoInput(true); + conn.setDoOutput(true); + + final DataOutputStream out = new DataOutputStream(conn.getOutputStream()); + out.writeBytes(MESSAGE_TO_SEND); + out.flush(); + out.close(); + + final BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream())); + final StringBuffer buffer = new StringBuffer(256); + + synchronized (buffer) { + String line; + + while ((line = in.readLine()) != null) { + buffer.append(line); + } + return buffer.toString(); + } + } catch (final IOException e) { + throw new RuntimeException(e); + } finally { + if (conn != null) { + conn.disconnect(); + } + } + } + + public void setTolerance(final long tolerance) { + this.tolerance = tolerance; + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidationException.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidationException.java new file mode 100644 index 0000000..c2e6ca2 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidationException.java @@ -0,0 +1,38 @@ +package org.jasig.cas.client.validation; + +/** + * Generic exception to be thrown when ticket validation fails. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public class TicketValidationException extends Exception { + + /** + * Constructs an exception with the supplied messsage. + * + * @param string the message + */ + public TicketValidationException(final String string) { + super(string); + } + + /** + * Constructs an exception with the supplied message and chained throwable. + * + * @param string the message + * @param throwable the original exception + */ + public TicketValidationException(final String string, final Throwable throwable) { + super(string, throwable); + } + + /** + * Constructs an exception with the chained throwable. + * @param throwable the original exception. + */ + public TicketValidationException(final Throwable throwable) { + super(throwable); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidator.java new file mode 100644 index 0000000..d50dd51 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/TicketValidator.java @@ -0,0 +1,24 @@ +package org.jasig.cas.client.validation; + +/** + * Contract for a validator that will confirm the validity of a supplied ticket. + *

+ * Validator makes no statement about how to validate the ticket or the format of the ticket (other than that it must be a String). + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1 + */ +public interface TicketValidator { + + /** + * Attempts to validatea ticket for the provided service. + * + * @param ticket the ticket to attempt to validate. + * @param service the service this ticket is valid for. + * @return an assertion from the ticket. + * @throws TicketValidationException if the ticket cannot be validated. + * + */ + Assertion validate(String ticket, String service) throws TicketValidationException; +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/PublicTestHttpServer.java b/cas-client-core/src/test/java/org/jasig/cas/client/PublicTestHttpServer.java new file mode 100644 index 0000000..499f9c6 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/PublicTestHttpServer.java @@ -0,0 +1,106 @@ +package org.jasig.cas.client; + + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * @author Scott Battaglia + * @version $Revision: 11721 $ $Date: 2006-08-09 15:17:44 -0400 (Wed, 09 Aug 2006) $ + * @since 3.0 + */ +public final class PublicTestHttpServer extends Thread { + + private static PublicTestHttpServer httpServer; + + public byte[] content; + + private byte[] header; + + private int port = 80; + + public String encoding; + + private PublicTestHttpServer(String data, String encoding, String MIMEType, + int port) throws UnsupportedEncodingException { + this(data.getBytes(encoding), encoding, MIMEType, port); + } + + private PublicTestHttpServer(byte[] data, String encoding, String MIMEType, + int port) throws UnsupportedEncodingException { + + this.content = data; + this.port = port; + this.encoding = encoding; + String header = "HTTP/1.0 200 OK\r\n" + "Server: OneFile 1.0\r\n" + // + "Content-length: " + this.content.length + "\r\n" + + "Content-type: " + MIMEType + "\r\n\r\n"; + this.header = header.getBytes("ASCII"); + + } + + public static synchronized PublicTestHttpServer instance() { + if (httpServer == null) { + try { + httpServer = new PublicTestHttpServer("test", "ASCII", + "text/plain", 8085); + } catch (Exception e) { + throw new RuntimeException(e); + } + httpServer.start(); + Thread.yield(); + } + + return httpServer; + } + + public void run() { + + try { + ServerSocket server = new ServerSocket(this.port); + System.out.println("Accepting connections on port " + + server.getLocalPort()); + while (true) { + + Socket connection = null; + try { + connection = server.accept(); + OutputStream out = new BufferedOutputStream(connection + .getOutputStream()); + InputStream in = new BufferedInputStream(connection + .getInputStream()); + // read the first line only; that's all we need + StringBuffer request = new StringBuffer(80); + while (true) { + int c = in.read(); + if (c == '\r' || c == '\n' || c == -1) + break; + request.append((char) c); + } + + if (request.toString().startsWith("STOP")) { + connection.close(); + break; + } + if (request.toString().indexOf("HTTP/") != -1) { + out.write(this.header); + } + out.write(this.content); + out.flush(); + } // end try + catch (IOException e) { + // nothing to do with this IOException + } finally { + if (connection != null) + connection.close(); + } + + } // end while + } // end try + catch (IOException e) { + System.err.println("Could not start server. Port Occupied"); + } + + } // end run +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java new file mode 100644 index 0000000..fd56c1e --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/authentication/AuthenticationFilterTests.java @@ -0,0 +1,171 @@ +/* + * Copyright 2006 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.authentication; + +import junit.framework.TestCase; +import org.jasig.cas.client.util.AbstractCasFilter; +import org.jasig.cas.client.validation.AssertionImpl; +import org.springframework.mock.web.MockFilterConfig; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import java.io.IOException; +import java.net.URLEncoder; + +/** + * Tests for the AuthenticationFilter. + * + * @author Scott Battaglia + * @version $Revision: 11753 $ $Date: 2007-01-03 13:37:26 -0500 (Wed, 03 Jan 2007) $ + * @since 3.0 + */ +public final class AuthenticationFilterTests extends TestCase { + + private static final String CAS_SERVICE_URL = "https://localhost:8443/service"; + + private static final String CAS_LOGIN_URL = "https://localhost:8443/cas/login"; + + private AuthenticationFilter filter; + + protected void setUp() throws Exception { + // TODO CAS_SERVICE_URL, false, CAS_LOGIN_URL + this.filter = new AuthenticationFilter(); + final MockFilterConfig config = new MockFilterConfig(); + config.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + config.addInitParameter("service", "https://localhost:8443/service"); + this.filter.init(config); + } + + protected void tearDown() throws Exception { + this.filter.destroy(); + } + + public void testRedirect() throws Exception { + final MockHttpSession session = new MockHttpSession(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest arg0, ServletResponse arg1) + throws IOException, ServletException { + // nothing to do + } + }; + + request.setSession(session); + this.filter.doFilter(request, response, filterChain); + + assertEquals(CAS_LOGIN_URL + "?service=" + + URLEncoder.encode(CAS_SERVICE_URL, "UTF-8"), response + .getRedirectedUrl()); + } + + public void testRedirectWithQueryString() throws Exception { + final MockHttpSession session = new MockHttpSession(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + request.setQueryString("test=12456"); + request.setRequestURI("/test"); + request.setSecure(true); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest arg0, ServletResponse arg1) + throws IOException, ServletException { + // nothing to do + } + }; + + request.setSession(session); + this.filter = new AuthenticationFilter(); + + final MockFilterConfig config = new MockFilterConfig(); + config.addInitParameter("casServerLoginUrl", CAS_LOGIN_URL); + config.addInitParameter("serverName", "localhost:8443"); + this.filter.init(config); + + this.filter.doFilter(request, response, filterChain); + + assertEquals(CAS_LOGIN_URL + + "?service=" + + URLEncoder.encode("https://localhost:8443" + + request.getRequestURI() + "?" + request.getQueryString(), + "UTF-8"), response.getRedirectedUrl()); + } + + public void testAssertion() throws Exception { + final MockHttpSession session = new MockHttpSession(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest arg0, ServletResponse arg1) + throws IOException, ServletException { + // nothing to do + } + }; + + request.setSession(session); + session.setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, + new AssertionImpl("test")); + this.filter.doFilter(request, response, filterChain); + + assertNull(response.getRedirectedUrl()); + } + + public void testRenew() throws Exception { + final MockHttpSession session = new MockHttpSession(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest arg0, ServletResponse arg1) + throws IOException, ServletException { + // nothing to do + } + }; + + // TODO: "localhost:8443", true, CAS_LOGIN_URL + this.filter = new AuthenticationFilter(); + this.filter.setRenew(true); + request.setSession(session); + this.filter.doFilter(request, response, filterChain); + + assertNotNull(response.getRedirectedUrl()); + assertTrue(response.getRedirectedUrl().indexOf("renew=true") != -1); + } + + public void testGateway() throws Exception { + final MockHttpSession session = new MockHttpSession(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest arg0, ServletResponse arg1) + throws IOException, ServletException { + // nothing to do + } + }; + + request.setSession(session); + // TODO: "localhost:8443", true, CAS_LOGIN_URL + this.filter = new AuthenticationFilter(); + this.filter.setRenew(true); + this.filter.setGateway(true); + this.filter.doFilter(request, response, filterChain); + assertNotNull(session.getAttribute(AuthenticationFilter.CONST_CAS_GATEWAY)); + assertNotNull(response.getRedirectedUrl()); + + final MockHttpServletResponse response2 = new MockHttpServletResponse(); + this.filter.doFilter(request, response2, filterChain); + assertNull(session.getAttribute(AuthenticationFilter.CONST_CAS_GATEWAY)); + assertNull(response2.getRedirectedUrl()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java new file mode 100644 index 0000000..8e8f2bb --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java @@ -0,0 +1,91 @@ +/* + * Copyright 2006 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.util; + +import junit.framework.TestCase; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Tests for the CommonUtils. + * + * @author Scott Battaglia + * @version $Revision: 11731 $ $Date: 2006-09-27 11:27:21 -0400 (Wed, 27 Sep 2006) $ + * @since 3.0 + */ +public final class CommonUtilsTests extends TestCase { + + public void testAssertNotNull() { + final String CONST_MESSAGE = "test"; + CommonUtils.assertNotNull(new Object(), CONST_MESSAGE); + try { + CommonUtils.assertNotNull(null, CONST_MESSAGE); + } catch (IllegalArgumentException e) { + assertEquals(CONST_MESSAGE, e.getMessage()); + } + } + + public void testAssertNotEmpty() { + final String CONST_MESSAGE = "test"; + final Collection c = new ArrayList(); + c.add(new Object()); + CommonUtils.assertNotEmpty(c, CONST_MESSAGE); + try { + CommonUtils.assertNotEmpty(new ArrayList(), CONST_MESSAGE); + } catch (IllegalArgumentException e) { + assertEquals(CONST_MESSAGE, e.getMessage()); + } + + try { + CommonUtils.assertNotEmpty(null, CONST_MESSAGE); + } catch (IllegalArgumentException e) { + assertEquals(CONST_MESSAGE, e.getMessage()); + } + } + + public void testAssertTrue() { + final String CONST_MESSAGE = "test"; + CommonUtils.assertTrue(true, CONST_MESSAGE); + try { + CommonUtils.assertTrue(false, CONST_MESSAGE); + } catch (IllegalArgumentException e) { + assertEquals(CONST_MESSAGE, e.getMessage()); + } + } + + public void testIsEmpty() { + assertFalse(CommonUtils.isEmpty("test")); + assertFalse(CommonUtils.isEmpty(" test")); + assertTrue(CommonUtils.isEmpty("")); + assertTrue(CommonUtils.isEmpty(null)); + assertFalse(CommonUtils.isEmpty(" ")); + } + + public void testIsNotEmpty() { + assertTrue(CommonUtils.isNotEmpty("test")); + assertTrue(CommonUtils.isNotEmpty(" test")); + assertFalse(CommonUtils.isNotEmpty("")); + assertFalse(CommonUtils.isNotEmpty(null)); + assertTrue(CommonUtils.isNotEmpty(" ")); + } + + public void testIsBlank() { + assertFalse(CommonUtils.isBlank("test")); + assertFalse(CommonUtils.isBlank(" test")); + assertTrue(CommonUtils.isBlank("")); + assertTrue(CommonUtils.isBlank(null)); + assertTrue(CommonUtils.isBlank(" ")); + } + + public void testIsNotBlank() { + assertTrue(CommonUtils.isNotBlank("test")); + assertTrue(CommonUtils.isNotBlank(" test")); + assertFalse(CommonUtils.isNotBlank("")); + assertFalse(CommonUtils.isNotBlank(null)); + assertFalse(CommonUtils.isNotBlank(" ")); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilterTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilterTests.java new file mode 100644 index 0000000..2280165 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/HttpServletRequestWrapperFilterTests.java @@ -0,0 +1,60 @@ +/* + * Copyright 2006 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.util; + +import junit.framework.TestCase; +import org.jasig.cas.client.validation.AssertionImpl; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Tests for the HttpServletRequestWrapperFilter. + * + * @author Scott Battaglia + * @version $Revision: 11742 $ $Date: 2006-10-05 14:03:58 -0400 (Thu, 05 Oct 2006) $ + * @since 3.0 + */ + +public final class HttpServletRequestWrapperFilterTests extends TestCase { + + private HttpServletRequestWrapperFilter filter = new HttpServletRequestWrapperFilter(); + + protected HttpServletRequest mockRequest; + + protected void setUp() throws Exception { + this.filter.init(null); + this.filter.destroy(); + } + + public void testWrappedRequest() throws Exception { + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpSession session = new MockHttpSession(); + final FilterChain filterChain = new FilterChain() { + + public void doFilter(ServletRequest request, + ServletResponse response) throws IOException, ServletException { + HttpServletRequestWrapperFilterTests.this.mockRequest = (HttpServletRequest) request; + } + + }; + session.setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, + new AssertionImpl("test")); + + request.setSession(session); + + this.filter.doFilter(request, new MockHttpServletResponse(), + filterChain); + assertEquals("test", this.mockRequest.getRemoteUser()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/AbstractTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/AbstractTicketValidatorTests.java new file mode 100644 index 0000000..f1a6b57 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/AbstractTicketValidatorTests.java @@ -0,0 +1,22 @@ +/* + * Copyright 2006 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.validation; + +import junit.framework.TestCase; + +/** + * Base class for all TicketValidator tests to inherit from. + * + * @author Scott Battaglia + * @version $Revision: 11731 $ $Date: 2006-09-27 11:27:21 -0400 (Wed, 27 Sep 2006) $ + * @since 3.0 + */ +public abstract class AbstractTicketValidatorTests extends TestCase { + + protected static final String CONST_CAS_SERVER_URL = "http://localhost:8085/"; + + protected static final String CONST_USERNAME = "username"; +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/AssertionImplTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/AssertionImplTests.java new file mode 100644 index 0000000..7819db1 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/AssertionImplTests.java @@ -0,0 +1,49 @@ +/* + * Copyright 2006 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.validation; + +import junit.framework.TestCase; +import org.jasig.cas.client.authentication.AttributePrincipal; +import org.jasig.cas.client.authentication.AttributePrincipalImpl; + +import java.util.HashMap; +import java.util.Map; + +/** + * Test cases for the {@link AssertionImpl}. + * + * @author Scott Battaglia + * @version $Revision: 11737 $ $Date: 2006-10-03 09:14:02 -0400 (Tue, 03 Oct 2006) $ + * @since 3.0 + */ +public final class AssertionImplTests extends TestCase { + + private static final AttributePrincipal CONST_PRINCIPAL = new AttributePrincipalImpl("test"); + + private static final String CONST_PROXY_GRANTING_TICKET_IOU = "proxyGrantingTicketIou"; + + private static final Map CONST_ATTRIBUTES = new HashMap(); + + static { + CONST_ATTRIBUTES.put("test", "test"); + } + + public void testPrincipalConstructor() { + final Assertion assertion = new AssertionImpl(CONST_PRINCIPAL); + + assertEquals(CONST_PRINCIPAL, assertion.getPrincipal()); + assertTrue(assertion.getAttributes().isEmpty()); + assertNull(assertion.getPrincipal().getProxyTicketFor("test")); + } + + public void testCompleteConstructor() { + final Assertion assertion = new AssertionImpl(CONST_PRINCIPAL, + CONST_ATTRIBUTES); + + assertEquals(CONST_PRINCIPAL, assertion.getPrincipal()); + assertEquals(CONST_ATTRIBUTES, assertion.getAttributes()); + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidatorTests.java new file mode 100644 index 0000000..d5d7a84 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas10TicketValidatorTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2006 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.validation; + + +import org.jasig.cas.client.PublicTestHttpServer; + +import java.io.UnsupportedEncodingException; + +/** + * Test cases for the {@link Cas10TicketValidator}. + * + * @author Scott Battaglia + * @version $Revision: 11731 $ $Date: 2006-09-27 11:27:21 -0400 (Wed, 27 Sep 2006) $ + * @since 3.0 + */ +public final class Cas10TicketValidatorTests extends AbstractTicketValidatorTests { + + private Cas10TicketValidator ticketValidator; + + public Cas10TicketValidatorTests() { + super(); + } + + protected void setUp() throws Exception { + this.ticketValidator = new Cas10TicketValidator(CONST_CAS_SERVER_URL); + } + + public void testNoResponse() throws Exception { + PublicTestHttpServer.instance().content = "no\n\n" + .getBytes(PublicTestHttpServer.instance().encoding); + try { + this.ticketValidator.validate("testTicket", + "myService"); + fail("ValidationException expected."); + } catch (final TicketValidationException e) { + // expected + } + } + + public void testYesResponse() throws TicketValidationException, + UnsupportedEncodingException { + PublicTestHttpServer.instance().content = "yes\nusername\n\n" + .getBytes(PublicTestHttpServer.instance().encoding); + final Assertion assertion = this.ticketValidator.validate("testTicket", + "myService"); + assertEquals(CONST_USERNAME, assertion.getPrincipal().getName()); + } + + public void testBadResponse() throws UnsupportedEncodingException { + PublicTestHttpServer.instance().content = "falalala\n\n" + .getBytes(PublicTestHttpServer.instance().encoding); + try { + this.ticketValidator.validate("testTicket", + "myService"); + fail("ValidationException expected."); + } catch (final TicketValidationException e) { + // expected + } + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorTests.java new file mode 100644 index 0000000..097b1ef --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ProxyTicketValidatorTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2006 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.validation; + +import org.jasig.cas.client.PublicTestHttpServer; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl; +import org.jasig.cas.client.proxy.ProxyRetriever; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +/** + * Test cases for the {@link Cas20ProxyTicketValidator}. + * + * @author Scott Battaglia + * @version $Revision: 11737 $ $Date: 2006-10-03 09:14:02 -0400 (Tue, 03 Oct 2006) $ + * @since 3.0 + */ +public final class Cas20ProxyTicketValidatorTests extends + AbstractTicketValidatorTests { + + private Cas20ProxyTicketValidator ticketValidator; + + public Cas20ProxyTicketValidatorTests() { + super(); + } + + protected void setUp() throws Exception { + final ProxyGrantingTicketStorage proxyGrantingTicketStorage = getProxyGrantingTicketStorage(); + final List list = new ArrayList(); + list.add(new String[] {"proxy1", "proxy2", "proxy3"}); + + this.ticketValidator = new Cas20ProxyTicketValidator(CONST_CAS_SERVER_URL); + this.ticketValidator.setRenew(true); + this.ticketValidator.setProxyCallbackUrl("test"); + this.ticketValidator.setProxyGrantingTicketStorage(getProxyGrantingTicketStorage()); + this.ticketValidator.setProxyRetriever(getProxyRetriever()); + this.ticketValidator.setAllowedProxyChains(list); + } + + private ProxyGrantingTicketStorage getProxyGrantingTicketStorage() { + final ProxyGrantingTicketStorageImpl proxyGrantingTicketStorageImpl = new ProxyGrantingTicketStorageImpl(); + + return proxyGrantingTicketStorageImpl; + } + + private ProxyRetriever getProxyRetriever() { + final ProxyRetriever proxyRetriever = new ProxyRetriever() { + + public String getProxyTicketIdFor(String proxyGrantingTicketId, String targetService) { + return "test"; + } + }; + + return proxyRetriever; + } + + public void testProxyChainWithValidProxy() throws TicketValidationException, + UnsupportedEncodingException { + final String USERNAME = "username"; + final String RESPONSE = "usernamePGTIOU-84678-8a9d...proxy1proxy2proxy3"; + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + + final Assertion assertion = this.ticketValidator.validate("test", + "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + } + + public void testProxyChainWithInvalidProxy() throws TicketValidationException, + UnsupportedEncodingException { + final String RESPONSE = "usernamePGTIOU-84678-8a9d...proxy7proxy2proxy3"; + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + + try { + this.ticketValidator.validate("test", "test"); + fail("Invalid proxy chain"); + } catch (InvalidProxyChainTicketValidationException e) { + // expected + } + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java new file mode 100644 index 0000000..8e14c46 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Cas20ServiceTicketValidatorTests.java @@ -0,0 +1,125 @@ +/* + * Copyright 2006 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.validation; + + +import org.jasig.cas.client.PublicTestHttpServer; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorage; +import org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl; +import org.jasig.cas.client.proxy.ProxyRetriever; + +import java.io.UnsupportedEncodingException; + +/** + * Test cases for the {@link Cas20ServiceTicketValidator}. + * + * @author Scott Battaglia + * @version $Revision: 11737 $ $Date: 2006-10-03 09:14:02 -0400 (Tue, 03 Oct 2006) $ + * @since 3.0 + */ +public final class Cas20ServiceTicketValidatorTests extends + AbstractTicketValidatorTests { + + private Cas20ServiceTicketValidator ticketValidator; + + private ProxyGrantingTicketStorage proxyGrantingTicketStorage; + + private ProxyRetriever proxyRetriever; + + public Cas20ServiceTicketValidatorTests() { + super(); + } + + public Cas20ServiceTicketValidatorTests(Cas20ServiceTicketValidator ticketValidator) { + this.ticketValidator = ticketValidator; + } + + protected void setUp() throws Exception { + this.proxyGrantingTicketStorage = getProxyGrantingTicketStorage(); + this.ticketValidator = new Cas20ServiceTicketValidator(CONST_CAS_SERVER_URL); + this.ticketValidator.setProxyCallbackUrl("test"); + this.ticketValidator.setProxyGrantingTicketStorage(getProxyGrantingTicketStorage()); + this.ticketValidator.setProxyRetriever(getProxyRetriever()); + this.ticketValidator.setRenew(true); + } + + private ProxyGrantingTicketStorage getProxyGrantingTicketStorage() { + final ProxyGrantingTicketStorageImpl proxyGrantingTicketStorageImpl = new ProxyGrantingTicketStorageImpl(); + + return proxyGrantingTicketStorageImpl; + } + + private ProxyRetriever getProxyRetriever() { + final ProxyRetriever proxyRetriever = new ProxyRetriever() { + + public String getProxyTicketIdFor(String proxyGrantingTicketId, String targetService) { + return "test"; + } + }; + + return proxyRetriever; + } + + public void testNoResponse() throws UnsupportedEncodingException { + final String RESPONSE = "Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized"; + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + try { + this.ticketValidator.validate("test", "test"); + fail("ValidationException expected due to 'no' response"); + } catch (final TicketValidationException e) { + // expected + } + } + + public void testYesResponseButNoPgt() throws TicketValidationException, + UnsupportedEncodingException { + final String USERNAME = "username"; + final String RESPONSE = "" + + USERNAME + + ""; + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + + final Assertion assertion = this.ticketValidator.validate("test", + "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); + } + + public void testYesResponseWithPgt() throws TicketValidationException, + UnsupportedEncodingException { + final String USERNAME = "username"; + final String PGTIOU = "testPgtIou"; + final String PGT = "test"; + final String RESPONSE = "" + + USERNAME + + "" + + PGTIOU + + ""; + + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + + this.proxyGrantingTicketStorage.save(PGTIOU, PGT); + + final Assertion assertion = this.ticketValidator.validate("test", + "test"); + assertEquals(USERNAME, assertion.getPrincipal().getName()); +// assertEquals(PGT, assertion.getProxyGrantingTicketId()); + } + + public void testInvalidResponse() throws Exception { + final String RESPONSE = ""; + PublicTestHttpServer.instance().content = RESPONSE + .getBytes(PublicTestHttpServer.instance().encoding); + try { + this.ticketValidator.validate("test", "test"); + fail("ValidationException expected due to invalid response."); + } catch (final TicketValidationException e) { + // expected + } + } +} diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/SamlTicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/SamlTicketValidatorTests.java new file mode 100644 index 0000000..9f28306 --- /dev/null +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/SamlTicketValidatorTests.java @@ -0,0 +1,42 @@ +/* + * Copyright 2006 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.validation; + +import junit.framework.TestCase; + +/** + * Test cases for the {@link org.jasig.cas.client.validation.Saml11TicketValidator}. + * + * @author Scott Battaglia + * @version $Revision: 11737 $ $Date: 2006-10-03 09:14:02 -0400 (Tue, 03 Oct 2006) $ + * @since 3.1 + */ +public class SamlTicketValidatorTests extends TestCase { + + final Saml11TicketValidator validator = new Saml11TicketValidator("https://cas.rutgers.edu"); + /* + public void testValidationWithTicketIdWithPlus() throws Exception { + final Saml10SuccessResponseView view = new Saml10SuccessResponseView(); + final MockHttpServletRequest request = new MockHttpServletRequest(); + final MockHttpServletResponse response = new MockHttpServletResponse(); + final Map model = new HashMap(); + + final Authentication authentication = new ImmutableAuthentication(new SimplePrincipal("test")); + final List authentications = new ArrayList(); + authentications.add(authentication); + final ImmutableAssertionImpl assertion = new ImmutableAssertionImpl(authentications, new SimpleService("test"), true); + model.put("assertion", assertion); + request.addParameter("SAMLArt", "AAIYG64MrQ2+793pMM8J0sRjXf6uG2h0dHBzOi8vbG9jYWxob3N0Ojg0NDM="); + + view.setIssuer("https://cas.rutgers.edu"); + view.render(model, request, response); + final String content = response.getContentAsString(); + + Assertion assertionResponse = validator.parseResponse(content); + + assertEquals("test", assertionResponse.getPrincipal().getId()); + } */ +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..7d69db1 --- /dev/null +++ b/pom.xml @@ -0,0 +1,156 @@ + + 4.0.0 + org.jasig.cas + 3.1-SNAPSHOT + cas-client + pom + JA-SIG CAS Client for Java + JA-SIG CAS Client for Java is the integration point for applications that want to speak with a CAS + server, either via the CAS 1.0 or CAS 2.0 protocol. + + http://www.ja-sig.org/products/cas/ + + JIRA + http://www.ja-sig.org/issues + + + QuickBuild + http://developer.ja-sig.org/builds/ + + 2006 + + + CAS Community Discussion List + http://tp.its.yale.edu/mailman/listinfo/cas + http://tp.its.yale.edu/mailman/listinfo/cas + cas@tp.its.yale.edu + http://tp.its.yale.edu/pipermail/cas/ + + http://news.gmane.org/gmane.comp.java.jasig.cas.user + + + + CAS Developers Discussion List + http://tp.its.yale.edu/mailman/listinfo/cas-dev + http://tp.its.yale.edu/mailman/listinfo/cas-dev + cas-dev@tp.its.yale.edu + http://tp.its.yale.edu/pipermail/cas-dev/ + + http://news.gmane.org/gmane.comp.java.jasig.cas.devel + + + + + + battags + Scott Battaglia + scott_battaglia@rutgers.edu + http://www.scottbattaglia.com + Rutgers, the State University of New Jersey + http://www.rutgers.edu + + Project Admin + Developer + + -5 + + + + + JA-SIG License for Use + http://www.ja-sig.org/products/cas/overview/license/ + manual + + + + scm:cvs:pserver:anonymous:@developer.ja-sig.org:2401/home/cvs/jasig:cas-clients/java-client + + + scm:cvs:pserver:${username}@developer.ja-sig.org:2401/home/cvs/jasig:cas-clients/java-client + + http://developer.ja-sig.org/source/browse/jasig/cas-clients/java-client + + + JA-SIG + http://www.ja-sig.org + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.2-beta-1 + + + ${basedir}/assembly.xml + + + + + + + + + junit + junit + 3.8.1 + test + + + commons-logging + commons-logging + 1.1 + compile + + + log4j + log4j + + + logkit + logkit + + + avalon-framework + avalon-framework + + + + + javax.servlet + servlet-api + 2.4 + provided + + + + + + jasig + JA-SIG Maven Repository + http://developer.ja-sig.org/maven2/ + default + + + ibiblio + Ibiblio Repository + http://www.ibiblio.org/maven2 + + + + + mojo + Codehaus Repository + http://snapshots.maven.codehaus.org/maven2 + + + ibiblio + Ibiblio Repository + http://www.ibiblio.org/maven2 + + + + cas-client-core + + \ No newline at end of file