Skip to content

Commit

Permalink
SES-82 Adding support for ECP profile. Proxy count can now be configu…
Browse files Browse the repository at this point in the history
…red in the WebSSOProfileOptions. List of allowed IDPs can now be configured in the WebSSOProfileOptions.
  • Loading branch information
vschafer committed Mar 26, 2011
1 parent 7810d98 commit 9858f6a
Show file tree
Hide file tree
Showing 17 changed files with 527 additions and 90 deletions.
3 changes: 3 additions & 0 deletions spring-security-saml/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@
<contributor>
<name>Rob Moore</name>
</contributor>
<contributor>
<name>Jonathan Tellier</name>
</contributor>
</contributors>

<issueManagement>
Expand Down
20 changes: 20 additions & 0 deletions spring-security-saml/saml2-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,26 @@
<version>4.4</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>xmlunit</groupId>
<artifactId>xmlunit</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-mock</artifactId>
<version>2.0.8</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
/* Copyright 2011 Vladimir Schaefer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml;

import org.opensaml.DefaultBootstrap;
import org.opensaml.PaosBootstrap;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.parse.ParserPool;
import org.springframework.beans.BeansException;
Expand All @@ -11,6 +25,8 @@

/**
* Initialization for SAML library. Is automatically called as part of Spring initialization.
*
* @author Vladimir Schaefer
*/
public class SAMLBootstrap implements BeanFactoryPostProcessor {

Expand All @@ -22,12 +38,12 @@ public class SAMLBootstrap implements BeanFactoryPostProcessor {
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
DefaultBootstrap.bootstrap();
PaosBootstrap.bootstrap();
ParserPool pool = beanFactory.getBean(ParserPool.class);
new ParserPoolHolder(pool);
} catch (ConfigurationException e) {
throw new BootstrapException("Error invoking OpenSAML bootrap", e);
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ public class SAMLConstants {

public static final String SUCCESS = "SUCCESS";
public static final String FAILURE = "FAILURE";

public static final String PAOS_HTTP_ACCEPT_HEADER = "application/vnd.paos+xml";
public static final String PAOS_HTTP_HEADER = "PAOS";

}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public class SAMLEntryPoint extends GenericFilterBean implements AuthenticationE
protected String idpSelectionPath;
protected WebSSOProfileOptions defaultOptions;
protected WebSSOProfile webSSOprofile;
protected WebSSOProfile webSSOprofileECP;
protected MetadataManager metadata;
protected SAMLLogger samlLogger;
protected SAMLContextProvider contextProvider;
Expand Down Expand Up @@ -130,14 +131,30 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A

try {

if (idpSelectionPath != null && !isLoginRequest(request)) {
boolean ecpRequest = isECPRequest(request);

if (!ecpRequest && idpSelectionPath != null && !isLoginRequest(request)) {

request.getRequestDispatcher(idpSelectionPath).include(request, response);

} else {

SAMLMessageContext context = contextProvider.getLocalEntity(request, response);
SAMLMessageStorage storage = new HttpSessionStorage(request);
WebSSOProfileOptions options = getProfileOptions(request, response, e);
webSSOprofile.sendAuthenticationRequest(context, options, storage);
WebSSOProfileOptions options = getProfileOptions(request, response, context, e);

if (ecpRequest) {
if (webSSOprofileECP == null) {
throw new ServletException("ECP profile isn't available in the entry point, check your configuration");
} else {
webSSOprofileECP.sendAuthenticationRequest(context, options, storage);
}
} else {
webSSOprofile.sendAuthenticationRequest(context, options, storage);
}

samlLogger.log(SAMLConstants.AUTH_N_REQUEST, SAMLConstants.SUCCESS, context, e);

}

} catch (SAMLException e1) {
Expand All @@ -150,19 +167,38 @@ public void commence(HttpServletRequest request, HttpServletResponse response, A

}

/**
* Analyzes the request headers in order to determine if it comes from an ECP-enabled
* client and based on this decides whether ECP profile will be used. Subclasses can override
* the method to control when is the ECP invoked.
*
* @param request request to analyze
* @return whether the request comes from an ECP-enabled client or not
*/
protected boolean isECPRequest(HttpServletRequest request) {
String acceptHeader = request.getHeader("Accept");

return acceptHeader != null
&& acceptHeader.contains(SAMLConstants.PAOS_HTTP_ACCEPT_HEADER)
&& ("ver='" + org.opensaml.common.xml.SAMLConstants.PAOS_NS + "';'"
+ org.opensaml.common.xml.SAMLConstants.SAML20ECP_NS + "'").equals(
request.getHeader(SAMLConstants.PAOS_HTTP_HEADER));
}

/**
* Method is supposed to populate preferences used to construct the SAML message. Method can be overridden to provide
* logic appropriate for given application. In case defaultOptions object was set it will be used as basis for construction
* and request specific values will be update (idp field).
*
* @param request request
* @param response response
* @param context containing local entity
* @param exception exception causing invocation of this entry point (can be null)
* @return populated webSSOprofile
* @throws MetadataProviderException in case metadata loading fails
* @throws ServletException in case any other error occurs
*/
protected WebSSOProfileOptions getProfileOptions(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws MetadataProviderException, ServletException {
protected WebSSOProfileOptions getProfileOptions(HttpServletRequest request, HttpServletResponse response, SAMLMessageContext context, AuthenticationException exception) throws MetadataProviderException, ServletException {

WebSSOProfileOptions ssoProfileOptions;
if (defaultOptions != null) {
Expand Down Expand Up @@ -270,6 +306,14 @@ public void setWebSSOprofile(WebSSOProfile webSSOprofile) {
this.webSSOprofile = webSSOprofile;
}

public WebSSOProfile getWebSSOprofileECP() {
return webSSOprofileECP;
}

public void setWebSSOprofileECP(WebSSOProfile webSSOprofileECP) {
this.webSSOprofileECP = webSSOprofileECP;
}

/**
* Logger for SAML events, cannot be null, must be set.
*
Expand Down Expand Up @@ -317,4 +361,4 @@ public void afterPropertiesSet() throws ServletException {
Assert.notNull(contextProvider, "Context provider must be set");
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ public class MetadataGenerator implements ApplicationContextAware {
private boolean wantAssertionSigned = true;
private boolean signMetadata = true;



private String signingKey = null;
private String encryptionKey = null;

Expand All @@ -82,6 +80,7 @@ public class MetadataGenerator implements ApplicationContextAware {
NameIDType.X509_SUBJECT);

private static final Collection<String> defaultBindings = Arrays.asList(SAMLConstants.SAML2_POST_BINDING_URI,
SAMLConstants.SAML2_PAOS_BINDING_URI,
SAMLConstants.SAML2_ARTIFACT_BINDING_URI,
SAMLConstants.SAML2_REDIRECT_BINDING_URI,
SAMLConstants.SAML2_SOAP11_BINDING_URI);
Expand Down Expand Up @@ -195,6 +194,11 @@ protected SPSSODescriptor buildSPSSODescriptor(String entityBaseURL, String enti
index++;
isDefault = false;
}
if (includedBindings.contains(SAMLConstants.SAML2_PAOS_BINDING_URI)) {
spDescriptor.getAssertionConsumerServices().add(getAssertionConsumerService(entityBaseURL, entityAlias, isDefault, index, SAMLConstants.SAML2_PAOS_BINDING_URI));
index++;
isDefault = false;
}
if (includedBindings.contains(SAMLConstants.SAML2_REDIRECT_BINDING_URI)) {
spDescriptor.getSingleLogoutServices().add(getSingleLogoutService(entityBaseURL, entityAlias, SAMLConstants.SAML2_REDIRECT_BINDING_URI));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2010 Jonathan Tellier
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.saml.processor;

import javax.servlet.http.HttpServletRequest;

import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.liberty.binding.decoding.HTTPPAOS11Decoder;
import org.opensaml.liberty.binding.encoding.HTTPPAOS11Encoder;
import org.opensaml.ws.transport.InTransport;
import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
import org.opensaml.xml.parse.ParserPool;
import org.springframework.security.saml.processor.HTTPSOAP11Binding;

public class HTTPPAOS11Binding extends HTTPSOAP11Binding {

public HTTPPAOS11Binding(ParserPool parserPool) {
super(new HTTPPAOS11Decoder(parserPool), new HTTPPAOS11Encoder());
}

@Override
public boolean supports(InTransport transport) {
if (transport instanceof HttpServletRequestAdapter) {
HttpServletRequestAdapter t = (HttpServletRequestAdapter) transport;
HttpServletRequest request = t.getWrappedRequest();
return "POST".equalsIgnoreCase(t.getHTTPMethod())
&& request.getContentType().startsWith(
org.springframework.security.saml.SAMLConstants.PAOS_HTTP_ACCEPT_HEADER);
} else {
return false;
}
}

@Override
public String getCommunicationProfileId() {
return SAMLConstants.SAML2_PAOS_BINDING_URI;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.security.MetadataCriteria;
import org.opensaml.security.SAMLSignatureProfileValidator;
import org.opensaml.ws.message.encoder.MessageEncodingException;
import org.opensaml.xml.XMLObjectBuilderFactory;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.credential.UsageType;
Expand Down Expand Up @@ -144,6 +145,20 @@ protected SPSSODescriptor getSPDescriptor(String spId) throws MetadataProviderEx
return spDescriptor;
}

/**
* Method calls the processor and sends the message containted in the context. Subclasses can provide additional
* processing before the message delivery.
*
* @param context context
* @param sign whether the message should be signed
* @throws MetadataProviderException metadata error
* @throws SAMLException SAML encoding error
* @throws org.opensaml.ws.message.encoder.MessageEncodingException message encoding error
*/
protected void sendMessage(SAMLMessageContext context, boolean sign) throws MetadataProviderException, SAMLException, MessageEncodingException {
processor.sendMessage(context, sign);
}

protected Status getStatus(String code, String statusMessage) {
SAMLObjectBuilder<StatusCode> codeBuilder = (SAMLObjectBuilder<StatusCode>) builderFactory.getBuilder(StatusCode.DEFAULT_ELEMENT_NAME);
StatusCode statusCode = codeBuilder.buildObject();
Expand All @@ -170,11 +185,17 @@ protected Status getStatus(String code, String statusMessage) {
* @param service service to use as destination for the request
*/
protected void buildCommonAttributes(RequestAbstractType request, Endpoint service) {

request.setID(generateID());
request.setIssuer(getIssuer());
request.setVersion(SAMLVersion.VERSION_20);
request.setIssueInstant(new DateTime());
request.setDestination(service.getLocation());

if (service != null) {
// Service is now known when we do not know which IDP will be used
request.setDestination(service.getLocation());
}

}

protected Issuer getIssuer() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public void sendLogoutRequest(SAMLMessageContext context, SAMLCredential credent
context.setPeerExtendedMetadata(idpExtendedMetadata);

boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutRequestSigned();
processor.sendMessage(context, signMessage);
sendMessage(context, signMessage);
messageStorage.storeMessage(logoutRequest.getID(), logoutRequest);

}
Expand Down Expand Up @@ -270,7 +270,7 @@ protected void sendLogoutResponse(Status status, SAMLMessageContext context) thr
context.setPeerEntityRoleMetadata(idpDescriptor);

boolean signMessage = context.getPeerExtendedMetadata().isRequireLogoutResponseSigned();
processor.sendMessage(context, signMessage);
sendMessage(context, signMessage);

}

Expand Down
Loading

0 comments on commit 9858f6a

Please sign in to comment.