diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index b0de44b..e8d0a31 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT cas-client 4.0.0 @@ -10,7 +10,30 @@ jar Jasig CAS Client for Java - Core + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + test-jar + + + + + + + + + commons-lang + commons-lang + 2.6 + + xml-security xmlsec @@ -19,20 +42,6 @@ true - - org.opensaml - opensaml - ${opensaml.version} - jar - compile - - - org.slf4j - jcl-over-slf4j - - - - commons-codec commons-codec @@ -51,27 +60,18 @@ org.springframework spring-test - ${spring.version} test org.springframework spring-core - ${spring.version} test - - - commons-logging - commons-logging - - org.springframework spring-context - ${spring.version} test @@ -97,8 +97,4 @@ - - 3.1.3.RELEASE - 2.5.1-1 - diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java index 5f2ca61..95eff51 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/CommonUtils.java @@ -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. diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java new file mode 100644 index 0000000..b003775 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java @@ -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 + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java new file mode 100644 index 0000000..6eb5b62 --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java @@ -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 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(); + 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 namespaceMap) { + this.namespaceMap = namespaceMap; + } + + public String getNamespaceURI(final String prefix) { + return namespaceMap.get(prefix); + } + + public String getPrefix(final String namespaceURI) { + for (final Map.Entry 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(); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java new file mode 100644 index 0000000..2e51c0c --- /dev/null +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java @@ -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 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"); + } + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java index f882b04..77831f4 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java @@ -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 features = new HashMap(); + features.put(XMLConstants.FEATURE_SECURE_PROCESSING, true); + features.put("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + for (final Map.Entry 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. diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java index b5d5c2f..146280d 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractCasProtocolUrlBasedTicketValidator.java @@ -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. */ diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java index fab0581..59c4d88 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/AbstractUrlBasedTicketValidator.java @@ -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. diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java index deefe22..5fe1337 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Cas20ProxyReceivingTicketValidationFilter.java @@ -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() diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java deleted file mode 100644 index 007fc5c..0000000 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java +++ /dev/null @@ -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 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 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 attributes = getAttributesFor(assertion, subject); - final Map personAttributes = new HashMap(); - 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 authenticationAttributes = new HashMap(); - 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 statements = assertion.getAuthenticationStatements(); - - if (statements.isEmpty()) { - return null; - } - - return statements.get(0); - } - - private List getAttributesFor(final org.opensaml.saml1.core.Assertion assertion, final Subject subject) { - final List attributes = new ArrayList(); - 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 list = new ArrayList(); - 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 = "" - + "" - + ticket - + ""; - 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; - } -} diff --git a/cas-client-core/src/main/resources/META-INF/cas/samlRequestTemplate.xml b/cas-client-core/src/main/resources/META-INF/cas/samlRequestTemplate.xml new file mode 100644 index 0000000..4247909 --- /dev/null +++ b/cas-client-core/src/main/resources/META-INF/cas/samlRequestTemplate.xml @@ -0,0 +1,8 @@ + + + + + %s + + + diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java index 8b585b4..1b28ad5 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java @@ -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; diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index 96895e9..009a58d 100644 --- a/cas-client-integration-atlassian/pom.xml +++ b/cas-client-integration-atlassian/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT cas-client 4.0.0 @@ -46,6 +46,11 @@ true + + org.springframework + spring-context + + atlassian-osuser com.atlassian.osuser diff --git a/cas-client-integration-jboss/pom.xml b/cas-client-integration-jboss/pom.xml index 5a118ea..43c24da 100644 --- a/cas-client-integration-jboss/pom.xml +++ b/cas-client-integration-jboss/pom.xml @@ -1,7 +1,7 @@ org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT cas-client 4.0.0 diff --git a/cas-client-integration-tomcat-common/pom.xml b/cas-client-integration-tomcat-common/pom.xml index 06a1c33..13c2317 100644 --- a/cas-client-integration-tomcat-common/pom.xml +++ b/cas-client-integration-tomcat-common/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT 4.0.0 diff --git a/cas-client-integration-tomcat-v6/pom.xml b/cas-client-integration-tomcat-v6/pom.xml index 5d40ce4..d7dd9bd 100644 --- a/cas-client-integration-tomcat-v6/pom.xml +++ b/cas-client-integration-tomcat-v6/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT 4.0.0 @@ -20,6 +20,14 @@ jar compile + + org.jasig.cas.client + cas-client-support-saml + ${project.version} + jar + compile + true + org.apache.tomcat catalina diff --git a/cas-client-integration-tomcat-v7/pom.xml b/cas-client-integration-tomcat-v7/pom.xml index 5bc817d..0c83322 100644 --- a/cas-client-integration-tomcat-v7/pom.xml +++ b/cas-client-integration-tomcat-v7/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT 4.0.0 @@ -20,6 +20,14 @@ jar compile + + org.jasig.cas.client + cas-client-support-saml + ${project.version} + jar + compile + true + org.apache.tomcat tomcat-catalina diff --git a/cas-client-support-distributed-ehcache/pom.xml b/cas-client-support-distributed-ehcache/pom.xml index 1e1f712..c4bcde0 100644 --- a/cas-client-support-distributed-ehcache/pom.xml +++ b/cas-client-support-distributed-ehcache/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT 4.0.0 Jasig CAS Client for Java - Distributed Proxy Storage Support: EhCache diff --git a/cas-client-support-distributed-memcached/pom.xml b/cas-client-support-distributed-memcached/pom.xml index 52a8640..3a1cb2a 100644 --- a/cas-client-support-distributed-memcached/pom.xml +++ b/cas-client-support-distributed-memcached/pom.xml @@ -3,7 +3,7 @@ cas-client org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT 4.0.0 diff --git a/cas-client-support-saml/pom.xml b/cas-client-support-saml/pom.xml new file mode 100644 index 0000000..6c68d43 --- /dev/null +++ b/cas-client-support-saml/pom.xml @@ -0,0 +1,44 @@ + + + org.jasig.cas.client + 3.4.0-SNAPSHOT + cas-client + + 4.0.0 + org.jasig.cas.client + cas-client-support-saml + jar + Jasig CAS Client for Java - SAML Protocol Support + + + + org.jasig.cas.client + cas-client-core + ${project.version} + + + joda-time + joda-time + 2.7 + + + + + org.jasig.cas.client + cas-client-core + ${project.version} + test-jar + test + + + org.springframework + spring-test + test + + + org.springframework + spring-core + test + + + diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java b/cas-client-support-saml/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java similarity index 100% rename from cas-client-core/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java rename to cas-client-support-saml/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java diff --git a/cas-client-support-saml/src/main/java/org/jasig/cas/client/util/SamlUtils.java b/cas-client-support-saml/src/main/java/org/jasig/cas/client/util/SamlUtils.java new file mode 100644 index 0000000..8e74a71 --- /dev/null +++ b/cas-client-support-saml/src/main/java/org/jasig/cas/client/util/SamlUtils.java @@ -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(); + } +} diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java b/cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java similarity index 92% rename from cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java rename to cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java index 73aede0..2d2e18f 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java +++ b/cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java @@ -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; } } diff --git a/cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java b/cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java new file mode 100644 index 0000000..4a59081 --- /dev/null +++ b/cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java @@ -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 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 principalAttributes = new HashMap(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 items = new ArrayList(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(); + } +} diff --git a/cas-client-support-saml/src/test/java/org/jasig/cas/client/util/SamlUtilsTest.java b/cas-client-support-saml/src/test/java/org/jasig/cas/client/util/SamlUtilsTest.java new file mode 100644 index 0000000..bd42201 --- /dev/null +++ b/cas-client-support-saml/src/test/java/org/jasig/cas/client/util/SamlUtilsTest.java @@ -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")); + } +} \ No newline at end of file diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java b/cas-client-support-saml/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java similarity index 100% rename from cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java rename to cas-client-support-saml/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java b/cas-client-support-saml/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java similarity index 79% rename from cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java rename to cas-client-support-saml/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java index 417db57..8d90800 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java +++ b/cas-client-support-saml/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java @@ -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 = "testtestPrincipalurn:oasis:names:tc:SAML:1.0:cm:artifact"; server.content = RESPONSE.getBytes(server.encoding); try { @@ -106,22 +107,28 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes final String response = "" + "" + "" + "" + "" - + "https://example.com/test-client/secure/" + + "https://example.com/test-client/secure/" + + "" + "" - + "testPrincipalurn:oasis:names:tc:SAML:1.0:cm:artifacttestPrincipalurn:oasis:names:tc:SAML:1.0:cm:artifact12345" + + "testPrincipal" + + "urn:oasis:names:tc:SAML:1.0:cm:artifact" + + "" + + "testPrincipal" + + "urn:oasis:names:tc:SAML:1.0:cm:artifact" + + "12345" + "" + "ACTIVE" + "" @@ -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()); } diff --git a/pom.xml b/pom.xml index 70befae..e17ab10 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ 4.0.0 org.jasig.cas.client - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT cas-client pom @@ -159,6 +159,56 @@ + + + + org.springframework + spring-core + ${spring.version} + + + commons-logging + commons-logging + + + + + + org.springframework + spring-context + ${spring.version} + + + + org.springframework + spring-test + ${spring.version} + test + + + + log4j + log4j + test + 1.2.15 + + + jmxri + com.sun.jmx + + + com.sun.jdmk + jmxtools + + + javax.jms + jms + + + + + + junit @@ -198,12 +248,14 @@ cas-client-integration-jboss cas-client-support-distributed-ehcache cas-client-support-distributed-memcached + cas-client-support-saml cas-client-integration-tomcat-common cas-client-integration-tomcat-v6 cas-client-integration-tomcat-v7 + 3.1.3.RELEASE 2.2.0 3.0.2 1.7.1