commit before we move to the new repository.  We cannot release this yet because we're missing a contributor license agreement.
This commit is contained in:
Scott Battaglia 2010-06-01 22:56:09 +00:00
parent b9208f9077
commit 23711eb6a1
18 changed files with 1191 additions and 154 deletions

View File

@ -2,7 +2,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.jasig.cas</groupId>
<version>3.1.10</version>
<version>3.1.11-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -10,50 +10,6 @@
<artifactId>cas-client-core</artifactId>
<packaging>jar</packaging>
<name>JA-SIG CAS Client for Java - Core</name>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>false</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.4</source>
<target>1.4</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests*</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clover-plugin</artifactId>
<configuration>
<licenseLocation>${basedir}/src/test/clover/clover.license</licenseLocation>
</configuration>
<executions>
<execution>
<phase>pre-site</phase>
<goals>
<goal>instrument</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>

View File

@ -15,15 +15,12 @@ import java.util.Map;
* @version $Revision$ $Date$
* @since 3.1
*/
public class AttributePrincipalImpl implements AttributePrincipal {
public class AttributePrincipalImpl extends SimplePrincipal implements AttributePrincipal {
private static final Log LOG = LogFactory.getLog(AttributePrincipalImpl.class);
private static final Log LOG = LogFactory.getLog(AttributePrincipalImpl.class);
/** Unique Id for Serialization */
private static final long serialVersionUID = -8810123156070148535L;
/** The unique identifier for this principal. */
private final String name;
private static final long serialVersionUID = -1443182634624927187L;
/** Map of key/value pairs about this principal. */
private final Map attributes;
@ -73,12 +70,11 @@ public class AttributePrincipalImpl implements AttributePrincipal {
* @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;
super(name);
this.attributes = attributes;
this.proxyGrantingTicket = proxyGrantingTicket;
this.proxyRetriever = proxyRetriever;
CommonUtils.assertNotNull(this.name, "name cannot be null.");
CommonUtils.assertNotNull(this.attributes, "attributes cannot be null.");
}
@ -94,8 +90,4 @@ public class AttributePrincipalImpl implements AttributePrincipal {
LOG.debug("No ProxyGrantingTicket was supplied, so no Proxy Ticket can be retrieved.");
return null;
}
public String getName() {
return this.name;
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.authentication;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Simple security group implementation
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public final class SimpleGroup extends SimplePrincipal implements Group {
private static final long serialVersionUID = 1541943977571896383L;
private final Set members = new HashSet();
/**
* Creates a new group with the given name.
* @param name Group name.
*/
public SimpleGroup(final String name) {
super(name);
}
public boolean addMember(final Principal user) {
return this.members.add(user);
}
public boolean isMember(final Principal member) {
return this.members.contains(member);
}
public Enumeration members() {
return new EnumerationAdapter(this.members.iterator());
}
public boolean removeMember(final Principal user) {
return this.members.remove(user);
}
public String toString() {
return super.toString() + ": " + members.toString();
}
/**
* Adapts a {@link java.util.Iterator} onto an {@link java.util.Enumeration}.
*/
private static class EnumerationAdapter implements Enumeration {
/** Iterator backing enumeration operations */
private Iterator iterator;
/**
* Creates a new instance backed by the given iterator.
* @param i Iterator backing enumeration operations.
*/
public EnumerationAdapter(final Iterator i) {
this.iterator = i;
}
public boolean hasMoreElements() {
return this.iterator.hasNext();
}
public Object nextElement() {
return this.iterator.next();
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.authentication;
import java.io.Serializable;
import java.security.Principal;
import org.jasig.cas.client.util.CommonUtils;
/**
* Simple security principal implementation.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class SimplePrincipal implements Principal, Serializable {
/** SimplePrincipal.java */
private static final long serialVersionUID = -5645357206342793145L;
/** The unique identifier for this principal. */
private final String name;
/**
* Creates a new principal with the given name.
* @param name Principal name.
*/
public SimplePrincipal(final String name) {
this.name = name;
CommonUtils.assertNotNull(this.name, "name cannot be null.");
}
public final String getName() {
return this.name;
}
public String toString() {
return getName();
}
public boolean equals(final Object o) {
if (o == null) {
return false;
} else if (!(o instanceof SimplePrincipal)) {
return false;
} else {
return getName().equals(((SimplePrincipal)o).getName());
}
}
public int hashCode() {
return 37 * getName().hashCode();
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.jaas;
import java.io.Serializable;
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.validation.Assertion;
/**
* Principal implementation that contains the CAS ticket validation assertion.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class AssertionPrincipal extends SimplePrincipal implements Serializable {
/** AssertionPrincipal.java */
private static final long serialVersionUID = 2288520214366461693L;
private Assertion assertion;
/**
* Creates a new principal containing the CAS assertion.
*
* @param name Principal name.
* @param assertion CAS assertion.
*/
public AssertionPrincipal(final String name, final Assertion assertion) {
super(name);
this.assertion = assertion;
}
/**
* @return CAS ticket validation assertion.
*/
public Assertion getAssertion() {
return this.assertion;
}
}

View File

@ -0,0 +1,365 @@
/*
* 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.jaas;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.security.acl.Group;
import java.util.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jasig.cas.client.authentication.SimpleGroup;
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidator;
/**
* JAAS login module that delegates to a CAS {@link TicketValidator} component
* for authentication, and on success populates a {@link Subject} with principal
* data including NetID and principal attributes. The module expects to be provided
* with the CAS ticket (required) and service (optional) parameters via
* {@link PasswordCallback} and {@link NameCallback}, respectively, by the
* {@link CallbackHandler} that is part of the JAAS framework in which the servlet
* resides.
*
* <p>
* Module configuration options:
* <ul>
* <li>ticketValidatorClass - Fully-qualified class name of CAS ticket validator class.</li>
* <li>casServerUrlPrefix - URL to root of CAS Web application context.</li>
* <li>service (optional) - CAS service parameter that may be overridden by callback handler.
* NOTE: service must be specified by at least one component such that it is available at
* service ticket validation time</li>
* <li>defaultRoles (optional) - Comma-delimited list of static roles applied to all
* authenticated principals.</li>
* <li>roleAttributeNames (optional) - Comma-delimited list of attribute names that describe
* role data delivered to CAS in the service-ticket validation response that should be
* applied to the current authenticated principal.</li>
* <li>principalGroupName (optional) - The name of a group principal containing the
* primary principal name of the current JAAS subject. The default value is "CallerPrincipal",
* which is suitable for JBoss.</li>
* <li>roleGroupName (optional) - The name of a group principal containing all role data.
* The default value is "Roles", which is suitable for JBoss.</li>
* </ul>
*
* <p>
* Module options not explicitly listed above are treated as attributes of the
* given ticket validator class, e.g. <code>tolerance</code> in the following example.
*
* <p>
* Sample jaas.config file entry for this module:
* <pre>
* cas {
* org.jasig.cas.client.jaas.CasLoginModule required
* ticketValidatorClass="org.jasig.cas.client.validation.Saml11TicketValidator"
* casServerUrlPrefix="https://cas.example.com/cas"
* tolerance="20000"
* service="https://webapp.example.com/webapp"
* defaultRoles="admin,operator"
* roleAttributeNames="memberOf,eduPersonAffiliation"
* principalGroupName="CallerPrincipal"
* roleGroupName="Roles";
* }
* </pre>
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class CasLoginModule implements LoginModule {
/** Constant for login name stored in shared state. */
public static final String LOGIN_NAME = "javax.security.auth.login.name";
/**
* Default group name for storing caller principal.
* The default value supports JBoss, but is configurable to hopefully
* support other JEE containers.
*/
public static final String DEFAULT_PRINCIPAL_GROUP_NAME = "CallerPrincipal";
/**
* Default group name for storing role membership data.
* The default value supports JBoss, but is configurable to hopefully
* support other JEE containers.
*/
public static final String DEFAULT_ROLE_GROUP_NAME = "Roles";
/** Logger instance */
protected final Log log = LogFactory.getLog(getClass());
/** JAAS authentication subject */
protected Subject subject;
/** JAAS callback handler */
protected CallbackHandler callbackHandler;
/** CAS ticket validator */
protected TicketValidator ticketValidator;
/** CAS service parameter used if no service is provided via TextCallback on login */
protected String service;
/** CAS assertion */
protected Assertion assertion;
/** Login module shared state */
protected Map sharedState;
/** Roles to be added to all authenticated principals by default */
protected String[] defaultRoles;
/** Names of attributes in the CAS assertion that should be used for role data */
protected Set roleAttributeNames = new HashSet();
/** Name of JAAS Group containing caller principal */
protected String principalGroupName = DEFAULT_PRINCIPAL_GROUP_NAME;
/** Name of JAAS Group containing role data */
protected String roleGroupName = DEFAULT_ROLE_GROUP_NAME;
/**
* Initializes the CAS login module.
* @param subject Authentication subject.
* @param handler Callback handler.
* @param state Shared state map.
* @param options Login module options. The following are supported:
* <ul>
* <li>service - CAS service URL used for service ticket validation</li>
* <li>ticketValidatorClass - fully-qualified class name of service ticket validator component</li>
* <li>defaultRoles (optional) - comma-delimited list of roles to be added to all authenticated principals</li>
* <li>roleAttributeNames (optional) - comma-delimited list of attributes in the CAS assertion that contain role data</li>
* <li>principalGroupName (optional) - name of JAAS Group containing caller principal</li>
* <li>roleGroupName (optional) - name of JAAS Group containing role data</li>
* </ul>
*/
public void initialize(final Subject subject, final CallbackHandler handler, final Map state, final Map options) {
this.callbackHandler = handler;
this.subject = subject;
this.sharedState = state;
String ticketValidatorClass = null;
final Iterator iter = options.keySet().iterator();
while (iter.hasNext()) {
final Object key = iter.next();
log.trace("Processing option " + key);
if ("service".equals(key)) {
this.service = (String) options.get(key);
log.debug("Set service=" + this.service);
} else if ("ticketValidatorClass".equals(key)) {
ticketValidatorClass = (String) options.get(key);
log.debug("Set ticketValidatorClass=" + ticketValidatorClass);
} else if ("defaultRoles".equals(key)) {
final String roles = (String) options.get(key);
log.trace("Got defaultRoles value " + roles);
this.defaultRoles = roles.split(",\\s*");
log.debug("Set defaultRoles=" + this.defaultRoles);
} else if ("roleAttributeNames".equals(key)) {
final String attrNames = (String) options.get(key);
log.trace("Got roleAttributeNames value " + attrNames);
final String[] attributes = attrNames.split(",\\s*");
this.roleAttributeNames.addAll(Arrays.asList(attributes));
log.debug("Set roleAttributeNames=" + this.roleAttributeNames);
} else if ("principalGroupName".equals(key)) {
this.principalGroupName = (String) options.get(key);
log.debug("Set principalGroupName=" + this.principalGroupName);
} else if ("roleGroupName".equals(key)) {
this.roleGroupName = (String) options.get(key);
log.debug("Set roleGroupName=" + this.principalGroupName);
}
}
CommonUtils.assertNotNull(ticketValidatorClass, "ticketValidatorClass is required.");
this.ticketValidator = createTicketValidator(ticketValidatorClass, options);
}
public boolean login() throws LoginException {
log.debug("Performing login.");
final NameCallback serviceCallback = new NameCallback("service");
final PasswordCallback ticketCallback = new PasswordCallback("ticket", false);
try {
this.callbackHandler.handle(new Callback[] { ticketCallback, serviceCallback });
} catch (final IOException e) {
log.info("Login failed due to IO exception in callback handler: " + e);
throw new LoginException("IO exception in callback handler: " + e);
} catch (final UnsupportedCallbackException e) {
log.info("Login failed due to unsupported callback: " + e);
throw new LoginException("Callback handler does not support PasswordCallback and TextInputCallback.");
}
if (ticketCallback.getPassword() != null) {
final String ticket = new String(ticketCallback.getPassword());
final String service = CommonUtils.isNotBlank(serviceCallback.getName()) ? serviceCallback.getName() : this.service;
if (CommonUtils.isBlank(service)) {
log.info("Login failed because required CAS service parameter not provided.");
throw new LoginException("Neither login module nor callback handler provided required service parameter.");
}
try {
log.debug("Attempting ticket validation with service=" + service + " and ticket=" + ticket);
this.assertion = this.ticketValidator.validate(ticket, service);
} catch (final Exception e) {
log.info("Login failed due to CAS ticket validation failure: " + e);
throw new LoginException("CAS ticket validation failed: " + e);
}
} else {
log.info("Login failed because callback handler did not provide CAS ticket.");
throw new LoginException("Callback handler did not provide CAS ticket.");
}
log.info("Login succeeded.");
return true;
}
public boolean abort() throws LoginException {
if (this.assertion != null) {
this.assertion = null;
return true;
}
return false;
}
public boolean commit() throws LoginException {
if (this.assertion != null) {
final AssertionPrincipal casPrincipal = new AssertionPrincipal(this.assertion.getPrincipal().getName(), this.assertion);
this.subject.getPrincipals().add(casPrincipal);
// Add group containing principal as sole member
// Supports JBoss JAAS use case
final Group principalGroup = new SimpleGroup(this.principalGroupName);
principalGroup.addMember(casPrincipal);
this.subject.getPrincipals().add(principalGroup);
// Add group principal containing role data
final Group roleGroup = new SimpleGroup(this.roleGroupName);
for (int i = 0; i < defaultRoles.length; i++) {
roleGroup.addMember(new SimplePrincipal(defaultRoles[i]));
}
final Map attributes = this.assertion.getPrincipal().getAttributes();
final Iterator nameIterator = attributes.keySet().iterator();
while (nameIterator.hasNext()) {
final Object key = nameIterator.next();
if (this.roleAttributeNames.contains(key)) {
// Attribute value is Object if singular or Collection if plural
final Object value = attributes.get(key);
if (value instanceof Collection) {
final Iterator valueIterator = ((Collection) value).iterator();
while (valueIterator.hasNext()) {
roleGroup.addMember(new SimplePrincipal(valueIterator.next().toString()));
}
} else {
roleGroup.addMember(new SimplePrincipal(value.toString()));
}
}
}
this.subject.getPrincipals().add(roleGroup);
// Place principal name in shared state for downstream JAAS modules (module chaining use case)
this.sharedState.put(LOGIN_NAME, casPrincipal.getName());
if (log.isDebugEnabled()) {
log.debug("Created JAAS subject with principals: " + subject.getPrincipals());
}
return true;
}
return false;
}
public boolean logout() throws LoginException {
if (this.assertion != null) {
log.debug("Performing logout.");
this.subject.getPrincipals().remove(this.assertion.getPrincipal());
// Remove all SimpleGroup principals
final Iterator iter = this.subject.getPrincipals().iterator();
while (iter.hasNext()) {
if (iter.next() instanceof SimpleGroup) {
iter.remove();
}
}
this.assertion = null;
log.info("Logout succeeded.");
return true;
}
return false;
}
/**
* Creates a {@link TicketValidator} instance from a class name and map of property name/value pairs.
* @param className Fully-qualified name of {@link TicketValidator} concrete class.
* @param propertyMap Map of property name/value pairs to set on validator instance.
* @return Ticket validator with properties set.
*/
private TicketValidator createTicketValidator(final String className, final Map propertyMap) {
CommonUtils.assertTrue(propertyMap.containsKey("casServerUrlPrefix"), "Required property casServerUrlPrefix not found.");
final Class validatorClass = ReflectUtils.loadClass(className);
final TicketValidator validator = (TicketValidator) ReflectUtils.newInstance(validatorClass, new Object[] {propertyMap.get("casServerUrlPrefix")});
try {
final BeanInfo info = Introspector.getBeanInfo(validatorClass);
final Iterator iter = propertyMap.keySet().iterator();
while (iter.hasNext()) {
final String property = (String) iter.next();
if (!"casServerUrlPrefix".equals(property)) {
log.debug("Attempting to set TicketValidator property " + property);
final String value = (String) propertyMap.get(property);
final PropertyDescriptor pd = ReflectUtils.getPropertyDescriptor(info, property);
if (pd != null) {
ReflectUtils.setProperty(property, convertIfNecessary(pd, value), validator, info);
log.debug("Set " + property + "=" + value);
} else {
log.warn("Cannot find property " + property + " on " + className);
}
}
}
} catch (final IntrospectionException e) {
throw new RuntimeException("Error getting bean info for " + validatorClass);
}
return validator;
}
/**
* Attempts to do simple type conversion from a string value to the type expected
* by the given property.
*
* Currently only conversion to int, long, and boolean are supported.
*
* @param pd Property descriptor of target property to set.
* @param value Property value as a string.
* @return Value converted to type expected by property if a conversion strategy exists.
*/
private static Object convertIfNecessary(final PropertyDescriptor pd, final String value) {
if (String.class.equals(pd.getPropertyType())) {
return value;
} else if (boolean.class.equals(pd.getPropertyType())) {
return Boolean.valueOf(value);
} else if (int.class.equals(pd.getPropertyType())) {
return new Integer(value);
} else if (long.class.equals(pd.getPropertyType())) {
return new Long(value);
} else {
throw new IllegalArgumentException(
"No conversion strategy exists for property " + pd.getName()
+ " of type " + pd.getPropertyType());
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.jaas;
import java.io.IOException;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
/**
* Callback handler that provides the CAS service and ticket to a
* {@link NameCallback} and {@link PasswordCallback} respectively,
* which meets the requirements of the {@link CasLoginModule} JAAS module.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class ServiceAndTicketCallbackHandler implements CallbackHandler {
/** CAS service URL */
private final String service;
/** CAS service ticket */
private final String ticket;
/**
* Creates a new instance with the given service and ticket.
*
* @param service CAS service URL.
* @param ticket CAS service ticket.
*/
public ServiceAndTicketCallbackHandler(final String service, final String ticket) {
this.service = service;
this.ticket = ticket;
}
public void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
((NameCallback) callbacks[i]).setName(this.service);
} else if (callbacks[i] instanceof PasswordCallback) {
((PasswordCallback) callbacks[i]).setPassword(this.ticket.toCharArray());
} else {
throw new UnsupportedCallbackException(callbacks[i], "Callback not supported.");
}
}
}
}

View File

@ -0,0 +1,141 @@
/*
* 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.util;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
/**
* Helper class with reflection utility methods.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public final class ReflectUtils {
private ReflectUtils() {
// private constructor to prevent instanciation.
}
/**
* Attempts to create a class from a String.
* @param className the name of the class to create.
* @return the class. CANNOT be NULL.
* @throws IllegalArgumentException if the className does not exist.
*/
public static Class loadClass(final String className) throws IllegalArgumentException {
try {
return Class.forName(className);
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(className + " class not found.");
}
}
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
* @param className Name of class to be created.
* @param args Constructor arguments.
* @return New instance of given class.
*/
public static Object newInstance(final String className, final Object[] args) {
try {
return newInstance(Class.forName(className), args);
} catch (final ClassNotFoundException e) {
throw new IllegalArgumentException(className + " not found");
}
}
/**
* Creates a new instance of the given class by passing the given arguments
* to the constructor.
* @param clazz Class of instance to be created.
* @param args Constructor arguments.
* @return New instance of given class.
*/
public static Object newInstance(final Class clazz, final Object[] args) {
final Class[] argClasses = new Class[args.length];
for (int i = 0; i < args.length; i++) {
argClasses[i] = args[i].getClass();
}
try {
return clazz.getConstructor(argClasses).newInstance(args);
} catch (final Exception e) {
throw new IllegalArgumentException("Error creating new instance of " + clazz, e);
}
}
/**
* Gets the property descriptor for the named property on the given class.
* @param clazz Class to which property belongs.
* @param propertyName Name of property.
* @return Property descriptor for given property or null if no property with given
* name exists in given class.
*/
public static PropertyDescriptor getPropertyDescriptor(final Class clazz, final String propertyName) {
try {
return getPropertyDescriptor(Introspector.getBeanInfo(clazz), propertyName);
} catch (final IntrospectionException e) {
throw new RuntimeException("Failed getting bean info for " + clazz, e);
}
}
/**
* Gets the property descriptor for the named property from the bean info describing
* a particular class to which property belongs.
* @param info Bean info describing class to which property belongs.
* @param propertyName Name of property.
* @return Property descriptor for given property or null if no property with given
* name exists.
*/
public static PropertyDescriptor getPropertyDescriptor(final BeanInfo info, final String propertyName) {
for (int i = 0; i < info.getPropertyDescriptors().length; i++) {
final PropertyDescriptor pd = info.getPropertyDescriptors()[i];
if (pd.getName().equals(propertyName)) {
return pd;
}
}
return null;
}
/**
* Sets the given property on the target javabean using bean instrospection.
* @param propertyName Property to set.
* @param value Property value to set.
* @param target Target java bean on which to set property.
*/
public static void setProperty(final String propertyName, final Object value, final Object target) {
try {
setProperty(propertyName, value, target, Introspector.getBeanInfo(target.getClass()));
} catch (final IntrospectionException e) {
throw new RuntimeException("Failed getting bean info on target javabean " + target, e);
}
}
/**
* Sets the given property on the target javabean using bean instrospection.
* @param propertyName Property to set.
* @param value Property value to set.
* @param target Target javabean on which to set property.
* @param info BeanInfo describing the target javabean.
*/
public static void setProperty(final String propertyName, final Object value, final Object target, final BeanInfo info) {
try {
final PropertyDescriptor pd = getPropertyDescriptor(info, propertyName);
pd.getWriteMethod().invoke(target, new Object[] { value });
} catch (final InvocationTargetException e) {
throw new RuntimeException("Error setting property " + propertyName, e.getCause());
} catch (final Exception e) {
throw new RuntimeException("Error setting property " + propertyName, e);
}
}
}

View File

@ -1,6 +1,5 @@
package org.jasig.cas.client;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
@ -16,35 +15,28 @@ public final class PublicTestHttpServer extends Thread {
public byte[] content;
private byte[] header;
private final byte[] header;
private int port = 80;
private final int port;
public String encoding;
public final String encoding;
private PublicTestHttpServer(String data, String encoding, String MIMEType,
int port) throws UnsupportedEncodingException {
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 {
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";
String header = "HTTP/1.0 200 OK\r\n" + "Server: OneFile 1.0\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);
httpServer = new PublicTestHttpServer("test", "ASCII", "text/plain", 8085);
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -59,19 +51,16 @@ public final class PublicTestHttpServer extends Thread {
try {
ServerSocket server = new ServerSocket(this.port);
System.out.println("Accepting connections on port "
+ server.getLocalPort());
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());
final OutputStream out = new BufferedOutputStream(connection.getOutputStream());
final InputStream in = new BufferedInputStream(connection.getInputStream());
// read the first line only; that's all we need
StringBuffer request = new StringBuffer(80);
final StringBuffer request = new StringBuffer(80);
while (true) {
int c = in.read();
if (c == '\r' || c == '\n' || c == -1)
@ -89,7 +78,7 @@ public final class PublicTestHttpServer extends Thread {
out.write(this.content);
out.flush();
} // end try
catch (IOException e) {
catch (final IOException e) {
// nothing to do with this IOException
} finally {
if (connection != null)
@ -98,7 +87,7 @@ public final class PublicTestHttpServer extends Thread {
} // end while
} // end try
catch (IOException e) {
catch (final IOException e) {
System.err.println("Could not start server. Port Occupied");
}

View File

@ -0,0 +1,75 @@
/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import junit.framework.Assert;
import junit.framework.TestCase;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.authentication.SimpleGroup;
import org.jasig.cas.client.authentication.SimplePrincipal;
import org.jasig.cas.client.jaas.AssertionPrincipal;
import org.jasig.cas.client.validation.AssertionImpl;
/**
* Confirms serialization support for classes intended for session storage or
* other potential serialization use cases.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class SerializationTests extends TestCase {
public void testSerializeDeserialize() throws Exception {
final Object[] subjects = getTestSubjects();
for (int i = 0; i < subjects.length; i++) {
final ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
final ObjectOutputStream out = new ObjectOutputStream(byteOut);
try {
out.writeObject(subjects[i]);
} catch (Exception e) {
Assert.fail("Serialization failed for " + subjects[i]);
} finally {
out.close();
}
final ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());
final ObjectInputStream in = new ObjectInputStream(byteIn);
try {
Assert.assertEquals(subjects[i], in.readObject());
} catch (Exception e) {
Assert.fail("Deserialization failed for " + subjects[i]);
} finally {
in.close();
}
}
}
private Object[] getTestSubjects() {
final SimplePrincipal simplePrincipal = new SimplePrincipal("simple");
final SimpleGroup simpleGroup = new SimpleGroup("group");
final AttributePrincipalImpl attributePrincipal =
new AttributePrincipalImpl("attr", Collections.singletonMap("LOA", "3"));
final AssertionPrincipal assertionPrincipal = new AssertionPrincipal(
"assertion",
new AssertionImpl(attributePrincipal, Collections.singletonMap("authenticationMethod", "username")));
return new Object[] {
simplePrincipal,
simpleGroup,
attributePrincipal,
assertionPrincipal,
};
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.jaas;
import java.security.Principal;
import java.security.acl.Group;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import org.jasig.cas.client.PublicTestHttpServer;
import junit.framework.TestCase;
/**
* Unit test for {@link CasLoginModule} class.
*
* @author Marvin S. Addison
* @version $Revision$
*
*/
public class CasLoginModuleTests extends TestCase {
private static final String CONST_CAS_SERVER_URL = "http://localhost:8085/";
private CasLoginModule module;
private Subject subject;
/** {@inheritDoc} */
protected void setUp() throws Exception {
super.setUp();
module = new CasLoginModule();
subject = new Subject();
final Map options = new HashMap();
options.put("service", "https://service.example.com/webapp");
options.put("ticketValidatorClass", "org.jasig.cas.client.validation.Cas20ServiceTicketValidator");
options.put("casServerUrlPrefix", CONST_CAS_SERVER_URL);
options.put("proxyCallbackUrl", "https://service.example.com/webapp/proxy");
options.put("renew", "true");
options.put("defaultRoles", "ADMIN");
options.put("principalGroupName", "CallerPrincipal");
options.put("roleGroupName", "Roles");
module.initialize(
subject,
new ServiceAndTicketCallbackHandler("myService", "myTicket"),
new HashMap(),
options);
}
/**
* Test JAAS login success.
*/
public void testLoginSuccess() throws Exception {
final String USERNAME = "username";
final String RESPONSE = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>"
+ "<cas:authenticationSuccess><cas:user>"
+ USERNAME
+ "</cas:user></cas:authenticationSuccess></cas:serviceResponse>";
PublicTestHttpServer.instance().content = RESPONSE
.getBytes(PublicTestHttpServer.instance().encoding);
module.login();
module.commit();
assertEquals(this.subject.getPrincipals().size(), 3);
assertTrue(hasPrincipalName(this.subject, AssertionPrincipal.class, USERNAME));
assertTrue(hasPrincipalName(this.subject, Group.class, "CallerPrincipal"));
assertTrue(hasPrincipalName(this.subject, Group.class, "Roles"));
}
/**
* Test JAAS login failure.
*/
public void testLoginFailure() throws Exception {
final String RESPONSE = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'><cas:authenticationFailure code=\"INVALID_TICKET\">Ticket ST-1856339-aA5Yuvrxzpv8Tau1cYQ7 not recognized</cas:authenticationFailure></cas:serviceResponse>";
PublicTestHttpServer.instance().content = RESPONSE.getBytes(PublicTestHttpServer.instance().encoding);
try {
module.login();
fail("Login did not throw LoginException as expected.");
} catch (Exception e) {
assertTrue(e instanceof LoginException);
}
}
private boolean hasPrincipalName(final Subject subject, final Class principalClass, final String name) {
final Set principals = subject.getPrincipals(principalClass);
final Iterator iter = principals.iterator();
while (iter.hasNext()) {
final Principal p = (Principal) iter.next();
if (p.getName().equals(name)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.util;
import junit.framework.TestCase;
/**
* Unit test for {@link ReflectUtils} class.
*
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*
*/
public class ReflectUtilsTests extends TestCase {
/**
* Test method for {@link org.jasig.cas.client.util.ReflectUtils#newInstance(java.lang.String, java.lang.Object[])}.
*/
public void testNewInstanceStringObjectArray() {
final Object result = ReflectUtils.newInstance(
"org.jasig.cas.client.validation.Cas10TicketValidator",
new Object[] {"https://localhost/cas"} );
assertNotNull(result);
}
/**
* Test method for {@link org.jasig.cas.client.util.ReflectUtils#setProperty(java.lang.String, java.lang.Object, java.lang.Object)}.
*/
public void testSetPropertyStringObjectObject() {
final TestBean bean = new TestBean();
ReflectUtils.setProperty("count", new Integer(30000), bean);
assertEquals(30000, bean.getCount());
ReflectUtils.setProperty("name", "bob", bean);
assertEquals("bob", bean.getName());
ReflectUtils.setProperty("flag", Boolean.TRUE, bean);
assertTrue(bean.isFlag());
}
static class TestBean {
private int count;
private boolean flag;
private String name;
/**
* @return the count
*/
public int getCount() {
return count;
}
/**
* @param count the count to set
*/
public void setCount(int count) {
this.count = count;
}
/**
* @return the name
*/
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/**
* @return the flag
*/
public boolean isFlag() {
return flag;
}
/**
* @param flag the flag to set
*/
public void setFlag(boolean flag) {
this.flag = flag;
}
}
}

View File

@ -2,7 +2,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.jasig.cas</groupId>
<version>3.1.10</version>
<version>3.1.11-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -10,50 +10,7 @@
<artifactId>cas-client-integration-atlassian</artifactId>
<packaging>jar</packaging>
<name>JA-SIG CAS Client for Java - Atlassian Integration</name>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<testSourceDirectory>src/test/java</testSourceDirectory>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<filtering>false</filtering>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.4</source>
<target>1.4</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests*</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clover-plugin</artifactId>
<configuration>
<licenseLocation>${basedir}/src/test/clover/clover.license</licenseLocation>
</configuration>
<executions>
<execution>
<phase>pre-site</phase>
<goals>
<goal>instrument</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<artifactId>atlassian-seraph</artifactId>
@ -554,7 +511,8 @@
</dependency>
</dependencies>
<!--
<repositories>
<repository>
<id>atlassian</id>
@ -562,4 +520,5 @@
<url>http://repository.atlassian.com/maven2/</url>
</repository>
</repositories>
-->
</project>

View File

@ -0,0 +1,42 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<groupId>org.jasig.cas</groupId>
<version>3.1.11-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-client-integration-jboss</artifactId>
<packaging>jar</packaging>
<name>JA-SIG CAS Client for Java - JBoss Integration</name>
<dependencies>
<dependency>
<groupId>org.jasig.cas</groupId>
<artifactId>cas-client-core</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jboss.jbossas</groupId>
<artifactId>jboss-as-tomcat</artifactId>
<version>${jboss.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<!--
<repositories>
<repository>
<id>jboss</id>
<name>JBoss Repository</name>
<url>http://repository.jboss.com/maven2/</url>
</repository>
</repositories>
-->
<properties>
<jboss.version>5.1.0.GA</jboss.version>
</properties>
</project>

View File

@ -0,0 +1,77 @@
/*
* 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.jboss.authentication;
import java.io.IOException;
import java.security.GeneralSecurityException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.jasig.cas.client.jaas.AssertionPrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jboss.web.tomcat.security.login.WebAuthentication;
/**
* This servlet filter performs a programmatic JAAS login using the JBoss
* <a href="http://community.jboss.org/wiki/WebAuthentication">WebAuthentication</a> class.
* The filter executes when it receives a CAS ticket and expects the
* {@link org.jasig.cas.client.jaas.CasLoginModule} JAAS module to perform the CAS
* ticket validation in order to produce an {@link AssertionPrincipal} from which
* the CAS assertion is obtained and inserted into the session to enable SSO.
* <p>
* If a <code>service</code> init-param is specified for this filter, it supersedes
* the service defined for the {@link org.jasig.cas.client.jaas.CasLoginModule}.
*
* @author Daniel Fisher
* @author Marvin S. Addison
* @version $Revision$
* @since 3.1.11
*/
public final class WebAuthenticationFilter extends AbstractCasFilter {
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final HttpSession session = request.getSession();
final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());
if (session != null && session.getAttribute(CONST_CAS_ASSERTION) == null && ticket != null) {
try {
final String service = constructServiceUrl(request, response);
log.debug("Attempting CAS ticket validation with service=" + service + " and ticket=" + ticket);
if (!new WebAuthentication().login(service, ticket)) {
log.debug("JBoss Web authentication failed.");
throw new GeneralSecurityException("JBoss Web authentication failed.");
}
if (request.getUserPrincipal() instanceof AssertionPrincipal) {
final AssertionPrincipal principal = (AssertionPrincipal) request.getUserPrincipal();
log.debug("Installing CAS assertion into session.");
session.setAttribute(CONST_CAS_ASSERTION, principal.getAssertion());
} else {
log.debug("Aborting -- principal is not of type AssertionPrincipal");
throw new GeneralSecurityException("JBoss Web authentication did not produce CAS AssertionPrincipal.");
}
} catch (final GeneralSecurityException e) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
}
} else if (session != null && request.getUserPrincipal() == null) {
// There is evidence that in some cases the principal can disappear
// in JBoss despite a valid session.
// This block forces consistency between principal and assertion.
log.info("User principal not found. Removing CAS assertion from session to force reauthentication.");
session.removeAttribute(CONST_CAS_ASSERTION);
}
chain.doFilter(request, response);
}
}

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas</groupId>
<version>3.1.10</version>
<version>3.1.11-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Jasig CAS Client for Java - Distributed Proxy Storage Support: EhCache</name>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas</groupId>
<version>3.1.10</version>
<version>3.1.11-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -27,7 +27,7 @@
<dependency>
<groupId>spy</groupId>
<artifactId>memcached</artifactId>
<version>2.4.2</version>
<version>2.5</version>
<type>jar</type>
<scope>provided</scope>
</dependency>

52
pom.xml
View File

@ -1,7 +1,7 @@
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jasig.cas</groupId>
<version>3.1.10</version>
<version>3.1.11-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
<packaging>pom</packaging>
<name>JA-SIG CAS Client for Java</name>
@ -20,28 +20,7 @@
<url>https://www.ja-sig.org/svn/cas-clients/java-client</url>
</scm>
<inceptionYear>2006</inceptionYear>
<mailingLists>
<mailingList>
<name>CAS Community Discussion List</name>
<subscribe>http://tp.its.yale.edu/mailman/listinfo/cas</subscribe>
<unsubscribe>http://tp.its.yale.edu/mailman/listinfo/cas</unsubscribe>
<post>cas@tp.its.yale.edu</post>
<archive>http://tp.its.yale.edu/pipermail/cas/</archive>
<otherArchives>
<otherArchive>http://news.gmane.org/gmane.comp.java.jasig.cas.user</otherArchive>
</otherArchives>
</mailingList>
<mailingList>
<name>CAS Developers Discussion List</name>
<subscribe>http://tp.its.yale.edu/mailman/listinfo/cas-dev</subscribe>
<unsubscribe>http://tp.its.yale.edu/mailman/listinfo/cas-dev</unsubscribe>
<post>cas-dev@tp.its.yale.edu</post>
<archive>http://tp.its.yale.edu/pipermail/cas-dev/</archive>
<otherArchives>
<otherArchive>http://news.gmane.org/gmane.comp.java.jasig.cas.devel</otherArchive>
</otherArchives>
</mailingList>
</mailingLists>
<developers>
<developer>
<id>battags</id>
@ -66,7 +45,7 @@
</licenses>
<organization>
<name>JA-SIG</name>
<url>http://www.ja-sig.org</url>
<url>http://www.jasig.org</url>
</organization>
<build>
<plugins>
@ -88,6 +67,30 @@
<target>1.4</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<includes>
<include>**/*Tests*</include>
</includes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clover-plugin</artifactId>
<configuration>
<licenseLocation>${basedir}/src/test/clover/clover.license</licenseLocation>
</configuration>
<executions>
<execution>
<phase>pre-site</phase>
<goals>
<goal>instrument</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
@ -129,6 +132,7 @@
<modules>
<module>cas-client-core</module>
<module>cas-client-integration-atlassian</module>
<module>cas-client-integration-jboss</module>
<module>cas-client-support-distributed-ehcache</module>
<module>cas-client-support-distributed-memcached</module>
</modules>