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/12 15:59:05 UTC
[cxf-fediz] branch master updated: FEDIZ-221 - Sending back
LogoutResponse from the IdP
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
The following commit(s) were added to refs/heads/master by this push:
new 579893c FEDIZ-221 - Sending back LogoutResponse from the IdP
579893c is described below
commit 579893ccfdac7fc6d5d9520706f046bb95b8f31a
Author: Colm O hEigeartaigh <co...@apache.org>
AuthorDate: Thu Jul 12 16:58:48 2018 +0100
FEDIZ-221 - Sending back LogoutResponse from the IdP
---
.../idp/beans/samlsso/AuthnRequestParser.java | 59 ++++++++++++++--------
.../idp/beans/samlsso/SamlResponseCreator.java | 29 +++++++++++
.../samlsso/SAML2PResponseComponentBuilder.java | 24 +++++++++
.../webapp/WEB-INF/flows/saml-validate-request.xml | 53 +++++++++++--------
.../WEB-INF/views/samlsignoutresponseform.jsp | 45 +++++++++++++++++
.../apache/cxf/fediz/systests/samlsso/IdpTest.java | 16 +++++-
6 files changed, 183 insertions(+), 43 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 852926b..40e53d8 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.core.util.CertsUtils;
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.SAMLAbstractRequest;
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;
@@ -113,7 +114,7 @@ public class AuthnRequestParser {
LOG.warn("Error parsing request: {}", ex.getMessage());
throw new ProcessingException(TYPE.BAD_REQUEST);
}
-
+
// Store various attributes from the AuthnRequest/LogoutRequest
if (parsedRequest instanceof AuthnRequest) {
SAMLAuthnRequest authnRequest = new SAMLAuthnRequest((AuthnRequest)parsedRequest);
@@ -122,7 +123,7 @@ public class AuthnRequestParser {
SAMLLogoutRequest logoutRequest = new SAMLLogoutRequest((LogoutRequest)parsedRequest);
WebUtils.putAttributeInFlowScope(context, IdpConstants.SAML_LOGOUT_REQUEST, logoutRequest);
}
-
+
validateRequest(parsedRequest);
// Check the signature
@@ -132,7 +133,7 @@ public class AuthnRequestParser {
checkDestination(context, parsedRequest);
// Check signature
- X509Certificate validatingCert =
+ X509Certificate validatingCert =
getValidatingCertificate(idp, parsedRequest.getIssuer().getValue());
Crypto issuerCrypto = new CertificateStore(new X509Certificate[] {validatingCert});
validateRequestSignature(parsedRequest.getSignature(), issuerCrypto);
@@ -159,16 +160,20 @@ public class AuthnRequestParser {
}
public String retrieveRealm(RequestContext context) {
- SAMLAuthnRequest authnRequest =
- (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ SAMLAbstractRequest request =
+ (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ if (request == null) {
+ request = (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context,
+ IdpConstants.SAML_LOGOUT_REQUEST);
+ }
- if (authnRequest != null) {
- String issuer = authnRequest.getIssuer();
- LOG.debug("Parsed SAML AuthnRequest Issuer: {}", issuer);
+ if (request != null) {
+ String issuer = request.getIssuer();
+ LOG.debug("Parsed SAML Request Issuer: {}", issuer);
return issuer;
}
- LOG.debug("No AuthnRequest available to be parsed");
+ LOG.debug("No AuthnRequest or LogoutRequest available to be parsed");
return null;
}
@@ -190,37 +195,47 @@ public class AuthnRequestParser {
if (serviceConfig != null) {
String racs = serviceConfig.getPassiveRequestorEndpoint();
LOG.debug("Attempting to use the configured passive requestor endpoint instead: {}", racs);
- return racs;
+ if (racs != null) {
+ return racs;
+ }
}
return null;
}
public String retrieveRequestId(RequestContext context) {
- SAMLAuthnRequest authnRequest =
- (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ SAMLAbstractRequest request =
+ (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ if (request == null) {
+ request = (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context,
+ IdpConstants.SAML_LOGOUT_REQUEST);
+ }
- if (authnRequest != null && authnRequest.getRequestId() != null) {
- String id = authnRequest.getRequestId();
- LOG.debug("Parsed SAML AuthnRequest Id: {}", id);
+ if (request != null && request.getRequestId() != null) {
+ String id = request.getRequestId();
+ LOG.debug("Parsed SAML Request Id: {}", id);
return id;
}
- LOG.debug("No AuthnRequest available to be parsed");
+ LOG.debug("No AuthnRequest/LogoutRequest available to be parsed");
return null;
}
public String retrieveRequestIssuer(RequestContext context) {
- SAMLAuthnRequest authnRequest =
- (SAMLAuthnRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ SAMLAbstractRequest request =
+ (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST);
+ if (request == null) {
+ request = (SAMLAbstractRequest)WebUtils.getAttributeFromFlowScope(context,
+ IdpConstants.SAML_LOGOUT_REQUEST);
+ }
- if (authnRequest != null && authnRequest.getIssuer() != null) {
- String issuer = authnRequest.getIssuer();
- LOG.debug("Parsed SAML AuthnRequest Issuer: {}", issuer);
+ if (request != null && request.getIssuer() != null) {
+ String issuer = request.getIssuer();
+ LOG.debug("Parsed SAML Request Issuer: {}", issuer);
return issuer;
}
- LOG.debug("No AuthnRequest available to be parsed");
+ LOG.debug("No AuthnRequest/LogoutRequest available to be parsed");
return null;
}
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 6824202..9ca6b4b 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
@@ -50,6 +50,7 @@ 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;
@@ -96,6 +97,17 @@ public class SamlResponseCreator {
}
}
+ public String createSAMLLogoutResponse(RequestContext context, Idp idp, String requestId)
+ throws ProcessingException {
+ try {
+ Element response = createLogoutResponse(idp, requestId);
+ return encodeResponse(response);
+ } catch (Exception ex) {
+ LOG.warn("Error marshalling SAML Token: {}", ex.getMessage());
+ throw new ProcessingException(TYPE.BAD_REQUEST);
+ }
+ }
+
private Assertion createSAML2Assertion(RequestContext context, Idp idp, SamlAssertionWrapper receivedToken,
String requestID, String requestIssuer,
String remoteAddr, String racs) throws Exception {
@@ -167,6 +179,23 @@ public class SamlResponseCreator {
return policyElement;
}
+ protected Element createLogoutResponse(Idp idp, 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);
+
+ 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);
diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAML2PResponseComponentBuilder.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAML2PResponseComponentBuilder.java
index 998df5b..7e48340 100644
--- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/samlsso/SAML2PResponseComponentBuilder.java
+++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/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;
@@ -39,6 +40,8 @@ public final class SAML2PResponseComponentBuilder {
private static SAMLObjectBuilder<Response> responseBuilder;
+ private static SAMLObjectBuilder<LogoutResponse> logoutResponseBuilder;
+
private static SAMLObjectBuilder<Issuer> issuerBuilder;
private static SAMLObjectBuilder<Status> statusBuilder;
@@ -76,6 +79,27 @@ public final class SAML2PResponseComponentBuilder {
return response;
}
+ public static LogoutResponse createSAMLLogoutResponse(
+ String inResponseTo,
+ String issuer,
+ Status status
+ ) {
+ 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);
+
+ return response;
+ }
+
@SuppressWarnings("unchecked")
public static Issuer createIssuer(
String issuerValue
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 616786b..3122fcf 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
@@ -89,22 +89,21 @@
<evaluate expression="authnRequestParser.parseSAMLRequest(flowRequestContext, flowScope.idpConfig,
flowScope.SAMLRequest, flowScope.SigAlg,
flowScope.Signature, flowScope.RelayState)" />
- <transition to="determineRequestType"/>
+ <transition to="retrieveConsumerURL"/>
<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"/>
- <transition to="retrieveRealm"/>
+ <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="retrieveRealm" />
+ </decision-state>
+
<action-state id="retrieveRealm">
<evaluate expression="authnRequestParser.retrieveRealm(flowRequestContext)"
result="flowScope.realm"/>
@@ -219,33 +218,47 @@
<decision-state id="selectSignOutProcess">
<if test="flowScope.idpConfig.rpSingleSignOutConfirmation == true
or flowScope.idpConfig.rpSingleSignOutCleanupConfirmation == true"
- then="viewSignoutConfirmation" else="invalidateSessionAction" />
+ then="viewSignoutConfirmation" else="produceSAMLLogoutResponse" />
</decision-state>
- <!-- normal exit point for logout -->
<view-state id="viewSignoutConfirmation" view="signoutconfirmationresponse">
- <transition on="submit" to="invalidateSessionAction"/>
+ <transition on="submit" to="produceSAMLLogoutResponse"/>
<transition on="cancel" to="redirect" />
</view-state>
- <!-- normal exit point for logout -->
- <decision-state id="invalidateSessionAction">
+ <action-state id="produceSAMLLogoutResponse">
+ <on-entry>
+ <evaluate expression="authnRequestParser.retrieveRequestId(flowRequestContext)"
+ result="flowScope.requestId"/>
+ <evaluate expression="authnRequestParser.retrieveRequestIssuer(flowRequestContext)"
+ result="flowScope.requestIssuer"/>
+ </on-entry>
+ <evaluate expression="samlResponseCreator.createSAMLLogoutResponse(flowRequestContext, flowScope.idpConfig, flowScope.requestId)"
+ result="flowScope.logoutResponse"/>
+ <transition to="invalidateSessionAction" />
+ </action-state>
+
+ <action-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" />
+ <evaluate expression="logoutAction.submit(flowRequestContext)" />
+ <transition to="signOutFormResponseView" />
+ </action-state>
- <end-state id="redirectToRPLogoutPage" view="externalRedirect:#{flowScope.wreply}" />
+ <!-- normal exit point for logout -->
+ <!-- browser redirection (self-submitted form 'samlsignoutresponseform.jsp') -->
+ <end-state id="signOutFormResponseView" view="samlsignoutresponseform">
+ <on-entry>
+ <evaluate expression="flowScope.consumerURL" result="requestScope.samlAction" />
+ <evaluate expression="flowScope.RelayState" result="requestScope.relayState" />
+ <evaluate expression="flowScope.logoutResponse" result="requestScope.samlResponse" />
+ </on-entry>
+ </end-state>
<!-- abnormal exit point -->
<decision-state id="viewBadRequest">
diff --git a/services/idp/src/main/webapp/WEB-INF/views/samlsignoutresponseform.jsp b/services/idp/src/main/webapp/WEB-INF/views/samlsignoutresponseform.jsp
new file mode 100644
index 0000000..ca3eaef
--- /dev/null
+++ b/services/idp/src/main/webapp/WEB-INF/views/samlsignoutresponseform.jsp
@@ -0,0 +1,45 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
+<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
+
+<html>
+<head>
+<title>IDP SignOut Response Form</title>
+</head>
+<body onload='documentLoaded()'>
+ <form:form method="POST" id="samlsignoutresponseform" name="samlsignoutresponseform" action="${samlAction}" htmlEscape="true">
+ <input type="hidden" name="SAMLResponse" value="${samlResponse}" /><br />
+ <input type="hidden" name="RelayState" value="${relayState}" /><br />
+ <noscript>
+ <p>Script is disabled. Click Submit to continue.</p>
+ <input type="submit" name="_eventId_submit" value="Submit" /><br />
+ </noscript>
+ </form:form>
+ <script language="javascript">
+ /**
+ * Prepares the form for submission by appending any URI
+ * fragment (hash) to the form action in order to propagate it
+ * through the re-direct
+ * @param form The login form object.
+ * @returns the form.
+ */
+ function propagateUriFragment(form) {
+ // Extract the fragment from the browser's current location.
+ var hash = decodeURIComponent(self.document.location.hash);
+
+ // The fragment value may not contain a leading # symbol
+ if (hash && hash.indexOf("#") === -1) {
+ hash = "#" + hash;
+ }
+
+ // Append the fragment to the current action so that it persists to the redirected URL.
+ form.action = form.action + hash;
+ return form;
+ }
+ function documentLoaded() {
+ propagateUriFragment(document.forms[0]);
+ window.setTimeout('document.forms[0].submit()',0);
+ }
+ </script>
+</body>
+</html>
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 a2236c1..d442dab 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
@@ -86,6 +86,7 @@ import org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration;
import org.opensaml.saml.saml2.core.AuthnRequest;
import org.opensaml.saml.saml2.core.Issuer;
import org.opensaml.saml.saml2.core.LogoutRequest;
+import org.opensaml.saml.saml2.core.LogoutResponse;
import org.opensaml.saml.saml2.core.NameID;
import org.opensaml.saml.saml2.core.NameIDPolicy;
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
@@ -1713,7 +1714,20 @@ public class IdpTest {
HtmlForm form = idpPage.getFormByName("signoutconfirmationresponseform");
HtmlSubmitInput button = form.getInputByName("_eventId_submit");
- button.click();
+ HtmlPage signoutPage = button.click();
+
+ // Check Response
+ HtmlForm responseForm = signoutPage.getFormByName("samlsignoutresponseform");
+ String responseValue = responseForm.getInputByName("SAMLResponse").getAttributeNS(null, "value");
+ Assert.assertNotNull(responseValue);
+
+ 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);
+ // TODO further checks
webClient.close();