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