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 2f71f20 FEDIZ-221 - Got basic logout functionality working for SAML SSO 2f71f20 is described below commit 2f71f20100d4bbbbed7d4cb913023fc983288977 Author: Colm O hEigeartaigh <cohei...@apache.org> AuthorDate: Wed Jul 11 16:51:33 2018 +0100 FEDIZ-221 - Got basic logout functionality working for SAML SSO --- .../idp/beans/SigninParametersCacheAction.java | 40 +++---- .../idp/beans/samlsso/AuthnRequestParser.java | 6 +- .../WEB-INF/flows/federation-validate-request.xml | 2 +- .../webapp/WEB-INF/flows/saml-validate-request.xml | 3 +- .../apache/cxf/fediz/systests/samlsso/IdpTest.java | 119 ++++++++++++++++++++- 5 files changed, 144 insertions(+), 26 deletions(-) diff --git a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/SigninParametersCacheAction.java b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/SigninParametersCacheAction.java index bbecc5a..ac69ce6 100644 --- a/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/SigninParametersCacheAction.java +++ b/services/idp-core/src/main/java/org/apache/cxf/fediz/service/idp/beans/SigninParametersCacheAction.java @@ -62,10 +62,6 @@ public class SigninParametersCacheAction { if (value != null) { signinParams.put(IdpConstants.RETURN_ADDRESS, value); } - value = WebUtils.getAttributeFromFlowScope(context, IdpConstants.RETURN_ADDRESS); - if (value != null) { - signinParams.put(IdpConstants.RETURN_ADDRESS, value); - } if ("samlsso".equals(protocol)) { value = WebUtils.getAttributeFromFlowScope(context, IdpConstants.SAML_AUTHN_REQUEST); @@ -132,18 +128,21 @@ public class SigninParametersCacheAction { } } - public void storeRPConfigInSession(RequestContext context) throws ProcessingException { + public void storeRPConfigInSession(RequestContext context, String replyAddress) throws ProcessingException { - String wtrealm = (String)WebUtils.getAttributeFromFlowScope(context, FederationConstants.PARAM_TREALM); + String realm = (String)WebUtils.getAttributeFromFlowScope(context, IdpConstants.REALM); + if (realm == null) { + realm = (String)WebUtils.getAttributeFromFlowScope(context, FederationConstants.PARAM_TREALM); + } Idp idpConfig = (Idp) WebUtils.getAttributeFromFlowScope(context, IdpConstants.IDP_CONFIG); - if (wtrealm == null || idpConfig == null) { + if (realm == null || idpConfig == null) { return; } - Application serviceConfig = idpConfig.findApplication(wtrealm); + Application serviceConfig = idpConfig.findApplication(realm); if (serviceConfig != null) { if (serviceConfig.getPassiveRequestorEndpoint() == null) { - String url = guessPassiveRequestorURL(context, wtrealm); + String url = guessPassiveRequestorURL(context, replyAddress, realm); serviceConfig.setPassiveRequestorEndpoint(url); } @@ -157,22 +156,25 @@ public class SigninParametersCacheAction { WebUtils.putAttributeInExternalContext(context, ACTIVE_APPLICATIONS, realmConfigMap); } - if (realmConfigMap.get(wtrealm) == null) { - realmConfigMap.put(wtrealm, serviceConfig); + if (realmConfigMap.get(realm) == null) { + realmConfigMap.put(realm, serviceConfig); } } } - protected String guessPassiveRequestorURL(RequestContext context, String wtrealm) throws ProcessingException { - String url = (String)WebUtils.getAttributeFromFlowScope(context, FederationConstants.PARAM_REPLY); - try { - //basic check if the url is correctly formed - new URL(url); - } catch (Exception e) { - url = null; + protected String guessPassiveRequestorURL(RequestContext context, String replyAddress, + String realm) throws ProcessingException { + String url = replyAddress; + if (url != null) { + try { + //basic check if the url is correctly formed + new URL(url); + } catch (Exception e) { + url = null; + } } if (url == null) { - url = wtrealm; + url = realm; try { //basic check if the url is correctly formed new URL(url); 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 74fac7f..852926b 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 @@ -135,7 +135,7 @@ public class AuthnRequestParser { X509Certificate validatingCert = getValidatingCertificate(idp, parsedRequest.getIssuer().getValue()); Crypto issuerCrypto = new CertificateStore(new X509Certificate[] {validatingCert}); - validateAuthnRequestSignature(parsedRequest.getSignature(), issuerCrypto); + validateRequestSignature(parsedRequest.getSignature(), issuerCrypto); } else if (signature != null) { // Check destination checkDestination(context, parsedRequest); @@ -339,9 +339,9 @@ public class AuthnRequestParser { } /** - * Validate the AuthnRequest signature + * Validate the AuthnRequest or LogoutRequest signature */ - private void validateAuthnRequestSignature( + private void validateRequestSignature( Signature signature, Crypto sigCrypto ) throws WSSecurityException { diff --git a/services/idp/src/main/webapp/WEB-INF/flows/federation-validate-request.xml b/services/idp/src/main/webapp/WEB-INF/flows/federation-validate-request.xml index 0e24af6..7ab8967 100644 --- a/services/idp/src/main/webapp/WEB-INF/flows/federation-validate-request.xml +++ b/services/idp/src/main/webapp/WEB-INF/flows/federation-validate-request.xml @@ -167,7 +167,7 @@ <evaluate expression="tokenSerializer.serialize(flowRequestContext, flowScope.rpTokenElement)" result="flowScope.rpToken"/> </on-entry> - <evaluate expression="signinParametersCacheAction.storeRPConfigInSession(flowRequestContext)" /> + <evaluate expression="signinParametersCacheAction.storeRPConfigInSession(flowRequestContext, flowScope.wreply)" /> <transition to="isWReplyProvided" /> <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" /> <transition on-exception="java.lang.Throwable" to="scInternalServerError" /> 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 2319ee6..616786b 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 @@ -164,6 +164,7 @@ <set name="flowScope.idpToken" value="currentEvent.attributes.idpToken" /> <set name="flowScope.saml_authn_request" value="currentEvent.attributes.saml_authn_request" /> <set name="flowScope.RelayState" value="currentEvent.attributes.request_context" /> + <set name="flowScope.consumerURL" value="currentEvent.attributes.return_address" /> </transition> <transition on="viewBadRequest" to="viewBadRequest" /> <transition on="scInternalServerError" to="scInternalServerError" /> @@ -177,7 +178,7 @@ <evaluate expression="stsClientForRpAction.submit(flowRequestContext, flowScope.realm, flowScope.home_realm)" result="flowScope.rpTokenElement"/> </on-entry> - <evaluate expression="signinParametersCacheAction.storeRPConfigInSession(flowRequestContext)"/> + <evaluate expression="signinParametersCacheAction.storeRPConfigInSession(flowRequestContext, flowScope.consumerURL)"/> <transition to="produceSAMLResponse" /> <transition on-exception="org.apache.cxf.fediz.core.exception.ProcessingException" to="viewBadRequest" /> <transition on-exception="java.lang.Throwable" to="scInternalServerError" /> 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 fcf824f..a2236c1 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 @@ -47,7 +47,9 @@ import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.WebRequest; import com.gargoylesoftware.htmlunit.html.DomElement; import com.gargoylesoftware.htmlunit.html.DomNodeList; +import com.gargoylesoftware.htmlunit.html.HtmlForm; import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; import com.gargoylesoftware.htmlunit.util.NameValuePair; import com.gargoylesoftware.htmlunit.xml.XmlPage; @@ -83,6 +85,8 @@ import org.opensaml.saml.saml2.core.AuthnContextClassRef; 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.NameID; import org.opensaml.saml.saml2.core.NameIDPolicy; import org.opensaml.saml.saml2.core.RequestedAuthnContext; import org.opensaml.security.x509.BasicX509Credential; @@ -1614,6 +1618,118 @@ public class IdpTest { webClient.close(); } + @org.junit.Test + public void testIdPLogout() 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 + + // 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_submit"); + button.click(); + + 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 + webClient = new WebClient(); + webClient.setCookieManager(cookieManager); + webClient.getOptions().setUseInsecureSSL(true); + webClient.getOptions().setThrowExceptionOnFailingStatusCode(false); + idpPage = webClient.getPage(url); + + Assert.assertEquals(401, idpPage.getWebResponse().getStatusCode()); + + webClient.close(); + } + private String encodeAuthnRequest(Element authnRequest) throws IOException { String requestMessage = DOM2Writer.nodeToString(authnRequest); @@ -1623,7 +1739,7 @@ public class IdpTest { return Base64Utility.encode(deflatedBytes); } - private void signAuthnRequest(AuthnRequest authnRequest) throws Exception { + private void signAuthnRequest(SignableSAMLObject signableObject) throws Exception { Crypto crypto = CryptoFactory.getInstance("stsKeystoreA.properties"); CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS); @@ -1655,7 +1771,6 @@ public class IdpTest { "Error generating KeyInfo from signing credential", ex); } - SignableSAMLObject signableObject = (SignableSAMLObject) authnRequest; signableObject.setSignature(signature); signableObject.releaseDOM(); signableObject.releaseChildrenDOM(true);