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/13 17:03:10 UTC

[cxf-fediz] 04/04: FEDIZ-221 - Return errors in a LogoutResponse as well

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

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf-fediz.git

commit 43b07bed4dc0434dac7d66be4fe8c9c21f83fcf8
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Fri Jul 13 17:48:03 2018 +0100

    FEDIZ-221 - Return errors in a LogoutResponse as well
---
 .../idp/beans/samlsso/AuthnRequestParser.java      |  2 +-
 .../beans/samlsso/SamlResponseErrorCreator.java    | 48 ++++++++++++++++++----
 .../webapp/WEB-INF/flows/saml-validate-request.xml | 23 +++++++++--
 .../apache/cxf/fediz/systests/samlsso/IdpTest.java | 32 ++++++++++++---
 4 files changed, 86 insertions(+), 19 deletions(-)

diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
index 6b6b88a..f501458 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/samlsso/AuthnRequestParser.java
@@ -122,11 +122,11 @@ public class AuthnRequestParser {
                 WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST, authnRequest);
             } else if (parsedRequest instanceof LogoutRequest) {
                 SAMLLogoutRequest logoutRequest = new SAMLLogoutRequest((LogoutRequest)parsedRequest);
+                WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_LOGOUT_REQUEST, logoutRequest);
                 if (logoutRequest.getNotOnOrAfter() != null && (new Date()).after(logoutRequest.getNotOnOrAfter())) {
                     LOG.debug("The LogoutRequest is expired");
                     throw new ProcessingException(TYPE.BAD_REQUEST);
                 }
-                WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_LOGOUT_REQUEST, logoutRequest);
             }
 
             validateRequest(parsedRequest);
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 761883a..d201d0a 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
@@ -33,6 +33,7 @@ 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;
@@ -48,25 +49,32 @@ public class SamlResponseErrorCreator {
 
     private static final Logger LOG = LoggerFactory.getLogger(SamlResponseErrorCreator.class);
     private boolean supportDeflateEncoding;
+    private boolean useRealmForIssuer;
 
-    public String createSAMLResponse(RequestContext context, boolean requestor,
-                                     Idp idp, String requestID) throws ProcessingException {
+    public String createSAMLResponse(RequestContext context, boolean logout, boolean requestor,
+                                     Idp idp, String requestID, String destination) throws ProcessingException {
         Document doc = DOMUtils.newDocument();
 
         String statusValue = "urn:oasis:names:tc:SAML:2.0:status:Responder";
         if (requestor) {
             statusValue = "urn:oasis:names:tc:SAML:2.0:status:Requester";
         }
+
         Status status =
             SAML2PResponseComponentBuilder.createStatus(statusValue, null);
-        Response response =
-            SAML2PResponseComponentBuilder.createSAMLResponse(requestID, idp.getRealm(), status);
-
+        Element responseElement = null;
         try {
-            Element policyElement = OpenSAMLUtil.toDom(response, doc);
-            doc.appendChild(policyElement);
+            if (logout) {
+                responseElement = createLogoutResponse(idp, statusValue, destination, requestID);
+            } else {
+                Response response =
+                    SAML2PResponseComponentBuilder.createSAMLResponse(requestID, idp.getRealm(), status);
+                Element policyElement = OpenSAMLUtil.toDom(response, doc);
+                doc.appendChild(policyElement);
+
+                responseElement = policyElement;
+            }
 
-            Element responseElement = policyElement;
             return encodeResponse(responseElement);
         } catch (Exception e) {
             LOG.warn("Error marshalling SAML Token: {}", e.getMessage());
@@ -74,6 +82,22 @@ 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);
@@ -95,4 +119,12 @@ public class SamlResponseErrorCreator {
     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/src/main/webapp/WEB-INF/flows/saml-validate-request.xml b/services/idp/src/main/webapp/WEB-INF/flows/saml-validate-request.xml
index 61cdadc..ce6c253 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
@@ -272,8 +272,10 @@
                       result="requestScope.samlAction"/>
         </on-entry>
         <!-- See if we managed to at least parse the request to get the response URL -->
-        <if test="requestScope.samlAction == null or requestScope.samlAction.isEmpty()"
-            then="viewBadRequestParsingError" else="viewBadRequestResponse"/>
+        <if test="requestScope.samlAction == null or requestScope.samlAction.isEmpty() or requestScope.samlAction == '/'"
+            then="viewBadRequestParsingError"/>
+        <if test="flowScope.saml_logout_request != null"
+            then="viewBadLogoutRequestResponse" else="viewBadRequestResponse"/>
     </decision-state>
     
     <end-state id="viewBadRequestResponse" view="samlsigninresponseform">
@@ -283,8 +285,21 @@
             <evaluate expression="authnRequestParser.retrieveRequestId(flowRequestContext)" 
                       result="flowScope.requestId"/>
             <evaluate expression="flowScope.RelayState" result="requestScope.relayState" />
-            <evaluate expression="samlResponseErrorCreator.createSAMLResponse(flowRequestContext, true, flowScope.idpConfig, 
-                                                                     flowScope.requestId)"
+            <evaluate expression="samlResponseErrorCreator.createSAMLResponse(flowRequestContext, false,
+            	true, flowScope.idpConfig, flowScope.requestId, requestScope.samlAction)"
+                      result="requestScope.samlResponse"/>     
+        </on-entry>
+    </end-state>
+    
+    <end-state id="viewBadLogoutRequestResponse" view="samlsignoutresponseform">
+        <on-entry>
+            <evaluate expression="authnRequestParser.retrieveConsumerURL(flowRequestContext)" 
+                      result="requestScope.samlAction"/>
+            <evaluate expression="authnRequestParser.retrieveRequestId(flowRequestContext)" 
+                      result="flowScope.requestId"/>
+            <evaluate expression="flowScope.RelayState" result="requestScope.relayState" />
+            <evaluate expression="samlResponseErrorCreator.createSAMLResponse(flowRequestContext, true,
+            	true, flowScope.idpConfig, flowScope.requestId, requestScope.samlAction)"
                       result="requestScope.samlResponse"/>     
         </on-entry>
     </end-state>
diff --git a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
index ee76fd4..d83b06a 100644
--- a/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
+++ b/systests/samlsso/src/test/java/org/apache/cxf/fediz/systests/samlsso/IdpTest.java
@@ -1732,6 +1732,10 @@ public class IdpTest {
         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:Success";
+        Assert.assertEquals(success, logoutResponse.getStatus().getStatusCode().getValue());
 
         webClient.close();
 
@@ -1838,12 +1842,28 @@ public class IdpTest {
             new UsernamePasswordCredentials(user, password));
 
         webClient.getOptions().setJavaScriptEnabled(false);
-        try {
-            webClient.getPage(logoutURL);
-            Assert.fail("Authentication failure expected");
-        }  catch (FailingHttpStatusCodeException ex) {
-            Assert.assertEquals(ex.getStatusCode(), 400);
-        }
+        idpPage = webClient.getPage(logoutURL);
+        webClient.getOptions().setJavaScriptEnabled(true);
+
+        // Check Response
+        HtmlForm responseForm = idpPage.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