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/09 14:11:56 UTC

[cxf-fediz] branch 1.4.x-fixes updated: FEDIZ-221 - Reworked SAML Authentication parsing in the IdP to make it more generic.

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 cd4594c  FEDIZ-221 - Reworked SAML Authentication parsing in the IdP to make it more generic.
cd4594c is described below

commit cd4594c995e8627e2b2c81100bd9b146a64ac24f
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Mon Jul 9 14:48:47 2018 +0100

    FEDIZ-221 - Reworked SAML Authentication parsing in the IdP to make it more generic.
---
 pom.xml                                            |  2 +-
 .../apache/cxf/fediz/service/idp/IdpConstants.java |  5 ++
 .../idp/beans/samlsso/AuthnRequestParser.java      | 58 +++++++++++++---------
 ...LAuthnRequest.java => SAMLAbstractRequest.java} | 33 +++---------
 .../service/idp/samlsso/SAMLAuthnRequest.java      | 19 +------
 .../service/idp/samlsso/SAMLLogoutRequest.java     | 47 ++++++++++++++++++
 .../webapp/WEB-INF/flows/saml-validate-request.xml | 42 ++++++++++++++--
 7 files changed, 135 insertions(+), 71 deletions(-)

diff --git a/pom.xml b/pom.xml
index 9b4eaa7..76a578b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -44,7 +44,7 @@
         <commons.logging.version>1.2</commons.logging.version>
         <commons.io.version>2.5</commons.io.version>
         <commons.validator.version>1.6</commons.validator.version>
-        <cxf.version>3.1.16</cxf.version>
+        <cxf.version>3.1.17-SNAPSHOT</cxf.version>
         <cxf.build-utils.version>3.2.0</cxf.build-utils.version>
         <dbcp.version>2.1.1</dbcp.version>
         <easymock.version>3.4</easymock.version>
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java
index 1e2969b..691cc41 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/IdpConstants.java
@@ -42,6 +42,11 @@ public final class IdpConstants {
      * The SAML Authn Request
      */
     public static final String SAML_AUTHN_REQUEST = "saml_authn_request";
+    
+    /**
+     * The SAML Logout Request
+     */
+    public static final String SAML_LOGOUT_REQUEST = "saml_logout_request";
 
     /**
      * A Context variable associated with the request (independent of protocol)
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 a299499..cbc9a8c 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
@@ -38,6 +38,7 @@ import org.apache.cxf.fediz.service.idp.IdpConstants;
 import org.apache.cxf.fediz.service.idp.domain.Application;
 import org.apache.cxf.fediz.service.idp.domain.Idp;
 import org.apache.cxf.fediz.service.idp.samlsso.SAMLAuthnRequest;
+import org.apache.cxf.fediz.service.idp.samlsso.SAMLLogoutRequest;
 import org.apache.cxf.fediz.service.idp.util.WebUtils;
 import org.apache.cxf.rs.security.saml.DeflateEncoderDecoder;
 import org.apache.cxf.rs.security.saml.sso.SSOConstants;
@@ -59,6 +60,8 @@ import org.apache.wss4j.dom.validate.Validator;
 import org.apache.xml.security.algorithms.JCEMapper;
 import org.apache.xml.security.utils.Base64;
 import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.RequestAbstractType;
 import org.opensaml.saml.security.impl.SAMLSignatureProfileValidator;
 import org.opensaml.security.credential.BasicCredential;
 import org.opensaml.security.x509.BasicX509Credential;
@@ -72,7 +75,7 @@ import org.springframework.stereotype.Component;
 import org.springframework.webflow.execution.RequestContext;
 
 /**
- * Parse the received SAMLRequest into an OpenSAML AuthnRequest
+ * Parse the received SAMLRequest into an OpenSAML AuthnRequest or LogoutRequest
  */
 @Component
 public class AuthnRequestParser {
@@ -103,17 +106,24 @@ public class AuthnRequestParser {
             WebUtils.removeAttribute(context, IdpConstants.SAML_AUTHN_REQUEST);
             throw new ProcessingException(TYPE.BAD_REQUEST);
         } else {
-            AuthnRequest parsedRequest = null;
+            RequestAbstractType parsedRequest = null;
             try {
                 parsedRequest = extractRequest(context, samlRequest);
             } catch (Exception ex) {
                 LOG.warn("Error parsing request: {}", ex.getMessage());
                 throw new ProcessingException(TYPE.BAD_REQUEST);
             }
-
-            // Store various attributes from the AuthnRequest
-            SAMLAuthnRequest authnRequest = new SAMLAuthnRequest(parsedRequest);
-            WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST, authnRequest);
+            
+            // Store various attributes from the AuthnRequest/LogoutRequest
+            if (parsedRequest instanceof AuthnRequest) {
+                SAMLAuthnRequest authnRequest = new SAMLAuthnRequest((AuthnRequest)parsedRequest);
+                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);
+            }
+            
+            validateRequest(parsedRequest);
 
             // Check the signature
             try {
@@ -122,7 +132,8 @@ public class AuthnRequestParser {
                     checkDestination(context, parsedRequest);
 
                     // Check signature
-                    X509Certificate validatingCert = getValidatingCertificate(idp, authnRequest.getIssuer());
+                    X509Certificate validatingCert = 
+                        getValidatingCertificate(idp, parsedRequest.getIssuer().getValue());
                     Crypto issuerCrypto = new CertificateStore(new X509Certificate[] {validatingCert});
                     validateAuthnRequestSignature(parsedRequest.getSignature(), issuerCrypto);
                 } else if (signature != null) {
@@ -131,7 +142,7 @@ public class AuthnRequestParser {
 
                     // Check signature
                     validateSeparateSignature(idp, sigAlg, signature, relayState,
-                              samlRequest, authnRequest.getIssuer());
+                              samlRequest, parsedRequest.getIssuer().getValue());
                 } else if (requireSignature) {
                     LOG.debug("No signature is present, therefore the request is rejected");
                     throw new ProcessingException(TYPE.BAD_REQUEST);
@@ -143,8 +154,6 @@ public class AuthnRequestParser {
                 throw new ProcessingException(TYPE.BAD_REQUEST);
             }
 
-            validateRequest(parsedRequest);
-
             LOG.debug("SAML Request with id '{}' successfully parsed", parsedRequest.getID());
         }
     }
@@ -226,7 +235,7 @@ public class AuthnRequestParser {
         return false;
     }
 
-    protected AuthnRequest extractRequest(RequestContext context, String samlRequest) throws Exception {
+    protected RequestAbstractType extractRequest(RequestContext context, String samlRequest) throws Exception {
         byte[] deflatedToken = Base64Utility.decode(samlRequest);
         String httpMethod = WebUtils.getHttpServletRequest(context).getMethod();
 
@@ -235,12 +244,10 @@ public class AuthnRequestParser {
                  : new ByteArrayInputStream(deflatedToken);
 
         Document responseDoc = StaxUtils.read(new InputStreamReader(tokenStream, StandardCharsets.UTF_8));
-        AuthnRequest request =
-            (AuthnRequest)OpenSAMLUtil.fromDom(responseDoc.getDocumentElement());
         if (LOG.isDebugEnabled()) {
             LOG.debug(DOM2Writer.nodeToString(responseDoc));
         }
-        return request;
+        return (RequestAbstractType)OpenSAMLUtil.fromDom(responseDoc.getDocumentElement());
     }
 
     public boolean isSupportDeflateEncoding() {
@@ -251,9 +258,9 @@ public class AuthnRequestParser {
         this.supportDeflateEncoding = supportDeflateEncoding;
     }
 
-    private void validateRequest(AuthnRequest parsedRequest) throws ProcessingException {
+    private void validateRequest(RequestAbstractType parsedRequest) throws ProcessingException {
         if (parsedRequest.getIssuer() == null) {
-            LOG.debug("No Issuer is present in the AuthnRequest");
+            LOG.debug("No Issuer is present in the AuthnRequest/LogoutRequest");
             throw new ProcessingException(TYPE.BAD_REQUEST);
         }
 
@@ -264,12 +271,15 @@ public class AuthnRequestParser {
             throw new ProcessingException(TYPE.BAD_REQUEST);
         }
 
-        // No SubjectConfirmation Elements are allowed
-        if (parsedRequest.getSubject() != null
-            && parsedRequest.getSubject().getSubjectConfirmations() != null
-            && !parsedRequest.getSubject().getSubjectConfirmations().isEmpty()) {
-            LOG.debug("An invalid SubjectConfirmation Element was received");
-            throw new ProcessingException(TYPE.BAD_REQUEST);
+        if (parsedRequest instanceof AuthnRequest) {
+            // No SubjectConfirmation Elements are allowed
+            AuthnRequest authnRequest = (AuthnRequest)parsedRequest;
+            if (authnRequest.getSubject() != null
+                && authnRequest.getSubject().getSubjectConfirmations() != null
+                && !authnRequest.getSubject().getSubjectConfirmations().isEmpty()) {
+                LOG.debug("An invalid SubjectConfirmation Element was received");
+                throw new ProcessingException(TYPE.BAD_REQUEST);
+            }
         }
     }
 
@@ -316,9 +326,9 @@ public class AuthnRequestParser {
         return CertsUtils.parseX509Certificate(serviceConfig.getValidatingCertificate());
     }
 
-    private void checkDestination(RequestContext context, AuthnRequest authnRequest) throws ProcessingException {
+    private void checkDestination(RequestContext context, RequestAbstractType request) throws ProcessingException {
         // Check destination
-        String destination = authnRequest.getDestination();
+        String destination = request.getDestination();
         LOG.debug("Validating destination: {}", destination);
 
         String localAddr = WebUtils.getHttpServletRequest(context).getRequestURL().toString();
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAbstractRequest.java
similarity index 52%
copy from services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java
copy to services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAbstractRequest.java
index d1606ac..55ed6d3 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAbstractRequest.java
@@ -21,54 +21,35 @@ package org.apache.cxf.fediz.service.idp.samlsso;
 
 import java.io.Serializable;
 
-import org.opensaml.saml.saml2.core.AuthnRequest;
+import org.opensaml.saml.saml2.core.RequestAbstractType;
 
 /**
- * This class encapsulates a (parsed) SAML AuthnRequest Object. The OpenSAML AuthnRequest Object is not
+ * This class encapsulates a (parsed) SAML RequestAbstractType Object. The OpenSAML RequestAbstractType Object is not
  * serializable.
  */
-public class SAMLAuthnRequest implements Serializable {
+public abstract class SAMLAbstractRequest implements Serializable {
     /**
      *
      */
     private static final long serialVersionUID = 4353024755428346545L;
 
     private String issuer;
-    private String consumerServiceURL;
     private String requestId;
-    private boolean forceAuthn;
-    private String subjectNameId;
 
-    public SAMLAuthnRequest(AuthnRequest authnRequest) {
-        if (authnRequest.getIssuer() != null) {
-            issuer = authnRequest.getIssuer().getValue();
+    public SAMLAbstractRequest(RequestAbstractType request) {
+        if (request.getIssuer() != null) {
+            issuer = request.getIssuer().getValue();
         }
 
-        consumerServiceURL = authnRequest.getAssertionConsumerServiceURL();
-        requestId = authnRequest.getID();
-        forceAuthn = authnRequest.isForceAuthn().booleanValue();
-        if (authnRequest.getSubject() != null && authnRequest.getSubject().getNameID() != null) {
-            subjectNameId = authnRequest.getSubject().getNameID().getValue();
-        }
+        requestId = request.getID();
     }
 
     public String getIssuer() {
         return issuer;
     }
 
-    public String getConsumerServiceURL() {
-        return consumerServiceURL;
-    }
-
     public String getRequestId() {
         return requestId;
     }
 
-    public boolean isForceAuthn() {
-        return forceAuthn;
-    }
-
-    public String getSubjectNameId() {
-        return subjectNameId;
-    }
 }
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java
index d1606ac..ba43db8 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLAuthnRequest.java
@@ -19,51 +19,36 @@
 
 package org.apache.cxf.fediz.service.idp.samlsso;
 
-import java.io.Serializable;
-
 import org.opensaml.saml.saml2.core.AuthnRequest;
 
 /**
  * This class encapsulates a (parsed) SAML AuthnRequest Object. The OpenSAML AuthnRequest Object is not
  * serializable.
  */
-public class SAMLAuthnRequest implements Serializable {
+public class SAMLAuthnRequest extends SAMLAbstractRequest {
     /**
      *
      */
     private static final long serialVersionUID = 4353024755428346545L;
 
-    private String issuer;
     private String consumerServiceURL;
-    private String requestId;
     private boolean forceAuthn;
     private String subjectNameId;
 
     public SAMLAuthnRequest(AuthnRequest authnRequest) {
-        if (authnRequest.getIssuer() != null) {
-            issuer = authnRequest.getIssuer().getValue();
-        }
+        super(authnRequest);
 
         consumerServiceURL = authnRequest.getAssertionConsumerServiceURL();
-        requestId = authnRequest.getID();
         forceAuthn = authnRequest.isForceAuthn().booleanValue();
         if (authnRequest.getSubject() != null && authnRequest.getSubject().getNameID() != null) {
             subjectNameId = authnRequest.getSubject().getNameID().getValue();
         }
     }
 
-    public String getIssuer() {
-        return issuer;
-    }
-
     public String getConsumerServiceURL() {
         return consumerServiceURL;
     }
 
-    public String getRequestId() {
-        return requestId;
-    }
-
     public boolean isForceAuthn() {
         return forceAuthn;
     }
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLLogoutRequest.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLLogoutRequest.java
new file mode 100644
index 0000000..9e47764
--- /dev/null
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAMLLogoutRequest.java
@@ -0,0 +1,47 @@
+/**
+ * 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.samlsso;
+
+import org.opensaml.saml.saml2.core.LogoutRequest;
+
+/**
+ * This class encapsulates a (parsed) SAML LogoutRequest Object. The OpenSAML LogoutRequest Object is not
+ * serializable.
+ */
+public class SAMLLogoutRequest extends SAMLAbstractRequest {
+    /**
+     *
+     */
+    private static final long serialVersionUID = 4353024755428346545L;
+
+    private String subjectNameId;
+
+    public SAMLLogoutRequest(LogoutRequest logoutRequest) {
+        super(logoutRequest);
+
+        if (logoutRequest.getNameID() != null) {
+            subjectNameId = logoutRequest.getNameID().getValue();
+        }
+    }
+
+    public String getSubjectNameId() {
+        return subjectNameId;
+    }
+}
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 0f76a57..2319ee6 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
@@ -68,7 +68,7 @@
         <if test="requestParameters.RelayState == null or requestParameters.RelayState.isEmpty()"
             then="viewBadRequest" />
         <if test="requestParameters.SAMLRequest != null and !requestParameters.SAMLRequest.isEmpty()"
-            then="parseSAMLAuthnRequest" />
+            then="parseSAMLRequest" />
         <if test="requestParameters.SAMLResponse == null or requestParameters.SAMLResponse.isEmpty()"
             then="viewBadRequest" else="signinResponse" />
     </decision-state>
@@ -85,14 +85,19 @@
             then="viewBadRequest" else="signinResponse" />
     </decision-state>
     
-    <action-state id="parseSAMLAuthnRequest">
+    <action-state id="parseSAMLRequest">
         <evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig,
                                                               flowScope.SAMLRequest, flowScope.SigAlg,
                                                               flowScope.Signature, flowScope.RelayState)" />
-        <transition to="retrieveConsumerURL"/>
+        <transition to="determineRequestType"/>
         <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" />
     </action-state>
     
+    <decision-state id="determineRequestType">
+        <if test="flowScope.saml_authn_request == null"
+            then="selectSignOutProcess" else="retrieveConsumerURL" />
+    </decision-state>
+    
     <action-state id="retrieveConsumerURL">
         <evaluate expression="authnRequestParser.retrieveConsumerURL(flowRequestContext)" 
                   result="flowScope.consumerURL"/>
@@ -209,6 +214,37 @@
         <transition to="redirectToTrustedIDP" />
         <transition on-exception="java.lang.Throwable" to="scInternalServerError" />
     </action-state>
+    
+    <decision-state id="selectSignOutProcess">
+        <if test="flowScope.idpConfig.rpSingleSignOutConfirmation == true
+            or flowScope.idpConfig.rpSingleSignOutCleanupConfirmation == true"
+            then="viewSignoutConfirmation" else="invalidateSessionAction" />
+    </decision-state>
+    
+    <!-- normal exit point for logout -->
+    <view-state id="viewSignoutConfirmation" view="signoutconfirmationresponse">
+        <transition on="submit" to="invalidateSessionAction"/>
+        <transition on="cancel" to="redirect" />
+    </view-state>
+    
+    <!-- normal exit point for logout -->
+    <decision-state id="invalidateSessionAction">
+        <on-entry>
+            <!-- store the realmConfigMap in the request map before we invalidate the session below.
+            Its needed in the signoutresponse.jsp page -->
+            <set name="externalContext.requestMap.realmConfigMap" 
+                value="externalContext.sessionMap.realmConfigMap"/>
+            <set name="externalContext.requestMap.wreply" value="flowScope.wreply"/>
+            <evaluate expression="homeRealmReminder.removeCookie(flowRequestContext)" />
+            <evaluate expression="logoutAction.submit(flowRequestContext)" />
+        </on-entry>
+        <if test="flowScope.idpConfig.isAutomaticRedirectToRpAfterLogout()"
+            then="redirectToRPLogoutPage" else="showLogoutResponsePage" />
+    </decision-state>
+    
+    <end-state id="showLogoutResponsePage" view="signoutresponse" />
+    
+    <end-state id="redirectToRPLogoutPage" view="externalRedirect:#{flowScope.wreply}" />
 
     <!-- abnormal exit point -->
     <decision-state id="viewBadRequest">