From e998985732c5dd0d710cd9cfe83a636f1b254781 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Fri, 20 Feb 2015 09:18:05 -0500 Subject: [PATCH 1/9] SAML validation with XPath instead of OpenSAML. --- cas-client-core/pom.xml | 27 +- .../configuration/ConfigurationKeys.java | 1 - .../jasig/cas/client/util/CommonUtils.java | 20 +- .../org/jasig/cas/client/util/IOUtils.java | 74 +++++ .../cas/client/util/MapNamespaceContext.java | 62 ++++ .../org/jasig/cas/client/util/XmlUtils.java | 99 +++++- ...actCasProtocolUrlBasedTicketValidator.java | 4 - .../AbstractUrlBasedTicketValidator.java | 7 - ...0ProxyReceivingTicketValidationFilter.java | 2 +- .../Saml11TicketValidationFilter.java | 4 +- .../validation/Saml11TicketValidator.java | 301 +++++++----------- .../META-INF/cas/samlRequestTemplate.xml | 8 + .../cas/client/util/CommonUtilsTests.java | 7 + .../Saml11TicketValidatorTests.java | 17 +- 14 files changed, 417 insertions(+), 216 deletions(-) create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/util/IOUtils.java create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/util/MapNamespaceContext.java create mode 100644 cas-client-core/src/main/resources/META-INF/cas/samlRequestTemplate.xml diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index b0de44b..795b674 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -11,6 +11,18 @@ Jasig CAS Client for Java - Core + + commons-lang + commons-lang + 2.6 + + + + joda-time + joda-time + 2.7 + + xml-security xmlsec @@ -19,20 +31,6 @@ true - - org.opensaml - opensaml - ${opensaml.version} - jar - compile - - - org.slf4j - jcl-over-slf4j - - - - commons-codec commons-codec @@ -99,6 +97,5 @@ 3.1.3.RELEASE - 2.5.1-1 diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java index 9418151..b9838cb 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java @@ -54,7 +54,6 @@ public interface ConfigurationKeys { ConfigurationKey CAS_SERVER_URL_PREFIX = new ConfigurationKey("casServerUrlPrefix", null); ConfigurationKey ENCODING = new ConfigurationKey("encoding", null); ConfigurationKey TOLERANCE = new ConfigurationKey("tolerance", 1000L); - ConfigurationKey DISABLE_XML_SCHEMA_VALIDATION = new ConfigurationKey("disableXmlSchemaValidation", Boolean.FALSE); ConfigurationKey IGNORE_PATTERN = new ConfigurationKey("ignorePattern", null); ConfigurationKey IGNORE_URL_PATTERN_TYPE = new ConfigurationKey("ignoreUrlPatternType", "REGEX"); ConfigurationKey> HOSTNAME_VERIFIER = new ConfigurationKey>("hostnameVerifier", null); 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..e7cc375 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 @@ -23,6 +23,7 @@ import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import java.text.DateFormat; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import javax.servlet.http.HttpServletRequest; @@ -32,6 +33,11 @@ import org.jasig.cas.client.ssl.HttpURLConnectionFactory; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; import org.jasig.cas.client.validation.ProxyList; import org.jasig.cas.client.validation.ProxyListEditor; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.joda.time.LocalDateTime; +import org.joda.time.format.DateTimeFormatter; +import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,14 +64,22 @@ public final class CommonUtils { private static final HttpURLConnectionFactory DEFAULT_URL_CONNECTION_FACTORY = new HttpsURLConnectionFactory(); + private static final DateTimeFormatter ISO_FORMAT = ISODateTimeFormat.dateTimeNoMillis(); + private 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); + return ISO_FORMAT.print(new DateTime(date).withZone(DateTimeZone.UTC)); + } + + + public static Date parseUtcDate(final String date) { + if (isEmpty(date)) { + return null; + } + return ISODateTimeFormat.dateTimeParser().parseDateTime(date).toDate(); } /** 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..7220465 --- /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.3.1 + */ +public 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..0dd773c --- /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.3.1 + */ +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/XmlUtils.java b/cas-client-core/src/main/java/org/jasig/cas/client/util/XmlUtils.java index f882b04..926ec91 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,17 +19,24 @@ 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.w3c.dom.NodeList; 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.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; +import javax.xml.xpath.*; /** * Common utilities for easily parsing XML without duplicating logic. @@ -45,6 +52,93 @@ 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); + } + } + + + /** + * Compiles the given XPath expression. + * + * @param expression XPath expression. + * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. + * + * @return Compiled XPath expression. + */ + public static XPathExpression compileXPath(final String expression, final NamespaceContext nsContext) { + try { + final XPath xPath = XPathFactory.newInstance().newXPath(); + xPath.setNamespaceContext(nsContext); + return xPath.compile(expression); + } catch (XPathExpressionException e) { + throw new IllegalArgumentException("Invalid XPath expression"); + } + } + + + /** + * Evaluates the given XPath expression as a string result. + * + * @param expression XPath expression. + * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. + * @param document DOM document on which to evaluate expression. + * + * @return Evaluated XPath expression as a string. + */ + public static String evaluateXPathString( + final String expression, final NamespaceContext nsContext, final Document document) { + try { + return (String) compileXPath(expression, nsContext).evaluate(document, XPathConstants.STRING); + } catch (XPathExpressionException e) { + throw new RuntimeException("XPath evaluation error", e); + } + } + + + + /** + * Evaluates the given XPath expression as a node list result. + * + * @param expression XPath expression. + * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. + * @param document DOM document on which to evaluate expression. + * + * @return Evaluated XPath expression as a node list. + */ + public static NodeList evaluateXPathNodeList( + final String expression, final NamespaceContext nsContext, final Document document) { + try { + return (NodeList) compileXPath(expression, nsContext).evaluate(document, XPathConstants.NODESET); + } catch (XPathExpressionException e) { + throw new RuntimeException("XPath evaluation error", e); + } + } + + /** * Get an instance of an XML reader from the XMLReaderFactory. * @@ -62,6 +156,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 8c7a632..c43e958 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/Saml11TicketValidationFilter.java b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java index 73aede0..78d37a3 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java @@ -47,12 +47,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-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 index 007fc5c..506d9a3 100644 --- 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 @@ -22,29 +22,22 @@ 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.AttributePrincipal; import org.jasig.cas.client.authentication.AttributePrincipalImpl; import org.jasig.cas.client.util.CommonUtils; +import org.jasig.cas.client.util.IOUtils; +import org.jasig.cas.client.util.MapNamespaceContext; +import org.jasig.cas.client.util.XmlUtils; 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; +import org.w3c.dom.NodeList; + +import javax.xml.namespace.NamespaceContext; /** * TicketValidator that can understand validating a SAML artifact. This includes the SOAP request/response. @@ -54,34 +47,59 @@ import org.w3c.dom.Element; */ 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); - } - } + /** 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 SAML_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 String XPATH_ASSERTION_DATE_START = "//sa:Assertion/sa:Conditions/@NotBefore"; + + /** XPath expression to extract Assertion validity end date. */ + private static final String XPATH_ASSERTION_DATE_END = "//sa:Assertion/sa:Conditions/@NotOnOrAfter"; + + /** XPath expression to extract NameIdentifier. */ + private static final String XPATH_NAME_ID = "//sa:AuthenticationStatement/sa:Subject/sa:NameIdentifier"; + + /** XPath expression to extract authentication method. */ + private static final String XPATH_AUTH_METHOD = "//sa:AuthenticationStatement/@AuthenticationMethod"; + + /** XPath expression to extract attributes. */ + private static final String XPATH_ATTRIBUTES = "//sa:AttributeStatement/sa:Attribute"; + + private static final String HEX_CHARS = "0123456789abcdef"; /** Time tolerance to allow for time drifting. */ private long tolerance = 1000L; - private final BasicParserPool basicParserPool; + private final Random random; - private final IdentifierGenerator identifierGenerator; + + /** 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); - this.basicParserPool = new BasicParserPool(); - this.basicParserPool.setNamespaceAware(true); try { - this.identifierGenerator = new SecureRandomIdentifierGenerator(); - } catch (final Exception e) { - throw new RuntimeException(e); + random = SecureRandom.getInstance("SHA1PRNG"); + } catch (NoSuchAlgorithmException e) { + throw new IllegalStateException("Cannot find required SHA1PRNG algorithm"); } } @@ -96,95 +114,62 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator 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."); + final Document document = XmlUtils.newDocument(response); + final Date assertionValidityStart = CommonUtils.parseUtcDate( + XmlUtils.evaluateXPathString(XPATH_ASSERTION_DATE_START, SAML_NS_CONTEXT, document)); + final Date assertionValidityEnd = CommonUtils.parseUtcDate( + XmlUtils.evaluateXPathString(XPATH_ASSERTION_DATE_END, SAML_NS_CONTEXT, document)); + if (!isValidAssertion(assertionValidityStart, assertionValidityEnd)) { + throw new TicketValidationException("Invalid SAML assertion"); } - - 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); + final String nameId = XmlUtils.evaluateXPathString(XPATH_NAME_ID, SAML_NS_CONTEXT, document); + if (nameId == null) { + throw new TicketValidationException("SAML assertion does not contain NameIdentifier element"); } - } catch (final UnmarshallingException e) { - throw new TicketValidationException(e); - } catch (final XMLParserException e) { - throw new TicketValidationException(e); + final String authMethod = XmlUtils.evaluateXPathString(XPATH_AUTH_METHOD, SAML_NS_CONTEXT, document); + final NodeList attributes = XmlUtils.evaluateXPathNodeList(XPATH_ATTRIBUTES, SAML_NS_CONTEXT, 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); } - - 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(); - + private boolean isValidAssertion(final Date notBefore, final Date notOnOrAfter) { if (notBefore == null || notOnOrAfter == null) { - logger.debug("Assertion has no bounding dates. Will not process."); + 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(notBefore.minus(this.tolerance), notOnOrAfter.plus(this.tolerance)); + 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."); @@ -192,93 +177,41 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator } if (currentTime.isBefore(validityRange.getStart())) { - logger.debug("skipping assertion that's not yet valid..."); - return false; + logger.debug("Assertion is not yet valid"); + } else { + logger.debug("Assertion is expired"); } - - 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 - + ""; + final String request = String.format( + SAML_REQUEST_TEMPLATE, + generateId(), + CommonUtils.formatForUtcTime(new Date()), + 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("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); - 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); + final Charset charset = CommonUtils.isNotBlank(getEncoding()) ? + Charset.forName(getEncoding()) : IOUtils.UTF8; + conn.getOutputStream().write(request.getBytes(charset)); + conn.getOutputStream().flush(); - String line; - - while ((line = in.readLine()) != null) { - buffer.append(line); - } - return buffer.toString(); + return IOUtils.readString(conn.getInputStream(), charset); } catch (final IOException e) { - throw new RuntimeException(e); + throw new RuntimeException("IO error sending HTTP request to /samlValidate", e); } finally { - CommonUtils.closeQuietly(out); - CommonUtils.closeQuietly(in); if (conn != null) { conn.disconnect(); } @@ -288,4 +221,16 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator 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-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..479a3f3 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,8 @@ package org.jasig.cas.client.util; import java.net.URL; import java.util.ArrayList; import java.util.Collection; +import java.util.Date; + import junit.framework.TestCase; import org.jasig.cas.client.PublicTestHttpServer; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; @@ -192,4 +194,9 @@ public final class CommonUtilsTests extends TestCase { public void testUrlEncode() { assertEquals("this+is+a+very+special+parameter+with+%3D%25%2F", CommonUtils.urlEncode("this is a very special parameter with =%/")); } + + public void testParseUtcDate() { + final Date expected = new Date(1424437961025L); + assertEquals(expected, CommonUtils.parseUtcDate("2015-02-20T08:12:41.025-0500")); + } } diff --git a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java index 417db57..c648232 100644 --- a/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java +++ b/cas-client-core/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java @@ -21,6 +21,7 @@ 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; @@ -117,11 +118,17 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes + "\" NotOnOrAfter=\"" + CommonUtils.formatForUtcTime(range.getEnd().toDate()) + "\">" - + "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()); } From 4527671568414162790e3c4a99d8f04365253236 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Fri, 20 Feb 2015 10:26:01 -0500 Subject: [PATCH 2/9] Sensible XPath processing optimizations. --- .../util/ThreadLocalXPathExpression.java | 87 +++++++++++++++++++ .../org/jasig/cas/client/util/XmlUtils.java | 62 ------------- .../validation/Saml11TicketValidator.java | 32 +++---- 3 files changed, 104 insertions(+), 77 deletions(-) create mode 100644 cas-client-core/src/main/java/org/jasig/cas/client/util/ThreadLocalXPathExpression.java 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..23d6ce1 --- /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.3 + */ +public class ThreadLocalXPathExpression extends ThreadLocal implements XPathExpression { + + /** XPath expression */ + private String expression; + + /** Namespace context. */ + private 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 926ec91..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 @@ -24,7 +24,6 @@ import java.util.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; -import org.w3c.dom.NodeList; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -32,11 +31,9 @@ import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import javax.xml.XMLConstants; -import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; -import javax.xml.xpath.*; /** * Common utilities for easily parsing XML without duplicating logic. @@ -80,65 +77,6 @@ public final class XmlUtils { } } - - /** - * Compiles the given XPath expression. - * - * @param expression XPath expression. - * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. - * - * @return Compiled XPath expression. - */ - public static XPathExpression compileXPath(final String expression, final NamespaceContext nsContext) { - try { - final XPath xPath = XPathFactory.newInstance().newXPath(); - xPath.setNamespaceContext(nsContext); - return xPath.compile(expression); - } catch (XPathExpressionException e) { - throw new IllegalArgumentException("Invalid XPath expression"); - } - } - - - /** - * Evaluates the given XPath expression as a string result. - * - * @param expression XPath expression. - * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. - * @param document DOM document on which to evaluate expression. - * - * @return Evaluated XPath expression as a string. - */ - public static String evaluateXPathString( - final String expression, final NamespaceContext nsContext, final Document document) { - try { - return (String) compileXPath(expression, nsContext).evaluate(document, XPathConstants.STRING); - } catch (XPathExpressionException e) { - throw new RuntimeException("XPath evaluation error", e); - } - } - - - - /** - * Evaluates the given XPath expression as a node list result. - * - * @param expression XPath expression. - * @param nsContext XML namespace context for resolving namespace prefixes in XPath expressions. - * @param document DOM document on which to evaluate expression. - * - * @return Evaluated XPath expression as a node list. - */ - public static NodeList evaluateXPathNodeList( - final String expression, final NamespaceContext nsContext, final Document document) { - try { - return (NodeList) compileXPath(expression, nsContext).evaluate(document, XPathConstants.NODESET); - } catch (XPathExpressionException e) { - throw new RuntimeException("XPath evaluation error", e); - } - } - - /** * Get an instance of an XML reader from the XMLReaderFactory. * 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 index 506d9a3..33cb005 100644 --- 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 @@ -26,10 +26,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.*; import org.jasig.cas.client.authentication.AttributePrincipalImpl; -import org.jasig.cas.client.util.CommonUtils; -import org.jasig.cas.client.util.IOUtils; -import org.jasig.cas.client.util.MapNamespaceContext; -import org.jasig.cas.client.util.XmlUtils; +import org.jasig.cas.client.util.*; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Interval; @@ -54,25 +51,30 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator private static final String SAML_REQUEST_TEMPLATE; /** SAML 1.1. namespace context. */ - private static final NamespaceContext SAML_NS_CONTEXT = new MapNamespaceContext( + 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 String XPATH_ASSERTION_DATE_START = "//sa:Assertion/sa:Conditions/@NotBefore"; + 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 String XPATH_ASSERTION_DATE_END = "//sa:Assertion/sa:Conditions/@NotOnOrAfter"; + 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 String XPATH_NAME_ID = "//sa:AuthenticationStatement/sa:Subject/sa: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 String XPATH_AUTH_METHOD = "//sa:AuthenticationStatement/@AuthenticationMethod"; + private static final ThreadLocalXPathExpression XPATH_AUTH_METHOD = + new ThreadLocalXPathExpression("//sa:AuthenticationStatement/@AuthenticationMethod", NS_CONTEXT); /** XPath expression to extract attributes. */ - private static final String XPATH_ATTRIBUTES = "//sa:AttributeStatement/sa:Attribute"; + private static final ThreadLocalXPathExpression XPATH_ATTRIBUTES = + new ThreadLocalXPathExpression("//sa:AttributeStatement/sa:Attribute", NS_CONTEXT); private static final String HEX_CHARS = "0123456789abcdef"; @@ -118,18 +120,18 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator try { final Document document = XmlUtils.newDocument(response); final Date assertionValidityStart = CommonUtils.parseUtcDate( - XmlUtils.evaluateXPathString(XPATH_ASSERTION_DATE_START, SAML_NS_CONTEXT, document)); + XPATH_ASSERTION_DATE_START.evaluateAsString(document)); final Date assertionValidityEnd = CommonUtils.parseUtcDate( - XmlUtils.evaluateXPathString(XPATH_ASSERTION_DATE_END, SAML_NS_CONTEXT, document)); + XPATH_ASSERTION_DATE_END.evaluateAsString(document)); if (!isValidAssertion(assertionValidityStart, assertionValidityEnd)) { throw new TicketValidationException("Invalid SAML assertion"); } - final String nameId = XmlUtils.evaluateXPathString(XPATH_NAME_ID, SAML_NS_CONTEXT, document); + final String nameId = XPATH_NAME_ID.evaluateAsString(document); if (nameId == null) { throw new TicketValidationException("SAML assertion does not contain NameIdentifier element"); } - final String authMethod = XmlUtils.evaluateXPathString(XPATH_AUTH_METHOD, SAML_NS_CONTEXT, document); - final NodeList attributes = XmlUtils.evaluateXPathNodeList(XPATH_ATTRIBUTES, SAML_NS_CONTEXT, document); + 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; From dd0818b84f94fdc1342162479c1c381db9157314 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Fri, 20 Feb 2015 10:35:05 -0500 Subject: [PATCH 3/9] Fix @since version. --- .../src/main/java/org/jasig/cas/client/util/IOUtils.java | 2 +- .../java/org/jasig/cas/client/util/MapNamespaceContext.java | 2 +- .../org/jasig/cas/client/util/ThreadLocalXPathExpression.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 index 7220465..e8dd77e 100644 --- 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 @@ -8,7 +8,7 @@ import java.nio.charset.Charset; * IO utility class. * * @author Marvin S. Addison - * @since 3.3.1 + * @since 3.3.4 */ public class IOUtils { 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 index 0dd773c..f6c2199 100644 --- 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 @@ -10,7 +10,7 @@ import java.util.Map; * Namespace context implementation backed by a map of XML prefixes to namespace URIs. * * @author Marvin S. Addison - * @since 3.3.1 + * @since 3.3.4 */ public class MapNamespaceContext implements NamespaceContext { 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 index 23d6ce1..86a4a09 100644 --- 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 @@ -11,7 +11,7 @@ import javax.xml.xpath.*; * Thread local XPath expression. * * @author Marvin S. Addison - * @since 3.3 + * @since 3.3.4 */ public class ThreadLocalXPathExpression extends ThreadLocal implements XPathExpression { From d8980535b774c67edc883d6c23aaf0d9ec50e3aa Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Tue, 14 Apr 2015 16:19:12 -0400 Subject: [PATCH 4/9] Move SAML components into separate module. --- cas-client-core/pom.xml | 29 ++++++----- cas-client-integration-atlassian/pom.xml | 5 ++ cas-client-integration-tomcat-v6/pom.xml | 8 +++ cas-client-integration-tomcat-v7/pom.xml | 8 +++ cas-client-support-saml/pom.xml | 38 ++++++++++++++ .../Saml11AuthenticationFilter.java | 0 .../Saml11TicketValidationFilter.java | 0 .../validation/Saml11TicketValidator.java | 0 .../Saml11TicketValidationFilterTests.java | 0 .../Saml11TicketValidatorTests.java | 2 +- pom.xml | 52 +++++++++++++++++++ 11 files changed, 129 insertions(+), 13 deletions(-) create mode 100644 cas-client-support-saml/pom.xml rename {cas-client-core => cas-client-support-saml}/src/main/java/org/jasig/cas/client/authentication/Saml11AuthenticationFilter.java (100%) rename {cas-client-core => cas-client-support-saml}/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidationFilter.java (100%) rename {cas-client-core => cas-client-support-saml}/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java (100%) rename {cas-client-core => cas-client-support-saml}/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidationFilterTests.java (100%) rename {cas-client-core => cas-client-support-saml}/src/test/java/org/jasig/cas/client/validation/Saml11TicketValidatorTests.java (98%) diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index 795b674..08a42b1 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -10,6 +10,23 @@ jar Jasig CAS Client for Java - Core + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + + test-jar + + + + + + + commons-lang @@ -49,27 +66,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 @@ -95,7 +103,4 @@ - - 3.1.3.RELEASE - diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index 96895e9..ce13bce 100644 --- a/cas-client-integration-atlassian/pom.xml +++ b/cas-client-integration-atlassian/pom.xml @@ -46,6 +46,11 @@ true + + org.springframework + spring-context + + atlassian-osuser com.atlassian.osuser diff --git a/cas-client-integration-tomcat-v6/pom.xml b/cas-client-integration-tomcat-v6/pom.xml index 5d40ce4..37d0465 100644 --- a/cas-client-integration-tomcat-v6/pom.xml +++ b/cas-client-integration-tomcat-v6/pom.xml @@ -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..e1068ba 100644 --- a/cas-client-integration-tomcat-v7/pom.xml +++ b/cas-client-integration-tomcat-v7/pom.xml @@ -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-saml/pom.xml b/cas-client-support-saml/pom.xml new file mode 100644 index 0000000..60c49f7 --- /dev/null +++ b/cas-client-support-saml/pom.xml @@ -0,0 +1,38 @@ + + + org.jasig.cas.client + 3.3.4-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} + + + + 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-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 100% 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 diff --git a/cas-client-core/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 similarity index 100% rename from cas-client-core/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java rename to cas-client-support-saml/src/main/java/org/jasig/cas/client/validation/Saml11TicketValidator.java 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 98% 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 c648232..7738966 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 @@ -46,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); } diff --git a/pom.xml b/pom.xml index 70befae..bac41ce 100644 --- a/pom.xml +++ b/pom.xml @@ -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 From 6e261e7251700f00e78e99a34b6628fcf15a49d0 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Mon, 20 Apr 2015 10:34:28 -0400 Subject: [PATCH 5/9] Issue #100 Respond to code review feedback. --- .../src/main/java/org/jasig/cas/client/util/IOUtils.java | 2 +- .../org/jasig/cas/client/util/ThreadLocalXPathExpression.java | 4 ++-- .../jasig/cas/client/validation/Saml11TicketValidator.java | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) 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 index e8dd77e..f3c3107 100644 --- 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 @@ -10,7 +10,7 @@ import java.nio.charset.Charset; * @author Marvin S. Addison * @since 3.3.4 */ -public class IOUtils { +public final class IOUtils { /** UTF-8 character set. */ public static final Charset UTF8 = Charset.forName("UTF-8"); 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 index 86a4a09..f1ed1f4 100644 --- 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 @@ -16,10 +16,10 @@ import javax.xml.xpath.*; public class ThreadLocalXPathExpression extends ThreadLocal implements XPathExpression { /** XPath expression */ - private String expression; + private final String expression; /** Namespace context. */ - private NamespaceContext context; + private final NamespaceContext context; /** * Creates a new instance from an XPath expression and namespace context. 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 index 33cb005..6446544 100644 --- 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 @@ -197,7 +197,6 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator conn = this.getURLConnectionFactory().buildHttpURLConnection(validationUrl.openConnection()); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "text/xml"); - 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); From 70ed5fd8bbe7bd324e62cb5f03904c4bb3d422e1 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Mon, 4 May 2015 16:07:47 -0400 Subject: [PATCH 6/9] Add Marvin to SAML validator component authorship. --- .../cas/client/validation/Saml11TicketValidationFilter.java | 1 + .../org/jasig/cas/client/validation/Saml11TicketValidator.java | 1 + 2 files changed, 2 insertions(+) diff --git a/cas-client-support-saml/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 index 78d37a3..2d2e18f 100644 --- a/cas-client-support-saml/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 */ 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 index 6446544..f1e5f3a 100644 --- 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 @@ -40,6 +40,7 @@ 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 { From 7c586299585c365842ee8c12707027005a04c7a3 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Mon, 4 May 2015 16:09:32 -0400 Subject: [PATCH 7/9] Issue 100 Restore configuration key for backward compatibility. --- .../org/jasig/cas/client/configuration/ConfigurationKeys.java | 1 + 1 file changed, 1 insertion(+) diff --git a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java index b9838cb..9418151 100644 --- a/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java +++ b/cas-client-core/src/main/java/org/jasig/cas/client/configuration/ConfigurationKeys.java @@ -54,6 +54,7 @@ public interface ConfigurationKeys { ConfigurationKey CAS_SERVER_URL_PREFIX = new ConfigurationKey("casServerUrlPrefix", null); ConfigurationKey ENCODING = new ConfigurationKey("encoding", null); ConfigurationKey TOLERANCE = new ConfigurationKey("tolerance", 1000L); + ConfigurationKey DISABLE_XML_SCHEMA_VALIDATION = new ConfigurationKey("disableXmlSchemaValidation", Boolean.FALSE); ConfigurationKey IGNORE_PATTERN = new ConfigurationKey("ignorePattern", null); ConfigurationKey IGNORE_URL_PATTERN_TYPE = new ConfigurationKey("ignoreUrlPatternType", "REGEX"); ConfigurationKey> HOSTNAME_VERIFIER = new ConfigurationKey>("hostnameVerifier", null); From 1edef62ecb9b4fbeb8ddb993d28c192b1146daa8 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Mon, 11 May 2015 12:01:48 -0400 Subject: [PATCH 8/9] Issue #100 Isolate JodaTime to SAML module. --- cas-client-core/pom.xml | 6 --- .../jasig/cas/client/util/CommonUtils.java | 22 -------- .../cas/client/util/CommonUtilsTests.java | 6 --- cas-client-support-saml/pom.xml | 6 +++ .../org/jasig/cas/client/util/SamlUtils.java | 53 +++++++++++++++++++ .../validation/Saml11TicketValidator.java | 6 +-- .../jasig/cas/client/util/SamlUtilsTest.java | 40 ++++++++++++++ .../Saml11TicketValidatorTests.java | 20 +++---- 8 files changed, 112 insertions(+), 47 deletions(-) create mode 100644 cas-client-support-saml/src/main/java/org/jasig/cas/client/util/SamlUtils.java create mode 100644 cas-client-support-saml/src/test/java/org/jasig/cas/client/util/SamlUtilsTest.java diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index 08a42b1..f82ac35 100644 --- a/cas-client-core/pom.xml +++ b/cas-client-core/pom.xml @@ -34,12 +34,6 @@ 2.6 - - joda-time - joda-time - 2.7 - - xml-security xmlsec 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 e7cc375..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,9 +22,6 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; import java.util.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -33,11 +30,6 @@ import org.jasig.cas.client.ssl.HttpURLConnectionFactory; import org.jasig.cas.client.ssl.HttpsURLConnectionFactory; import org.jasig.cas.client.validation.ProxyList; import org.jasig.cas.client.validation.ProxyListEditor; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.LocalDateTime; -import org.joda.time.format.DateTimeFormatter; -import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,24 +56,10 @@ public final class CommonUtils { private static final HttpURLConnectionFactory DEFAULT_URL_CONNECTION_FACTORY = new HttpsURLConnectionFactory(); - private static final DateTimeFormatter ISO_FORMAT = ISODateTimeFormat.dateTimeNoMillis(); - private CommonUtils() { // 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 (isEmpty(date)) { - return null; - } - return ISODateTimeFormat.dateTimeParser().parseDateTime(date).toDate(); - } - /** * 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/test/java/org/jasig/cas/client/util/CommonUtilsTests.java b/cas-client-core/src/test/java/org/jasig/cas/client/util/CommonUtilsTests.java index 479a3f3..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,7 +21,6 @@ package org.jasig.cas.client.util; import java.net.URL; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import junit.framework.TestCase; import org.jasig.cas.client.PublicTestHttpServer; @@ -194,9 +193,4 @@ public final class CommonUtilsTests extends TestCase { public void testUrlEncode() { assertEquals("this+is+a+very+special+parameter+with+%3D%25%2F", CommonUtils.urlEncode("this is a very special parameter with =%/")); } - - public void testParseUtcDate() { - final Date expected = new Date(1424437961025L); - assertEquals(expected, CommonUtils.parseUtcDate("2015-02-20T08:12:41.025-0500")); - } } diff --git a/cas-client-support-saml/pom.xml b/cas-client-support-saml/pom.xml index 60c49f7..b63ae11 100644 --- a/cas-client-support-saml/pom.xml +++ b/cas-client-support-saml/pom.xml @@ -16,7 +16,13 @@ cas-client-core ${project.version} + + joda-time + joda-time + 2.7 + + org.jasig.cas.client cas-client-core 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-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 index f1e5f3a..4a59081 100644 --- 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 @@ -120,9 +120,9 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator protected Assertion parseResponseFromServer(final String response) throws TicketValidationException { try { final Document document = XmlUtils.newDocument(response); - final Date assertionValidityStart = CommonUtils.parseUtcDate( + final Date assertionValidityStart = SamlUtils.parseUtcDate( XPATH_ASSERTION_DATE_START.evaluateAsString(document)); - final Date assertionValidityEnd = CommonUtils.parseUtcDate( + final Date assertionValidityEnd = SamlUtils.parseUtcDate( XPATH_ASSERTION_DATE_END.evaluateAsString(document)); if (!isValidAssertion(assertionValidityStart, assertionValidityEnd)) { throw new TicketValidationException("Invalid SAML assertion"); @@ -191,7 +191,7 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator final String request = String.format( SAML_REQUEST_TEMPLATE, generateId(), - CommonUtils.formatForUtcTime(new Date()), + SamlUtils.formatForUtcTime(new Date()), ticket); HttpURLConnection conn = null; try { 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-support-saml/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 index 7738966..8d90800 100644 --- a/cas-client-support-saml/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 @@ -24,7 +24,7 @@ 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; @@ -83,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 { @@ -107,21 +107,21 @@ public final class Saml11TicketValidatorTests extends AbstractTicketValidatorTes final String response = "" + "" + "" + "" + "" + "https://example.com/test-client/secure/" + "" + "" + "testPrincipal" + "urn:oasis:names:tc:SAML:1.0:cm:artifact" From f5b2275913090136b70681eda703422d0b7b4102 Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Mon, 11 May 2015 12:09:00 -0400 Subject: [PATCH 9/9] Issue #100 Bump minor version due to refactoring. --- cas-client-core/pom.xml | 2 +- .../src/main/java/org/jasig/cas/client/util/IOUtils.java | 2 +- .../java/org/jasig/cas/client/util/MapNamespaceContext.java | 2 +- .../org/jasig/cas/client/util/ThreadLocalXPathExpression.java | 2 +- cas-client-integration-atlassian/pom.xml | 2 +- cas-client-integration-jboss/pom.xml | 2 +- cas-client-integration-tomcat-common/pom.xml | 2 +- cas-client-integration-tomcat-v6/pom.xml | 2 +- cas-client-integration-tomcat-v7/pom.xml | 2 +- cas-client-support-distributed-ehcache/pom.xml | 2 +- cas-client-support-distributed-memcached/pom.xml | 2 +- cas-client-support-saml/pom.xml | 2 +- pom.xml | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cas-client-core/pom.xml b/cas-client-core/pom.xml index f82ac35..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 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 index f3c3107..b003775 100644 --- 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 @@ -8,7 +8,7 @@ import java.nio.charset.Charset; * IO utility class. * * @author Marvin S. Addison - * @since 3.3.4 + * @since 3.4 */ public final class IOUtils { 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 index f6c2199..6eb5b62 100644 --- 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 @@ -10,7 +10,7 @@ import java.util.Map; * Namespace context implementation backed by a map of XML prefixes to namespace URIs. * * @author Marvin S. Addison - * @since 3.3.4 + * @since 3.4 */ public class MapNamespaceContext implements NamespaceContext { 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 index f1ed1f4..2e51c0c 100644 --- 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 @@ -11,7 +11,7 @@ import javax.xml.xpath.*; * Thread local XPath expression. * * @author Marvin S. Addison - * @since 3.3.4 + * @since 3.4 */ public class ThreadLocalXPathExpression extends ThreadLocal implements XPathExpression { diff --git a/cas-client-integration-atlassian/pom.xml b/cas-client-integration-atlassian/pom.xml index ce13bce..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 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 37d0465..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 diff --git a/cas-client-integration-tomcat-v7/pom.xml b/cas-client-integration-tomcat-v7/pom.xml index e1068ba..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 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 index b63ae11..6c68d43 100644 --- a/cas-client-support-saml/pom.xml +++ b/cas-client-support-saml/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/pom.xml b/pom.xml index bac41ce..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