You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2014/07/16 13:23:49 UTC
git commit: Adding more SAML SSO work
Repository: cxf-fediz
Updated Branches:
refs/heads/master 489659968 -> d6362d094
Adding more SAML SSO work
Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/d6362d09
Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/d6362d09
Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/d6362d09
Branch: refs/heads/master
Commit: d6362d0947d9bc20ca900efa8f278f676a931ae7
Parents: 4896599
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Wed Jul 16 12:23:23 2014 +0100
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Wed Jul 16 12:23:23 2014 +0100
----------------------------------------------------------------------
.../cxf/fediz/core/config/FedizContext.java | 4 +
.../cxf/fediz/core/config/SAMLProtocol.java | 52 +-
.../core/processor/FedizProcessorFactory.java | 6 +-
.../cxf/fediz/core/processor/FedizRequest.java | 7 +
.../fediz/core/processor/SAMLProcessorImpl.java | 598 +++++++++++++++++++
.../core/samlsso/EHCacheSPStateManager.java | 176 ++++++
.../cxf/fediz/core/samlsso/ResponseState.java | 81 +++
.../samlsso/SAMLProtocolResponseValidator.java | 255 ++++++++
.../core/samlsso/SAMLSSOResponseValidator.java | 330 ++++++++++
.../cxf/fediz/core/samlsso/SPStateManager.java | 44 ++
.../core/samlsso/SSOValidatorResponse.java | 54 ++
.../src/main/resources/schemas/FedizConfig.xsd | 3 +
.../fediz/jetty/FederationAuthenticator.java | 1 +
.../web/FederationAuthenticationFilter.java | 1 +
.../web/FederationAuthenticationFilter.java | 1 +
.../fediz/tomcat/FederationAuthenticator.java | 2 +-
.../fediz/integrationtests/HTTPTestUtils.java | 67 +++
17 files changed, 1672 insertions(+), 10 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
index 2eae858..dc8b1ed 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/FedizContext.java
@@ -270,6 +270,10 @@ public class FedizContext implements Closeable {
if (replayCache != null) {
replayCache.close();
}
+ if (protocol instanceof SAMLProtocol
+ && ((SAMLProtocol)protocol).getStateManager() != null) {
+ ((SAMLProtocol)protocol).getStateManager().close();
+ }
}
private Properties createCryptoProperties(TrustManagersType tm) {
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
index a137a6f..a1dee0b 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/config/SAMLProtocol.java
@@ -19,10 +19,18 @@
package org.apache.cxf.fediz.core.config;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cxf.fediz.core.TokenValidator;
import org.apache.cxf.fediz.core.config.jaxb.ProtocolType;
import org.apache.cxf.fediz.core.config.jaxb.SamlProtocolType;
+import org.apache.cxf.fediz.core.saml.SAMLTokenValidator;
import org.apache.cxf.fediz.core.samlsso.AuthnRequestBuilder;
import org.apache.cxf.fediz.core.samlsso.DefaultAuthnRequestBuilder;
+import org.apache.cxf.fediz.core.samlsso.EHCacheSPStateManager;
+import org.apache.cxf.fediz.core.samlsso.SPStateManager;
+import org.apache.cxf.fediz.core.util.ClassLoaderUtils;
import org.apache.wss4j.common.util.Loader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -32,13 +40,15 @@ public class SAMLProtocol extends Protocol {
private static final Logger LOG = LoggerFactory.getLogger(SAMLProtocol.class);
private AuthnRequestBuilder authnRequestBuilder;
+ private SPStateManager stateManager;
+ private List<TokenValidator> validators = new ArrayList<TokenValidator>();
public SAMLProtocol(ProtocolType protocolType) {
super(protocolType);
- /*FederationProtocolType fp = (FederationProtocolType)protocolType;
- if (fp.getTokenValidators() != null && fp.getTokenValidators().getValidator() != null) {
- for (String validatorClassname : fp.getTokenValidators().getValidator()) {
+ SamlProtocolType sp = (SamlProtocolType)protocolType;
+ if (sp.getTokenValidators() != null && sp.getTokenValidators().getValidator() != null) {
+ for (String validatorClassname : sp.getTokenValidators().getValidator()) {
Object obj = null;
try {
if (super.getClassloader() == null) {
@@ -56,13 +66,13 @@ public class SAMLProtocol extends Protocol {
LOG.error("Invalid TokenValidator implementation class: '" + validatorClassname + "'");
}
}
- }*/
+ }
// add SAMLTokenValidator as the last one
// Fediz chooses the first validator in the list if its
// canHandleToken or canHandleTokenType method return true
- //SAMLTokenValidator validator = new SAMLTokenValidator();
- //validators.add(validators.size(), validator);
+ SAMLTokenValidator validator = new SAMLTokenValidator();
+ validators.add(validators.size(), validator);
}
protected SamlProtocolType getSAMLProtocol() {
@@ -88,6 +98,32 @@ public class SAMLProtocol extends Protocol {
public void setWebAppDomain(String webAppDomain) {
getSAMLProtocol().setWebAppDomain(webAppDomain);
}
+
+ public SPStateManager getStateManager() {
+ if (stateManager != null) {
+ return stateManager;
+ }
+ String stateManagerStr = getSAMLProtocol().getStateManager();
+ if (stateManagerStr == null || "".equals(stateManagerStr)) {
+ stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+ } else {
+ try {
+ Class<?> stateManagerClass = Loader.loadClass(stateManagerStr);
+ stateManager = (SPStateManager) stateManagerClass.newInstance();
+ } catch (ClassNotFoundException e) {
+ stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+ } catch (InstantiationException e) {
+ stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+ } catch (IllegalAccessException e) {
+ stateManager = new EHCacheSPStateManager("fediz-ehcache.xml");
+ }
+ }
+ return stateManager;
+ }
+
+ public void setStateManager(SPStateManager stateManager) {
+ this.stateManager = stateManager;
+ }
public long getStateTimeToLive() {
long ttl = getSAMLProtocol().getStateTimeToLive();
@@ -130,6 +166,10 @@ public class SAMLProtocol extends Protocol {
public void setAuthnRequestBuilder(AuthnRequestBuilder authnRequestBuilder) {
this.authnRequestBuilder = authnRequestBuilder;
}
+
+ public List<TokenValidator> getTokenValidators() {
+ return validators;
+ }
}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
index c44a40b..ebc441e 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizProcessorFactory.java
@@ -21,7 +21,7 @@ package org.apache.cxf.fediz.core.processor;
import org.apache.cxf.fediz.core.config.FederationProtocol;
import org.apache.cxf.fediz.core.config.Protocol;
-// import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
/**
* A Factory to return FedizProcessor instances depending on the Protocol
@@ -35,9 +35,9 @@ public final class FedizProcessorFactory {
public static FedizProcessor newFedizProcessor(Protocol protocol) {
if (protocol instanceof FederationProtocol) {
return new FederationProcessorImpl();
- } /*else if (protocol instanceof SAMLProtocol) {
+ } else if (protocol instanceof SAMLProtocol) {
return new SAMLProcessorImpl();
- }*/
+ }
return null;
}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
index 68c8675..388cf36 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/FedizRequest.java
@@ -29,6 +29,7 @@ public class FedizRequest implements Serializable {
private String action;
private String responseToken;
private String freshness;
+ private String state;
private Certificate[] certs;
public Certificate[] getCerts() {
@@ -55,6 +56,12 @@ public class FedizRequest implements Serializable {
public void setFreshness(String freshness) {
this.freshness = freshness;
}
+ public String getState() {
+ return state;
+ }
+ public void setState(String state) {
+ this.state = state;
+ }
}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
new file mode 100644
index 0000000..918452a
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/SAMLProcessorImpl.java
@@ -0,0 +1,598 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.processor;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.zip.DataFormatException;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.fediz.core.FederationConstants;
+import org.apache.cxf.fediz.core.TokenValidator;
+import org.apache.cxf.fediz.core.TokenValidatorRequest;
+import org.apache.cxf.fediz.core.TokenValidatorResponse;
+import org.apache.cxf.fediz.core.config.FederationProtocol;
+import org.apache.cxf.fediz.core.config.FedizContext;
+import org.apache.cxf.fediz.core.config.SAMLProtocol;
+import org.apache.cxf.fediz.core.exception.ProcessingException;
+import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
+import org.apache.cxf.fediz.core.metadata.MetadataWriter;
+import org.apache.cxf.fediz.core.samlsso.AuthnRequestBuilder;
+import org.apache.cxf.fediz.core.samlsso.CompressionUtils;
+import org.apache.cxf.fediz.core.samlsso.RequestState;
+import org.apache.cxf.fediz.core.samlsso.SAMLProtocolResponseValidator;
+import org.apache.cxf.fediz.core.spi.IDPCallback;
+import org.apache.cxf.fediz.core.util.DOMUtils;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.OpenSAMLUtil;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.apache.xml.security.exceptions.Base64DecodingException;
+import org.apache.xml.security.utils.Base64;
+import org.opensaml.common.xml.SAMLConstants;
+import org.opensaml.saml2.core.AuthnRequest;
+import org.opensaml.xml.XMLObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SAMLProcessorImpl implements FedizProcessor {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SAMLProcessorImpl.class);
+
+ /**
+ * Default constructor
+ */
+ public SAMLProcessorImpl() {
+ super();
+ }
+
+ @Override
+ public FedizResponse processRequest(FedizRequest request,
+ FedizContext config)
+ throws ProcessingException {
+
+ if (!(config.getProtocol() instanceof SAMLProtocol)) {
+ LOG.error("Unsupported protocol");
+ throw new IllegalStateException("Unsupported protocol");
+ }
+
+ if (request.getResponseToken() == null || request.getState() == null) {
+ LOG.error("Missing response token or RelayState parameters");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ return processSignInRequest(request, config);
+ }
+
+
+ public Document getMetaData(FedizContext config) throws ProcessingException {
+ return new MetadataWriter().getMetaData(config);
+ }
+ /*
+ private RequestState processRelayState(String relayState, SAMLProtocol samlProtocol)
+ throws ProcessingException {
+ if (relayState.getBytes().length < 0 || relayState.getBytes().length > 80) {
+ LOG.error("Invalid RelayState");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ RequestState requestState = samlProtocol.getStateManager().removeRequestState(relayState);
+ if (requestState == null) {
+ LOG.error("Missing Request State");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ if (isStateExpired(requestState.getCreatedAt(), 0, samlProtocol.getStateTimeToLive())) {
+ LOG.error("EXPIRED_REQUEST_STATE");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ return requestState;
+ }
+
+ private boolean isStateExpired(long stateCreatedAt, long expiresAt, long stateTTL) {
+ Date currentTime = new Date();
+ if (currentTime.after(new Date(stateCreatedAt + stateTTL))) {
+ return true;
+ }
+
+ if (expiresAt > 0 && currentTime.after(new Date(expiresAt))) {
+ return true;
+ }
+
+ return false;
+ }
+ */
+ protected FedizResponse processSignInRequest(
+ FedizRequest request, FedizContext config)
+ throws ProcessingException {
+ SAMLProtocol protocol = (SAMLProtocol)config.getProtocol();
+ // TODO RequestState requestState = processRelayState(request.getState(), protocol);
+
+ InputStream tokenStream = null;
+ try {
+ byte[] deflatedToken = Base64.decode(request.getResponseToken());
+ tokenStream = CompressionUtils.inflate(deflatedToken);
+ } catch (DataFormatException ex) {
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ } catch (Base64DecodingException e) {
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ Document doc = null;
+ Element el = null;
+ try {
+ doc = DOMUtils.readXml(tokenStream);
+ el = doc.getDocumentElement();
+
+ } catch (Exception e) {
+ LOG.warn("Failed to parse token: " + e.getMessage());
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ LOG.debug("Received response: " + DOM2Writer.nodeToString(el));
+
+ XMLObject responseObject = null;
+ try {
+ responseObject = OpenSAMLUtil.fromDom(el);
+ } catch (WSSecurityException ex) {
+ LOG.debug(ex.getMessage(), ex);
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ if (!(responseObject instanceof org.opensaml.saml2.core.Response)) {
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ // Validate the Response
+ validateSamlResponseProtocol((org.opensaml.saml2.core.Response)responseObject);
+
+ // Validate the internal assertion(s)
+ TokenValidatorResponse validatorResponse = null;
+ List<Element> assertions =
+ DOMUtils.getChildrenWithName(el, SAMLConstants.SAML20_NS, "Assertion");
+
+ if (assertions.isEmpty()) {
+ LOG.debug("No Assertion extracted from SAML Response");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ Element token = assertions.get(0);
+
+ List<TokenValidator> validators = protocol.getTokenValidators();
+ for (TokenValidator validator : validators) {
+ boolean canHandle = validator.canHandleToken(token);
+ if (canHandle) {
+ try {
+ TokenValidatorRequest validatorRequest =
+ new TokenValidatorRequest(token, request.getCerts());
+ validatorResponse = validator.validateAndProcessToken(validatorRequest, config);
+ } catch (ProcessingException ex) {
+ throw ex;
+ } catch (Exception ex) {
+ LOG.warn("Failed to validate token", ex);
+ throw new ProcessingException(TYPE.TOKEN_INVALID);
+ }
+ break;
+ } else {
+ LOG.warn("No security token validator found for '" + token.getLocalName() + "'");
+ throw new ProcessingException(TYPE.BAD_REQUEST);
+ }
+ }
+
+ /* TODO
+ SSOValidatorResponse validatorResponse =
+ validateSamlSSOResponse(postBinding, samlResponse, requestState);
+ */
+
+ FedizResponse fedResponse = new FedizResponse(
+ validatorResponse.getUsername(), validatorResponse.getIssuer(),
+ validatorResponse.getRoles(), validatorResponse.getClaims(),
+ validatorResponse.getAudience(),
+ null, // TODO
+ null, // TODO
+ token,
+ validatorResponse.getUniqueTokenId());
+
+ return fedResponse;
+ }
+
+ /**
+ * Validate the received SAML Response as per the protocol
+ * @throws ProcessingException
+ */
+ protected void validateSamlResponseProtocol(
+ org.opensaml.saml2.core.Response samlResponse
+ ) throws ProcessingException {
+ try {
+ SAMLProtocolResponseValidator protocolValidator = new SAMLProtocolResponseValidator();
+ protocolValidator.validateSamlResponse(samlResponse);
+ } catch (WSSecurityException ex) {
+ LOG.debug(ex.getMessage(), ex);
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+ }
+
+ /**
+ * Validate the received SAML Response as per the Web SSO profile
+ protected SSOValidatorResponse validateSamlSSOResponse(
+ boolean postBinding,
+ org.opensaml.saml2.core.Response samlResponse,
+ RequestState requestState
+ ) {
+ try {
+ SAMLSSOResponseValidator ssoResponseValidator = new SAMLSSOResponseValidator();
+ ssoResponseValidator.setAssertionConsumerURL(
+ messageContext.getUriInfo().getAbsolutePath().toString());
+
+ ssoResponseValidator.setClientAddress(
+ messageContext.getHttpServletRequest().getRemoteAddr());
+
+ ssoResponseValidator.setIssuerIDP(requestState.getIdpServiceAddress());
+ ssoResponseValidator.setRequestId(requestState.getSamlRequestId());
+ ssoResponseValidator.setSpIdentifier(requestState.getIssuerId());
+ ssoResponseValidator.setEnforceAssertionsSigned(enforceAssertionsSigned);
+ ssoResponseValidator.setEnforceKnownIssuer(enforceKnownIssuer);
+ ssoResponseValidator.setReplayCache(getReplayCache());
+
+ return ssoResponseValidator.validateSamlResponse(samlResponse, postBinding);
+ } catch (WSSecurityException ex) {
+ reportError("INVALID_SAML_RESPONSE");
+ throw ExceptionUtils.toBadRequestException(ex, null);
+ }
+ }
+ */
+
+ @Override
+ public RedirectionResponse createSignInRequest(HttpServletRequest request, FedizContext config)
+ throws ProcessingException {
+
+ String redirectURL = null;
+ try {
+ if (!(config.getProtocol() instanceof SAMLProtocol)) {
+ LOG.error("Unsupported protocol");
+ throw new IllegalStateException("Unsupported protocol");
+ }
+
+ String issuerURL = resolveIssuer(request, config);
+ LOG.info("Issuer url: " + issuerURL);
+ if (issuerURL != null && issuerURL.length() > 0) {
+ redirectURL = issuerURL;
+ }
+
+ AuthnRequestBuilder authnRequestBuilder =
+ ((SAMLProtocol)config.getProtocol()).getAuthnRequestBuilder();
+
+ Document doc = DOMUtils.createDocument();
+ doc.appendChild(doc.createElement("root"));
+
+ // Create the AuthnRequest
+ String requestURL = request.getRequestURL().toString();
+ AuthnRequest authnRequest =
+ authnRequestBuilder.createAuthnRequest(config.getName(), requestURL);
+
+ if (((SAMLProtocol)config.getProtocol()).isSignRequest()) {
+ authnRequest.setDestination(redirectURL);
+ }
+
+ Element authnRequestElement = OpenSAMLUtil.toDom(authnRequest, doc);
+ String authnRequestEncoded = encodeAuthnRequest(authnRequestElement);
+
+ String webAppDomain = ((SAMLProtocol)config.getProtocol()).getWebAppDomain();
+
+ RequestState requestState = new RequestState(requestURL,
+ redirectURL,
+ authnRequest.getID(),
+ config.getName(),
+ requestURL,
+ webAppDomain,
+ System.currentTimeMillis());
+
+ String relayState = URLEncoder.encode(UUID.randomUUID().toString(), "UTF-8");
+ ((SAMLProtocol)config.getProtocol()).getStateManager().setRequestState(relayState, requestState);
+
+ String urlEncodedRequest =
+ URLEncoder.encode(authnRequestEncoded, "UTF-8");
+
+ StringBuilder sb = new StringBuilder();
+ sb.append("SAMLRequest").append('=').append(urlEncodedRequest);
+ sb.append("&RelayState").append('=').append(relayState);
+
+ if (((SAMLProtocol)config.getProtocol()).isSignRequest()) {
+ // TODO Sign the request
+ }
+
+ String contextCookie = createCookie("RelayState",
+ relayState,
+ request.getRequestURI(),
+ webAppDomain,
+ ((SAMLProtocol)config.getProtocol()).getStateTimeToLive());
+
+ RedirectionResponse response = new RedirectionResponse();
+ response.addHeader("Set-Cookie", contextCookie);
+ response.addHeader("Cache-Control", "no-cache, no-store");
+ response.addHeader("Pragma", "no-cache");
+
+ redirectURL = redirectURL + "?" + sb.toString();
+ response.setRedirectionURL(redirectURL);
+
+ return response;
+ } catch (Exception ex) {
+ LOG.error("Failed to create SignInRequest", ex);
+ throw new ProcessingException("Failed to create SignInRequest");
+ }
+ }
+
+ protected String createCookie(String name,
+ String value,
+ String path,
+ String domain,
+ long stateTimeToLive) {
+
+ String contextCookie = name + "=" + value;
+ // Setting a specific path restricts the browsers
+ // to return a cookie only to the web applications
+ // listening on that specific context path
+ if (path != null) {
+ contextCookie += ";Path=" + path;
+ }
+
+ // Setting a specific domain further restricts the browsers
+ // to return a cookie only to the web applications
+ // listening on the specific context path within a particular domain
+ if (domain != null) {
+ contextCookie += ";Domain=" + domain;
+ }
+
+ // Keep the cookie across the browser restarts until it actually expires.
+ // Note that the Expires property has been deprecated but apparently is
+ // supported better than 'max-age' property by different browsers
+ // (Firefox, IE, etc)
+ Date expiresDate = new Date(System.currentTimeMillis() + stateTimeToLive);
+ String cookieExpires = getHttpDateFormat().format(expiresDate);
+ contextCookie += ";Expires=" + cookieExpires;
+
+ return contextCookie;
+ }
+
+ protected static SimpleDateFormat getHttpDateFormat() {
+ SimpleDateFormat dateFormat =
+ new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+ TimeZone tZone = TimeZone.getTimeZone("GMT");
+ dateFormat.setTimeZone(tZone);
+ return dateFormat;
+ }
+
+ protected String encodeAuthnRequest(Element authnRequest) throws IOException {
+ String requestMessage = DOM2Writer.nodeToString(authnRequest);
+
+ byte[] deflatedBytes = CompressionUtils.deflate(requestMessage.getBytes("UTF-8"));
+
+ return Base64.encode(deflatedBytes);
+ }
+
+ @Override
+ public RedirectionResponse createSignOutRequest(HttpServletRequest request, FedizContext config)
+ throws ProcessingException {
+
+ String redirectURL = null;
+ try {
+ if (!(config.getProtocol() instanceof FederationProtocol)) {
+ LOG.error("Unsupported protocol");
+ throw new IllegalStateException("Unsupported protocol");
+ }
+
+ String issuerURL = resolveIssuer(request, config);
+ LOG.info("Issuer url: " + issuerURL);
+ if (issuerURL != null && issuerURL.length() > 0) {
+ redirectURL = issuerURL;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(FederationConstants.PARAM_ACTION).append('=').append(FederationConstants.ACTION_SIGNOUT);
+
+ String logoutRedirectTo = config.getLogoutRedirectTo();
+ if (logoutRedirectTo != null && !logoutRedirectTo.isEmpty()) {
+
+ if (logoutRedirectTo.startsWith("/")) {
+ logoutRedirectTo = extractFullContextPath(request).concat(logoutRedirectTo.substring(1));
+ } else {
+ logoutRedirectTo = extractFullContextPath(request).concat(logoutRedirectTo);
+ }
+
+ LOG.debug("wreply=" + logoutRedirectTo);
+
+ sb.append('&').append(FederationConstants.PARAM_REPLY).append('=');
+ sb.append(URLEncoder.encode(logoutRedirectTo, "UTF-8"));
+ }
+
+ redirectURL = redirectURL + "?" + sb.toString();
+ } catch (Exception ex) {
+ LOG.error("Failed to create SignInRequest", ex);
+ throw new ProcessingException("Failed to create SignInRequest");
+ }
+
+ RedirectionResponse response = new RedirectionResponse();
+ response.setRedirectionURL(redirectURL);
+ return response;
+ }
+/*
+ private String resolveSignInQuery(HttpServletRequest request, FedizContext config)
+ throws IOException, UnsupportedCallbackException, UnsupportedEncodingException {
+ Object signInQueryObj = ((FederationProtocol)config.getProtocol()).getSignInQuery();
+ String signInQuery = null;
+ if (signInQueryObj != null) {
+ if (signInQueryObj instanceof String) {
+ signInQuery = (String)signInQueryObj;
+ } else if (signInQueryObj instanceof CallbackHandler) {
+ CallbackHandler frCB = (CallbackHandler)signInQueryObj;
+ SignInQueryCallback callback = new SignInQueryCallback(request);
+ frCB.handle(new Callback[] {callback});
+ Map<String, String> signInQueryMap = callback.getSignInQueryParamMap();
+ StringBuilder sbQuery = new StringBuilder();
+ for (String key : signInQueryMap.keySet()) {
+ if (sbQuery.length() > 0) {
+ sbQuery.append("&");
+ }
+ sbQuery.append(key).append('=').
+ append(URLEncoder.encode(signInQueryMap.get(key), "UTF-8"));
+ }
+ signInQuery = sbQuery.toString();
+
+ }
+ }
+ return signInQuery;
+ }
+
+ private String resolveFreshness(HttpServletRequest request, FedizContext config) throws IOException,
+ UnsupportedCallbackException {
+ Object freshnessObj = ((FederationProtocol)config.getProtocol()).getFreshness();
+ String freshness = null;
+ if (freshnessObj != null) {
+ if (freshnessObj instanceof String) {
+ freshness = (String)freshnessObj;
+ } else if (freshnessObj instanceof CallbackHandler) {
+ CallbackHandler frCB = (CallbackHandler)freshnessObj;
+ FreshnessCallback callback = new FreshnessCallback(request);
+ frCB.handle(new Callback[] {callback});
+ freshness = callback.getFreshness();
+ }
+ }
+ return freshness;
+ }
+
+ private String resolveHomeRealm(HttpServletRequest request, FedizContext config) throws IOException,
+ UnsupportedCallbackException {
+ Object homeRealmObj = ((FederationProtocol)config.getProtocol()).getHomeRealm();
+ String homeRealm = null;
+ if (homeRealmObj != null) {
+ if (homeRealmObj instanceof String) {
+ homeRealm = (String)homeRealmObj;
+ } else if (homeRealmObj instanceof CallbackHandler) {
+ CallbackHandler hrCB = (CallbackHandler)homeRealmObj;
+ HomeRealmCallback callback = new HomeRealmCallback(request);
+ hrCB.handle(new Callback[] {callback});
+ homeRealm = callback.getHomeRealm();
+ }
+ }
+ return homeRealm;
+ }
+
+ private String resolveAuthenticationType(HttpServletRequest request, FedizContext config)
+ throws IOException, UnsupportedCallbackException {
+ Object wAuthObj = ((FederationProtocol)config.getProtocol()).getAuthenticationType();
+ String wAuth = null;
+ if (wAuthObj != null) {
+ if (wAuthObj instanceof String) {
+ wAuth = (String)wAuthObj;
+ } else if (wAuthObj instanceof CallbackHandler) {
+ CallbackHandler wauthCB = (CallbackHandler)wAuthObj;
+ WAuthCallback callback = new WAuthCallback(request);
+ wauthCB.handle(new Callback[] {callback});
+ wAuth = callback.getWauth();
+ }
+ }
+ return wAuth;
+ }
+
+ private String resolveRequest(HttpServletRequest request, FedizContext config)
+ throws IOException, UnsupportedCallbackException {
+ Object wReqObj = ((FederationProtocol)config.getProtocol()).getRequest();
+ String wReq = null;
+ if (wReqObj != null) {
+ if (wReqObj instanceof String) {
+ wReq = (String)wReqObj;
+ } else if (wReqObj instanceof CallbackHandler) {
+ CallbackHandler wauthCB = (CallbackHandler)wReqObj;
+ WReqCallback callback = new WReqCallback(request);
+ wauthCB.handle(new Callback[] {callback});
+ wReq = callback.getWreq();
+ }
+ }
+ return wReq;
+ }
+*/
+ private String resolveIssuer(HttpServletRequest request, FedizContext config) throws IOException,
+ UnsupportedCallbackException {
+ Object issuerObj = config.getProtocol().getIssuer();
+ String issuerURL = null;
+ if (issuerObj instanceof String) {
+ issuerURL = (String)issuerObj;
+ } else if (issuerObj instanceof CallbackHandler) {
+ CallbackHandler issuerCB = (CallbackHandler)issuerObj;
+ IDPCallback callback = new IDPCallback(request);
+ issuerCB.handle(new Callback[] {callback});
+ issuerURL = callback.getIssuerUrl().toString();
+ }
+ return issuerURL;
+ }
+/*
+ private String resolveWTRealm(HttpServletRequest request, FedizContext config) throws IOException,
+ UnsupportedCallbackException {
+ Object wtRealmObj = ((FederationProtocol)config.getProtocol()).getRealm();
+ String wtRealm = null;
+ if (wtRealmObj != null) {
+ if (wtRealmObj instanceof String) {
+ wtRealm = (String)wtRealmObj;
+ } else if (wtRealmObj instanceof CallbackHandler) {
+ CallbackHandler hrCB = (CallbackHandler)wtRealmObj;
+ RealmCallback callback = new RealmCallback(request);
+ hrCB.handle(new Callback[] {callback});
+ wtRealm = callback.getRealm();
+ }
+ } else {
+ wtRealm = extractFullContextPath(request); //default value
+ }
+ return wtRealm;
+ }
+
+*/
+ private String extractFullContextPath(HttpServletRequest request) throws MalformedURLException {
+ String result = null;
+ String contextPath = request.getContextPath();
+ String requestUrl = request.getRequestURL().toString();
+ String requestPath = new URL(requestUrl).getPath();
+ // Cut request path of request url and add context path if not ROOT
+ if (requestPath != null && requestPath.length() > 0) {
+ int lastIndex = requestUrl.lastIndexOf(requestPath);
+ result = requestUrl.substring(0, lastIndex);
+ } else {
+ result = requestUrl;
+ }
+ if (contextPath != null && contextPath.length() > 0) {
+ // contextPath contains starting slash
+ result = result + contextPath + "/";
+ } else {
+ result = result + "/";
+ }
+ return result;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
new file mode 100644
index 0000000..0daeb2a
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/EHCacheSPStateManager.java
@@ -0,0 +1,176 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.IOException;
+import java.net.URL;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Ehcache;
+import net.sf.ehcache.Element;
+import net.sf.ehcache.config.CacheConfiguration;
+
+import org.apache.wss4j.common.cache.EHCacheManagerHolder;
+import org.apache.wss4j.common.util.Loader;
+
+/**
+ * An in-memory EHCache implementation of the SPStateManager interface.
+ * The default TTL is 5 minutes.
+ */
+public class EHCacheSPStateManager implements SPStateManager {
+
+ public static final long DEFAULT_TTL = 60L * 5L;
+ public static final String REQUEST_CACHE_KEY = "cxf.fediz.samlp.request.state.cache";
+ public static final String RESPONSE_CACHE_KEY = "cxf.fediz.samlp.response.state.cache";
+
+ private Ehcache requestCache;
+ private Ehcache responseCache;
+ private CacheManager cacheManager;
+ private long ttl = DEFAULT_TTL;
+
+ public EHCacheSPStateManager(String configFile) {
+ this(getConfigFileURL(configFile));
+ }
+
+ public EHCacheSPStateManager(URL configFileURL) {
+ this(EHCacheManagerHolder.getCacheManager("", configFileURL));
+ }
+
+ public EHCacheSPStateManager(CacheManager cacheManager) {
+ this.cacheManager = cacheManager;
+
+ CacheConfiguration requestCC = EHCacheManagerHolder.getCacheConfiguration(REQUEST_CACHE_KEY, cacheManager);
+
+ Ehcache newCache = new Cache(requestCC);
+ requestCache = cacheManager.addCacheIfAbsent(newCache);
+
+ CacheConfiguration responseCC = EHCacheManagerHolder.getCacheConfiguration(RESPONSE_CACHE_KEY, cacheManager);
+
+ newCache = new Cache(responseCC);
+ responseCache = cacheManager.addCacheIfAbsent(newCache);
+ }
+
+ private static URL getConfigFileURL(Object o) {
+ if (o instanceof String) {
+ try {
+ URL url = Loader.getResource((String)o);
+ if (url == null) {
+ url = new URL((String)o);
+ }
+ return url;
+ } catch (IOException e) {
+ // Do nothing
+ }
+ } else if (o instanceof URL) {
+ return (URL)o;
+ }
+ return null;
+ }
+
+ /**
+ * Set a new (default) TTL value in seconds
+ * @param newTtl a new (default) TTL value in seconds
+ */
+ public void setTTL(long newTtl) {
+ ttl = newTtl;
+ }
+
+ /**
+ * Get the (default) TTL value in seconds
+ * @return the (default) TTL value in seconds
+ */
+ public long getTTL() {
+ return ttl;
+ }
+
+ public ResponseState getResponseState(String securityContextKey) {
+ Element element = responseCache.get(securityContextKey);
+ if (element != null) {
+ if (responseCache.isExpired(element)) {
+ responseCache.remove(securityContextKey);
+ return null;
+ }
+ return (ResponseState)element.getObjectValue();
+ }
+ return null;
+ }
+
+ public ResponseState removeResponseState(String securityContextKey) {
+ Element element = responseCache.get(securityContextKey);
+ if (element != null) {
+ responseCache.remove(securityContextKey);
+ return (ResponseState)element.getObjectValue();
+ }
+ return null;
+ }
+
+ public void setResponseState(String securityContextKey, ResponseState state) {
+ if (securityContextKey == null || "".equals(securityContextKey)) {
+ return;
+ }
+
+ int parsedTTL = (int)ttl;
+ if (ttl != (long)parsedTTL) {
+ // Fall back to 5 minutes if the default TTL is set incorrectly
+ parsedTTL = 60 * 5;
+ }
+ Element element = new Element(securityContextKey, state);
+ element.setTimeToLive(parsedTTL);
+ element.setTimeToIdle(parsedTTL);
+
+ responseCache.put(element);
+ }
+
+ public void setRequestState(String relayState, RequestState state) {
+ if (relayState == null || "".equals(relayState)) {
+ return;
+ }
+
+ int parsedTTL = (int)ttl;
+ if (ttl != (long)parsedTTL) {
+ // Fall back to 60 minutes if the default TTL is set incorrectly
+ parsedTTL = 3600;
+ }
+
+ Element element = new Element(relayState, state);
+ element.setTimeToLive(parsedTTL);
+ element.setTimeToIdle(parsedTTL);
+ requestCache.put(element);
+ }
+
+ public RequestState removeRequestState(String relayState) {
+ Element element = requestCache.get(relayState);
+ if (element != null) {
+ requestCache.remove(relayState);
+ return (RequestState)element.getObjectValue();
+ }
+ return null;
+ }
+
+ public void close() throws IOException {
+ if (cacheManager != null) {
+ cacheManager.shutdown();
+ cacheManager = null;
+ requestCache = null;
+ responseCache = null;
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
new file mode 100644
index 0000000..dfbf9ff
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/ResponseState.java
@@ -0,0 +1,81 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.Serializable;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ResponseState implements Serializable {
+
+ private static final long serialVersionUID = -3247188797004342462L;
+
+ private String assertion;
+ private String relayState;
+ private String webAppContext;
+ private String webAppDomain;
+ private long createdAt;
+ private long expiresAt;
+
+ public ResponseState() {
+
+ }
+
+ public ResponseState(String assertion,
+ String relayState,
+ String webAppContext,
+ String webAppDomain,
+ long createdAt,
+ long expiresAt) {
+ this.assertion = assertion;
+ this.relayState = relayState;
+ this.webAppContext = webAppContext;
+ this.webAppDomain = webAppDomain;
+ this.createdAt = createdAt;
+ this.expiresAt = expiresAt;
+ }
+
+ public long getCreatedAt() {
+ return createdAt;
+ }
+
+ public long getExpiresAt() {
+ return expiresAt;
+ }
+
+ public String getRelayState() {
+ return relayState;
+ }
+
+ public String getWebAppContext() {
+ return webAppContext;
+ }
+
+ public String getWebAppDomain() {
+ return webAppDomain;
+ }
+
+ public String getAssertion() {
+ return assertion;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
new file mode 100644
index 0000000..2269aa4
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLProtocolResponseValidator.java
@@ -0,0 +1,255 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import org.w3c.dom.Document;
+
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.SAMLKeyInfo;
+import org.apache.wss4j.common.saml.SAMLUtil;
+import org.apache.wss4j.dom.WSDocInfo;
+import org.apache.wss4j.dom.WSSConfig;
+import org.apache.wss4j.dom.handler.RequestData;
+import org.apache.wss4j.dom.saml.WSSSAMLKeyInfoProcessor;
+import org.opensaml.security.SAMLSignatureProfileValidator;
+import org.opensaml.xml.security.x509.BasicX509Credential;
+import org.opensaml.xml.signature.KeyInfo;
+import org.opensaml.xml.signature.Signature;
+import org.opensaml.xml.signature.SignatureValidator;
+import org.opensaml.xml.validation.ValidationException;
+import org.opensaml.xml.validation.ValidatorSuite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validate a SAML (1.1 or 2.0) Protocol Response. It validates the Response against the specs,
+ * the signature of the Response (if it exists), and any internal Assertion stored in the Response
+ * - including any signature. It validates the status code of the Response as well.
+ */
+public class SAMLProtocolResponseValidator {
+
+ public static final String SAML2_STATUSCODE_SUCCESS =
+ "urn:oasis:names:tc:SAML:2.0:status:Success";
+ public static final String SAML1_STATUSCODE_SUCCESS = "Success";
+
+ private static final Logger LOG = LoggerFactory.getLogger(SAMLProtocolResponseValidator.class);
+
+ // private Validator signatureValidator = new SignatureTrustValidator();
+
+ /**
+ * Validate a SAML 2 Protocol Response
+ * @param samlResponse
+ * @throws WSSecurityException
+ */
+ public void validateSamlResponse(
+ org.opensaml.saml2.core.Response samlResponse
+ ) throws WSSecurityException {
+ // Check the Status Code
+ if (samlResponse.getStatus() == null
+ || samlResponse.getStatus().getStatusCode() == null) {
+ LOG.debug("Either the SAML Response Status or StatusCode is null");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ if (!SAML2_STATUSCODE_SUCCESS.equals(samlResponse.getStatus().getStatusCode().getValue())) {
+ LOG.debug(
+ "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue()
+ + "does not equal " + SAML2_STATUSCODE_SUCCESS
+ );
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ validateResponseAgainstSchemas(samlResponse);
+ validateResponseSignature(samlResponse);
+ }
+
+ /**
+ * Validate a SAML 1.1 Protocol Response
+ * @param samlResponse
+ * @throws WSSecurityException
+ */
+ public void validateSamlResponse(
+ org.opensaml.saml1.core.Response samlResponse
+ ) throws WSSecurityException {
+ // Check the Status Code
+ if (samlResponse.getStatus() == null
+ || samlResponse.getStatus().getStatusCode() == null
+ || samlResponse.getStatus().getStatusCode().getValue() == null) {
+ LOG.debug("Either the SAML Response Status or StatusCode is null");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ String statusValue = samlResponse.getStatus().getStatusCode().getValue().getLocalPart();
+ if (!SAML1_STATUSCODE_SUCCESS.equals(statusValue)) {
+ LOG.debug(
+ "SAML Status code of " + samlResponse.getStatus().getStatusCode().getValue()
+ + "does not equal " + SAML1_STATUSCODE_SUCCESS
+ );
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ validateResponseAgainstSchemas(samlResponse);
+ validateResponseSignature(samlResponse);
+ }
+
+ /**
+ * Validate the Response against the schemas
+ */
+ private void validateResponseAgainstSchemas(
+ org.opensaml.saml2.core.Response samlResponse
+ ) throws WSSecurityException {
+ // Validate SAML Response against schemas
+ ValidatorSuite schemaValidators =
+ org.opensaml.Configuration.getValidatorSuite("saml2-core-schema-validator");
+ try {
+ schemaValidators.validate(samlResponse);
+ } catch (ValidationException e) {
+ LOG.debug("Saml Validation error: " + e.getMessage(), e);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+ /**
+ * Validate the Response against the schemas
+ */
+ private void validateResponseAgainstSchemas(
+ org.opensaml.saml1.core.Response samlResponse
+ ) throws WSSecurityException {
+ // Validate SAML Response against schemas
+ ValidatorSuite schemaValidators =
+ org.opensaml.Configuration.getValidatorSuite("saml1-core-schema-validator");
+ try {
+ schemaValidators.validate(samlResponse);
+ } catch (ValidationException e) {
+ LOG.debug("Saml Validation error: " + e.getMessage(), e);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+ /**
+ * Validate the Response signature (if it exists)
+ */
+ private void validateResponseSignature(
+ org.opensaml.saml2.core.Response samlResponse
+ ) throws WSSecurityException {
+ if (!samlResponse.isSigned()) {
+ return;
+ }
+
+ validateResponseSignature(
+ samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument()
+ );
+ }
+
+ /**
+ * Validate the Response signature (if it exists)
+ */
+ private void validateResponseSignature(
+ org.opensaml.saml1.core.Response samlResponse
+ ) throws WSSecurityException {
+ if (!samlResponse.isSigned()) {
+ return;
+ }
+
+ validateResponseSignature(
+ samlResponse.getSignature(), samlResponse.getDOM().getOwnerDocument()
+ );
+ }
+
+ /**
+ * Validate the response signature
+ */
+ private void validateResponseSignature(
+ Signature signature,
+ Document doc
+ ) throws WSSecurityException {
+ RequestData requestData = new RequestData();
+ WSSConfig wssConfig = WSSConfig.getNewInstance();
+ requestData.setWssConfig(wssConfig);
+
+ SAMLKeyInfo samlKeyInfo = null;
+
+ KeyInfo keyInfo = signature.getKeyInfo();
+ if (keyInfo != null) {
+ try {
+ samlKeyInfo =
+ SAMLUtil.getCredentialFromKeyInfo(
+ keyInfo.getDOM(), new WSSSAMLKeyInfoProcessor(requestData, new WSDocInfo(doc)),
+ requestData.getSigVerCrypto()
+ );
+ } catch (WSSecurityException ex) {
+ LOG.debug("Error in getting KeyInfo from SAML Response: " + ex.getMessage(), ex);
+ throw ex;
+ }
+ }
+ if (samlKeyInfo == null) {
+ LOG.debug("No KeyInfo supplied in the SAMLResponse signature");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Validate Signature against profiles
+ validateSignatureAgainstProfiles(signature, samlKeyInfo);
+
+ // Now verify trust on the signature
+ /* TODO Credential trustCredential = new Credential();
+ trustCredential.setPublicKey(samlKeyInfo.getPublicKey());
+ trustCredential.setCertificates(samlKeyInfo.getCerts());
+
+ try {
+ signatureValidator.validate(trustCredential, requestData);
+ } catch (WSSecurityException e) {
+ LOG.debug("Error in validating signature on SAML Response: " + e.getMessage(), e);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ */
+ }
+
+ /**
+ * Validate a signature against the profiles
+ */
+ private void validateSignatureAgainstProfiles(
+ Signature signature,
+ SAMLKeyInfo samlKeyInfo
+ ) throws WSSecurityException {
+ // Validate Signature against profiles
+ SAMLSignatureProfileValidator validator = new SAMLSignatureProfileValidator();
+ try {
+ validator.validate(signature);
+ } catch (ValidationException ex) {
+ LOG.debug("Error in validating the SAML Signature: " + ex.getMessage(), ex);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ BasicX509Credential credential = new BasicX509Credential();
+ if (samlKeyInfo.getCerts() != null) {
+ credential.setEntityCertificate(samlKeyInfo.getCerts()[0]);
+ } else if (samlKeyInfo.getPublicKey() != null) {
+ credential.setPublicKey(samlKeyInfo.getPublicKey());
+ } else {
+ LOG.debug("Can't get X509Certificate or PublicKey to verify signature");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ SignatureValidator sigValidator = new SignatureValidator(credential);
+ try {
+ sigValidator.validate(signature);
+ } catch (ValidationException ex) {
+ LOG.debug("Error in validating the SAML Signature: " + ex.getMessage(), ex);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
new file mode 100644
index 0000000..4d75027
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SAMLSSOResponseValidator.java
@@ -0,0 +1,330 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.util.Date;
+import java.util.List;
+
+import org.w3c.dom.Element;
+
+import org.apache.wss4j.common.cache.ReplayCache;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.builder.SAML2Constants;
+import org.apache.wss4j.common.util.DOM2Writer;
+import org.opensaml.saml2.core.AudienceRestriction;
+import org.opensaml.saml2.core.AuthnStatement;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Validate a SAML 2.0 Protocol Response according to the Web SSO profile. The Response
+ * should be validated by the SAMLProtocolResponseValidator first.
+ */
+public class SAMLSSOResponseValidator {
+
+ private static final Logger LOG = LoggerFactory.getLogger(SAMLSSOResponseValidator.class);
+
+ private String issuerIDP;
+ private String assertionConsumerURL;
+ private String clientAddress;
+ private String requestId;
+ private String spIdentifier;
+ private boolean enforceAssertionsSigned = true;
+ private boolean enforceKnownIssuer = true;
+ private ReplayCache replayCache;
+
+ /**
+ * Enforce that Assertions must be signed if the POST binding was used. The default is true.
+ */
+ public void setEnforceAssertionsSigned(boolean enforceAssertionsSigned) {
+ this.enforceAssertionsSigned = enforceAssertionsSigned;
+ }
+
+ /**
+ * Enforce that the Issuer of the received Response/Assertion is known. The default is true.
+ */
+ public void setEnforceKnownIssuer(boolean enforceKnownIssuer) {
+ this.enforceKnownIssuer = enforceKnownIssuer;
+ }
+
+ /**
+ * Validate a SAML 2 Protocol Response
+ * @param samlResponse
+ * @param postBinding
+ * @return a SSOValidatorResponse object
+ * @throws WSSecurityException
+ */
+ public SSOValidatorResponse validateSamlResponse(
+ org.opensaml.saml2.core.Response samlResponse,
+ boolean postBinding
+ ) throws WSSecurityException {
+ // Check the Issuer
+ validateIssuer(samlResponse.getIssuer());
+
+ // The Response must contain at least one Assertion.
+ if (samlResponse.getAssertions() == null || samlResponse.getAssertions().isEmpty()) {
+ LOG.debug("The Response must contain at least one Assertion");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // The Response must contain a Destination that matches the assertionConsumerURL if it is
+ // signed
+ String destination = samlResponse.getDestination();
+ if (samlResponse.isSigned()
+ && (destination == null || !destination.equals(assertionConsumerURL))) {
+ LOG.debug("The Response must contain a destination that matches the assertion consumer URL");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Validate Assertions
+ boolean foundValidSubject = false;
+ Date sessionNotOnOrAfter = null;
+ for (org.opensaml.saml2.core.Assertion assertion : samlResponse.getAssertions()) {
+ // Check the Issuer
+ if (assertion.getIssuer() == null) {
+ LOG.debug("Assertion Issuer must not be null");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ validateIssuer(assertion.getIssuer());
+
+ if (enforceAssertionsSigned && postBinding && assertion.getSignature() == null) {
+ LOG.debug("If the HTTP Post binding is used to deliver the Response, "
+ + "the enclosed assertions must be signed");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Check for AuthnStatements and validate the Subject accordingly
+ if (assertion.getAuthnStatements() != null
+ && !assertion.getAuthnStatements().isEmpty()) {
+ org.opensaml.saml2.core.Subject subject = assertion.getSubject();
+ if (validateAuthenticationSubject(subject, assertion.getID(), postBinding)) {
+ validateAudienceRestrictionCondition(assertion.getConditions());
+ foundValidSubject = true;
+ // Store Session NotOnOrAfter
+ for (AuthnStatement authnStatment : assertion.getAuthnStatements()) {
+ if (authnStatment.getSessionNotOnOrAfter() != null) {
+ sessionNotOnOrAfter = authnStatment.getSessionNotOnOrAfter().toDate();
+ }
+ }
+ }
+ }
+
+ }
+
+ if (!foundValidSubject) {
+ LOG.debug("The Response did not contain any Authentication Statement that matched "
+ + "the Subject Confirmation criteria");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ SSOValidatorResponse validatorResponse = new SSOValidatorResponse();
+ validatorResponse.setResponseId(samlResponse.getID());
+ validatorResponse.setSessionNotOnOrAfter(sessionNotOnOrAfter);
+ // the assumption for now is that SAMLResponse will contain only a single assertion
+ Element assertionElement = samlResponse.getAssertions().get(0).getDOM();
+ validatorResponse.setAssertion(DOM2Writer.nodeToString(assertionElement.cloneNode(true)));
+ return validatorResponse;
+ }
+
+ /**
+ * Validate the Issuer (if it exists)
+ */
+ private void validateIssuer(org.opensaml.saml2.core.Issuer issuer) throws WSSecurityException {
+ if (issuer == null) {
+ return;
+ }
+
+ // Issuer value must match (be contained in) Issuer IDP
+ if (enforceKnownIssuer && !issuerIDP.startsWith(issuer.getValue())) {
+ LOG.debug("Issuer value: " + issuer.getValue() + " does not match issuer IDP: "
+ + issuerIDP);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Format must be nameid-format-entity
+ if (issuer.getFormat() != null
+ && !SAML2Constants.NAMEID_FORMAT_ENTITY.equals(issuer.getFormat())) {
+ LOG.debug("Issuer format is not null and does not equal: "
+ + SAML2Constants.NAMEID_FORMAT_ENTITY);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+ /**
+ * Validate the Subject (of an Authentication Statement).
+ */
+ private boolean validateAuthenticationSubject(
+ org.opensaml.saml2.core.Subject subject, String id, boolean postBinding
+ ) throws WSSecurityException {
+ if (subject.getSubjectConfirmations() == null) {
+ return false;
+ }
+ // We need to find a Bearer Subject Confirmation method
+ for (org.opensaml.saml2.core.SubjectConfirmation subjectConf
+ : subject.getSubjectConfirmations()) {
+ if (SAML2Constants.CONF_BEARER.equals(subjectConf.getMethod())) {
+ validateSubjectConfirmation(subjectConf.getSubjectConfirmationData(), id, postBinding);
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Validate a (Bearer) Subject Confirmation
+ */
+ private void validateSubjectConfirmation(
+ org.opensaml.saml2.core.SubjectConfirmationData subjectConfData, String id, boolean postBinding
+ ) throws WSSecurityException {
+ if (subjectConfData == null) {
+ LOG.debug("Subject Confirmation Data of a Bearer Subject Confirmation is null");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Recipient must match assertion consumer URL
+ String recipient = subjectConfData.getRecipient();
+ if (recipient == null || !recipient.equals(assertionConsumerURL)) {
+ LOG.debug("Recipient " + recipient + " does not match assertion consumer URL "
+ + assertionConsumerURL);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // We must have a NotOnOrAfter timestamp
+ if (subjectConfData.getNotOnOrAfter() == null
+ || subjectConfData.getNotOnOrAfter().isBeforeNow()) {
+ LOG.debug("Subject Conf Data does not contain NotOnOrAfter or it has expired");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // Need to keep bearer assertion IDs based on NotOnOrAfter to detect replay attacks
+ if (postBinding && replayCache != null) {
+ if (replayCache.contains(id)) {
+ Date expires = subjectConfData.getNotOnOrAfter().toDate();
+ Date currentTime = new Date();
+ long ttl = expires.getTime() - currentTime.getTime();
+ replayCache.add(id, ttl / 1000L);
+ } else {
+ LOG.debug("Replay attack with token id: " + id);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+ // Check address
+ if (subjectConfData.getAddress() != null
+ && !subjectConfData.getAddress().equals(clientAddress)) {
+ LOG.debug("Subject Conf Data address " + subjectConfData.getAddress() + " does match"
+ + " client address " + clientAddress);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // It must not contain a NotBefore timestamp
+ if (subjectConfData.getNotBefore() != null) {
+ LOG.debug("The Subject Conf Data must not contain a NotBefore timestamp");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ // InResponseTo must match the AuthnRequest request Id
+ if (requestId != null && !requestId.equals(subjectConfData.getInResponseTo())) {
+ LOG.debug("The InResponseTo String does match the original request id " + requestId);
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+
+ }
+
+ private void validateAudienceRestrictionCondition(
+ org.opensaml.saml2.core.Conditions conditions
+ ) throws WSSecurityException {
+ if (conditions == null) {
+ LOG.debug("Conditions are null");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ List<AudienceRestriction> audienceRestrs = conditions.getAudienceRestrictions();
+ if (!matchSaml2AudienceRestriction(spIdentifier, audienceRestrs)) {
+ LOG.debug("Assertion does not contain unique subject provider identifier "
+ + spIdentifier + " in the audience restriction conditions");
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "invalidSAMLsecurity");
+ }
+ }
+
+
+ private boolean matchSaml2AudienceRestriction(
+ String appliesTo, List<AudienceRestriction> audienceRestrictions
+ ) {
+ boolean found = false;
+ if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
+ for (AudienceRestriction audienceRestriction : audienceRestrictions) {
+ if (audienceRestriction.getAudiences() != null) {
+ for (org.opensaml.saml2.core.Audience audience : audienceRestriction.getAudiences()) {
+ if (appliesTo.equals(audience.getAudienceURI())) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ return found;
+ }
+
+ public String getIssuerIDP() {
+ return issuerIDP;
+ }
+
+ public void setIssuerIDP(String issuerIDP) {
+ this.issuerIDP = issuerIDP;
+ }
+
+ public String getAssertionConsumerURL() {
+ return assertionConsumerURL;
+ }
+
+ public void setAssertionConsumerURL(String assertionConsumerURL) {
+ this.assertionConsumerURL = assertionConsumerURL;
+ }
+
+ public String getClientAddress() {
+ return clientAddress;
+ }
+
+ public void setClientAddress(String clientAddress) {
+ this.clientAddress = clientAddress;
+ }
+
+ public String getRequestId() {
+ return requestId;
+ }
+
+ public void setRequestId(String requestId) {
+ this.requestId = requestId;
+ }
+
+ public String getSpIdentifier() {
+ return spIdentifier;
+ }
+
+ public void setSpIdentifier(String spIdentifier) {
+ this.spIdentifier = spIdentifier;
+ }
+
+ public void setReplayCache(ReplayCache replayCache) {
+ this.replayCache = replayCache;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
new file mode 100644
index 0000000..d55dce0
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SPStateManager.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * SSO Service Provider State Manager.
+ *
+ * TODO: review the possibility of working with the Servlet HTTPSession
+ * instead; in that case it can be tricky to configure various containers
+ * (Tomcat, Jetty) to make sure the cookies are shared across multiple
+ * war contexts which will be needed if RequestAssertionConsumerService
+ * needs to be run in its own war file instead of having every application
+ * war on the SP side have a dedicated RequestAssertionConsumerService endpoint
+ */
+public interface SPStateManager extends Closeable {
+
+ void setRequestState(String relayState, RequestState state);
+ RequestState removeRequestState(String relayState);
+
+ void setResponseState(String contextKey, ResponseState state);
+ ResponseState getResponseState(String contextKey);
+ ResponseState removeResponseState(String contextKey);
+
+ void close() throws IOException;
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
new file mode 100644
index 0000000..13bd839
--- /dev/null
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/samlsso/SSOValidatorResponse.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.cxf.fediz.core.samlsso;
+
+import java.util.Date;
+
+/**
+ * Some information that encapsulates a successful validation by the SAMLSSOResponseValidator
+ */
+public class SSOValidatorResponse {
+ private Date sessionNotOnOrAfter;
+ private String responseId;
+ private String assertion;
+
+ public String getAssertion() {
+ return assertion;
+ }
+
+ public void setAssertion(String assertion) {
+ this.assertion = assertion;
+ }
+
+ public Date getSessionNotOnOrAfter() {
+ return sessionNotOnOrAfter;
+ }
+
+ public void setSessionNotOnOrAfter(Date sessionNotOnOrAfter) {
+ this.sessionNotOnOrAfter = sessionNotOnOrAfter;
+ }
+
+ public String getResponseId() {
+ return responseId;
+ }
+
+ public void setResponseId(String responseId) {
+ this.responseId = responseId;
+ }
+}
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/core/src/main/resources/schemas/FedizConfig.xsd
----------------------------------------------------------------------
diff --git a/plugins/core/src/main/resources/schemas/FedizConfig.xsd b/plugins/core/src/main/resources/schemas/FedizConfig.xsd
index 3420b09..748b8a7 100644
--- a/plugins/core/src/main/resources/schemas/FedizConfig.xsd
+++ b/plugins/core/src/main/resources/schemas/FedizConfig.xsd
@@ -117,6 +117,8 @@
<xs:element ref="stateTimeToLive" />
<xs:element ref="webAppDomain" />
<xs:element ref="authnRequestBuilder"/>
+ <xs:element ref="stateManager"/>
+ <xs:element ref="tokenValidators" />
</xs:sequence>
<xs:attribute name="version" use="required" type="xs:string" />
</xs:extension>
@@ -132,6 +134,7 @@
<xs:element name="stateTimeToLive" type="xs:long" default="120000" />
<xs:element name="webAppDomain" type="xs:string" />
<xs:element name="authnRequestBuilder" type="xs:string" />
+ <xs:element name="stateManager" type="xs:string" />
<xs:complexType name="protocolType" abstract="true">
<xs:sequence>
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java b/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
index e2d774d..ce23c0c 100644
--- a/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
+++ b/plugins/jetty/src/main/java/org/apache/cxf/fediz/jetty/FederationAuthenticator.java
@@ -195,6 +195,7 @@ public class FederationAuthenticator extends LoginAuthenticator {
FedizRequest wfReq = new FedizRequest();
wfReq.setAction(action);
wfReq.setResponseToken(responseToken);
+ wfReq.setState(request.getParameter("RelayState"));
X509Certificate certs[] =
(X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java b/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
index 93491b6..3e20030 100644
--- a/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
+++ b/plugins/spring/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
@@ -51,6 +51,7 @@ public class FederationAuthenticationFilter extends AbstractAuthenticationProces
FedizRequest wfReq = new FedizRequest();
wfReq.setAction(wa);
wfReq.setResponseToken(responseToken);
+ wfReq.setState(request.getParameter("RelayState"));
X509Certificate certs[] =
(X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
----------------------------------------------------------------------
diff --git a/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java b/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
index 255a941..466f7c3 100644
--- a/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
+++ b/plugins/spring2/src/main/java/org/apache/cxf/fediz/spring/web/FederationAuthenticationFilter.java
@@ -64,6 +64,7 @@ public class FederationAuthenticationFilter extends AbstractProcessingFilter {
FedizRequest wfReq = new FedizRequest();
wfReq.setAction(wa);
wfReq.setResponseToken(responseToken);
+ wfReq.setState(request.getParameter("RelayState"));
X509Certificate certs[] =
(X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
----------------------------------------------------------------------
diff --git a/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java b/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
index 7daddd0..ce49565 100644
--- a/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
+++ b/plugins/tomcat/src/main/java/org/apache/cxf/fediz/tomcat/FederationAuthenticator.java
@@ -421,6 +421,7 @@ public class FederationAuthenticator extends FormAuthenticator {
FedizRequest wfReq = new FedizRequest();
wfReq.setAction(action);
wfReq.setResponseToken(responseToken);
+ wfReq.setState(request.getParameter("RelayState"));
X509Certificate certs[] =
(X509Certificate[])request.getAttribute("javax.servlet.request.X509Certificate");
@@ -436,7 +437,6 @@ public class FederationAuthenticator extends FormAuthenticator {
return false;
}
-
// Validate the AudienceRestriction in Security Token (e.g. SAML)
// against the configured list of audienceURIs
if (wfRes.getAudience() != null) {
http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/d6362d09/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
----------------------------------------------------------------------
diff --git a/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java b/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
index af57bd1..e62275e 100644
--- a/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
+++ b/systests/tests/src/test/java/org/apache/cxf/fediz/integrationtests/HTTPTestUtils.java
@@ -172,5 +172,72 @@ public final class HTTPTestUtils {
}
}
}
+
+ public static String sendHttpGetForSAMLSSO(String url, String user,
+ String password, int idpPort) throws Exception {
+ return sendHttpGetForSAMLSSO(url, user, password, 200, 200, idpPort);
+ }
+
+ public static String sendHttpGetForSAMLSSO(String url, String user, String password,
+ int returnCodeIDP, int returnCodeRP, int idpPort)
+ throws Exception {
+
+ CloseableHttpClient httpClient = null;
+ try {
+ CredentialsProvider credsProvider = new BasicCredentialsProvider();
+ credsProvider.setCredentials(
+ new AuthScope("localhost", idpPort),
+ new UsernamePasswordCredentials(user, password));
+
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ FileInputStream instream = new FileInputStream(new File("./target/test-classes/client.jks"));
+ try {
+ trustStore.load(instream, "clientpass".toCharArray());
+ } finally {
+ try {
+ instream.close();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+
+ SSLContextBuilder sslContextBuilder = new SSLContextBuilder();
+ sslContextBuilder.loadTrustMaterial(trustStore, new TrustSelfSignedStrategy());
+ sslContextBuilder.loadKeyMaterial(trustStore, "clientpass".toCharArray());
+
+ SSLContext sslContext = sslContextBuilder.build();
+ SSLConnectionSocketFactory sslSocketFactory =
+ new SSLConnectionSocketFactory(sslContext);
+
+ HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
+ httpClientBuilder.setDefaultCredentialsProvider(credsProvider);
+ httpClientBuilder.setSSLSocketFactory(sslSocketFactory);
+ httpClientBuilder.setRedirectStrategy(new LaxRedirectStrategy());
+
+ httpClient = httpClientBuilder.build();
+
+ HttpGet httpget = new HttpGet(url);
+
+ HttpResponse response = httpClient.execute(httpget);
+ HttpEntity entity = response.getEntity();
+
+ System.out.println(response.getStatusLine());
+ if (entity != null) {
+ System.out.println("Response content length: " + entity.getContentLength());
+ }
+ Assert.assertTrue("RP HTTP Response code: " + response.getStatusLine().getStatusCode()
+ + " [Expected: " + returnCodeRP + "]",
+ returnCodeRP == response.getStatusLine().getStatusCode());
+
+ return EntityUtils.toString(entity);
+ } finally {
+ // When HttpClient instance is no longer needed,
+ // shut down the connection manager to ensure
+ // immediate deallocation of all system resources
+ if (httpClient != null) {
+ httpClient.close();
+ }
+ }
+ }
}