Merge pull request #101 from Jasig/no-opensaml

Remove OpenSAML and consolidate SAML components in support module
This commit is contained in:
Marvin S. Addison 2015-05-12 09:29:02 -04:00
commit 863038605f
28 changed files with 776 additions and 365 deletions

View File

@ -1,7 +1,7 @@
<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.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -10,7 +10,30 @@
<packaging>jar</packaging>
<name>Jasig CAS Client for Java - Core</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>xml-security</groupId>
<artifactId>xmlsec</artifactId>
@ -19,20 +42,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.opensaml</groupId>
<artifactId>opensaml</artifactId>
<version>${opensaml.version}</version>
<type>jar</type>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
@ -51,27 +60,18 @@
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
@ -97,8 +97,4 @@
</dependency>
</dependencies>
<properties>
<spring.version>3.1.3.RELEASE</spring.version>
<opensaml.version>2.5.1-1</opensaml.version>
</properties>
</project>

View File

@ -22,8 +22,6 @@ import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -62,12 +60,6 @@ public final class CommonUtils {
// nothing to do
}
public static String formatForUtcTime(final Date date) {
final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat.format(date);
}
/**
* Check whether the object is null or not. If it is, throw an exception and
* display the message.

View File

@ -0,0 +1,74 @@
package org.jasig.cas.client.util;
import java.io.*;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
/**
* IO utility class.
*
* @author Marvin S. Addison
* @since 3.4
*/
public final class IOUtils {
/** UTF-8 character set. */
public static final Charset UTF8 = Charset.forName("UTF-8");
private IOUtils() { /** Utility class pattern. */ }
/**
* Reads all data from the given stream as UTF-8 character data and closes it on completion or errors.
*
* @param in Input stream containing character data.
*
* @return String of all data in stream.
*
* @throws IOException On IO errors.
*/
public static String readString(final InputStream in) throws IOException {
return readString(in, UTF8);
}
/**
* Reads all data from the given stream as character data in the given character set and closes it on completion
* or errors.
*
* @param in Input stream containing character data.
* @param charset Character set of data in stream.
*
* @return String of all data in stream.
*
* @throws IOException On IO errors.
*/
public static String readString(final InputStream in, final Charset charset) throws IOException {
final Reader reader = new InputStreamReader(in, charset);
final StringBuilder builder = new StringBuilder();
final CharBuffer buffer = CharBuffer.allocate(2048);
try {
while (reader.read(buffer) > -1) {
buffer.flip();
builder.append(buffer);
}
} finally {
closeQuietly(reader);
}
return builder.toString();
}
/**
* Unconditionally close a {@link Closeable} resource. Errors on close are ignored.
*
* @param resource Resource to close.
*/
public static void closeQuietly(final Closeable resource) {
try {
if (resource != null) {
resource.close();
}
} catch (final IOException e) {
//ignore
}
}
}

View File

@ -0,0 +1,62 @@
package org.jasig.cas.client.util;
import javax.xml.namespace.NamespaceContext;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Namespace context implementation backed by a map of XML prefixes to namespace URIs.
*
* @author Marvin S. Addison
* @since 3.4
*/
public class MapNamespaceContext implements NamespaceContext {
private final Map<String, String> namespaceMap;
/**
* Creates a new instance from an array of namespace delcarations.
*
* @param namespaceDeclarations An array of namespace declarations of the form prefix->uri.
*/
public MapNamespaceContext(final String ... namespaceDeclarations) {
namespaceMap = new HashMap<String, String>();
int index;
String key;
String value;
for (final String decl : namespaceDeclarations) {
index = decl.indexOf('-');
key = decl.substring(0, index);
value = decl.substring(index + 2);
namespaceMap.put(key, value);
}
}
/**
* Creates a new instance from a map.
*
* @param namespaceMap Map of XML namespace prefixes (keys) to URIs (values).
*/
public MapNamespaceContext(final Map<String, String> namespaceMap) {
this.namespaceMap = namespaceMap;
}
public String getNamespaceURI(final String prefix) {
return namespaceMap.get(prefix);
}
public String getPrefix(final String namespaceURI) {
for (final Map.Entry<String, String> entry : namespaceMap.entrySet()) {
if (entry.getValue().equalsIgnoreCase(namespaceURI)) {
return entry.getKey();
}
}
return null;
}
public Iterator getPrefixes(final String namespaceURI) {
return Collections.singleton(getPrefix(namespaceURI)).iterator();
}
}

View File

@ -0,0 +1,87 @@
package org.jasig.cas.client.util;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.xpath.*;
/**
* Thread local XPath expression.
*
* @author Marvin S. Addison
* @since 3.4
*/
public class ThreadLocalXPathExpression extends ThreadLocal<XPathExpression> implements XPathExpression {
/** XPath expression */
private final String expression;
/** Namespace context. */
private final NamespaceContext context;
/**
* Creates a new instance from an XPath expression and namespace context.
*
* @param xPath XPath expression.
* @param context Namespace context for handling namespace prefix to URI mappings.
*/
public ThreadLocalXPathExpression(final String xPath, final NamespaceContext context) {
this.expression = xPath;
this.context = context;
}
public Object evaluate(final Object o, final QName qName) throws XPathExpressionException {
return get().evaluate(o, qName);
}
public String evaluate(final Object o) throws XPathExpressionException {
return get().evaluate(o);
}
public Object evaluate(final InputSource inputSource, final QName qName) throws XPathExpressionException {
return get().evaluate(inputSource, qName);
}
public String evaluate(final InputSource inputSource) throws XPathExpressionException {
return get().evaluate(inputSource);
}
/**
* Evaluates the XPath expression and returns the result coerced to a string.
*
* @param o Object on which to evaluate the expression; typically a DOM node.
*
* @return Evaluation result as a string.
*
* @throws XPathExpressionException On XPath evaluation errors.
*/
public String evaluateAsString(final Object o) throws XPathExpressionException {
return (String) evaluate(o, XPathConstants.STRING);
}
/**
* Evaluates the XPath expression and returns the result coerced to a node list.
*
* @param o Object on which to evaluate the expression; typically a DOM node.
*
* @return Evaluation result as a node list.
*
* @throws XPathExpressionException On XPath evaluation errors.
*/
public NodeList evaluateAsNodeList(final Object o) throws XPathExpressionException {
return (NodeList) evaluate(o, XPathConstants.NODESET);
}
@Override
protected XPathExpression initialValue() {
try {
final XPath xPath = XPathFactory.newInstance().newXPath();
xPath.setNamespaceContext(context);
return xPath.compile(expression);
} catch (XPathExpressionException e) {
throw new IllegalArgumentException("Invalid XPath expression");
}
}
}

View File

@ -19,16 +19,20 @@
package org.jasig.cas.client.util;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
/**
@ -45,6 +49,34 @@ public final class XmlUtils {
*/
private final static Logger LOGGER = LoggerFactory.getLogger(XmlUtils.class);
/**
* Creates a new namespace-aware DOM document object by parsing the given XML.
*
* @param xml XML content.
*
* @return DOM document.
*/
public static Document newDocument(final String xml) {
final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
final Map<String, Boolean> features = new HashMap<String, Boolean>();
features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true);
features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
for (final Map.Entry<String, Boolean> entry : features.entrySet()) {
try {
factory.setFeature(entry.getKey(), entry.getValue());
} catch (ParserConfigurationException e) {
LOGGER.warn("Failed setting XML feature {}: {}", entry.getKey(), e);
}
}
factory.setNamespaceAware(true);
try {
return factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
} catch (Exception e) {
throw new RuntimeException("XML parsing error: " + e);
}
}
/**
* Get an instance of an XML reader from the XMLReaderFactory.
*
@ -62,6 +94,7 @@ public final class XmlUtils {
}
}
/**
* Retrieve the text for a group of elements. Each text element is an entry
* in a list.

View File

@ -34,10 +34,6 @@ public abstract class AbstractCasProtocolUrlBasedTicketValidator extends Abstrac
super(casServerUrlPrefix);
}
protected final void setDisableXmlSchemaValidation(final boolean disable) {
// nothing to do
}
/**
* Retrieves the response from the server by opening a connection and merely reading the response.
*/

View File

@ -90,13 +90,6 @@ public abstract class AbstractUrlBasedTicketValidator implements TicketValidator
*/
protected abstract String getUrlSuffix();
/**
* Disable XML Schema validation. Note, setting this to true may not be reversable. Defaults to false. Setting it to false
* after setting it to true may not have any affect.
*
* @param disabled whether to disable or not.
*/
protected abstract void setDisableXmlSchemaValidation(boolean disabled);
/**
* Constructs the URL to send the validation request to.

View File

@ -51,7 +51,7 @@ public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketVal
private static final String[] RESERVED_INIT_PARAMS = new String[]{ARTIFACT_PARAMETER_NAME.getName(), SERVER_NAME.getName(), SERVICE.getName(), RENEW.getName(), LOGOUT_PARAMETER_NAME.getName(),
ARTIFACT_PARAMETER_OVER_POST.getName(), EAGERLY_CREATE_SESSIONS.getName(), ENCODE_SERVICE_URL.getName(), SSL_CONFIG_FILE.getName(), ROLE_ATTRIBUTE.getName(), IGNORE_CASE.getName(),
CAS_SERVER_LOGIN_URL.getName(), GATEWAY.getName(), AUTHENTICATION_REDIRECT_STRATEGY_CLASS.getName(), GATEWAY_STORAGE_CLASS.getName(), CAS_SERVER_URL_PREFIX.getName(), ENCODING.getName(),
TOLERANCE.getName(), DISABLE_XML_SCHEMA_VALIDATION.getName(), IGNORE_PATTERN.getName(), IGNORE_URL_PATTERN_TYPE.getName(), HOSTNAME_VERIFIER.getName(), HOSTNAME_VERIFIER_CONFIG.getName(),
TOLERANCE.getName(), IGNORE_PATTERN.getName(), IGNORE_URL_PATTERN_TYPE.getName(), HOSTNAME_VERIFIER.getName(), HOSTNAME_VERIFIER_CONFIG.getName(),
EXCEPTION_ON_VALIDATION_FAILURE.getName(), REDIRECT_AFTER_VALIDATION.getName(), USE_SESSION.getName(), SECRET_KEY.getName(), CIPHER_ALGORITHM.getName(), PROXY_RECEPTOR_URL.getName(),
PROXY_GRANTING_TICKET_STORAGE_CLASS.getName(), MILLIS_BETWEEN_CLEAN_UPS.getName(), ACCEPT_ANY_PROXY.getName(), ALLOWED_PROXY_CHAINS.getName(), TICKET_VALIDATOR_CLASS.getName(),
PROXY_CALLBACK_URL.getName(), FRONT_LOGOUT_PARAMETER_NAME.getName(), RELAY_STATE_PARAMETER_NAME.getName()

View File

@ -1,291 +0,0 @@
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.validation;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.util.CommonUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.IdentifierGenerator;
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
import org.opensaml.saml1.core.*;
import org.opensaml.ws.soap.soap11.Envelope;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.io.Unmarshaller;
import org.opensaml.xml.io.UnmarshallerFactory;
import org.opensaml.xml.io.UnmarshallingException;
import org.opensaml.xml.parse.BasicParserPool;
import org.opensaml.xml.parse.XMLParserException;
import org.opensaml.xml.schema.XSAny;
import org.opensaml.xml.schema.XSString;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response.
*
* @author Scott Battaglia
* @since 3.1
*/
public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator {
static {
try {
// Check for prior OpenSAML initialization to prevent double init
// that would overwrite existing OpenSAML configuration
if (Configuration.getParserPool() == null) {
DefaultBootstrap.bootstrap();
}
} catch (final ConfigurationException e) {
throw new RuntimeException(e);
}
}
/** Time tolerance to allow for time drifting. */
private long tolerance = 1000L;
private final BasicParserPool basicParserPool;
private final IdentifierGenerator identifierGenerator;
public Saml11TicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
this.basicParserPool = new BasicParserPool();
this.basicParserPool.setNamespaceAware(true);
try {
this.identifierGenerator = new SecureRandomIdentifierGenerator();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}
protected String getUrlSuffix() {
return "samlValidate";
}
protected void populateUrlAttributeMap(final Map<String, String> urlParameters) {
final String service = urlParameters.get("service");
urlParameters.remove("service");
urlParameters.remove("ticket");
urlParameters.put("TARGET", service);
}
@Override
protected void setDisableXmlSchemaValidation(final boolean disabled) {
if (disabled) {
this.basicParserPool.setSchema(null);
}
}
protected byte[] getBytes(final String text) {
try {
return CommonUtils.isNotBlank(getEncoding()) ? text.getBytes(getEncoding()) : text.getBytes();
} catch (final Exception e) {
return text.getBytes();
}
}
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
try {
final Document responseDocument = this.basicParserPool.parse(new ByteArrayInputStream(getBytes(response)));
final Element responseRoot = responseDocument.getDocumentElement();
final UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
final Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(responseRoot);
final Envelope envelope = (Envelope) unmarshaller.unmarshall(responseRoot);
final Response samlResponse = (Response) envelope.getBody().getOrderedChildren().get(0);
final List<org.opensaml.saml1.core.Assertion> assertions = samlResponse.getAssertions();
if (assertions.isEmpty()) {
throw new TicketValidationException("No assertions found.");
}
for (final org.opensaml.saml1.core.Assertion assertion : assertions) {
if (!isValidAssertion(assertion)) {
continue;
}
final AuthenticationStatement authenticationStatement = getSAMLAuthenticationStatement(assertion);
if (authenticationStatement == null) {
throw new TicketValidationException("No AuthentiationStatement found in SAML Assertion.");
}
final Subject subject = authenticationStatement.getSubject();
if (subject == null) {
throw new TicketValidationException("No Subject found in SAML Assertion.");
}
final List<Attribute> attributes = getAttributesFor(assertion, subject);
final Map<String, Object> personAttributes = new HashMap<String, Object>();
for (final Attribute samlAttribute : attributes) {
final List<?> values = getValuesFrom(samlAttribute);
personAttributes.put(samlAttribute.getAttributeName(), values.size() == 1 ? values.get(0) : values);
}
final AttributePrincipal principal = new AttributePrincipalImpl(subject.getNameIdentifier()
.getNameIdentifier(), personAttributes);
final Map<String, Object> authenticationAttributes = new HashMap<String, Object>();
authenticationAttributes.put("samlAuthenticationStatement::authMethod",
authenticationStatement.getAuthenticationMethod());
final DateTime notBefore = assertion.getConditions().getNotBefore();
final DateTime notOnOrAfter = assertion.getConditions().getNotOnOrAfter();
final DateTime authenticationInstant = authenticationStatement.getAuthenticationInstant();
return new AssertionImpl(principal, notBefore.toDate(), notOnOrAfter.toDate(),
authenticationInstant.toDate(), authenticationAttributes);
}
} catch (final UnmarshallingException e) {
throw new TicketValidationException(e);
} catch (final XMLParserException e) {
throw new TicketValidationException(e);
}
throw new TicketValidationException(
"No Assertion found within valid time range. Either there's a replay of the ticket or there's clock drift. Check tolerance range, or server/client synchronization.");
}
private boolean isValidAssertion(final org.opensaml.saml1.core.Assertion assertion) {
final DateTime notBefore = assertion.getConditions().getNotBefore();
final DateTime notOnOrAfter = assertion.getConditions().getNotOnOrAfter();
if (notBefore == null || notOnOrAfter == null) {
logger.debug("Assertion has no bounding dates. Will not process.");
return false;
}
final DateTime currentTime = new DateTime(DateTimeZone.UTC);
final Interval validityRange = new Interval(notBefore.minus(this.tolerance), notOnOrAfter.plus(this.tolerance));
if (validityRange.contains(currentTime)) {
logger.debug("Current time is within the interval validity.");
return true;
}
if (currentTime.isBefore(validityRange.getStart())) {
logger.debug("skipping assertion that's not yet valid...");
return false;
}
logger.debug("skipping expired assertion...");
return false;
}
private AuthenticationStatement getSAMLAuthenticationStatement(final org.opensaml.saml1.core.Assertion assertion) {
final List<AuthenticationStatement> statements = assertion.getAuthenticationStatements();
if (statements.isEmpty()) {
return null;
}
return statements.get(0);
}
private List<Attribute> getAttributesFor(final org.opensaml.saml1.core.Assertion assertion, final Subject subject) {
final List<Attribute> attributes = new ArrayList<Attribute>();
for (final AttributeStatement attribute : assertion.getAttributeStatements()) {
if (subject.getNameIdentifier().getNameIdentifier()
.equals(attribute.getSubject().getNameIdentifier().getNameIdentifier())) {
attributes.addAll(attribute.getAttributes());
}
}
return attributes;
}
private List<?> getValuesFrom(final Attribute attribute) {
final List<Object> list = new ArrayList<Object>();
for (final Object o : attribute.getAttributeValues()) {
if (o instanceof XSAny) {
list.add(((XSAny) o).getTextContent());
} else if (o instanceof XSString) {
list.add(((XSString) o).getValue());
} else {
list.add(o.toString());
}
}
return list;
}
protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
final String MESSAGE_TO_SEND = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\""
+ this.identifierGenerator.generateIdentifier()
+ "\" IssueInstant=\""
+ CommonUtils.formatForUtcTime(new Date())
+ "\">"
+ "<samlp:AssertionArtifact>"
+ ticket
+ "</samlp:AssertionArtifact></samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
HttpURLConnection conn = null;
DataOutputStream out = null;
BufferedReader in = null;
try {
conn = this.getURLConnectionFactory().buildHttpURLConnection(validationUrl.openConnection());
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Content-Length", Integer.toString(MESSAGE_TO_SEND.length()));
conn.setRequestProperty("SOAPAction", "http://www.oasis-open.org/committees/security");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
out = new DataOutputStream(conn.getOutputStream());
out.writeBytes(MESSAGE_TO_SEND);
out.flush();
in = new BufferedReader(CommonUtils.isNotBlank(getEncoding()) ? new InputStreamReader(
conn.getInputStream(), Charset.forName(getEncoding())) : new InputStreamReader(
conn.getInputStream()));
final StringBuilder buffer = new StringBuilder(256);
String line;
while ((line = in.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
} catch (final IOException e) {
throw new RuntimeException(e);
} finally {
CommonUtils.closeQuietly(out);
CommonUtils.closeQuietly(in);
if (conn != null) {
conn.disconnect();
}
}
}
public void setTolerance(final long tolerance) {
this.tolerance = tolerance;
}
}

View File

@ -0,0 +1,8 @@
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns="urn:oasis:names:tc:SAML:1.0:protocol">
<soap:Header/>
<soap:Body>
<Request MajorVersion="1" MinorVersion="1" RequestID="%s" IssueInstant="%s">
<AssertionArtifact>%s</AssertionArtifact>
</Request>
</soap:Body>
</soap:Envelope>

View File

@ -21,6 +21,7 @@ package org.jasig.cas.client.util;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import junit.framework.TestCase;
import org.jasig.cas.client.PublicTestHttpServer;
import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;

View File

@ -1,7 +1,7 @@
<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.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -46,6 +46,11 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<artifactId>atlassian-osuser</artifactId>
<groupId>com.atlassian.osuser</groupId>

View File

@ -1,7 +1,7 @@
<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.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -20,6 +20,14 @@
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-support-saml</artifactId>
<version>${project.version}</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>catalina</artifactId>

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -20,6 +20,14 @@
<type>jar</type>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-support-saml</artifactId>
<version>${project.version}</version>
<type>jar</type>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>

View File

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

View File

@ -3,7 +3,7 @@
<parent>
<artifactId>cas-client</artifactId>
<groupId>org.jasig.cas.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -0,0 +1,44 @@
<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.client</groupId>
<version>3.4.0-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-support-saml</artifactId>
<packaging>jar</packaging>
<name>Jasig CAS Client for Java - SAML Protocol Support</name>
<dependencies>
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.7</version>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.jasig.cas.client</groupId>
<artifactId>cas-client-core</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,53 @@
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
* *
* http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.util;
import java.util.Date;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.format.DateTimeFormatter;
import org.joda.time.format.ISODateTimeFormat;
/**
* SAML utility class.
*
* @author Marvin S. Addison
* @since 3.4
*/
public final class SamlUtils {
private static final DateTimeFormatter ISO_FORMAT = ISODateTimeFormat.dateTimeNoMillis();
private SamlUtils() {
// nothing to do
}
public static String formatForUtcTime(final Date date) {
return ISO_FORMAT.print(new DateTime(date).withZone(DateTimeZone.UTC));
}
public static Date parseUtcDate(final String date) {
if (CommonUtils.isEmpty(date)) {
return null;
}
return ISODateTimeFormat.dateTimeParser().parseDateTime(date).toDate();
}
}

View File

@ -32,6 +32,7 @@ import org.jasig.cas.client.ssl.HttpsURLConnectionFactory;
* context or filter init parameters.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @version $Revision$ $Date$
* @since 3.1
*/
@ -47,12 +48,10 @@ public class Saml11TicketValidationFilter extends AbstractTicketValidationFilter
validator.setTolerance(tolerance);
validator.setRenew(getBoolean(ConfigurationKeys.RENEW));
final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(),
getSSLConfig());
final HttpURLConnectionFactory factory = new HttpsURLConnectionFactory(getHostnameVerifier(), getSSLConfig());
validator.setURLConnectionFactory(factory);
validator.setEncoding(getString(ConfigurationKeys.ENCODING));
validator.setDisableXmlSchemaValidation(getBoolean(ConfigurationKeys.DISABLE_XML_SCHEMA_VALIDATION));
return validator;
}
}

View File

@ -0,0 +1,238 @@
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.validation;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.*;
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
import org.jasig.cas.client.util.*;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.namespace.NamespaceContext;
/**
* TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response.
*
* @author Scott Battaglia
* @author Marvin S. Addison
* @since 3.1
*/
public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator {
/** Authentication attribute containing SAML AuthenticationMethod attribute value. */
public static final String AUTH_METHOD_ATTRIBUTE = "samlAuthenticationStatement::authMethod";
/** SAML 1.1 request template. */
private static final String SAML_REQUEST_TEMPLATE;
/** SAML 1.1. namespace context. */
private static final NamespaceContext NS_CONTEXT = new MapNamespaceContext(
"soap->http://schemas.xmlsoap.org/soap/envelope/",
"sa->urn:oasis:names:tc:SAML:1.0:assertion",
"sp->urn:oasis:names:tc:SAML:1.0:protocol");
/** XPath expression to extract Assertion validity start date. */
private static final ThreadLocalXPathExpression XPATH_ASSERTION_DATE_START =
new ThreadLocalXPathExpression("//sa:Assertion/sa:Conditions/@NotBefore", NS_CONTEXT);
/** XPath expression to extract Assertion validity end date. */
private static final ThreadLocalXPathExpression XPATH_ASSERTION_DATE_END =
new ThreadLocalXPathExpression("//sa:Assertion/sa:Conditions/@NotOnOrAfter", NS_CONTEXT);
/** XPath expression to extract NameIdentifier. */
private static final ThreadLocalXPathExpression XPATH_NAME_ID =
new ThreadLocalXPathExpression("//sa:AuthenticationStatement/sa:Subject/sa:NameIdentifier", NS_CONTEXT);
/** XPath expression to extract authentication method. */
private static final ThreadLocalXPathExpression XPATH_AUTH_METHOD =
new ThreadLocalXPathExpression("//sa:AuthenticationStatement/@AuthenticationMethod", NS_CONTEXT);
/** XPath expression to extract attributes. */
private static final ThreadLocalXPathExpression XPATH_ATTRIBUTES =
new ThreadLocalXPathExpression("//sa:AttributeStatement/sa:Attribute", NS_CONTEXT);
private static final String HEX_CHARS = "0123456789abcdef";
/** Time tolerance to allow for time drifting. */
private long tolerance = 1000L;
private final Random random;
/** Class initializer. */
static {
try {
SAML_REQUEST_TEMPLATE = IOUtils.readString(
Saml11TicketValidator.class.getResourceAsStream("/META-INF/cas/samlRequestTemplate.xml"));
} catch (IOException e) {
throw new IllegalStateException("Cannot load SAML request template from classpath", e);
}
}
public Saml11TicketValidator(final String casServerUrlPrefix) {
super(casServerUrlPrefix);
try {
random = SecureRandom.getInstance("SHA1PRNG");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Cannot find required SHA1PRNG algorithm");
}
}
protected String getUrlSuffix() {
return "samlValidate";
}
protected void populateUrlAttributeMap(final Map<String, String> urlParameters) {
final String service = urlParameters.get("service");
urlParameters.remove("service");
urlParameters.remove("ticket");
urlParameters.put("TARGET", service);
}
protected Assertion parseResponseFromServer(final String response) throws TicketValidationException {
try {
final Document document = XmlUtils.newDocument(response);
final Date assertionValidityStart = SamlUtils.parseUtcDate(
XPATH_ASSERTION_DATE_START.evaluateAsString(document));
final Date assertionValidityEnd = SamlUtils.parseUtcDate(
XPATH_ASSERTION_DATE_END.evaluateAsString(document));
if (!isValidAssertion(assertionValidityStart, assertionValidityEnd)) {
throw new TicketValidationException("Invalid SAML assertion");
}
final String nameId = XPATH_NAME_ID.evaluateAsString(document);
if (nameId == null) {
throw new TicketValidationException("SAML assertion does not contain NameIdentifier element");
}
final String authMethod = XPATH_AUTH_METHOD.evaluateAsString(document);
final NodeList attributes = XPATH_ATTRIBUTES.evaluateAsNodeList(document);
final Map<String, Object> principalAttributes = new HashMap<String, Object>(attributes.getLength());
Element attribute;
NodeList values;
String name;
for (int i = 0; i < attributes.getLength(); i++) {
attribute = (Element) attributes.item(i);
name = attribute.getAttribute("AttributeName");
logger.trace("Processing attribute {}", name);
values = attribute.getElementsByTagNameNS("*", "AttributeValue");
if (values.getLength() == 1) {
principalAttributes.put(name, values.item(0).getTextContent());
} else {
final Collection<Object> items = new ArrayList<Object>(values.getLength());
for (int j = 0; j < values.getLength(); j++) {
items.add(values.item(j).getTextContent());
}
principalAttributes.put(name, items);
}
}
return new AssertionImpl(
new AttributePrincipalImpl(nameId, principalAttributes),
assertionValidityStart,
assertionValidityEnd,
new Date(),
Collections.singletonMap(AUTH_METHOD_ATTRIBUTE, (Object) authMethod));
} catch (final Exception e) {
throw new TicketValidationException("Error processing SAML response", e);
}
}
private boolean isValidAssertion(final Date notBefore, final Date notOnOrAfter) {
if (notBefore == null || notOnOrAfter == null) {
logger.debug("Assertion is not valid because it does not have bounding dates.");
return false;
}
final DateTime currentTime = new DateTime(DateTimeZone.UTC);
final Interval validityRange = new Interval(
new DateTime(notBefore).minus(this.tolerance),
new DateTime(notOnOrAfter).plus(this.tolerance));
if (validityRange.contains(currentTime)) {
logger.debug("Current time is within the interval validity.");
return true;
}
if (currentTime.isBefore(validityRange.getStart())) {
logger.debug("Assertion is not yet valid");
} else {
logger.debug("Assertion is expired");
}
return false;
}
protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
final String request = String.format(
SAML_REQUEST_TEMPLATE,
generateId(),
SamlUtils.formatForUtcTime(new Date()),
ticket);
HttpURLConnection conn = null;
try {
conn = this.getURLConnectionFactory().buildHttpURLConnection(validationUrl.openConnection());
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "text/xml");
conn.setRequestProperty("Content-Length", Integer.toString(request.length()));
conn.setRequestProperty("SOAPAction", "http://www.oasis-open.org/committees/security");
conn.setUseCaches(false);
conn.setDoInput(true);
conn.setDoOutput(true);
final Charset charset = CommonUtils.isNotBlank(getEncoding()) ?
Charset.forName(getEncoding()) : IOUtils.UTF8;
conn.getOutputStream().write(request.getBytes(charset));
conn.getOutputStream().flush();
return IOUtils.readString(conn.getInputStream(), charset);
} catch (final IOException e) {
throw new RuntimeException("IO error sending HTTP request to /samlValidate", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
public void setTolerance(final long tolerance) {
this.tolerance = tolerance;
}
private String generateId() {
final byte[] data = new byte[16];
random.nextBytes(data);
final StringBuilder id = new StringBuilder(33);
id.append('_');
for (int i = 0; i < data.length; i++) {
id.append(HEX_CHARS.charAt((data[i] & 0xF0) >> 4));
id.append(HEX_CHARS.charAt(data[i] & 0x0F));
}
return id.toString();
}
}

View File

@ -0,0 +1,40 @@
/*
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a
* copy of the License at the following location:
* *
* http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.jasig.cas.client.util;
import java.util.Date;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link SamlUtils}.
*
* @author Marvin S. Addison
*/
public class SamlUtilsTest {
@Test
public void testParseUtcDate() {
final Date expected = new Date(1424437961025L);
assertEquals(expected, SamlUtils.parseUtcDate("2015-02-20T08:12:41.025-0500"));
}
}

View File

@ -21,9 +21,10 @@ package org.jasig.cas.client.validation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.Date;
import org.jasig.cas.client.PublicTestHttpServer;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.SamlUtils;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Interval;
@ -45,7 +46,7 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes
@Before
public void setUp() throws Exception {
this.validator = new Saml11TicketValidator(CONST_CAS_SERVER_URL_PREFIX + "9051");
this.validator = new Saml11TicketValidator(AbstractTicketValidatorTests.CONST_CAS_SERVER_URL_PREFIX + "9051");
this.validator.setTolerance(1000L);
}
@ -82,13 +83,13 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes
final Interval range = currentTimeRangeInterval();
final Date now = new Date();
final String RESPONSE = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Header/><SOAP-ENV:Body><Response xmlns=\"urn:oasis:names:tc:SAML:1.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" IssueInstant=\""
+ CommonUtils.formatForUtcTime(now)
+ SamlUtils.formatForUtcTime(now)
+ "\" MajorVersion=\"1\" MinorVersion=\"1\" Recipient=\"test\" ResponseID=\"_e1e2124c08ab456eab0bbab3e1c0c433\"><Status><StatusCode Value=\"samlp:Success\"></StatusCode></Status><Assertion xmlns=\"urn:oasis:names:tc:SAML:1.0:assertion\" AssertionID=\"_d2fd0d6e4da6a6d7d2ba5274ab570d5c\" IssueInstant=\""
+ CommonUtils.formatForUtcTime(now)
+ SamlUtils.formatForUtcTime(now)
+ "\" Issuer=\"testIssuer\" MajorVersion=\"1\" MinorVersion=\"1\"><Conditions NotBefore=\""
+ CommonUtils.formatForUtcTime(range.getStart().toDate())
+ SamlUtils.formatForUtcTime(range.getStart().toDate())
+ "\" NotOnOrAfter=\""
+ CommonUtils.formatForUtcTime(range.getEnd().toDate())
+ SamlUtils.formatForUtcTime(range.getEnd().toDate())
+ "\"><AudienceRestrictionCondition><Audience>test</Audience></AudienceRestrictionCondition></Conditions><AuthenticationStatement AuthenticationInstant=\"2008-06-19T14:34:44.426Z\" AuthenticationMethod=\"urn:ietf:rfc:2246\"><Subject><NameIdentifier>testPrincipal</NameIdentifier><SubjectConfirmation><ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:artifact</ConfirmationMethod></SubjectConfirmation></Subject></AuthenticationStatement></Assertion></Response></SOAP-ENV:Body></SOAP-ENV:Envelope>";
server.content = RESPONSE.getBytes(server.encoding);
try {
@ -106,22 +107,28 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes
final String response = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><soap11:Envelope xmlns:soap11=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap11:Body>"
+ "<saml1p:Response xmlns:saml1p=\"urn:oasis:names:tc:SAML:1.0:protocol\" InResponseTo=\"_fd1632b5dfa921623e7ca6f9ab727161\" IssueInstant=\""
+ CommonUtils.formatForUtcTime(now)
+ SamlUtils.formatForUtcTime(now)
+ "\" MajorVersion=\"1\" MinorVersion=\"1\" Recipient=\"https://example.com/test-client/secure/?TARGET=https%3A%2F%2Fexample.com%2Ftest-client%2Fsecure%2F\" ResponseID=\"_436dbb2cca5166af29250f431a07888f\">"
+ "<saml1p:Status><saml1p:StatusCode Value=\"saml1p:Success\"/></saml1p:Status>"
+ "<saml1:Assertion xmlns:saml1=\"urn:oasis:names:tc:SAML:1.0:assertion\" IssueInstant=\""
+ CommonUtils.formatForUtcTime(now)
+ SamlUtils.formatForUtcTime(now)
+ "\" Issuer=\"localhost\" MajorVersion=\"1\" MinorVersion=\"1\">"
+ "<saml1:Conditions NotBefore=\""
+ CommonUtils.formatForUtcTime(range.getStart().toDate())
+ SamlUtils.formatForUtcTime(range.getStart().toDate())
+ "\" NotOnOrAfter=\""
+ CommonUtils.formatForUtcTime(range.getEnd().toDate())
+ SamlUtils.formatForUtcTime(range.getEnd().toDate())
+ "\">"
+ "<saml1:AudienceRestrictionCondition><saml1:Audience>https://example.com/test-client/secure/</saml1:Audience></saml1:AudienceRestrictionCondition></saml1:Conditions>"
+ "<saml1:AudienceRestrictionCondition><saml1:Audience>https://example.com/test-client/secure/</saml1:Audience>"
+ "</saml1:AudienceRestrictionCondition></saml1:Conditions>"
+ "<saml1:AuthenticationStatement AuthenticationInstant=\""
+ CommonUtils.formatForUtcTime(now)
+ SamlUtils.formatForUtcTime(now)
+ "\" AuthenticationMethod=\"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\">"
+ "<saml1:Subject><saml1:NameIdentifier>testPrincipal</saml1:NameIdentifier><saml1:SubjectConfirmation><saml1:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:artifact</saml1:ConfirmationMethod></saml1:SubjectConfirmation></saml1:Subject></saml1:AuthenticationStatement><saml1:AttributeStatement><saml1:Subject><saml1:NameIdentifier>testPrincipal</saml1:NameIdentifier><saml1:SubjectConfirmation><saml1:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:artifact</saml1:ConfirmationMethod></saml1:SubjectConfirmation></saml1:Subject><saml1:Attribute AttributeName=\"uid\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\"><saml1:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">12345</saml1:AttributeValue>"
+ "<saml1:Subject><saml1:NameIdentifier>testPrincipal</saml1:NameIdentifier>"
+ "<saml1:SubjectConfirmation><saml1:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:artifact</saml1:ConfirmationMethod></saml1:SubjectConfirmation>"
+ "</saml1:Subject></saml1:AuthenticationStatement>"
+ "<saml1:AttributeStatement><saml1:Subject><saml1:NameIdentifier>testPrincipal</saml1:NameIdentifier>"
+ "<saml1:SubjectConfirmation><saml1:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:artifact</saml1:ConfirmationMethod></saml1:SubjectConfirmation></saml1:Subject>"
+ "<saml1:Attribute AttributeName=\"uid\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\"><saml1:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">12345</saml1:AttributeValue>"
+ "</saml1:Attribute><saml1:Attribute AttributeName=\"accountState\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\">"
+ "<saml1:AttributeValue xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"xs:string\">ACTIVE</saml1:AttributeValue>"
+ "</saml1:Attribute><saml1:Attribute AttributeName=\"eduPersonAffiliation\" AttributeNamespace=\"http://www.ja-sig.org/products/cas/\">"
@ -132,7 +139,13 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes
server.content = response.getBytes(server.encoding);
try {
final Assertion a = this.validator.validate("test", "test");
assertEquals(
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
a.getAttributes().get(Saml11TicketValidator.AUTH_METHOD_ATTRIBUTE));
assertEquals("testPrincipal", a.getPrincipal().getName());
assertEquals("12345", a.getPrincipal().getAttributes().get("uid"));
assertEquals("ACTIVE", a.getPrincipal().getAttributes().get("accountState"));
assertEquals(3, ((Collection) a.getPrincipal().getAttributes().get("eduPersonAffiliation")).size());
} catch (final TicketValidationException e) {
fail(e.toString());
}

54
pom.xml
View File

@ -6,7 +6,7 @@
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.jasig.cas.client</groupId>
<version>3.3.4-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>cas-client</artifactId>
<packaging>pom</packaging>
@ -159,6 +159,56 @@
</plugins>
</build>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
<exclusions>
<exclusion>
<artifactId>commons-logging</artifactId>
<groupId>commons-logging</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<scope>test</scope>
<version>1.2.15</version>
<exclusions>
<exclusion>
<artifactId>jmxri</artifactId>
<groupId>com.sun.jmx</groupId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
@ -198,12 +248,14 @@
<module>cas-client-integration-jboss</module>
<module>cas-client-support-distributed-ehcache</module>
<module>cas-client-support-distributed-memcached</module>
<module>cas-client-support-saml</module>
<module>cas-client-integration-tomcat-common</module>
<module>cas-client-integration-tomcat-v6</module>
<module>cas-client-integration-tomcat-v7</module>
</modules>
<properties>
<spring.version>3.1.3.RELEASE</spring.version>
<ehcache.version>2.2.0</ehcache.version>
<clover.version>3.0.2</clover.version>
<slf4j.version>1.7.1</slf4j.version>