Skip to content

Commit

Permalink
SES-81 Enable WebSSO consumer to verify received AuthnContext
Browse files Browse the repository at this point in the history
  • Loading branch information
vschafer committed Mar 22, 2011
1 parent 1126cfb commit 09263eb
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.context.SAMLMessageContext;
Expand Down Expand Up @@ -201,7 +202,7 @@ public SAMLCredential processAuthenticationResponse(SAMLMessageContext context,

}

private void verifyAssertion(Assertion assertion, AuthnRequest request, SAMLMessageContext context) throws AuthenticationException, SAMLException, org.opensaml.xml.security.SecurityException, ValidationException, DecryptionException {
protected void verifyAssertion(Assertion assertion, AuthnRequest request, SAMLMessageContext context) throws AuthenticationException, SAMLException, org.opensaml.xml.security.SecurityException, ValidationException, DecryptionException {
// Verify storage time skew
if (!isDateTimeSkewValid(getMaxAssertionTime(), assertion.getIssueInstant())) {
log.debug("Authentication statement is too old to be used, value can be customized by setting maxAssertionTime value", assertion.getIssueInstant());
Expand All @@ -218,7 +219,11 @@ private void verifyAssertion(Assertion assertion, AuthnRequest request, SAMLMess
if (assertion.getAuthnStatements().size() > 0) {
verifyAssertionConditions(assertion.getConditions(), context, true);
for (AuthnStatement statement : assertion.getAuthnStatements()) {
verifyAuthenticationStatement(statement, context);
if (request != null) {
verifyAuthenticationStatement(statement, request.getRequestedAuthnContext(), context);
} else {
verifyAuthenticationStatement(statement, null, context);
}
}
} else {
verifyAssertionConditions(assertion.getConditions(), context, false);
Expand Down Expand Up @@ -379,10 +384,11 @@ protected void verifyAssertionConditions(Conditions conditions, SAMLMessageConte
* fields.
*
* @param auth statement to check
* @param requestedAuthnContext original requested context can be null for unsolicited messages or when no context was requested
* @param context message context
* @throws AuthenticationException in case the statement is invalid
*/
protected void verifyAuthenticationStatement(AuthnStatement auth, SAMLMessageContext context) throws AuthenticationException {
protected void verifyAuthenticationStatement(AuthnStatement auth, RequestedAuthnContext requestedAuthnContext, SAMLMessageContext context) throws AuthenticationException {
// Validate that user wasn't authenticated too long time ago
if (!isDateTimeSkewValid(getMaxAuthenticationAge(), auth.getAuthnInstant())) {
log.debug("Authentication statement is too old to be used", auth.getAuthnInstant());
Expand All @@ -395,6 +401,9 @@ protected void verifyAuthenticationStatement(AuthnStatement auth, SAMLMessageCon
throw new CredentialsExpiredException("Users authentication is expired");
}

// Verify context
verifyAuthnContext(requestedAuthnContext, auth.getAuthnContext(), context);

if (auth.getSubjectLocality() != null) {
HTTPInTransport httpInTransport = (HTTPInTransport) context.getInboundMessageTransport();
if (auth.getSubjectLocality().getAddress() != null) {
Expand All @@ -405,6 +414,58 @@ protected void verifyAuthenticationStatement(AuthnStatement auth, SAMLMessageCon
}
}

/**
* Implementation is expected to verify that the requested authentication context corresponds with the received value.
* Identity provider sending the context can be loaded from the SAMLContext.
* <p>
* By default verification is done only for "exact" context. It is checked whether received context contains one of the requested
* method.
* <p>
* In case requestedAuthnContext is null no verification is done.
* <p>
* Method can be reimplemented in subclasses.
*
* @param requestedAuthnContext context requested in the original request, null for unsolicited messages or when no context was required
* @param receivedContext context from the response message
* @param context saml context
* @throws InsufficientAuthenticationException in case expected context doesn't correspond with the received value
*/
protected void verifyAuthnContext(RequestedAuthnContext requestedAuthnContext, AuthnContext receivedContext, SAMLMessageContext context) throws InsufficientAuthenticationException {

if (requestedAuthnContext != null && AuthnContextComparisonTypeEnumeration.EXACT.equals(requestedAuthnContext.getComparison())) {

String classRef = null, declRef = null;

if (receivedContext.getAuthnContextClassRef() != null) {
classRef = receivedContext.getAuthnContextClassRef().getAuthnContextClassRef();
}

if (requestedAuthnContext.getAuthnContextClassRefs() != null) {
for (AuthnContextClassRef classRefRequested : requestedAuthnContext.getAuthnContextClassRefs()) {
if (classRefRequested.getAuthnContextClassRef().equals(classRef)) {
return;
}
}
}

if (receivedContext.getAuthnContextDeclRef() != null) {
declRef = receivedContext.getAuthnContextDeclRef().getAuthnContextDeclRef();
}

if (requestedAuthnContext.getAuthnContextDeclRefs() != null) {
for (AuthnContextDeclRef declRefRequested : requestedAuthnContext.getAuthnContextDeclRefs()) {
if (declRefRequested.getAuthnContextDeclRef().equals(declRef)) {
return;
}
}
}

throw new InsufficientAuthenticationException("Response doesn't contain any of the requested authentication context class or declaration references");

}

}

/**
* Maximum time between authentication of user and processing of an authentication statement.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public class WebSSOProfileOptions implements Serializable, Cloneable {
private boolean allowProxy = true;

private Collection<String> authnContexts;
private AuthnContextComparisonTypeEnumeration authnContextComparison = AuthnContextComparisonTypeEnumeration.MINIMUM;
private AuthnContextComparisonTypeEnumeration authnContextComparison = AuthnContextComparisonTypeEnumeration.EXACT;

public WebSSOProfileOptions() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@
import org.opensaml.Configuration;
import org.opensaml.common.SAMLException;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.*;
import org.opensaml.saml2.core.impl.AuthnContextClassRefImpl;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.saml.context.SAMLContextProvider;
import org.springframework.security.saml.context.SAMLMessageContext;
import org.springframework.security.saml.SAMLTestBase;
Expand All @@ -36,6 +37,8 @@

import javax.servlet.http.HttpServletRequest;

import java.util.Arrays;

import static org.easymock.EasyMock.*;

/**
Expand Down Expand Up @@ -111,7 +114,7 @@ public void testInvalidResponseObject() throws Exception {
@Test
public void testDefaultAuthNStatementPasses() throws Exception {
AuthnStatement statement = helper.getValidAuthStatement();
profile.verifyAuthenticationStatement(statement, messageContext);
profile.verifyAuthenticationStatement(statement, null, messageContext);
}

/**
Expand All @@ -125,7 +128,29 @@ public void testAuthNStatementWithExpiredSessionTime() throws Exception {
DateTime past = new DateTime();
past.minusHours(3);
statement.setSessionNotOnOrAfter(past);
profile.verifyAuthenticationStatement(statement, messageContext);
profile.verifyAuthenticationStatement(statement, null, messageContext);
}

/**
* Verifies that authnContext with exact comparison passes once one of the classRefs is satisifed.
* @throws Exception error
*/
@Test
public void testAuthnExactComparison() throws Exception {
RequestedAuthnContext requestedAuthnContext = helper.getRequestedAuthnContext(AuthnContextComparisonTypeEnumeration.EXACT, Arrays.asList("test", "test2"));
AuthnContext authnContext = helper.getAuthnContext(helper.getClassRef("test2"), null);
profile.verifyAuthnContext(requestedAuthnContext, authnContext, null);
}

/**
* Verifies that authnContext with exact comparison fails when none is satisfied.
* @throws Exception error
*/
@Test(expected = InsufficientAuthenticationException.class)
public void testAuthnExactComparison_none() throws Exception {
RequestedAuthnContext requestedAuthnContext = helper.getRequestedAuthnContext(AuthnContextComparisonTypeEnumeration.EXACT, Arrays.asList("test", "test2"));
AuthnContext authnContext = helper.getAuthnContext(helper.getClassRef("test5"), null);
profile.verifyAuthnContext(requestedAuthnContext, authnContext, null);
}

private void verifyMock() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@
import org.joda.time.DateTime;
import org.opensaml.Configuration;
import org.opensaml.common.SAMLObjectBuilder;
import org.opensaml.saml2.core.AuthnStatement;
import org.opensaml.saml2.core.Response;
import org.opensaml.saml2.core.Status;
import org.opensaml.saml2.core.StatusCode;
import org.opensaml.saml2.core.*;
import org.opensaml.xml.XMLObjectBuilderFactory;

import java.util.Collection;

/**
* Helper class for creation of SAML parts for testing.
*/
Expand Down Expand Up @@ -41,4 +40,36 @@ public AuthnStatement getValidAuthStatement() {
statement.setSessionNotOnOrAfter(expire);
return statement;
}

protected RequestedAuthnContext getRequestedAuthnContext(AuthnContextComparisonTypeEnumeration comparison, Collection<String> contexts) {

SAMLObjectBuilder<RequestedAuthnContext> builder = (SAMLObjectBuilder<RequestedAuthnContext>) builderFactory.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME);
RequestedAuthnContext authnContext = builder.buildObject();
authnContext.setComparison(comparison);

for (String context : contexts) {
authnContext.getAuthnContextClassRefs().add(getClassRef(context));
}

return authnContext;

}

protected AuthnContextClassRef getClassRef(String context) {
SAMLObjectBuilder<AuthnContextClassRef> contextRefBuilder = (SAMLObjectBuilder<AuthnContextClassRef>) builderFactory.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
AuthnContextClassRef authnContextClassRef = contextRefBuilder.buildObject();
authnContextClassRef.setAuthnContextClassRef(context);
return authnContextClassRef;
}

protected AuthnContext getAuthnContext(AuthnContextClassRef classRef, AuthnContextDeclRef declRef) {

SAMLObjectBuilder<AuthnContext> builder = (SAMLObjectBuilder<AuthnContext>) builderFactory.getBuilder(AuthnContext.DEFAULT_ELEMENT_NAME);
AuthnContext authnContext = builder.buildObject();
authnContext.setAuthnContextClassRef(classRef);
authnContext.setAuthnContextDeclRef(declRef);
return authnContext;

}

}

0 comments on commit 09263eb

Please sign in to comment.