parent
e10299896a
commit
616bbfa403
|
|
@ -24,10 +24,9 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.opensaml</groupId>
|
<groupId>org.opensaml</groupId>
|
||||||
<artifactId>opensaml</artifactId>
|
<artifactId>opensaml</artifactId>
|
||||||
<version>1.1</version>
|
<version>${opensaml.version}</version>
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
<scope>provided</scope>
|
<scope>compile</scope>
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
@ -90,5 +89,6 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<spring.version>2.5.6.SEC01</spring.version>
|
<spring.version>2.5.6.SEC01</spring.version>
|
||||||
|
<opensaml.version>2.5.1-1</opensaml.version>
|
||||||
</properties>
|
</properties>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,18 @@ package org.jasig.cas.client.validation;
|
||||||
import org.jasig.cas.client.authentication.AttributePrincipal;
|
import org.jasig.cas.client.authentication.AttributePrincipal;
|
||||||
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
|
import org.jasig.cas.client.authentication.AttributePrincipalImpl;
|
||||||
import org.jasig.cas.client.util.CommonUtils;
|
import org.jasig.cas.client.util.CommonUtils;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
import org.opensaml.*;
|
import org.opensaml.*;
|
||||||
|
import org.opensaml.common.IdentifierGenerator;
|
||||||
|
import org.opensaml.common.impl.SecureRandomIdentifierGenerator;
|
||||||
|
import org.opensaml.saml1.core.*;
|
||||||
|
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.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
|
|
@ -44,8 +55,21 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
|
||||||
/** Time tolerance to allow for time drifting. */
|
/** Time tolerance to allow for time drifting. */
|
||||||
private long tolerance = 1000L;
|
private long tolerance = 1000L;
|
||||||
|
|
||||||
|
private final BasicParserPool basicParserPool;
|
||||||
|
|
||||||
|
private final IdentifierGenerator identifierGenerator;
|
||||||
|
|
||||||
|
|
||||||
public Saml11TicketValidator(final String casServerUrlPrefix) {
|
public Saml11TicketValidator(final String casServerUrlPrefix) {
|
||||||
super(casServerUrlPrefix);
|
super(casServerUrlPrefix);
|
||||||
|
try {
|
||||||
|
DefaultBootstrap.bootstrap();
|
||||||
|
this.basicParserPool = new BasicParserPool();
|
||||||
|
this.basicParserPool.setNamespaceAware(true);
|
||||||
|
this.identifierGenerator = new SecureRandomIdentifierGenerator();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getUrlSuffix() {
|
protected String getUrlSuffix() {
|
||||||
|
|
@ -62,9 +86,7 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
|
||||||
@Override
|
@Override
|
||||||
protected void setDisableXmlSchemaValidation(final boolean disabled) {
|
protected void setDisableXmlSchemaValidation(final boolean disabled) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
// according to our reading of the SAML 1.1 code, this should disable the schema checking. However, there may be a couple
|
this.basicParserPool.setSchema(null);
|
||||||
// of error messages that slip through on start up!
|
|
||||||
XML.parserPool.setDefaultSchemas(null, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,69 +102,76 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
|
||||||
try {
|
try {
|
||||||
final String removeStartOfSoapBody = response.substring(response.indexOf("<SOAP-ENV:Body>") + 15);
|
final String removeStartOfSoapBody = response.substring(response.indexOf("<SOAP-ENV:Body>") + 15);
|
||||||
final String removeEndOfSoapBody = removeStartOfSoapBody.substring(0, removeStartOfSoapBody.indexOf("</SOAP-ENV:Body>"));
|
final String removeEndOfSoapBody = removeStartOfSoapBody.substring(0, removeStartOfSoapBody.indexOf("</SOAP-ENV:Body>"));
|
||||||
final SAMLResponse samlResponse = new SAMLResponse(new ByteArrayInputStream(getBytes(removeEndOfSoapBody)));
|
final Document responseDocument = this.basicParserPool.parse(new ByteArrayInputStream(getBytes(removeEndOfSoapBody)));
|
||||||
|
final Element responseRoot = responseDocument.getDocumentElement();
|
||||||
|
final UnmarshallerFactory unmarshallerFactory = Configuration.getUnmarshallerFactory();
|
||||||
|
final Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(responseRoot);
|
||||||
|
|
||||||
if (!samlResponse.getAssertions().hasNext()) {
|
final Response samlResponse = (Response) unmarshaller.unmarshall(responseRoot);
|
||||||
|
|
||||||
|
final List<org.opensaml.saml1.core.Assertion> assertions = samlResponse.getAssertions();
|
||||||
|
if (assertions.isEmpty()) {
|
||||||
throw new TicketValidationException("No assertions found.");
|
throw new TicketValidationException("No assertions found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (final Iterator<?> iter = samlResponse.getAssertions(); iter.hasNext();) {
|
for (final org.opensaml.saml1.core.Assertion assertion : assertions) {
|
||||||
final SAMLAssertion assertion = (SAMLAssertion) iter.next();
|
|
||||||
|
|
||||||
if (!isValidAssertion(assertion)) {
|
if (!isValidAssertion(assertion)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
final SAMLAuthenticationStatement authenticationStatement = getSAMLAuthenticationStatement(assertion);
|
final AuthenticationStatement authenticationStatement = getSAMLAuthenticationStatement(assertion);
|
||||||
|
|
||||||
if (authenticationStatement == null) {
|
if (authenticationStatement == null) {
|
||||||
throw new TicketValidationException("No AuthentiationStatement found in SAML Assertion.");
|
throw new TicketValidationException("No AuthentiationStatement found in SAML Assertion.");
|
||||||
}
|
}
|
||||||
final SAMLSubject subject = authenticationStatement.getSubject();
|
final Subject subject = authenticationStatement.getSubject();
|
||||||
|
|
||||||
if (subject == null) {
|
if (subject == null) {
|
||||||
throw new TicketValidationException("No Subject found in SAML Assertion.");
|
throw new TicketValidationException("No Subject found in SAML Assertion.");
|
||||||
}
|
}
|
||||||
|
|
||||||
final SAMLAttribute[] attributes = getAttributesFor(assertion, subject);
|
final List<Attribute> attributes = getAttributesFor(assertion, subject);
|
||||||
final Map<String,Object> personAttributes = new HashMap<String,Object>();
|
final Map<String,Object> personAttributes = new HashMap<String,Object>();
|
||||||
for (final SAMLAttribute samlAttribute : attributes) {
|
for (final Attribute samlAttribute : attributes) {
|
||||||
final List<?> values = getValuesFrom(samlAttribute);
|
final List<?> values = getValuesFrom(samlAttribute);
|
||||||
|
|
||||||
personAttributes.put(samlAttribute.getName(), values.size() == 1 ? values.get(0) : values);
|
personAttributes.put(samlAttribute.getAttributeName(), values.size() == 1 ? values.get(0) : values);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AttributePrincipal principal = new AttributePrincipalImpl(subject.getNameIdentifier().getName(), personAttributes);
|
final AttributePrincipal principal = new AttributePrincipalImpl(subject.getNameIdentifier().getNameIdentifier(), personAttributes);
|
||||||
|
|
||||||
final Map<String,Object> authenticationAttributes = new HashMap<String,Object>();
|
final Map<String,Object> authenticationAttributes = new HashMap<String,Object>();
|
||||||
authenticationAttributes.put("samlAuthenticationStatement::authMethod", authenticationStatement.getAuthMethod());
|
authenticationAttributes.put("samlAuthenticationStatement::authMethod", authenticationStatement.getAuthenticationMethod());
|
||||||
|
|
||||||
return new AssertionImpl(principal, authenticationAttributes);
|
return new AssertionImpl(principal, authenticationAttributes);
|
||||||
}
|
}
|
||||||
} catch (final SAMLException e) {
|
} catch (final UnmarshallingException e) {
|
||||||
|
throw new TicketValidationException(e);
|
||||||
|
} catch (final XMLParserException e) {
|
||||||
throw new TicketValidationException(e);
|
throw new TicketValidationException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new TicketValidationException("No Assertion found within valid time range. Either there's a replay of the ticket or there's clock drift. Check tolerance range, or server/client synchronization.");
|
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 SAMLAssertion assertion) {
|
private boolean isValidAssertion(final org.opensaml.saml1.core.Assertion assertion) {
|
||||||
final Date notBefore = assertion.getNotBefore();
|
final DateTime notBefore = assertion.getConditions().getNotBefore();
|
||||||
final Date notOnOrAfter = assertion.getNotOnOrAfter();
|
final DateTime notOnOrAfter = assertion.getConditions().getNotOnOrAfter();
|
||||||
|
|
||||||
if (assertion.getNotBefore() == null || assertion.getNotOnOrAfter() == null) {
|
if (notBefore == null || notOnOrAfter == null) {
|
||||||
log.debug("Assertion has no bounding dates. Will not process.");
|
log.debug("Assertion has no bounding dates. Will not process.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final long currentTime = getCurrentTimeInUtc().getTime();
|
final long currentTime = getCurrentTimeInUtc().getTime();
|
||||||
|
|
||||||
if (currentTime + tolerance < notBefore.getTime()) {
|
if (currentTime + tolerance < notBefore.getMillis()) {
|
||||||
log.debug("skipping assertion that's not yet valid...");
|
log.debug("skipping assertion that's not yet valid...");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notOnOrAfter.getTime() <= currentTime - tolerance) {
|
if (notOnOrAfter.getMillis() <= currentTime - tolerance) {
|
||||||
log.debug("skipping expired assertion...");
|
log.debug("skipping expired assertion...");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -150,40 +179,32 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SAMLAuthenticationStatement getSAMLAuthenticationStatement(final SAMLAssertion assertion) {
|
private AuthenticationStatement getSAMLAuthenticationStatement(final org.opensaml.saml1.core.Assertion assertion) {
|
||||||
for (final Iterator<?> iter = assertion.getStatements(); iter.hasNext();) {
|
final List<AuthenticationStatement> statements = assertion.getAuthenticationStatements();
|
||||||
final SAMLStatement statement = (SAMLStatement) iter.next();
|
|
||||||
|
|
||||||
if (statement instanceof SAMLAuthenticationStatement) {
|
if (statements.isEmpty()) {
|
||||||
return (SAMLAuthenticationStatement) statement;
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return statements.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Attribute> getAttributesFor(final org.opensaml.saml1.core.Assertion assertion, final Subject subject) {
|
||||||
|
final List<Attribute> attributes = new ArrayList<Attribute>();
|
||||||
|
for (final AttributeStatement attribute : assertion.getAttributeStatements()) {
|
||||||
|
if (subject.getNameIdentifier().getNameIdentifier().equals(attribute.getSubject().getNameIdentifier().getNameIdentifier())) {
|
||||||
|
attributes.addAll(attribute.getAttributes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SAMLAttribute[] getAttributesFor(final SAMLAssertion assertion, final SAMLSubject subject) {
|
private List<?> getValuesFrom(final Attribute attribute) {
|
||||||
final List<SAMLAttribute> attributes = new ArrayList<SAMLAttribute>();
|
|
||||||
for (final Iterator<?> iter = assertion.getStatements(); iter.hasNext();) {
|
|
||||||
final SAMLStatement statement = (SAMLStatement) iter.next();
|
|
||||||
|
|
||||||
if (statement instanceof SAMLAttributeStatement) {
|
|
||||||
final SAMLAttributeStatement attributeStatement = (SAMLAttributeStatement) statement;
|
|
||||||
// used because SAMLSubject does not implement equals
|
|
||||||
if (subject.getNameIdentifier().getName().equals(attributeStatement.getSubject().getNameIdentifier().getName())) {
|
|
||||||
for (final Iterator<?> iter2 = attributeStatement.getAttributes(); iter2.hasNext();)
|
|
||||||
attributes.add((SAMLAttribute) iter2.next());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return attributes.toArray(new SAMLAttribute[attributes.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<?> getValuesFrom(final SAMLAttribute attribute) {
|
|
||||||
final List<Object> list = new ArrayList<Object>();
|
final List<Object> list = new ArrayList<Object>();
|
||||||
for (final Iterator<?> iter = attribute.getValues(); iter.hasNext();) {
|
// TODO I'm not actually sure if this is safe!
|
||||||
list.add(iter.next());
|
for (final Object o : attribute.getAttributeValues()) {
|
||||||
|
list.add(o.toString());
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
@ -195,16 +216,10 @@ public final class Saml11TicketValidator extends AbstractUrlBasedTicketValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
|
protected String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
|
||||||
|
final String MESSAGE_TO_SEND = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\"" + this.identifierGenerator.generateIdentifier() + "\" IssueInstant=\"" + CommonUtils.formatForUtcTime(new Date()) + "\">"
|
||||||
String MESSAGE_TO_SEND;
|
|
||||||
|
|
||||||
try {
|
|
||||||
MESSAGE_TO_SEND = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Header/><SOAP-ENV:Body><samlp:Request xmlns:samlp=\"urn:oasis:names:tc:SAML:1.0:protocol\" MajorVersion=\"1\" MinorVersion=\"1\" RequestID=\"" + SAMLIdentifierFactory.getInstance().getIdentifier() + "\" IssueInstant=\"" + CommonUtils.formatForUtcTime(new Date()) + "\">"
|
|
||||||
+ "<samlp:AssertionArtifact>" + ticket
|
+ "<samlp:AssertionArtifact>" + ticket
|
||||||
+ "</samlp:AssertionArtifact></samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
+ "</samlp:AssertionArtifact></samlp:Request></SOAP-ENV:Body></SOAP-ENV:Envelope>";
|
||||||
} catch (final SAMLException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
HttpURLConnection conn = null;
|
HttpURLConnection conn = null;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue