Skip to content

Commit

Permalink
SES-89 Added support for specifying index of AssertionConsumerService…
Browse files Browse the repository at this point in the history
… caller wants to receive response from IDP at.
  • Loading branch information
vschafer committed Apr 11, 2011
1 parent 4aadd50 commit d651f27
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,11 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.saml.websso.WebSSOProfileOptions;
import org.springframework.util.Assert;
import sun.misc.Regexp;

import javax.servlet.http.HttpServletRequest;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Utility class for SAML entities
Expand All @@ -42,35 +38,62 @@ public class SAMLUtil {
private final static Logger log = LoggerFactory.getLogger(SAMLUtil.class);

/**
* Returns assertion consumer service of the given SP for the given binding. If the specified binding
* can't be found, default binding is returned. In case no binding is marked as default, first binding
* for assertionConsumer endpoint is used. In case SP doesn't contain any assertionConsumer endpoint
* exception is thrown.
* Returns assertion consumer service of the given SP.
* <p/>
* When binding is specified locates assertionConsumer which can accept message using given binding. Fails is none is found.
* <p/>
* When no binding is specified (null) tries to locate consumer service determined by the WebSSOProfileOptions field
* assertionConsumerIndex. In case index is invalid processing fails.
* <p/>
* When options do not contain any index default consumer service is used or first consumer service when no default
* is specified.
*
* @param descriptor descriptor to search for binding in
* @param binding binding type
* @return consumer service capable of handling the given binding
* @throws MetadataProviderException in case there is not service capable of handling the binding
* @param idpssoDescriptor idp, can be null when no IDP is known in advance
* @param spDescriptor sp
* @param options user supplied preferences
* @param binding binding to be used, overrides other settings
* @return consumer service or null
* @throws MetadataProviderException in case index supplied in options is invalid or no consumer service can be found
*/
public static AssertionConsumerService getAssertionConsumerForBinding(SPSSODescriptor descriptor, String binding) throws MetadataProviderException {
List<AssertionConsumerService> services = descriptor.getAssertionConsumerServices();
AssertionConsumerService foundService = null;
for (AssertionConsumerService service : services) {
if (binding.equals(service.getBinding())) {
return service;
} else if (foundService == null) {
foundService = service;
public static AssertionConsumerService getAssertionConsumerForBinding(IDPSSODescriptor idpssoDescriptor, SPSSODescriptor spDescriptor, WebSSOProfileOptions options, String binding) throws MetadataProviderException {

List<AssertionConsumerService> services = spDescriptor.getAssertionConsumerServices();

// Fixed binding
if (binding != null) {
for (AssertionConsumerService service : services) {
if (binding.equals(service.getBinding())) {
log.debug("Using consumer service determined by fixed binding {}", binding);
return service;
}
}
throw new MetadataProviderException("No consumer service found for binding " + binding);
}

// Use user preference
if (options.getAssertionConsumerIndex() != null) {
for (AssertionConsumerService service : services) {
if (options.getAssertionConsumerIndex().equals(service.getIndex())) {
log.debug("Using consumer service determined by user preference with binding {}", service.getBinding());
return service;
}
}
throw new MetadataProviderException("AssertionConsumerIndex " + options.getAssertionConsumerIndex() + " not found for spDescriptor " + spDescriptor);
}

if (descriptor.getDefaultAssertionConsumerService() != null) {
return descriptor.getDefaultAssertionConsumerService();
} else if (foundService != null) {
return foundService;
if (spDescriptor.getDefaultAssertionConsumerService() != null) {
AssertionConsumerService service = spDescriptor.getDefaultAssertionConsumerService();
log.debug("Using default consumer service with binding {}", service.getBinding());
return service;
} else if (services.size() > 0) {
AssertionConsumerService service = services.iterator().next();
log.debug("Using first available consumer service with binding {}", service.getBinding());
return service;
} else {
log.debug("No consumer service found for SP");
throw new MetadataProviderException("Service provider has no available consumer services " + spDescriptor);
}

log.debug("No binding found for SP with binding " + binding);
throw new MetadataProviderException("Binding " + binding + " is not supported for this SP and no other was found");
}

/**
Expand Down Expand Up @@ -111,6 +134,15 @@ public static SingleLogoutService getLogoutServiceForBinding(SSODescriptor descr
throw new MetadataProviderException("Binding " + binding + " is not supported for this IDP");
}

/**
* Selects binding used to sent message to the IDP.
*
* @param options user specified preferences
* @param idp idp
* @param sp sp
* @return binding to use for message delivery
* @throws MetadataProviderException error
*/
public static String getLoginBinding(WebSSOProfileOptions options, IDPSSODescriptor idp, SPSSODescriptor sp) throws MetadataProviderException {

String requiredBinding = options.getBinding();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
import org.springframework.security.saml.metadata.MetadataManager;
import org.springframework.security.saml.processor.SAMLProcessor;
import org.springframework.security.saml.storage.SAMLMessageStorage;
import org.springframework.security.saml.util.SAMLUtil;

import java.util.Set;

Expand All @@ -56,7 +55,7 @@ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileO
throws SAMLException, MetadataProviderException, MessageEncodingException {

SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
AssertionConsumerService assertionConsumer = SAMLUtil.getAssertionConsumerForBinding(spDescriptor, SAMLConstants.SAML2_PAOS_BINDING_URI);
AssertionConsumerService assertionConsumer = getAssertionConsumerService(null, spDescriptor, options, SAMLConstants.SAML2_PAOS_BINDING_URI);

// The last parameter refers to the IdP that should receive the message. However,
// in ECP, we don't know in advance which IdP will be contacted.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,33 +77,59 @@ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileO
SPSSODescriptor spDescriptor = (SPSSODescriptor) context.getLocalEntityRoleMetadata();
IDPSSODescriptor idpssoDescriptor = getIDPDescriptor(idpId);
ExtendedMetadata idpExtendedMetadata = metadata.getExtendedMetadata(idpId);
String binding = SAMLUtil.getLoginBinding(options, idpssoDescriptor, spDescriptor);
SingleSignOnService bindingService = SAMLUtil.getSSOServiceForBinding(idpssoDescriptor, binding);
boolean sign = spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned();

AssertionConsumerService assertionConsumerForBinding = SAMLUtil.getAssertionConsumerForBinding(spDescriptor, binding);
AuthnRequest authRequest = getAuthnRequest(context, options, assertionConsumerForBinding, bindingService);
SingleSignOnService ssoService = getSingleSignOnService(idpssoDescriptor, spDescriptor, options);
AssertionConsumerService consumerService = getAssertionConsumerService(idpssoDescriptor, spDescriptor, options, null);
AuthnRequest authRequest = getAuthnRequest(context, options, consumerService, ssoService);

// TODO optionally implement support for conditions, subject

context.setCommunicationProfileId(bindingService.getBinding());
context.setCommunicationProfileId(ssoService.getBinding());
context.setOutboundMessage(authRequest);
context.setOutboundSAMLMessage(authRequest);
context.setPeerEntityEndpoint(bindingService);
context.setPeerEntityEndpoint(ssoService);
context.setPeerEntityId(idpssoDescriptor.getID());
context.setPeerEntityRoleMetadata(idpssoDescriptor);
context.setPeerExtendedMetadata(idpExtendedMetadata);

boolean sign = spDescriptor.isAuthnRequestsSigned() || idpssoDescriptor.getWantAuthnRequestsSigned();
sendMessage(context, sign);
messageStorage.storeMessage(authRequest.getID(), authRequest);

}

/**
* Method determines SingleSignOn service used to deliver AuthNRequest to the IDP. Service also determines the used binding.
* When set value binding from WebSSOProfileOptions is used to prioritize the service.
*
* @param idpssoDescriptor idp
* @param spDescriptor sp
* @param options user supplied preferences
* @return service to send message to
* @throws MetadataProviderException in case service can't be determined
*/
protected SingleSignOnService getSingleSignOnService(IDPSSODescriptor idpssoDescriptor, SPSSODescriptor spDescriptor, WebSSOProfileOptions options) throws MetadataProviderException {
return SAMLUtil.getSSOServiceForBinding(idpssoDescriptor, SAMLUtil.getLoginBinding(options, idpssoDescriptor, spDescriptor));
}

/**
* Determines assertion consumer service where IDP should send reply to the AuthnRequest.
*
* @param idpssoDescriptor idp, can be null when no IDP is known in advance
* @param spDescriptor sp
* @param options user supplied preferences
* @param binding binding to be used, overrides other settings
* @return consumer service or null
* @throws MetadataProviderException in case index supplied in options is invalid or no consumer service can be found
*/
protected AssertionConsumerService getAssertionConsumerService(IDPSSODescriptor idpssoDescriptor, SPSSODescriptor spDescriptor, WebSSOProfileOptions options, String binding) throws MetadataProviderException {
return SAMLUtil.getAssertionConsumerForBinding(idpssoDescriptor, spDescriptor, options, binding);
}

/**
* Returns AuthnRequest SAML message to be used to demand authentication from an IDP described using
* idpEntityDescriptor, with an expected response to the assertionConsumer address.
*
* @param context message context
* @param context message context
* @param options preferences of message creation
* @param assertionConsumer assertion consumer where the IDP should respond
* @param bindingService service used to deliver the request
Expand All @@ -113,14 +139,15 @@ public void sendAuthenticationRequest(SAMLMessageContext context, WebSSOProfileO
*/
protected AuthnRequest getAuthnRequest(SAMLMessageContext context, WebSSOProfileOptions options,
AssertionConsumerService assertionConsumer,
SingleSignOnService bindingService) throws SAMLException,
MetadataProviderException {
SingleSignOnService bindingService) throws SAMLException, MetadataProviderException {

SAMLObjectBuilder<AuthnRequest> builder = (SAMLObjectBuilder<AuthnRequest>) builderFactory.getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME);
AuthnRequest request = builder.buildObject();

request.setIsPassive(options.getPassive());
request.setForceAuthn(options.getForceAuthN());
request.setProviderName(options.getProviderName());
request.setVersion(SAMLVersion.VERSION_20);

buildCommonAttributes(context.getLocalEntityId(), request, bindingService);

Expand Down Expand Up @@ -195,13 +222,14 @@ protected void buildAuthnContext(AuthnRequest request, WebSSOProfileOptions opti
* to be used to deliver response from the IDP.
*
* @param request request
* @param service service to deliver response to
* @param service service to deliver response to, building is skipped when null
* @throws MetadataProviderException error retrieving metadata information
*/
private void buildReturnAddress(AuthnRequest request, AssertionConsumerService service) throws MetadataProviderException {
request.setVersion(SAMLVersion.VERSION_20);
request.setAssertionConsumerServiceURL(service.getLocation());
request.setProtocolBinding(service.getBinding());
protected void buildReturnAddress(AuthnRequest request, AssertionConsumerService service) throws MetadataProviderException {
if (service != null) {
request.setAssertionConsumerServiceURL(service.getLocation());
request.setProtocolBinding(service.getBinding());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public class WebSSOProfileOptions implements Serializable, Cloneable {
private String binding;
private Set<String> allowedIDPs;
private String providerName;
private Integer assertionConsumerIndex;

// Name ID policy
private String nameID;
Expand Down Expand Up @@ -65,12 +66,12 @@ public String getBinding() {
}

/**
* Sets binding to be used for connection to IDP and back. Following values are supported:
* "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".
* Sets binding to be used for for sending SAML message to IDP.
*
* @param binding binding value
* @see org.opensaml.common.xml.SAMLConstants#SAML2_POST_BINDING_URI
* @see org.opensaml.common.xml.SAMLConstants#SAML2_REDIRECT_BINDING_URI
* @see org.opensaml.common.xml.SAMLConstants#SAML2_PAOS_BINDING_URI
*/
public void setBinding(String binding) {
this.binding = binding;
Expand Down Expand Up @@ -238,4 +239,18 @@ public void setProviderName(String providerName) {
this.providerName = providerName;
}

public Integer getAssertionConsumerIndex() {
return assertionConsumerIndex;
}

/**
* When set determines assertionConsumerService and binding to which should IDP send response. By default
* service is determined automatically. Available indexes can be found in metadata of this service provider.
*
* @param assertionConsumerIndex index
*/
public void setAssertionConsumerIndex(Integer assertionConsumerIndex) {
this.assertionConsumerIndex = assertionConsumerIndex;
}

}

0 comments on commit d651f27

Please sign in to comment.