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 2018/07/19 17:00:10 UTC
[cxf-fediz] branch 1.4.x-fixes updated: FEDIZ-222 - Added initial
support to process a SAML LogoutResponse
This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch 1.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf-fediz.git
The following commit(s) were added to refs/heads/1.4.x-fixes by this push:
new 03b04b5 FEDIZ-222 - Added initial support to process a SAML LogoutResponse
03b04b5 is described below
commit 03b04b574cc5115d916afe1c443aec96c87cfce7
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Jul 19 17:46:30 2018 +0100
FEDIZ-222 - Added initial support to process a SAML LogoutResponse
---
.../cxf/fediz/core/processor/FedizRequest.java | 7 ++
.../fediz/core/processor/RedirectionResponse.java | 9 ++
.../fediz/core/processor/SAMLProcessorImpl.java | 81 +++++++++++++-
.../samlsso/SAMLProtocolResponseValidator.java | 4 +-
.../samlsso/SAML2PResponseComponentBuilder.java | 28 ++++-
.../cxf/fediz/core/samlsso/SAMLResponseTest.java | 117 +++++++++++++++++++++
6 files changed, 240 insertions(+), 6 deletions(-)
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 4b07a57..03bb776 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
@@ -38,6 +38,7 @@ public class FedizRequest implements Serializable {
private Certificate[] certs;
private transient HttpServletRequest request;
private RequestState requestState;
+ private boolean signOutRequest;
public Certificate[] getCerts() {
if (certs != null) {
@@ -88,5 +89,11 @@ public class FedizRequest implements Serializable {
public void setRequestState(RequestState requestState) {
this.requestState = requestState;
}
+ public boolean isSignOutRequest() {
+ return signOutRequest;
+ }
+ public void setSignOutRequest(boolean signOutRequest) {
+ this.signOutRequest = signOutRequest;
+ }
}
diff --git a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/RedirectionResponse.java b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/RedirectionResponse.java
index 91ded34..32a90cf 100644
--- a/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/RedirectionResponse.java
+++ b/plugins/core/src/main/java/org/apache/cxf/fediz/core/processor/RedirectionResponse.java
@@ -35,6 +35,7 @@ public class RedirectionResponse implements Serializable {
private String redirectionURL;
private Map<String, String> headers = new HashMap<>();
private RequestState requestState;
+ private String state;
public String getRedirectionURL() {
return redirectionURL;
@@ -60,4 +61,12 @@ public class RedirectionResponse implements Serializable {
this.requestState = requestState;
}
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
}
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
index 638dd8a..1008674 100644
--- 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
@@ -26,6 +26,7 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.Signature;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@@ -36,6 +37,7 @@ import javax.servlet.http.HttpServletRequest;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
+import org.apache.cxf.fediz.core.Claim;
import org.apache.cxf.fediz.core.RequestState;
import org.apache.cxf.fediz.core.SAMLSSOConstants;
import org.apache.cxf.fediz.core.TokenValidator;
@@ -64,6 +66,7 @@ import org.opensaml.core.xml.XMLObject;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.StatusResponseType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -92,8 +95,17 @@ public class SAMLProcessorImpl extends AbstractFedizProcessor {
throw new IllegalStateException("Unsupported protocol");
}
- if (request.getResponseToken() == null || request.getState() == null) {
- LOG.error("Missing response token or RelayState parameters");
+ if (request.getResponseToken() == null) {
+ LOG.error("Missing response token parameter");
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ if (request.isSignOutRequest()) {
+ return processSignOutResponse(request, config);
+ }
+
+ if (request.getState() == null) {
+ LOG.error("Missing RelayState parameter");
throw new ProcessingException(TYPE.INVALID_REQUEST);
}
@@ -224,12 +236,74 @@ public class SAMLProcessorImpl extends AbstractFedizProcessor {
return fedResponse;
}
+ private FedizResponse processSignOutResponse(FedizRequest request, FedizContext config) throws ProcessingException {
+ SAMLProtocol protocol = (SAMLProtocol)config.getProtocol();
+
+ InputStream tokenStream = null;
+ try {
+ byte[] deflatedToken = Base64.decode(request.getResponseToken());
+ if (protocol.isDisableDeflateEncoding()) {
+ tokenStream = new ByteArrayInputStream(deflatedToken);
+ } else {
+ tokenStream = CompressionUtils.inflate(deflatedToken);
+ }
+ } catch (IllegalArgumentException | DataFormatException ex) {
+ LOG.warn("Invalid data format", 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);
+ 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.saml.saml2.core.LogoutResponse)) {
+ throw new ProcessingException(TYPE.INVALID_REQUEST);
+ }
+
+ org.opensaml.saml.saml2.core.LogoutResponse logoutResponse =
+ (org.opensaml.saml.saml2.core.LogoutResponse)responseObject;
+
+ // Validate the Response
+ validateSamlResponseProtocol(logoutResponse, config);
+
+ Date issueInstant = logoutResponse.getIssueInstant().toDate();
+
+ FedizResponse fedResponse = new FedizResponse(
+ null, logoutResponse.getIssuer().getValue(),
+ Collections.<String>emptyList(), Collections.<Claim>emptyList(),
+ null,
+ issueInstant,
+ null,
+ null,
+ logoutResponse.getID());
+
+ return fedResponse;
+ }
+
/**
* Validate the received SAML Response as per the protocol
* @throws ProcessingException
*/
protected void validateSamlResponseProtocol(
- org.opensaml.saml.saml2.core.Response samlResponse,
+ StatusResponseType samlResponse,
FedizContext config
) throws ProcessingException {
try {
@@ -471,6 +545,7 @@ public class SAMLProcessorImpl extends AbstractFedizProcessor {
RedirectionResponse response = new RedirectionResponse();
response.addHeader("Cache-Control", "no-cache, no-store");
response.addHeader("Pragma", "no-cache");
+ response.setState(relayState);
redirectURL = redirectURL + "?" + sb.toString();
response.setRedirectionURL(redirectURL);
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
index 9f2c038..fff1621 100644
--- 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
@@ -68,7 +68,7 @@ public class SAMLProtocolResponseValidator {
* @throws WSSecurityException
*/
public void validateSamlResponse(
- org.opensaml.saml.saml2.core.Response samlResponse,
+ org.opensaml.saml.saml2.core.StatusResponseType samlResponse,
FedizContext config
) throws WSSecurityException {
// Check the Status Code
@@ -120,7 +120,7 @@ public class SAMLProtocolResponseValidator {
* Validate the Response signature (if it exists)
*/
private void validateResponseSignature(
- org.opensaml.saml.saml2.core.Response samlResponse,
+ org.opensaml.saml.saml2.core.StatusResponseType samlResponse,
FedizContext config
) throws WSSecurityException {
if (!samlResponse.isSigned()) {
diff --git a/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAML2PResponseComponentBuilder.java b/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAML2PResponseComponentBuilder.java
index e280f63..a193cae 100644
--- a/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAML2PResponseComponentBuilder.java
+++ b/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAML2PResponseComponentBuilder.java
@@ -27,6 +27,7 @@ import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.saml.common.SAMLObjectBuilder;
import org.opensaml.saml.common.SAMLVersion;
import org.opensaml.saml.saml2.core.Issuer;
+import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.Status;
import org.opensaml.saml.saml2.core.StatusCode;
@@ -38,6 +39,8 @@ import org.opensaml.saml.saml2.core.StatusMessage;
public final class SAML2PResponseComponentBuilder {
private static SAMLObjectBuilder<Response> responseBuilder;
+
+ private static SAMLObjectBuilder<LogoutResponse> logoutResponseBuilder;
private static SAMLObjectBuilder<Issuer> issuerBuilder;
@@ -83,6 +86,29 @@ public final class SAML2PResponseComponentBuilder {
return response;
}
+
+ public static LogoutResponse createSAMLLogoutResponse(
+ String inResponseTo,
+ String issuer,
+ Status status,
+ String destination
+ ) {
+ if (logoutResponseBuilder == null) {
+ logoutResponseBuilder = (SAMLObjectBuilder<LogoutResponse>)
+ builderFactory.getBuilder(LogoutResponse.DEFAULT_ELEMENT_NAME);
+ }
+ LogoutResponse response = logoutResponseBuilder.buildObject();
+
+ response.setID(UUID.randomUUID().toString());
+ response.setIssueInstant(new DateTime());
+ response.setInResponseTo(inResponseTo);
+ response.setIssuer(createIssuer(issuer));
+ response.setStatus(status);
+ response.setVersion(SAMLVersion.VERSION_20);
+ response.setDestination(destination);
+
+ return response;
+ }
@SuppressWarnings("unchecked")
public static Issuer createIssuer(
@@ -148,4 +174,4 @@ public final class SAML2PResponseComponentBuilder {
}
-}
\ No newline at end of file
+}
diff --git a/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAMLResponseTest.java b/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAMLResponseTest.java
index a0b3681..e09e2e3 100644
--- a/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAMLResponseTest.java
+++ b/plugins/core/src/test/java/org/apache/cxf/fediz/core/samlsso/SAMLResponseTest.java
@@ -25,6 +25,8 @@ import java.math.BigInteger;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@@ -63,6 +65,7 @@ import org.apache.cxf.fediz.core.processor.FedizResponse;
import org.apache.cxf.fediz.core.processor.SAMLProcessorImpl;
import org.apache.wss4j.common.crypto.Crypto;
import org.apache.wss4j.common.crypto.CryptoFactory;
+import org.apache.wss4j.common.crypto.CryptoType;
import org.apache.wss4j.common.ext.WSPasswordCallback;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.saml.OpenSAMLUtil;
@@ -82,8 +85,16 @@ import org.joda.time.DateTimeZone;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
+import org.opensaml.saml.common.SAMLObjectContentReference;
+import org.opensaml.saml.common.SignableSAMLObject;
+import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.Response;
import org.opensaml.saml.saml2.core.Status;
+import org.opensaml.security.x509.BasicX509Credential;
+import org.opensaml.xmlsec.keyinfo.impl.X509KeyInfoGeneratorFactory;
+import org.opensaml.xmlsec.signature.KeyInfo;
+import org.opensaml.xmlsec.signature.Signature;
+import org.opensaml.xmlsec.signature.support.SignatureConstants;
import static org.junit.Assert.fail;
@@ -1239,6 +1250,32 @@ public class SAMLResponseTest {
// expected
}
}
+
+ @org.junit.Test
+ public void validateLogoutResponse() throws Exception {
+ // Mock up a LogoutResponse
+ FedizContext config = getFederationConfigurator().getFedizContext("ROOT");
+
+ String requestId = URLEncoder.encode(UUID.randomUUID().toString(), "UTF-8");
+
+ String status = "urn:oasis:names:tc:SAML:2.0:status:Success";
+ Element logoutResponse = createLogoutResponse(status, TEST_REQUEST_URL, true, requestId);
+
+ HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class);
+ EasyMock.expect(req.getRequestURL()).andReturn(new StringBuffer(TEST_REQUEST_URL));
+ EasyMock.expect(req.getRemoteAddr()).andReturn(TEST_CLIENT_ADDRESS);
+ EasyMock.replay(req);
+
+ FedizRequest wfReq = new FedizRequest();
+ wfReq.setResponseToken(encodeResponse(logoutResponse));
+ String relayState = URLEncoder.encode(UUID.randomUUID().toString(), "UTF-8");
+ wfReq.setState(relayState);
+ wfReq.setRequest(req);
+ wfReq.setSignOutRequest(true);
+
+ FedizProcessor wfProc = new SAMLProcessorImpl();
+ wfProc.processRequest(wfReq, config);
+ }
private String createSamlResponseStr(String requestId) throws Exception {
// Create SAML Assertion
@@ -1308,6 +1345,86 @@ public class SAMLResponseTest {
return policyElement;
}
+ private Element createLogoutResponse(String statusValue, String destination,
+ boolean sign, String requestID) throws Exception {
+ DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
+ Document doc = docBuilder.newDocument();
+
+ Status status =
+ SAML2PResponseComponentBuilder.createStatus(statusValue, null);
+ LogoutResponse response =
+ SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, TEST_IDP_ISSUER, status, destination);
+
+ // Sign the LogoutResponse
+ if (sign) {
+ signResponse(response, "mystskey");
+ }
+
+ Element policyElement = OpenSAMLUtil.toDom(response, doc);
+ doc.appendChild(policyElement);
+
+ return policyElement;
+ }
+
+ private void signResponse(SignableSAMLObject signableObject, String alias) throws Exception {
+
+ Signature signature = OpenSAMLUtil.buildSignature();
+ signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+ CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+ cryptoType.setAlias(alias);
+ X509Certificate[] issuerCerts = crypto.getX509Certificates(cryptoType);
+
+ String sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
+ String pubKeyAlgo = issuerCerts[0].getPublicKey().getAlgorithm();
+ if (pubKeyAlgo.equalsIgnoreCase("DSA")) {
+ sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_DSA;
+ } else if (pubKeyAlgo.equalsIgnoreCase("EC")) {
+ sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1;
+ }
+
+ WSPasswordCallback[] cb = {
+ new WSPasswordCallback(alias, WSPasswordCallback.SIGNATURE)
+ };
+ cbPasswordHandler.handle(cb);
+ String password = cb[0].getPassword();
+
+ PrivateKey privateKey;
+ try {
+ privateKey = crypto.getPrivateKey(alias, password);
+ } catch (Exception ex) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
+ }
+ if (privateKey == null) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
+ new Object[] {"No private key was found using issuer name: " + alias});
+ }
+
+ signature.setSignatureAlgorithm(sigAlgo);
+
+ BasicX509Credential signingCredential =
+ new BasicX509Credential(issuerCerts[0], privateKey);
+
+ signature.setSigningCredential(signingCredential);
+
+ X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory();
+ kiFactory.setEmitEntityCertificate(true);
+
+ try {
+ KeyInfo keyInfo = kiFactory.newInstance().generate(signingCredential);
+ signature.setKeyInfo(keyInfo);
+ } catch (org.opensaml.security.SecurityException ex) {
+ throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, "empty",
+ new Object[] {"Error generating KeyInfo from signing credential"});
+ }
+
+ signableObject.setSignature(signature);
+ String digestAlg = SignatureConstants.ALGO_ID_DIGEST_SHA1;
+ SAMLObjectContentReference contentRef =
+ (SAMLObjectContentReference)signature.getContentReferences().get(0);
+ contentRef.setDigestAlgorithm(digestAlg);
+ signableObject.releaseDOM();
+ signableObject.releaseChildrenDOM(true);
+ }
/**
* Returns the first element that matches <code>name</code> and