From de47c91c80f53d5f3deb3f5cec8dc6ff353e60c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Romain=20D=C3=A9nari=C3=A9?= Date: Tue, 12 Mar 2024 16:50:07 +0100 Subject: [PATCH] fix: SAML2 Logout not workibg - EXO-70293 Before this fix, SAML2 logout not working as idp redirection on logout was done on the same url as login This commit allow to configure a logout url in picketlink-sp.xml, and redirect on it during logout process Resolves meeds-io/meeds-1771 --- .../agent/saml/PortalSAML2LogOutHandler.java | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/sso-saml-plugin/src/main/java/org/gatein/sso/agent/saml/PortalSAML2LogOutHandler.java b/sso-saml-plugin/src/main/java/org/gatein/sso/agent/saml/PortalSAML2LogOutHandler.java index f6bd9d1cb..487f1f960 100644 --- a/sso-saml-plugin/src/main/java/org/gatein/sso/agent/saml/PortalSAML2LogOutHandler.java +++ b/sso-saml-plugin/src/main/java/org/gatein/sso/agent/saml/PortalSAML2LogOutHandler.java @@ -23,14 +23,27 @@ package org.gatein.sso.agent.saml; +import jakarta.servlet.http.HttpSession; import org.exoplatform.services.log.ExoLogger; import org.exoplatform.services.log.Log; import org.gatein.wci.ServletContainerFactory; +import org.picketlink.common.constants.GeneralConstants; +import org.picketlink.common.constants.JBossSAMLURIConstants; +import org.picketlink.common.exceptions.ConfigurationException; +import org.picketlink.config.federation.SPType; +import org.picketlink.identity.federation.api.saml.v2.request.SAML2Request; +import org.picketlink.identity.federation.api.saml.v2.response.SAML2Response; +import org.picketlink.identity.federation.core.saml.v2.common.IDGenerator; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerRequest; import org.picketlink.identity.federation.core.saml.v2.interfaces.SAML2HandlerResponse; +import org.picketlink.identity.federation.core.saml.v2.util.XMLTimeUtil; +import org.picketlink.identity.federation.saml.v2.SAML2Object; +import org.picketlink.identity.federation.saml.v2.assertion.NameIDType; import org.picketlink.identity.federation.saml.v2.protocol.LogoutRequestType; import org.picketlink.identity.federation.saml.v2.protocol.ResponseType; +import org.picketlink.identity.federation.saml.v2.protocol.StatusCodeType; import org.picketlink.identity.federation.saml.v2.protocol.StatusResponseType; +import org.picketlink.identity.federation.saml.v2.protocol.StatusType; import org.picketlink.identity.federation.web.core.HTTPContext; import org.picketlink.identity.federation.web.handlers.saml2.SAML2LogOutHandler; @@ -39,6 +52,9 @@ import org.picketlink.common.exceptions.ProcessingException; import jakarta.servlet.http.Cookie; +import java.net.URI; +import java.security.Principal; + /** * Extension of {@link SAML2LogOutHandler} because we need to enforce WCI (crossContext) logout in portal environment. * @@ -50,6 +66,8 @@ public class PortalSAML2LogOutHandler extends SAML2LogOutHandler private static final String OAUTH_COOKIE_NAME = "oauth_rememberme"; + private final SPLogOutHandler sp = new SPLogOutHandler(); + private static Log log = ExoLogger.getLogger(PortalSAML2LogOutHandler.class); @Override @@ -101,6 +119,20 @@ public void handleStatusResponseType(SAML2HandlerRequest request, SAML2HandlerRe } + + public void generateSAMLRequest(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException { + if (request.getTypeOfRequestToBeGenerated() == null) { + return; + } + if (SAML2HandlerRequest.GENERATE_REQUEST_TYPE.LOGOUT != request.getTypeOfRequestToBeGenerated()) + return; + + if (getType() == HANDLER_TYPE.IDP) { + super.generateSAMLRequest(request, response); + } else { + sp.generateSAMLRequest(request, response); + } + } /** * Performs portal logout by calling WCI logout. * @@ -133,5 +165,161 @@ protected void portalLogout(HttpServletRequest request, HttpServletResponse resp oauthCookie.setMaxAge(0); response.addCookie(oauthCookie); } + + + private class SPLogOutHandler { + public void generateSAMLRequest(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException { + // Generate the LogOut Request + SAML2Request samlRequest = new SAML2Request(); + + HTTPContext httpContext = (HTTPContext) request.getContext(); + HttpServletRequest httpRequest = httpContext.getRequest(); + Principal userPrincipal = getUserPrincipal(httpRequest); + if (userPrincipal == null) { + return; + } + try { + LogoutRequestType lot = samlRequest.createLogoutRequest(request.getIssuer().getValue()); + + NameIDType nameID = new NameIDType(); + nameID.setValue(userPrincipal.getName()); + lot.setNameID(nameID); + + SPType spConfiguration = getSPConfiguration(); + String logoutUrl = spConfiguration.getLogoutUrl(); + + if (logoutUrl == null) { + logoutUrl = getIdentityURL(request); + } + + lot.setDestination(URI.create(logoutUrl)); + response.setDestination(logoutUrl); + response.setResultingDocument(samlRequest.convert(lot)); + response.setSendRequest(true); + } catch (Exception e) { + throw logger.processingError(e); + } + } + + public void handleStatusResponseType(SAML2HandlerRequest request, SAML2HandlerResponse response) + throws ProcessingException { + // Handler a log out response from IDP + StatusResponseType statusResponseType = (StatusResponseType) request.getSAML2Object(); + + checkDestination(statusResponseType.getDestination(), getSPConfiguration().getServiceURL()); + + HTTPContext httpContext = (HTTPContext) request.getContext(); + HttpServletRequest servletRequest = httpContext.getRequest(); + HttpSession session = servletRequest.getSession(false); + + // TODO: Deal with partial logout report + + StatusType statusType = statusResponseType.getStatus(); + StatusCodeType statusCode = statusType.getStatusCode(); + URI statusCodeValueURI = statusCode.getValue(); + boolean success = false; + if (statusCodeValueURI != null) { + String statusCodeValue = statusCodeValueURI.toString(); + if (JBossSAMLURIConstants.STATUS_SUCCESS.get().equals(statusCodeValue)) { + success = true; + session.invalidate(); + } + } + } + + public void handleRequestType(SAML2HandlerRequest request, SAML2HandlerResponse response) throws ProcessingException { + SAML2Object samlObject = request.getSAML2Object(); + if (samlObject instanceof LogoutRequestType == false) + return; + //get the configuration to handle a logout request from idp and set the correct response location + SPType spConfiguration = getSPConfiguration(); + + LogoutRequestType logOutRequest = (LogoutRequestType) samlObject; + + checkDestination(logOutRequest.getDestination(), spConfiguration.getServiceURL()); + + HTTPContext httpContext = (HTTPContext) request.getContext(); + HttpServletRequest servletRequest = httpContext.getRequest(); + HttpSession session = servletRequest.getSession(false); + + String relayState = servletRequest.getParameter("RelayState"); + + session.invalidate(); // Invalidate the current session at the SP + + // Generate a Logout Response + StatusResponseType statusResponse = null; + try { + statusResponse = new StatusResponseType(IDGenerator.create("ID_"), XMLTimeUtil.getIssueInstant()); + } catch (ConfigurationException e) { + throw logger.processingError(e); + } + + // Status + StatusType statusType = new StatusType(); + StatusCodeType statusCodeType = new StatusCodeType(); + statusCodeType.setValue(URI.create(JBossSAMLURIConstants.STATUS_RESPONDER.get())); + + // 2nd level status code + StatusCodeType status2ndLevel = new StatusCodeType(); + status2ndLevel.setValue(URI.create(JBossSAMLURIConstants.STATUS_SUCCESS.get())); + statusCodeType.setStatusCode(status2ndLevel); + + statusType.setStatusCode(statusCodeType); + + statusResponse.setStatus(statusType); + + statusResponse.setInResponseTo(logOutRequest.getID()); + + statusResponse.setIssuer(request.getIssuer()); + + String logoutResponseLocation = spConfiguration.getLogoutResponseLocation(); + + if (logoutResponseLocation == null) { + response.setDestination(logOutRequest.getIssuer().getValue()); + } else { + response.setDestination(logoutResponseLocation); + } + + statusResponse.setDestination(response.getDestination()); + + SAML2Response saml2Response = new SAML2Response(); + try { + response.setResultingDocument(saml2Response.convert(statusResponse)); + } catch (Exception je) { + throw logger.processingError(je); + } + + response.setRelayState(relayState); + response.setDestination(logOutRequest.getIssuer().getValue()); + response.setSendRequest(false); + } + } + + private SPType getSPConfiguration() { + return (SPType) getProviderconfig(); + } + + Principal getUserPrincipal(HttpServletRequest request) { + HttpSession session = request.getSession(); + Principal userPrincipal = request.getUserPrincipal(); + if (userPrincipal == null) { + userPrincipal = (Principal) session.getAttribute(GeneralConstants.PRINCIPAL_ID); + } + + return userPrincipal; + } + + private String getIdentityURL(SAML2HandlerRequest request) { + SPType spConfiguration = getSPConfiguration(); + HTTPContext httpContext = (HTTPContext) request.getContext(); + HttpServletRequest httpServletRequest = httpContext.getRequest(); + String desiredIdP = (String) httpServletRequest.getAttribute(org.picketlink.identity.federation.web.constants.GeneralConstants.DESIRED_IDP); + + if (desiredIdP != null) { + return desiredIdP; + } + + return spConfiguration.getIdentityURL(); + } }