From 618b8a5dabf136586d0bcc2778bee1f3d500a9ac Mon Sep 17 00:00:00 2001 From: "Marvin S. Addison" Date: Fri, 20 Feb 2015 10:26:01 -0500 Subject: [PATCH] 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;