diff --git a/cas-client-integration-tomcat-common/pom.xml b/cas-client-integration-tomcat-common/pom.xml new file mode 100644 index 0000000..472c5c6 --- /dev/null +++ b/cas-client-integration-tomcat-common/pom.xml @@ -0,0 +1,27 @@ + + + + cas-client + org.jasig.cas.client + 3.1.12-SNAPSHOT + + 4.0.0 + + org.jasig.cas.client + cas-client-integration-tomcat-common + jar + JA-SIG CAS Client for Java - Common Tomcat Integration Support + + + + org.jasig.cas.client + cas-client-core + ${project.version} + jar + compile + + + + diff --git a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AssertionCasRealmDelegate.java b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AssertionCasRealmDelegate.java new file mode 100644 index 0000000..2a0ac37 --- /dev/null +++ b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AssertionCasRealmDelegate.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat; + +import java.security.Principal; +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.client.authentication.AttributePrincipal; + +/** + * {@link CasRealm} implementation with prinicpal and role data backed by the {@link Assertion}. + * In particular, an {@link AttributePrincipal} is expected from which the values of + * the role attribute are retrieved. The default role attribute name is "role", + * but this can be customized via {@link #setRoleAttributeName(String)}. + *

+ * Authentication always succeeds and simply returns the given principal. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class AssertionCasRealmDelegate implements CasRealm { + /** Default role attribute name */ + public static final String DEFAULT_ROLE_NAME = "role"; + + /** Log instance */ + private final Log log = LogFactory.getLog(getClass()); + + /** Name of the role attribute in the principal's attributes */ + private String roleAttributeName = DEFAULT_ROLE_NAME; + + + /** + * @param name Name of the attribute in the principal that contains role data. + */ + public void setRoleAttributeName(final String name) { + this.roleAttributeName = name; + } + + /** {@inheritDoc} */ + public Principal authenticate(final Principal p) { + return p; + } + + /** {@inheritDoc} */ + public String[] getRoles(final Principal p) { + if (p instanceof AttributePrincipal) { + final Collection roles = getRoleCollection(p); + if (roles != null) { + final String[] array = new String[roles.size()]; + roles.toArray(array); + return array; + } else { + return new String[0]; + } + } else { + throw new IllegalArgumentException("Expected instance of AttributePrincipal but got " + p); + } + } + + /** {@inheritDoc} */ + public boolean hasRole(final Principal principal, final String role) { + final Collection roles = getRoleCollection(principal); + if (roles != null) { + return roles.contains(role); + } else { + return false; + } + } + + private Collection getRoleCollection(final Principal p) { + if (p instanceof AttributePrincipal) { + final Collection attributes = + (Collection) ((AttributePrincipal) p).getAttributes().get(roleAttributeName); + if (attributes == null) { + log.debug(p + " has no attribute named " + roleAttributeName); + } + return attributes; + } else { + return null; + } + } +} diff --git a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java new file mode 100644 index 0000000..c582e79 --- /dev/null +++ b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/AuthenticatorDelegate.java @@ -0,0 +1,196 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat; + +import java.io.IOException; +import java.security.Principal; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.client.util.AbstractCasFilter; +import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.validation.Assertion; +import org.jasig.cas.client.validation.TicketValidationException; +import org.jasig.cas.client.validation.TicketValidator; + +/** + * Version-agnostic authenticator which encapsulates the core CAS workflow of + * redirecting to CAS for unauthenticated sessions and validating service tickets + * when found in the request. Implementations of the Tomcat Authenticator + * class are expected to be thin wrappers that delegate most if not all authentication + * logic to this class. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class AuthenticatorDelegate { + /** Log instance */ + private final Log log = LogFactory.getLog(getClass()); + + private String serviceUrl; + + private String serverName; + + private String casServerLoginUrl; + + private String artifactParameterName; + + private String serviceParameterName; + + private TicketValidator ticketValidator; + + private CasRealm realm; + + + /** + * Performs CAS authentication on the given request and returns the principal + * determined by the configured {@link CasRealm} on success. + * + * @param request HTTP request. + * @param response HTTP response. + * + * @return The authenticated principal on authentication success, otherwise + * null. In the case where authentication explicitly fails, either due to + * ticket validation failure or realm authentication failure, a 403 status + * code is set on the response. In cases where no existing CAS session exists, + * a 302 redirect is set on the response to redirect to the CAS server for + * authentication. + */ + public final Principal authenticate(final HttpServletRequest request, final HttpServletResponse response) { + Assertion assertion = null; + HttpSession session = request.getSession(); + if (session != null) { + assertion = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION); + } + if (assertion == null) { + log.debug("CAS assertion not found in session -- authentication required."); + final String token = request.getParameter(this.artifactParameterName); + final String service = CommonUtils.constructServiceUrl(request, response, this.serviceUrl, this.serverName, this.artifactParameterName, true); + if (CommonUtils.isBlank(token)) { + final String redirectUrl = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.serviceParameterName, service, false, false); + log.debug("Redirecting to " + redirectUrl); + try { + response.sendRedirect(redirectUrl); + } catch (IOException e) { + throw new IllegalStateException("Cannot redirect to " + redirectUrl, e); + } + return null; + } + try { + log.debug("Attempting to validate " + token + " for " + service); + assertion = this.ticketValidator.validate(token, service); + log.debug("CAS authentication succeeded."); + if (session == null) { + session = request.getSession(true); + } + session.setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, assertion); + } catch (final TicketValidationException e) { + setUnauthorized(response, e.getMessage()); + return null; + } + } + Principal p = realm.authenticate(assertion.getPrincipal()); + if (p == null) { + log.debug(assertion.getPrincipal().getName() + " failed to authenticate to " + realm); + setUnauthorized(response, null); + } + return p; + } + + /** + * @return the serviceUrl + */ + public String getServiceUrl() { + return serviceUrl; + } + + /** + * @param serviceUrl the serviceUrl to set + */ + public void setServiceUrl(final String serviceUrl) { + this.serviceUrl = serviceUrl; + } + + /** + * @return the serverName + */ + public String getServerName() { + return serverName; + } + + /** + * @param serverName the serverName to set + */ + public void setServerName(final String serverName) { + this.serverName = serverName; + } + + /** + * @return the casServerLoginUrl + */ + public String getCasServerLoginUrl() { + return casServerLoginUrl; + } + + /** + * @param casServerLoginUrl the casServerLoginUrl to set + */ + public void setCasServerLoginUrl(final String casServerLoginUrl) { + this.casServerLoginUrl = casServerLoginUrl; + } + + /** + * @return the ticketValidator + */ + public TicketValidator getTicketValidator() { + return ticketValidator; + } + + /** + * @param artifactParameterName the artifactParameterName to set + */ + public void setArtifactParameterName(final String artifactParameterName) { + this.artifactParameterName = artifactParameterName; + } + + /** + * @param serviceParameterName the serviceParameterName to set + */ + public void setServiceParameterName(final String serviceParameterName) { + this.serviceParameterName = serviceParameterName; + } + + /** + * @param ticketValidator the ticketValidator to set + */ + public void setTicketValidator(final TicketValidator ticketValidator) { + this.ticketValidator = ticketValidator; + } + + /** + * @param realm the realm to set + */ + public void setRealm(final CasRealm realm) { + this.realm = realm; + } + + private void setUnauthorized(final HttpServletResponse response, final String message) { + try { + if (message != null) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, message); + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } + } catch (IOException e) { + throw new IllegalStateException("Error setting 403 status.", e); + } + } +} diff --git a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/CasRealm.java b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/CasRealm.java new file mode 100644 index 0000000..1fceae4 --- /dev/null +++ b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/CasRealm.java @@ -0,0 +1,44 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat; + +import java.security.Principal; + +/** + * Describes Tomcat Realm implementations that do not require password + * for authentication. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public interface CasRealm { + /** + * Authenticates the given principal. + * + * @param p Principal to authenticate. + * + * @return New principal. + */ + Principal authenticate(Principal p); + + /** + * Gets the roles defined for the given principal. + * + * @return Roles for given principal or empty array if none exist. + */ + String[] getRoles(Principal p); + + /** + * Determines whether the given principal possesses the given role. + * + * @param principal Principal to evaluate. + * @param role Role to test for possession. + * + * @return True if principal has given role, false otherwise. + */ + boolean hasRole(Principal principal, String role); +} diff --git a/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegate.java b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegate.java new file mode 100644 index 0000000..ca3e843 --- /dev/null +++ b/cas-client-integration-tomcat-common/src/main/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegate.java @@ -0,0 +1,107 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.HashSet; + +import org.jasig.cas.client.util.CommonUtils; + +/** + * {@link CasRealm} implementation with users and roles defined by a properties + * file with the following format: + *

+ * username1=role1,role2,role3
+ * username2=role1
+ * username3=role2,role3
+ * 
+ * User authentication succeeds if the name of the given principal exists as + * a username in the properties file. + * + * @author Middleware + * @version $Revision$ + * + */ +public class PropertiesCasRealmDelegate implements CasRealm { + /** Path to backing properties file */ + private String propertiesFilePath; + + /** Map of usernames to roles */ + private Map roleMap; + + /** + * @param path Path to properties file container username/role data. + */ + public void setPropertiesFilePath(final String path) { + propertiesFilePath = path; + } + + /** {@inheritDoc} */ + public void readProperties() + { + CommonUtils.assertNotNull(propertiesFilePath, "PropertiesFilePath not set."); + File file = new File(propertiesFilePath); + if (!file.isAbsolute()) { + file = new File(System.getProperty("catalina.base"), propertiesFilePath); + } + CommonUtils.assertTrue(file.exists(), "File not found " + file); + CommonUtils.assertTrue(file.canRead(), "Cannot read " + file); + final Properties properties = new Properties(); + try { + properties.load(new BufferedInputStream(new FileInputStream(file))); + } catch (IOException e) { + throw new IllegalStateException("Error loading users/roles from " + file, e); + } + roleMap = new HashMap(properties.size()); + final Iterator keys = properties.keySet().iterator(); + while (keys.hasNext()) { + final String user = (String) keys.next(); + // Use TreeSet to sort roles + final Set roleSet = new HashSet(); + final String[] roles = properties.getProperty(user).split(",\\s*"); + for (int i = 0; i < roles.length; i++) { + roleSet.add(roles[i]); + } + roleMap.put(user, roleSet); + } + } + + /** {@inheritDoc} */ + public Principal authenticate(final Principal p) { + if (roleMap.get(p.getName()) != null) { + return p; + } else { + return null; + } + } + + /** {@inheritDoc} */ + public String[] getRoles(final Principal p) { + final Set roleSet = (Set) roleMap.get(p.getName()); + final String[] roles = new String[roleSet.size()]; + roleSet.toArray(roles); + return roles; + } + + /** {@inheritDoc} */ + public boolean hasRole(final Principal principal, final String role) { + final Set roles = (Set) roleMap.get(principal.getName()); + if (roles != null) { + return roles.contains(role); + } else { + return false; + } + } +} diff --git a/cas-client-integration-tomcat-common/src/test/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegateTests.java b/cas-client-integration-tomcat-common/src/test/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegateTests.java new file mode 100644 index 0000000..6aad839 --- /dev/null +++ b/cas-client-integration-tomcat-common/src/test/java/org/jasig/cas/client/tomcat/PropertiesCasRealmDelegateTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat; + +import java.security.Principal; + +import junit.framework.TestCase; + +import org.jasig.cas.client.authentication.AttributePrincipalImpl; + +/** + * Unit test for {@link PropertiesCasRealmDelegate} class. + * + * @author Middleware + * @version $Revision$ + * + */ +public class PropertiesCasRealmDelegateTests extends TestCase { + private PropertiesCasRealmDelegate realm = new PropertiesCasRealmDelegate(); + + /** {@inheritDoc} */ + protected void setUp() throws Exception { + super.setUp(); + realm.setPropertiesFilePath("src/test/resources/org/jasig/cas/client/tomcat/user-roles.properties"); + realm.readProperties(); + } + + public void testAuthenticate() { + final Principal p = new AttributePrincipalImpl("rosencrantz"); + assertTrue(p == realm.authenticate(p)); + } + + public void testGetRoles() { + final Principal p = new AttributePrincipalImpl("rosencrantz"); + final String[] expected = new String[] {"admins", "users"}; + final String[] actual = realm.getRoles(p); + assertEquals(expected.length, actual.length); + for (int i = 0; i < expected.length; i++) { + assertEquals(expected[i], actual[i]); + } + } + + public void testHasRole() { + assertTrue(realm.hasRole(new AttributePrincipalImpl("rosencrantz"), "admins")); + assertTrue(realm.hasRole(new AttributePrincipalImpl("rosencrantz"), "users")); + assertTrue(realm.hasRole(new AttributePrincipalImpl("guildenstern"), "users")); + } +} diff --git a/cas-client-integration-tomcat-common/src/test/resources/org/jasig/cas/client/tomcat/user-roles.properties b/cas-client-integration-tomcat-common/src/test/resources/org/jasig/cas/client/tomcat/user-roles.properties new file mode 100644 index 0000000..5e7558d --- /dev/null +++ b/cas-client-integration-tomcat-common/src/test/resources/org/jasig/cas/client/tomcat/user-roles.properties @@ -0,0 +1,2 @@ +rosencrantz=users,admins +guildenstern=users diff --git a/cas-client-integration-tomcat/pom.xml b/cas-client-integration-tomcat-v7/pom.xml similarity index 71% rename from cas-client-integration-tomcat/pom.xml rename to cas-client-integration-tomcat-v7/pom.xml index 76d487d..fc8e9de 100644 --- a/cas-client-integration-tomcat/pom.xml +++ b/cas-client-integration-tomcat-v7/pom.xml @@ -10,11 +10,18 @@ 4.0.0 org.jasig.cas.client - cas-client-integration-tomcat + cas-client-integration-tomcat-v7 jar - JA-SIG CAS Client for Java - Tomcat Integration + JA-SIG CAS Client for Java - Tomcat 7.x Integration + + org.jasig.cas.client + cas-client-integration-tomcat-common + ${project.version} + jar + compile + org.apache.tomcat tomcat-catalina @@ -32,4 +39,4 @@ - \ No newline at end of file + diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractAuthenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractAuthenticator.java new file mode 100644 index 0000000..f26523b --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractAuthenticator.java @@ -0,0 +1,153 @@ +/* + * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import org.apache.catalina.Lifecycle; +import org.apache.catalina.LifecycleEvent; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleListener; +import org.apache.catalina.Realm; +import org.apache.catalina.authenticator.AuthenticatorBase; +import org.apache.catalina.connector.Request; +import org.apache.catalina.deploy.LoginConfig; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jasig.cas.client.tomcat.AuthenticatorDelegate; +import org.jasig.cas.client.tomcat.CasRealm; +import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.validation.TicketValidator; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.security.Principal; + +/** + * Base authenticator for all authentication protocols supported by CAS. + * + * @author Scott Battaglia + * @version $Revision$ $Date$ + * @since 3.1.12 + */ +public abstract class AbstractAuthenticator extends AuthenticatorBase implements LifecycleListener { + + protected final Log log = LogFactory.getLog(getClass()); + + private final AuthenticatorDelegate delegate = new AuthenticatorDelegate(); + + private String casServerUrlPrefix; + + private String encoding; + + private boolean encode; + + private boolean renew; + + protected abstract String getAuthenticationMethod(); + + protected abstract String getArtifactParameterName(); + + protected abstract String getServiceParameterName(); + + protected abstract TicketValidator getTicketValidator(); + + + protected void startInternal() throws LifecycleException { + super.startInternal(); + final Realm realm = this.context.getRealm(); + if (!(realm instanceof CasRealm)) { + throw new LifecycleException("Expected CasRealm but got " + realm.getInfo()); + } + try { + CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null."); + CommonUtils.assertNotNull(this.delegate.getCasServerLoginUrl(), "casServerLoginUrl cannot be null."); + CommonUtils.assertTrue( + this.delegate.getServerName() != null || this.delegate.getServiceUrl() != null, + "either serverName or serviceUrl must be set."); + this.delegate.setRealm((CasRealm) realm); + } catch (final Exception e) { + throw new LifecycleException(e); + } + // Complete delegate initialization after the component is started. + // See #lifecycleEvent() method. + addLifecycleListener(this); + } + + protected final String getCasServerUrlPrefix() { + return this.casServerUrlPrefix; + } + + public final void setCasServerUrlPrefix(final String casServerUrlPrefix) { + this.casServerUrlPrefix = casServerUrlPrefix; + } + + public final void setCasServerLoginUrl(final String casServerLoginUrl) { + this.delegate.setCasServerLoginUrl(casServerLoginUrl); + } + + public final boolean isEncode() { + return this.encode; + } + + public final void setEncode(final boolean encode) { + this.encode = encode; + } + + protected final boolean isRenew() { + return this.renew; + } + + public void setRenew(final boolean renew) { + this.renew = renew; + } + + public final void setServerName(final String serverName) { + this.delegate.setServerName(serverName); + } + + public final void setServiceUrl(final String serviceUrl) { + this.delegate.setServiceUrl(serviceUrl); + } + + protected final String getEncoding() { + return this.encoding; + } + + public final void setEncoding(final String encoding) { + this.encoding = encoding; + } + + /** {@inheritDoc} */ + public final boolean authenticate(final Request request, final HttpServletResponse response, final LoginConfig loginConfig) throws IOException { + Principal principal = request.getUserPrincipal(); + boolean result = false; + if (principal == null) { + // Authentication sets the response headers for status and redirect if needed + principal = this.delegate.authenticate(request.getRequest(), response); + if (principal != null) { + request.setAuthType(getAuthenticationMethod()); + request.setUserPrincipal(principal); + result = true; + } + } else { + result = true; + } + return result; + } + + /** {@inheritDoc} */ + public void lifecycleEvent(final LifecycleEvent event) { + if (Lifecycle.AFTER_START_EVENT.equals(event.getType())) { + this.delegate.setTicketValidator(getTicketValidator()); + this.delegate.setArtifactParameterName(getArtifactParameterName()); + this.delegate.setServiceParameterName(getServiceParameterName()); + } + } + + /** {@inheritDoc} */ + public String getInfo() { + return getClass().getName() + "/1.0"; + } +} diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractCasAuthenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasAuthenticator.java similarity index 76% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractCasAuthenticator.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasAuthenticator.java index 61bf9aa..5c78876 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractCasAuthenticator.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasAuthenticator.java @@ -3,10 +3,10 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; /** - * + * Base class for all CAS protocol authenticators. * * @author Scott Battaglia * @version $Revision$ $Date$ @@ -14,17 +14,8 @@ package org.jasig.cas.client.tomcat; */ public abstract class AbstractCasAuthenticator extends AbstractAuthenticator { - private String encoding; - private String proxyCallbackUrl; - protected final String getEncoding() { - return this.encoding; - } - - public final void setEncoding(final String encoding) { - this.encoding = encoding; - } protected final String getProxyCallbackUrl() { return this.proxyCallbackUrl; diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasRealm.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasRealm.java new file mode 100644 index 0000000..4a7fab5 --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractCasRealm.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import java.security.Principal; + +import org.apache.catalina.realm.RealmBase; +import org.jasig.cas.client.tomcat.CasRealm; + +/** + * Base Realm implementation for all CAS realms. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public abstract class AbstractCasRealm extends RealmBase implements CasRealm { + /** {@inheritDoc} */ + public Principal authenticate(final Principal p) { + return getDelegate().authenticate(p); + } + + /** {@inheritDoc} */ + public String[] getRoles(final Principal p) { + return getDelegate().getRoles(p); + } + + /** {@inheritDoc} */ + public boolean hasRole(final Principal principal, final String role) { + return getDelegate().hasRole(principal, role); + } + + /** {@inheritDoc} */ + public String toString() { + return getName(); + } + + /** {@inheritDoc} */ + public String getInfo() { + return getClass().getName() + "/1.0"; + } + + /** {@inheritDoc} */ + protected String getName() { + return getClass().getSimpleName(); + } + + /** {@inheritDoc} */ + protected String getPassword(final String username) { + throw new UnsupportedOperationException(); + } + + /** {@inheritDoc} */ + protected Principal getPrincipal(String username) { + throw new UnsupportedOperationException(); + } + + /** + * @return Delegate that all {@link CasRealm} operations are delegated to. + */ + protected abstract CasRealm getDelegate(); +} diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractLogoutValveBase.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractLogoutValveBase.java similarity index 98% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractLogoutValveBase.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractLogoutValveBase.java index 461a0ba..6eb30b9 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractLogoutValveBase.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AbstractLogoutValveBase.java @@ -3,7 +3,7 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.connector.Request; import org.apache.catalina.connector.Response; diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AssertionCasRealm.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AssertionCasRealm.java new file mode 100644 index 0000000..02b3cfe --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/AssertionCasRealm.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import org.jasig.cas.client.tomcat.AssertionCasRealmDelegate; +import org.jasig.cas.client.tomcat.CasRealm; + +/** + * Tomcat Realm that implements {@link CasRealm} for principal and + * role data backed by the CAS {@link Assertion}. + *

+ * Authentication always succeeds and simply returns the given principal. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class AssertionCasRealm extends AbstractCasRealm { + private final AssertionCasRealmDelegate delegate = new AssertionCasRealmDelegate(); + + /** + * @param name Name of the attribute in the principal that contains role data. + */ + public void setRoleAttributeName(final String name) { + delegate.setRoleAttributeName(name); + } + + /** {@inheritDoc} */ + protected CasRealm getDelegate() { + return delegate; + } +} diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas10CasAuthenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas10CasAuthenticator.java similarity index 74% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas10CasAuthenticator.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas10CasAuthenticator.java index d5503df..a162bd5 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas10CasAuthenticator.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas10CasAuthenticator.java @@ -3,30 +3,34 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.jasig.cas.client.validation.Cas10TicketValidator; import org.jasig.cas.client.validation.TicketValidator; /** - * Authenticator that handles CAS 1.0 responses. + * Authenticator that handles CAS 1.0 protocol. * * @author Scott Battaglia * @version $Revision$ $Date$ * @since 3.1.12 */ public class Cas10CasAuthenticator extends AbstractCasAuthenticator { + public static final String AUTH_METHOD = "CAS10"; - private TicketValidator ticketValidator; + private Cas10TicketValidator ticketValidator; protected TicketValidator getTicketValidator() { return this.ticketValidator; } + + protected String getAuthenticationMethod() { + return AUTH_METHOD; + } protected void startInternal() throws LifecycleException { super.startInternal(); - this.ticketValidator = new Cas10TicketValidator(getCasServerUrlPrefix()); } } diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20CasAuthenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20CasAuthenticator.java similarity index 83% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20CasAuthenticator.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20CasAuthenticator.java index aa6e094..051fb32 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20CasAuthenticator.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20CasAuthenticator.java @@ -3,26 +3,31 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.jasig.cas.client.validation.Cas20ServiceTicketValidator; import org.jasig.cas.client.validation.TicketValidator; /** - * Authenticator that handles the CAS 2 protocol. + * Authenticator that handles the CAS 2.0 protocol. * * @author Scott Battaglia * @version $Revision$ $Date$ * @since 3.1.12 */ public final class Cas20CasAuthenticator extends AbstractCasAuthenticator { - + public static final String AUTH_METHOD = "CAS20"; + private Cas20ServiceTicketValidator ticketValidator; protected TicketValidator getTicketValidator() { return this.ticketValidator; } + + protected String getAuthenticationMethod() { + return AUTH_METHOD; + } protected void startInternal() throws LifecycleException { super.startInternal(); diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20ProxyCasAuthenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20ProxyCasAuthenticator.java similarity index 86% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20ProxyCasAuthenticator.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20ProxyCasAuthenticator.java index da21299..7b8b618 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/Cas20ProxyCasAuthenticator.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Cas20ProxyCasAuthenticator.java @@ -3,7 +3,7 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.jasig.cas.client.util.CommonUtils; @@ -11,14 +11,17 @@ import org.jasig.cas.client.validation.Cas20ProxyTicketValidator; import org.jasig.cas.client.validation.TicketValidator; /** + * Authenticator that handles the CAS 2.0 protocol with proxying support. + * * @author Scott Battaglia * @version $Revision$ $Date$ * @since 3.1.12 */ public final class Cas20ProxyCasAuthenticator extends AbstractCasAuthenticator { + public static final String AUTH_METHOD = "CAS20-PROXY"; private Cas20ProxyTicketValidator ticketValidator; - + private boolean acceptAnyProxy; private String allowedProxyChains; @@ -34,10 +37,13 @@ public final class Cas20ProxyCasAuthenticator extends AbstractCasAuthenticator { protected TicketValidator getTicketValidator() { return this.ticketValidator; } + + protected String getAuthenticationMethod() { + return AUTH_METHOD; + } protected void startInternal() throws LifecycleException { super.startInternal(); - this.ticketValidator = new Cas20ProxyTicketValidator(getCasServerUrlPrefix()); this.ticketValidator.setRenew(isRenew()); this.ticketValidator.setProxyCallbackUrl(getProxyCallbackUrl()); diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/PropertiesCasRealm.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/PropertiesCasRealm.java new file mode 100644 index 0000000..289fee7 --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/PropertiesCasRealm.java @@ -0,0 +1,48 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import org.apache.catalina.LifecycleException; +import org.jasig.cas.client.tomcat.CasRealm; +import org.jasig.cas.client.tomcat.PropertiesCasRealmDelegate; + +/** + * Tomcat Realm that implements {@link CasRealm} backed by properties file + * containing usernames/and roles of the following format: + *

+ * username1=role1,role2,role3
+ * username2=role1
+ * username3=role2,role3
+ * 
+ * User authentication succeeds if the name of the given principal exists as + * a username in the properties file. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class PropertiesCasRealm extends AbstractCasRealm { + private final PropertiesCasRealmDelegate delegate = new PropertiesCasRealmDelegate(); + + /** + * @param path Path to properties file container username/role data. + */ + public void setPropertiesFilePath(final String path) { + delegate.setPropertiesFilePath(path); + } + + /** {@inheritDoc} */ + protected void startInternal() throws LifecycleException { + super.startInternal(); + delegate.readProperties(); + } + + /** {@inheritDoc} */ + protected CasRealm getDelegate() { + return delegate; + } + +} diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/ProxyCallbackValve.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/ProxyCallbackValve.java similarity index 98% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/ProxyCallbackValve.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/ProxyCallbackValve.java index 5ed5035..378bc2a 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/ProxyCallbackValve.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/ProxyCallbackValve.java @@ -3,7 +3,7 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Request; diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/RegExpBasedLogoutValue.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/RegExpBasedLogoutValue.java similarity index 97% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/RegExpBasedLogoutValue.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/RegExpBasedLogoutValue.java index 6f2f75b..06c15b9 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/RegExpBasedLogoutValue.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/RegExpBasedLogoutValue.java @@ -3,7 +3,7 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Request; diff --git a/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Saml11Authenticator.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Saml11Authenticator.java new file mode 100644 index 0000000..d4b84c6 --- /dev/null +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/Saml11Authenticator.java @@ -0,0 +1,65 @@ +/* + * Copyright 2010 The JA-SIG Collaborative. All rights reserved. See license + * distributed with this file and available online at + * http://www.ja-sig.org/products/cas/overview/license/index.html + */ +package org.jasig.cas.client.tomcat.v7; + +import org.apache.catalina.LifecycleException; +import org.jasig.cas.client.validation.Saml11TicketValidator; +import org.jasig.cas.client.validation.TicketValidator; + +/** + * CAS authenticator that uses the SAML 1.1 protocol. + * + * @author Marvin S. Addison + * @version $Revision$ + * + */ +public class Saml11Authenticator extends AbstractAuthenticator { + public static final String AUTH_METHOD = "SAML11"; + + private Saml11TicketValidator ticketValidator; + + /** SAML protocol clock drift tolerance in ms */ + private int tolerance = -1; + + + /** + * @param ms SAML clock drift tolerance in milliseconds. + */ + public void setTolerance(final int ms) { + this.tolerance = ms; + } + + protected void startInternal() throws LifecycleException { + super.startInternal(); + this.ticketValidator = new Saml11TicketValidator(getCasServerUrlPrefix()); + if (this.tolerance > -1) { + this.ticketValidator.setTolerance(this.tolerance); + } + if (getEncoding() != null) { + this.ticketValidator.setEncoding(getEncoding()); + } + this.ticketValidator.setRenew(isRenew()); + } + + protected TicketValidator getTicketValidator() { + return this.ticketValidator; + } + + protected String getAuthenticationMethod() { + return AUTH_METHOD; + } + + /** {@inheritDoc} */ + protected String getArtifactParameterName() { + return "SAMLart"; + } + + /** {@inheritDoc} */ + protected String getServiceParameterName() { + return "TARGET"; + } + +} diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/UrlBasedLogoutValve.java b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/UrlBasedLogoutValve.java similarity index 97% rename from cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/UrlBasedLogoutValve.java rename to cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/UrlBasedLogoutValve.java index 737198d..42af52e 100644 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/UrlBasedLogoutValve.java +++ b/cas-client-integration-tomcat-v7/src/main/java/org/jasig/cas/client/tomcat/v7/UrlBasedLogoutValve.java @@ -3,7 +3,7 @@ * distributed with this file and available online at * http://www.ja-sig.org/products/cas/overview/license/index.html */ -package org.jasig.cas.client.tomcat; +package org.jasig.cas.client.tomcat.v7; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Request; diff --git a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractAuthenticator.java b/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractAuthenticator.java deleted file mode 100644 index 3cf0702..0000000 --- a/cas-client-integration-tomcat/src/main/java/org/jasig/cas/client/tomcat/AbstractAuthenticator.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license - * distributed with this file and available online at - * http://www.ja-sig.org/products/cas/overview/license/index.html - */ -package org.jasig.cas.client.tomcat; - -import org.apache.catalina.LifecycleException; -import org.apache.catalina.authenticator.AuthenticatorBase; -import org.apache.catalina.authenticator.Constants; -import org.apache.catalina.connector.Request; -import org.apache.catalina.deploy.LoginConfig; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.jasig.cas.client.util.AbstractCasFilter; -import org.jasig.cas.client.util.CommonUtils; -import org.jasig.cas.client.validation.Assertion; -import org.jasig.cas.client.validation.TicketValidationException; -import org.jasig.cas.client.validation.TicketValidator; - -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.security.Principal; - -/** - * @author Scott Battaglia - * @version $Revision$ $Date$ - * @since 3.1.12 - */ -public abstract class AbstractAuthenticator extends AuthenticatorBase { - - private static final String INFO = "org.jasig.cas.client.tomcat.AbstractAuthenticator/1.0"; - - private static final Log log = LogFactory.getLog(AbstractAuthenticator.class); - - private String casServerLoginUrl; - - private String casServerUrlPrefix; - - private boolean encode; - - private boolean renew; - - protected abstract String getArtifactParameterName(); - - protected abstract String getServiceParameterName(); - - protected abstract TicketValidator getTicketValidator(); - - private String serverName; - - private String serviceUrl; - - protected final String getCasServerUrlPrefix() { - return this.casServerUrlPrefix; - } - - protected void startInternal() throws LifecycleException { - super.startInternal(); - try { - CommonUtils.assertNotNull(this.casServerUrlPrefix, "casServerUrlPrefix cannot be null."); - CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null."); - CommonUtils.assertTrue(this.serverName != null || this.serviceUrl != null, "either serverName or serviceUrl must be set."); - - } catch (final Exception e) { - throw new LifecycleException(e); - } - } - - public final void setCasServerUrlPrefix(final String casServerUrlPrefix) { - this.casServerUrlPrefix = casServerUrlPrefix; - } - - public final void setCasServerLoginUrl(final String casServerLoginUrl) { - this.casServerLoginUrl = casServerLoginUrl; - } - - public final boolean isEncode() { - return this.encode; - } - - public final void setEncode(final boolean encode) { - this.encode = encode; - } - - protected final boolean isRenew() { - return this.renew; - } - - public void setRenew(final boolean renew) { - this.renew = renew; - } - - - public final void setServerName(final String serverName) { - this.serverName = serverName; - } - - public final void setServiceUrl(final String serviceUrl) { - this.serviceUrl = serviceUrl; - } - - public final String getInfo() { - return INFO; - } - - public final boolean authenticate(final Request request, final HttpServletResponse response, final LoginConfig loginConfig) throws IOException { - final Principal principal = request.getUserPrincipal(); - final String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE); - - if (principal != null && ssoId != null) { - associate(ssoId, request.getSessionInternal(true)); - return true; - } - - if (ssoId != null && reauthenticateFromSSO(ssoId, request)) { - return true; - } - - - final String token = request.getParameter(getArtifactParameterName()); - final String service = CommonUtils.constructServiceUrl(request, response, this.serviceUrl, this.serverName, getArtifactParameterName(), true); - - if (CommonUtils.isBlank(token)) { - final String redirectUrl = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), service, false, false); - response.sendRedirect(redirectUrl); - return false; - } - - try { - final Assertion newAssertion = getTicketValidator().validate(token, service); - request.getSession(true).setAttribute(AbstractCasFilter.CONST_CAS_ASSERTION, newAssertion); - final Principal p = context.getRealm().authenticate(newAssertion.getPrincipal().getName(), null); - - if (p != null) { - register(request, response, p, Constants.SINGLE_SIGN_ON_COOKIE, p.getName(), null); - return true; - } - } catch (final TicketValidationException e) { - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage()); - return false; - } - - response.sendError(HttpServletResponse.SC_UNAUTHORIZED); - return false; - } -} diff --git a/pom.xml b/pom.xml index cfa095a..e16d992 100644 --- a/pom.xml +++ b/pom.xml @@ -139,7 +139,8 @@ cas-client-integration-jboss cas-client-support-distributed-ehcache cas-client-support-distributed-memcached - cas-client-integration-tomcat + cas-client-integration-tomcat-common + cas-client-integration-tomcat-v7