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/16 17:13:41 UTC

[cxf-fediz] branch 1.4.x-fixes updated (e3462ea -> 4c9a56d)

This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a change to branch 1.4.x-fixes
in repository https://gitbox.apache.org/repos/asf/cxf-fediz.git.


    from e3462ea  Fixing checkstyl error
     new f8300f3  FEDIZ-221 - Allow the user to cancel logout
     new 4c9a56d  FEDIZ-221 - Sign the LogoutResponses from the IdP

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../beans/samlsso/AbstractSamlResponseCreator.java | 176 +++++++++++++++++++++
 .../idp/beans/samlsso/SamlResponseCreator.java     |  62 +-------
 .../beans/samlsso/SamlResponseErrorCreator.java    |  56 +------
 .../webapp/WEB-INF/flows/saml-validate-request.xml |   2 +-
 .../WEB-INF/views/signoutconfirmationresponse.jsp  |   5 +
 .../org/apache/cxf/fediz/systests/idp/IdpTest.java | 138 ++++++++++++++++
 6 files changed, 326 insertions(+), 113 deletions(-)
 create mode 100644 services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java


[cxf-fediz] 02/02: FEDIZ-221 - Sign the LogoutResponses from the IdP

Posted by co...@apache.org.
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

commit 4c9a56d106a4a2dd8bef9cc24bdcf6846854ce29
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Jul 16 17:51:46 2018 +0100

    FEDIZ-221 - Sign the LogoutResponses from the IdP
---
 .../beans/samlsso/AbstractSamlResponseCreator.java | 176 +++++++++++++++++++++
 .../idp/beans/samlsso/SamlResponseCreator.java     |  62 +-------
 .../beans/samlsso/SamlResponseErrorCreator.java    |  56 +------
 .../org/apache/cxf/fediz/systests/idp/IdpTest.java |   8 +-
 4 files changed, 189 insertions(+), 113 deletions(-)

diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.java
new file mode 100644
index 0000000..0e9c802
--- /dev/null
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AbstractSamlResponseCreator.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.service.idp.beans.samlsso;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import org.apache.cxf.common.util.Base64Utility;
+import org.apache.cxf.fediz.core.util.CertsUtils;
+import org.apache.cxf.fediz.service.idp.domain.Idp;
+import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
+import org.apache.cxf.helpers.DOMUtils;
+import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
+import org.apache.wss4j.common.crypto.Crypto;
+import org.apache.wss4j.common.crypto.CryptoType;
+import org.apache.wss4j.common.ext.WSSecurityException;
+import org.apache.wss4j.common.saml.OpenSAMLUtil;
+import org.apache.wss4j.common.util.DOM2Writer;
+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.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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+abstract class AbstractSamlResponseCreator {
+
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractSamlResponseCreator.class);
+    private boolean signLogoutResponse = true;
+    private boolean supportDeflateEncoding;
+    private boolean useRealmForIssuer;
+
+    protected Element createLogoutResponse(Idp idp, String statusValue,
+                                           String destination, String requestID) throws Exception {
+        Document doc = DOMUtils.newDocument();
+
+        Status status =
+            SAML2PResponseComponentBuilder.createStatus(statusValue, null);
+        String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+        LogoutResponse response =
+            SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
+
+        // Sign the LogoutResponse
+        signResponse(response, idp);
+
+        Element policyElement = OpenSAMLUtil.toDom(response, doc);
+        doc.appendChild(policyElement);
+
+        return policyElement;
+    }
+
+    protected void signResponse(SignableSAMLObject signableObject, Idp idp) throws Exception {
+        if (!signLogoutResponse) {
+            return;
+        }
+        Crypto issuerCrypto = CertsUtils.getCryptoFromCertificate(idp.getCertificate());
+        String issuerKeyName = issuerCrypto.getDefaultX509Identifier();
+        String issuerKeyPassword = idp.getCertificatePassword();
+
+        Signature signature = OpenSAMLUtil.buildSignature();
+        signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
+        CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
+        cryptoType.setAlias(issuerKeyName);
+        X509Certificate[] issuerCerts = null;
+        if (issuerCrypto != null) {
+            issuerCerts = issuerCrypto.getX509Certificates(cryptoType);
+        }
+        if (issuerCerts == null || issuerCerts.length == 0) {
+            throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, "empty",
+                new Object[] {"No issuer certs were found to sign the SAML Assertion using issuer name: "
+                                              + issuerKeyName});
+        }
+
+        String sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA1;
+        String pubKeyAlgo = issuerCerts[0].getPublicKey().getAlgorithm();
+        LOG.debug("automatic sig algo detection: {}", pubKeyAlgo);
+        if (pubKeyAlgo.equalsIgnoreCase("DSA")) {
+            sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_DSA;
+        } else if (pubKeyAlgo.equalsIgnoreCase("EC")) {
+            sigAlgo = SignatureConstants.ALGO_ID_SIGNATURE_ECDSA_SHA1;
+        }
+        LOG.debug("Using Signature algorithm {}", sigAlgo);
+        PrivateKey privateKey;
+        try {
+            privateKey = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPassword);
+        } 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: " + issuerKeyName});
+        }
+
+        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);
+    }
+
+    protected String encodeResponse(Element response) throws IOException {
+        String responseMessage = DOM2Writer.nodeToString(response);
+        LOG.debug("Created Response: {}", responseMessage);
+
+        if (supportDeflateEncoding) {
+            DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
+            byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
+
+            return Base64Utility.encode(deflatedBytes);
+        }
+
+        return Base64Utility.encode(responseMessage.getBytes());
+    }
+
+    public boolean isSupportDeflateEncoding() {
+        return supportDeflateEncoding;
+    }
+
+    public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
+        this.supportDeflateEncoding = supportDeflateEncoding;
+    }
+
+    public boolean isUseRealmForIssuer() {
+        return useRealmForIssuer;
+    }
+
+    public void setUseRealmForIssuer(boolean useRealmForIssuer) {
+        this.useRealmForIssuer = useRealmForIssuer;
+    }
+}
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
index 426bbf7..b91544d 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseCreator.java
@@ -18,15 +18,12 @@
  */
 package org.apache.cxf.fediz.service.idp.beans.samlsso;
 
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import org.apache.cxf.common.util.Base64Utility;
 import org.apache.cxf.fediz.core.exception.ProcessingException;
 import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
 import org.apache.cxf.fediz.core.util.CertsUtils;
@@ -37,7 +34,6 @@ import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
 import org.apache.cxf.fediz.service.idp.samlsso.SAMLAuthnRequest;
 import org.apache.cxf.fediz.service.idp.util.WebUtils;
 import org.apache.cxf.helpers.DOMUtils;
-import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
 import org.apache.wss4j.common.crypto.Crypto;
 import org.apache.wss4j.common.saml.OpenSAMLUtil;
 import org.apache.wss4j.common.saml.SAMLCallback;
@@ -46,11 +42,9 @@ import org.apache.wss4j.common.saml.SamlAssertionWrapper;
 import org.apache.wss4j.common.saml.bean.AudienceRestrictionBean;
 import org.apache.wss4j.common.saml.bean.ConditionsBean;
 import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean;
-import org.apache.wss4j.common.util.DOM2Writer;
 import org.apache.wss4j.dom.WSConstants;
 import org.joda.time.DateTime;
 import org.opensaml.saml.saml2.core.Assertion;
-import org.opensaml.saml.saml2.core.LogoutResponse;
 import org.opensaml.saml.saml2.core.NameID;
 import org.opensaml.saml.saml2.core.Response;
 import org.opensaml.saml.saml2.core.Status;
@@ -63,11 +57,9 @@ import org.springframework.webflow.execution.RequestContext;
  * Insert the SAML Token received from the STS into a SAML Response
  */
 @Component
-public class SamlResponseCreator {
+public class SamlResponseCreator extends AbstractSamlResponseCreator {
 
     private static final Logger LOG = LoggerFactory.getLogger(SamlResponseCreator.class);
-    private boolean supportDeflateEncoding;
-    private boolean useRealmForIssuer;
 
     public String createSAMLResponse(RequestContext context, Idp idp, Element rpToken,
                                      String consumerURL, String requestId, String requestIssuer)
@@ -100,7 +92,8 @@ public class SamlResponseCreator {
     public String createSAMLLogoutResponse(RequestContext context, Idp idp, String destination, String requestId)
                                          throws ProcessingException {
         try {
-            Element response = createLogoutResponse(idp, destination, requestId);
+            Element response = createLogoutResponse(idp, "urn:oasis:names:tc:SAML:2.0:status:Success",
+                                                    destination, requestId);
             return encodeResponse(response);
         } catch (Exception ex) {
             LOG.warn("Error marshalling SAML Token: {}", ex.getMessage());
@@ -113,7 +106,7 @@ public class SamlResponseCreator {
                                            String remoteAddr, String racs) throws Exception {
         // Create an AuthenticationAssertion
         SAML2CallbackHandler callbackHandler = new SAML2CallbackHandler();
-        String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+        String issuer = isUseRealmForIssuer() ? idp.getRealm() : idp.getIdpUrl().toString();
         callbackHandler.setIssuer(issuer);
         callbackHandler.setSubject(receivedToken.getSaml2().getSubject());
 
@@ -167,7 +160,7 @@ public class SamlResponseCreator {
             SAML2PResponseComponentBuilder.createStatus(
                 "urn:oasis:names:tc:SAML:2.0:status:Success", null
             );
-        String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
+        String issuer = isUseRealmForIssuer() ? idp.getRealm() : idp.getIdpUrl().toString();
         Response response =
             SAML2PResponseComponentBuilder.createSAMLResponse(requestID, issuer, status);
 
@@ -179,50 +172,5 @@ public class SamlResponseCreator {
         return policyElement;
     }
 
-    protected Element createLogoutResponse(Idp idp, String destination, String requestID) throws Exception {
-        Document doc = DOMUtils.newDocument();
-
-        Status status =
-            SAML2PResponseComponentBuilder.createStatus(
-                "urn:oasis:names:tc:SAML:2.0:status:Success", null
-            );
-        String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
-        LogoutResponse response =
-            SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
-
-        Element policyElement = OpenSAMLUtil.toDom(response, doc);
-        doc.appendChild(policyElement);
-
-        return policyElement;
-    }
-
-    protected String encodeResponse(Element response) throws IOException {
-        String responseMessage = DOM2Writer.nodeToString(response);
-        LOG.debug("Created Response: {}", responseMessage);
 
-        if (supportDeflateEncoding) {
-            DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
-            byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
-
-            return Base64Utility.encode(deflatedBytes);
-        }
-
-        return Base64Utility.encode(responseMessage.getBytes());
-    }
-
-    public boolean isSupportDeflateEncoding() {
-        return supportDeflateEncoding;
-    }
-
-    public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
-        this.supportDeflateEncoding = supportDeflateEncoding;
-    }
-
-    public boolean isUseRealmForIssuer() {
-        return useRealmForIssuer;
-    }
-
-    public void setUseRealmForIssuer(boolean useRealmForIssuer) {
-        this.useRealmForIssuer = useRealmForIssuer;
-    }
 }
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
index d201d0a..f6996c2 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/SamlResponseErrorCreator.java
@@ -18,22 +18,15 @@
  */
 package org.apache.cxf.fediz.service.idp.beans.samlsso;
 
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
-import org.apache.cxf.common.util.Base64Utility;
 import org.apache.cxf.fediz.core.exception.ProcessingException;
 import org.apache.cxf.fediz.core.exception.ProcessingException.TYPE;
 import org.apache.cxf.fediz.service.idp.domain.Idp;
 import org.apache.cxf.fediz.service.idp.samlsso.SAML2PResponseComponentBuilder;
 import org.apache.cxf.helpers.DOMUtils;
-import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
 import org.apache.wss4j.common.saml.OpenSAMLUtil;
-import org.apache.wss4j.common.util.DOM2Writer;
-import org.opensaml.saml.saml2.core.LogoutResponse;
 import org.opensaml.saml.saml2.core.Response;
 import org.opensaml.saml.saml2.core.Status;
 import org.slf4j.Logger;
@@ -45,11 +38,9 @@ import org.springframework.webflow.execution.RequestContext;
  * Create a SAML Error Response
  */
 @Component
-public class SamlResponseErrorCreator {
+public class SamlResponseErrorCreator extends AbstractSamlResponseCreator {
 
     private static final Logger LOG = LoggerFactory.getLogger(SamlResponseErrorCreator.class);
-    private boolean supportDeflateEncoding;
-    private boolean useRealmForIssuer;
 
     public String createSAMLResponse(RequestContext context, boolean logout, boolean requestor,
                                      Idp idp, String requestID, String destination) throws ProcessingException {
@@ -82,49 +73,4 @@ public class SamlResponseErrorCreator {
         }
     }
 
-    protected Element createLogoutResponse(Idp idp, String statusValue,
-                                           String destination, String requestID) throws Exception {
-        Document doc = DOMUtils.newDocument();
-
-        Status status =
-            SAML2PResponseComponentBuilder.createStatus(statusValue, null);
-        String issuer = useRealmForIssuer ? idp.getRealm() : idp.getIdpUrl().toString();
-        LogoutResponse response =
-            SAML2PResponseComponentBuilder.createSAMLLogoutResponse(requestID, issuer, status, destination);
-
-        Element policyElement = OpenSAMLUtil.toDom(response, doc);
-        doc.appendChild(policyElement);
-
-        return policyElement;
-    }
-
-    protected String encodeResponse(Element response) throws IOException {
-        String responseMessage = DOM2Writer.nodeToString(response);
-        LOG.debug("Created Response: {}", responseMessage);
-
-        if (supportDeflateEncoding) {
-            DeflateEncoderDecoder encoder = new DeflateEncoderDecoder();
-            byte[] deflatedBytes = encoder.deflateToken(responseMessage.getBytes(StandardCharsets.UTF_8));
-
-            return Base64Utility.encode(deflatedBytes);
-        }
-
-        return Base64Utility.encode(responseMessage.getBytes());
-    }
-
-    public boolean isSupportDeflateEncoding() {
-        return supportDeflateEncoding;
-    }
-
-    public void setSupportDeflateEncoding(boolean supportDeflateEncoding) {
-        this.supportDeflateEncoding = supportDeflateEncoding;
-    }
-
-    public boolean isUseRealmForIssuer() {
-        return useRealmForIssuer;
-    }
-
-    public void setUseRealmForIssuer(boolean useRealmForIssuer) {
-        this.useRealmForIssuer = useRealmForIssuer;
-    }
 }
diff --git a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
index d4a65be..6946759 100644
--- a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
+++ b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
@@ -1743,6 +1743,8 @@ public class IdpTest {
         String success = "urn:oasis:names:tc:SAML:2.0:status:Success";
         Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
 
+        Assert.assertNotNull(logoutResponse.getSignature());
+
         webClient.close();
 
         // 3. now we try to access the idp without authentication but with the existing cookies
@@ -1870,6 +1872,8 @@ public class IdpTest {
         Assert.assertEquals(expectedIssuer, logoutResponse.getIssuer().getValue());
         String success = "urn:oasis:names:tc:SAML:2.0:status:Requester";
         Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
+
+        Assert.assertNotNull(logoutResponse.getSignature());
         webClient.close();
 
         // 3. now we try to access the idp without authentication but with the existing cookies
@@ -1884,7 +1888,6 @@ public class IdpTest {
 
         webClient.close();
     }
-    // CHECKSTYLE:ON
 
     @org.junit.Test
     public void testIdpLogoutCancelled() throws Exception {
@@ -2003,6 +2006,7 @@ public class IdpTest {
         String success = "urn:oasis:names:tc:SAML:2.0:status:Requester";
         Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
 
+        Assert.assertNotNull(logoutResponse.getSignature());
         webClient.close();
 
         // 3. now we try to access the idp without authentication but with the existing cookies
@@ -2017,6 +2021,7 @@ public class IdpTest {
 
         webClient.close();
     }
+    // CHECKSTYLE:ON
 
     private String encodeAuthnRequest(Element authnRequest) throws IOException {
         String requestMessage = DOM2Writer.nodeToString(authnRequest);
@@ -2112,4 +2117,5 @@ public class IdpTest {
 
         return samlResponseObject;
     }
+
 }


[cxf-fediz] 01/02: FEDIZ-221 - Allow the user to cancel logout

Posted by co...@apache.org.
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

commit f8300f3845d3009f8fe07c7c85942e5bfb3bbc20
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Jul 16 16:04:46 2018 +0100

    FEDIZ-221 - Allow the user to cancel logout
---
 .../webapp/WEB-INF/flows/saml-validate-request.xml |   2 +-
 .../WEB-INF/views/signoutconfirmationresponse.jsp  |   5 +
 .../org/apache/cxf/fediz/systests/idp/IdpTest.java | 132 +++++++++++++++++++++
 3 files changed, 138 insertions(+), 1 deletion(-)

diff --git a/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml b/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
index ce6c253..ab80906 100644
--- a/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
+++ b/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
@@ -223,7 +223,7 @@
     
     <view-state id="viewSignoutConfirmation" view="signoutconfirmationresponse">
         <transition on="submit" to="produceSAMLLogoutResponse"/>
-        <!-- <transition on="cancel" to="redirect" /> -->
+        <transition on="cancel" to="viewBadLogoutRequestResponse" />
     </view-state>
     
     <action-state id="produceSAMLLogoutResponse">
diff --git a/services/idp/src/main/webapp/WEB-INF/views/signoutconfirmationresponse.jsp b/services/idp/src/main/webapp/WEB-INF/views/signoutconfirmationresponse.jsp
index 3e7a547..9d76ea6 100644
--- a/services/idp/src/main/webapp/WEB-INF/views/signoutconfirmationresponse.jsp
+++ b/services/idp/src/main/webapp/WEB-INF/views/signoutconfirmationresponse.jsp
@@ -17,6 +17,7 @@
         Map<String, Application> rcm =
         (Map<String, Application>) request.getSession().getAttribute(SigninParametersCacheAction.ACTIVE_APPLICATIONS);
     	String wreply = (String) request.getAttribute("wreply");
+    	String samlAction = (String) request.getAttribute("SAMLRequest");
 
         if (rcm == null) {
     %>
@@ -55,6 +56,10 @@
 			    <input type="hidden" name="wreply" value="<%= wreply%>" />        
 	            <input type="submit" name="_eventId_cancel" value="Cancel" />
 	            <%     
+			        } else if (samlAction != null && !samlAction.isEmpty()) {
+			    %>
+	            <input type="submit" name="_eventId_cancel" value="Cancel" />
+	            <%     
 			        }
 			    %>
 	        </form:form>
diff --git a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
index 576b4b3..d4a65be 100644
--- a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
+++ b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/idp/IdpTest.java
@@ -1886,6 +1886,138 @@ public class IdpTest {
     }
     // CHECKSTYLE:ON
 
+    @org.junit.Test
+    public void testIdpLogoutCancelled() throws Exception {
+        OpenSAMLUtil.initSamlEngine();
+
+        // 1. First let's login to the IdP
+
+        // Create SAML AuthnRequest
+        Document doc = DOMUtils.createDocument();
+        doc.appendChild(doc.createElement("root"));
+        // Create the AuthnRequest
+        String consumerURL = "https://localhost:" + getRpHttpsPort() + "/"
+            + getServletContextName() + "/secure/fedservlet";
+        AuthnRequest authnRequest =
+            new DefaultAuthnRequestBuilder().createAuthnRequest(
+                null, "urn:org:apache:cxf:fediz:fedizhelloworld", consumerURL
+            );
+        authnRequest.setDestination("https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml");
+        signAuthnRequest(authnRequest);
+
+        Element authnRequestElement = OpenSAMLUtil.toDom(authnRequest, doc);
+        String authnRequestEncoded = encodeAuthnRequest(authnRequestElement);
+
+        String urlEncodedRequest = URLEncoder.encode(authnRequestEncoded, "UTF-8");
+
+        String relayState = UUID.randomUUID().toString();
+        String url = "https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml?";
+        url += SSOConstants.RELAY_STATE + "=" + relayState;
+        url += "&" + SSOConstants.SAML_REQUEST + "=" + urlEncodedRequest;
+
+        String user = "alice";
+        String password = "ecila";
+
+        CookieManager cookieManager = new CookieManager();
+
+        WebClient webClient = new WebClient();
+        webClient.setCookieManager(cookieManager);
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getCredentialsProvider().setCredentials(
+            new AuthScope("localhost", Integer.parseInt(getIdpHttpsPort())),
+            new UsernamePasswordCredentials(user, password));
+
+        webClient.getOptions().setJavaScriptEnabled(false);
+        HtmlPage idpPage = webClient.getPage(url);
+        webClient.getOptions().setJavaScriptEnabled(true);
+        Assert.assertEquals("IDP SignIn Response Form", idpPage.getTitleText());
+
+        org.opensaml.saml.saml2.core.Response samlResponse =
+            parseSAMLResponse(idpPage, relayState, consumerURL, authnRequest.getID());
+        String expected = "urn:oasis:names:tc:SAML:2.0:status:Success";
+        Assert.assertEquals(expected, samlResponse.getStatus().getStatusCode().getValue());
+        NameID nameID = samlResponse.getAssertions().get(0).getSubject().getNameID();
+        Assert.assertNotNull(nameID);
+        nameID.detach();
+
+        webClient.close();
+
+        // 2. now we logout from IdP - but cancel the logout
+
+        // Create SAML LogoutRequest
+        doc = DOMUtils.createDocument();
+        doc.appendChild(doc.createElement("root"));
+
+        Issuer issuer = SamlpRequestComponentBuilder.createIssuer("urn:org:apache:cxf:fediz:fedizhelloworld");
+        String destination = "https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml";
+        LogoutRequest logoutRequest =
+            SamlpRequestComponentBuilder.createLogoutRequest(SAMLVersion.VERSION_20, issuer, destination,
+                                                             null, null, null, nameID);
+
+        signAuthnRequest(logoutRequest);
+
+        Element logoutRequestElement = OpenSAMLUtil.toDom(logoutRequest, doc);
+        String logoutRequestEncoded = encodeAuthnRequest(logoutRequestElement);
+
+        urlEncodedRequest = URLEncoder.encode(logoutRequestEncoded, "UTF-8");
+
+        relayState = UUID.randomUUID().toString();
+        String logoutURL = "https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml?";
+        logoutURL += SSOConstants.RELAY_STATE + "=" + relayState;
+        logoutURL += "&" + SSOConstants.SAML_REQUEST + "=" + urlEncodedRequest;
+
+        webClient = new WebClient();
+        webClient.setCookieManager(cookieManager);
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getCredentialsProvider().setCredentials(
+            new AuthScope("localhost", Integer.parseInt(getIdpHttpsPort())),
+            new UsernamePasswordCredentials(user, password));
+
+        webClient.getOptions().setJavaScriptEnabled(false);
+        idpPage = webClient.getPage(logoutURL);
+        webClient.getOptions().setJavaScriptEnabled(true);
+
+        Assert.assertEquals("IDP SignOut Confirmation Response Page", idpPage.getTitleText());
+
+        HtmlForm form = idpPage.getFormByName("signoutconfirmationresponseform");
+        HtmlSubmitInput button = form.getInputByName("_eventId_cancel");
+        HtmlPage signoutPage = button.click();
+
+        // Check Response
+        HtmlForm responseForm = signoutPage.getFormByName("samlsignoutresponseform");
+        Assert.assertEquals("https://localhost:8080/logout", responseForm.getActionAttribute());
+        String responseValue = responseForm.getInputByName("SAMLResponse").getAttributeNS(null, "value");
+        Assert.assertNotNull(responseValue);
+        String receivedRelayState = responseForm.getInputByName("RelayState").getAttributeNS(null, "value");
+        Assert.assertEquals(relayState, receivedRelayState);
+
+        byte[] deflatedToken = Base64Utility.decode(responseValue);
+        InputStream tokenStream = new ByteArrayInputStream(deflatedToken);
+        Document responseDoc = StaxUtils.read(new InputStreamReader(tokenStream, StandardCharsets.UTF_8));
+
+        LogoutResponse logoutResponse = (LogoutResponse)OpenSAMLUtil.fromDom(responseDoc.getDocumentElement());
+        Assert.assertNotNull(logoutResponse);
+        Assert.assertEquals("https://localhost:8080/logout", logoutResponse.getDestination());
+        String expectedIssuer = "https://localhost:" + getIdpHttpsPort() + "/fediz-idp/saml";
+        Assert.assertEquals(expectedIssuer, logoutResponse.getIssuer().getValue());
+        String success = "urn:oasis:names:tc:SAML:2.0:status:Requester";
+        Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
+
+        webClient.close();
+
+        // 3. now we try to access the idp without authentication but with the existing cookies
+        // to see if we are really logged out - we should still be logged in as we cancelled the Logout process
+        webClient = new WebClient();
+        webClient.setCookieManager(cookieManager);
+        webClient.getOptions().setUseInsecureSSL(true);
+        webClient.getOptions().setThrowExceptionOnFailingStatusCode(false);
+        idpPage = webClient.getPage(url);
+
+        Assert.assertEquals(200, idpPage.getWebResponse().getStatusCode());
+
+        webClient.close();
+    }
+
     private String encodeAuthnRequest(Element authnRequest) throws IOException {
         String requestMessage = DOM2Writer.nodeToString(authnRequest);