/** * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved * * The contents of this file are subject to the terms * of the Common Development and Distribution License * (the License). You may not use this file except in * compliance with the License. * * You can obtain a copy of the License at * https://opensso.dev.java.net/public/CDDLv1.0.html or * opensso/legal/CDDLv1.0.txt * See the License for the specific language governing * permission and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at opensso/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * your own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * $Id: SPSingleLogout.java,v 1.29 2009/11/24 21:53:28 madan_ranganath Exp $ * * Portions Copyrighted 2013-2015 ForgeRock AS. */ package com.sun.identity.saml2.profile; import com.sun.identity.federation.common.FSUtils; import com.sun.identity.plugin.monitoring.FedMonAgent; import com.sun.identity.plugin.monitoring.FedMonSAML2Svc; import com.sun.identity.plugin.monitoring.MonitorManager; import com.sun.identity.plugin.session.SessionException; import com.sun.identity.plugin.session.SessionManager; import com.sun.identity.plugin.session.SessionProvider; import com.sun.identity.saml2.assertion.AssertionFactory; import com.sun.identity.saml2.assertion.Issuer; import com.sun.identity.saml2.assertion.NameID; import com.sun.identity.saml2.common.AccountUtils; import com.sun.identity.saml2.common.NameIDInfoKey; import com.sun.identity.saml2.common.SAML2Constants; import com.sun.identity.saml2.common.SAML2Exception; import com.sun.identity.saml2.common.SAML2FailoverUtils; import com.sun.identity.saml2.common.SAML2Utils; import com.sun.identity.saml2.jaxb.entityconfig.BaseConfigType; import com.sun.identity.saml2.jaxb.entityconfig.IDPSSOConfigElement; import com.sun.identity.saml2.jaxb.metadata.IDPSSODescriptorElement; import com.sun.identity.saml2.jaxb.metadata.SPSSODescriptorElement; import com.sun.identity.saml2.logging.LogUtil; import com.sun.identity.saml2.meta.SAML2MetaException; import com.sun.identity.saml2.meta.SAML2MetaManager; import com.sun.identity.saml2.meta.SAML2MetaUtils; import com.sun.identity.saml2.plugins.FedletAdapter; import com.sun.identity.saml2.plugins.SAML2ServiceProviderAdapter; import com.sun.identity.saml2.protocol.LogoutRequest; import com.sun.identity.saml2.protocol.LogoutResponse; import com.sun.identity.saml2.protocol.ProtocolFactory; import com.sun.identity.saml2.protocol.Status; import com.sun.identity.shared.debug.Debug; import com.sun.identity.shared.xml.XMLUtils; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.soap.SOAPMessage; import org.forgerock.openam.audit.AMAuditEventBuilderUtils; import org.forgerock.openam.federation.saml2.SAML2TokenRepositoryException; import org.forgerock.openam.saml2.SAML2Store; import org.forgerock.openam.saml2.audit.SAML2EventLogger; /** * This class reads the required data from HttpServletRequest and * initiates the LogoutRequest from SP to IDP. */ public class SPSingleLogout { static SAML2MetaManager sm = null; static AssertionFactory af = AssertionFactory.getInstance(); static Debug debug = SAML2Utils.debug; static final Status SUCCESS_STATUS = SAML2Utils.generateStatus(SAML2Constants.SUCCESS, SAML2Utils.bundle.getString("requestSuccess")); static final Status PARTIAL_LOGOUT_STATUS = SAML2Utils.generateStatus(SAML2Constants.RESPONDER, SAML2Utils.bundle.getString("partialLogout")); static SessionProvider sessionProvider = null; private static FedMonAgent agent; private static FedMonSAML2Svc saml2Svc; static { try { sm = new SAML2MetaManager(); } catch (SAML2MetaException sme) { debug.error("Error retrieving metadata.", sme); } try { sessionProvider = SessionManager.getProvider(); } catch (SessionException se) { debug.error("Error retrieving session provider.", se); } agent = MonitorManager.getAgent(); saml2Svc = MonitorManager.getSAML2Svc(); } private SPSingleLogout() { } /** * Parses the request parameters and initiates the Logout * Request to be sent to the IDP. * * @param request the HttpServletRequest. * @param response the HttpServletResponse. * @param out The print writer for writing out presentation. * @param binding binding used for this request. * @param paramsMap Map of all other parameters. * Following parameters names with their respective * String values are allowed in this paramsMap. * "RelayState" - the target URL on successful Single Logout * "Destination" - A URI Reference indicating the address to * which the request has been sent. * "Consent" - Specifies a URI a SAML defined identifier * known as Consent Identifiers. * "Extension" - Specifies a list of Extensions as list of * String objects. * @param auditor the auditor for logging SAML2 Events - may be null * @throws SAML2Exception if error initiating request to IDP. */ public static void initiateLogoutRequest( HttpServletRequest request, HttpServletResponse response, PrintWriter out, String binding, Map paramsMap, SAML2EventLogger auditor) throws SAML2Exception { initiateLogoutRequest(request, response, out, binding, paramsMap, null, null, null, auditor); } /** * Parses the request parameters and initiates the Logout * Request to be sent to the IDP. * * @param request the HttpServletRequest. * @param response the HttpServletResponse. * @param out The print writer for writing out presentation. * @param binding binding used for this request. * @param paramsMap Map of all other parameters. * Following parameters names with their respective * String values are allowed in this paramsMap. * "RelayState" - the target URL on successful Single Logout * "Destination" - A URI Reference indicating the address to * which the request has been sent. * "Consent" - Specifies a URI a SAML defined identifier * known as Consent Identifiers. * "Extension" - Specifies a list of Extensions as list of * String objects. * @param origLogoutRequest original LogoutRequest * @param msg SOAPMessage * @param newSession Session object for IDP Proxy * @param audit the auditor for logging SAML2 Events - may be null * @throws SAML2Exception if error initiating request to IDP. */ public static void initiateLogoutRequest( HttpServletRequest request, HttpServletResponse response, PrintWriter out, String binding, Map paramsMap, LogoutRequest origLogoutRequest, SOAPMessage msg, Object newSession, SAML2EventLogger audit) throws SAML2Exception { if (debug.messageEnabled()) { debug.message("SPSingleLogout:initiateLogoutRequest"); debug.message("binding : " + binding); debug.message("paramsMap : " + paramsMap); } String metaAlias = (String)paramsMap.get(SAML2Constants.SP_METAALIAS); try { Object session = null; if (newSession != null) { session = newSession; } else { session = sessionProvider.getSession(request); } if (null != audit) { audit.setSSOTokenId(session); } if (!SPCache.isFedlet) { if (session == null) { throw new SAML2Exception( SAML2Utils.bundle.getString("nullSSOToken")); } } if (metaAlias == null) { if (!SPCache.isFedlet) { String[] values = sessionProvider.getProperty( session, SAML2Constants.SP_METAALIAS); if (values != null && values.length > 0) { metaAlias = values[0]; } } else { List spMetaAliases = sm.getAllHostedServiceProviderMetaAliases("/"); if ((spMetaAliases != null) && !spMetaAliases.isEmpty()) { // get first one metaAlias = (String) spMetaAliases.get(0); } } } if (metaAlias == null) { throw new SAML2Exception( SAML2Utils.bundle.getString("nullSPMetaAlias")); } paramsMap.put(SAML2Constants.METAALIAS, metaAlias); String realm = SAML2Utils. getRealm(SAML2MetaUtils.getRealmByMetaAlias(metaAlias)); debug.message("realm : " + realm); String spEntityID = sm.getEntityByMetaAlias(metaAlias); if (spEntityID == null) { debug.error("Service Provider ID is missing"); String[] data = {spEntityID}; LogUtil.error( Level.INFO,LogUtil.INVALID_SP,data,null); throw new SAML2Exception( SAML2Utils.bundle.getString("nullSPEntityID")); } debug.message("spEntityID : " + spEntityID); // clean up session index String tokenID = sessionProvider.getSessionID(session); String infoKeyString = null; if (SPCache.isFedlet) { infoKeyString = SAML2Utils.getParameter(paramsMap, SAML2Constants.INFO_KEY); } else { try { String[] values = sessionProvider.getProperty( session, AccountUtils.getNameIDInfoKeyAttribute()); if (values != null && values.length > 0) { infoKeyString = values[0]; } } catch (SessionException se) { debug.error("Unable to get infoKeyString from " + "session.", se); throw new SAML2Exception( SAML2Utils.bundle.getString("errorInfoKeyString")); } } if (debug.messageEnabled()) { debug.message("tokenID : " + tokenID); debug.message("infoKeyString : " + infoKeyString); } // get SPSSODescriptor SPSSODescriptorElement spsso = sm.getSPSSODescriptor(realm,spEntityID); if (spsso == null) { String[] data = {spEntityID}; LogUtil.error(Level.INFO,LogUtil.SP_METADATA_ERROR,data, null); throw new SAML2Exception( SAML2Utils.bundle.getString("metaDataError")); } List extensionsList = LogoutUtil.getExtensionsList(paramsMap); String relayState = SAML2Utils.getParameter(paramsMap, SAML2Constants.RELAY_STATE); if (relayState == null || relayState.equals("")) { relayState = SAML2Utils.getAttributeValueFromSSOConfig(realm, spEntityID, SAML2Constants.SP_ROLE, SAML2Constants.DEFAULT_RELAY_STATE); } // Validate the RelayState URL. SAML2Utils.validateRelayStateURL(realm, spEntityID, relayState, SAML2Constants.SP_ROLE); if (infoKeyString == null) { // termination case, do local logout only and send to // relay state if any debug.warning("SPSingleLogout.initiateLogoutRequest : Unable to get infoKeyString from session."); sessionProvider.invalidateSession(session, request, response); if ((relayState != null) && !relayState.equals("")) { try { response.sendRedirect(relayState); } catch (IOException e) { debug.error("SPSingleLogout.initiateLogoutRequest: " + "Error in send redirect to " + relayState, e); } } else { RequestDispatcher dispatcher = request.getRequestDispatcher( "saml2/jsp/default.jsp?message=spSloSuccess"); try { dispatcher.forward(request, response); } catch (IOException e) { debug.error("SPSingleLogout.initiateLogoutRequest: " + "Error in forwarding to default.jsp", e); } catch (ServletException e) { debug.error("SPSingleLogout.initiateLogoutRequest: " + "Error in forwarding to default.jsp", e); } } return; } StringTokenizer st = new StringTokenizer(infoKeyString, SAML2Constants.SECOND_DELIM); String requestID = null; while (st.hasMoreTokens()) { String tmpInfoKeyString = st.nextToken(); NameIDInfoKey nameIdInfoKey = NameIDInfoKey.parse(tmpInfoKeyString); //only try to perform the logout for the SP entity who is currently assigned to the session, this is //to cover the case when there are multiple hosted SPs authenticating against the same IdP. In this //scenario the sp metaalias will always be the SP who authenticated last, so we must ensure that we //send out the LogoutRequest to the single IdP correctly. Once that's done the IdP will send the //logout request to the other SP instance, invalidating the session for both SPs. if (nameIdInfoKey.getHostEntityID().equals(spEntityID)) { requestID = prepareForLogout(realm, tokenID, metaAlias, extensionsList, binding, relayState, request, response, paramsMap, tmpInfoKeyString, origLogoutRequest, msg); } } // IDP Proxy SOAPMessage soapMsg = (SOAPMessage) IDPCache.SOAPMessageByLogoutRequestID.get(requestID); if (soapMsg != null) { IDPProxyUtil.sendProxyLogoutResponseBySOAP(soapMsg, response, out); } // local log out for SOAP. For HTTP case, session will be destroyed // when SAML Response reached the SP side. if (binding.equals(SAML2Constants.SOAP) || (requestID == null)) { sessionProvider.invalidateSession(session, request, response); } } catch (SAML2MetaException sme) { debug.error("Error retreiving metadata",sme); throw new SAML2Exception( SAML2Utils.bundle.getString("metaDataError")); } catch (SessionException ssoe) { debug.error("Session exception: ",ssoe); throw new SAML2Exception( SAML2Utils.bundle.getString("metaDataError")); } } private static String prepareForLogout(String realm, String tokenID, String metaAlias, List extensionsList, String binding, String relayState, HttpServletRequest request, HttpServletResponse response, Map paramsMap, String infoKeyString, LogoutRequest origLogoutRequest, SOAPMessage msg) throws SAML2Exception, SessionException { NameIDInfoKey nameIdInfoKey = NameIDInfoKey.parse(infoKeyString); String sessionIndex = null; NameID nameID = null; if (SPCache.isFedlet) { sessionIndex = SAML2Utils.getParameter(paramsMap, SAML2Constants.SESSION_INDEX); nameID = AssertionFactory.getInstance().createNameID(); nameID.setValue(nameIdInfoKey.getNameIDValue()); nameID.setFormat(SAML2Constants.NAMEID_TRANSIENT_FORMAT); nameID.setNameQualifier(nameIdInfoKey.getRemoteEntityID()); nameID.setSPNameQualifier(nameIdInfoKey.getHostEntityID()); } else { SPFedSession fedSession = null; List list = (List)SPCache.fedSessionListsByNameIDInfoKey.get(infoKeyString); if (list != null) { synchronized (list) { ListIterator iter = list.listIterator(); while (iter.hasNext()) { fedSession = (SPFedSession)iter.next(); if (tokenID.equals(fedSession.spTokenID)) { iter.remove(); if ((agent != null) && agent.isRunning() && (saml2Svc != null)) { saml2Svc.setFedSessionCount( (long)SPCache. fedSessionListsByNameIDInfoKey.size()); } if (list.size() == 0) { SPCache.fedSessionListsByNameIDInfoKey. remove(infoKeyString); } break; } fedSession = null; } } } if (fedSession == null) { // just do local logout if (debug.messageEnabled()) { debug.message( "No session partner, just do local logout."); } return null; } sessionIndex = fedSession.idpSessionIndex; nameID = fedSession.info.getNameID(); } // get IDPSSODescriptor IDPSSODescriptorElement idpsso = sm.getIDPSSODescriptor(realm,nameIdInfoKey.getRemoteEntityID()); if (idpsso == null) { String[] data = {nameIdInfoKey.getRemoteEntityID()}; LogUtil.error(Level.INFO,LogUtil.IDP_METADATA_ERROR,data, null); throw new SAML2Exception( SAML2Utils.bundle.getString("metaDataError")); } List slosList = idpsso.getSingleLogoutService(); if (slosList == null) { String[] data = {nameIdInfoKey.getRemoteEntityID()}; LogUtil.error(Level.INFO,LogUtil.SLO_NOT_FOUND,data, null); throw new SAML2Exception( SAML2Utils.bundle.getString("sloServiceListNotfound")); } // get IDP entity config in case of SOAP, for basic auth info IDPSSOConfigElement idpConfig = null; if (binding.equals(SAML2Constants.SOAP)) { idpConfig = sm.getIDPSSOConfig( realm, nameIdInfoKey.getRemoteEntityID() ); } StringBuffer requestID = LogoutUtil.doLogout( metaAlias, nameIdInfoKey.getRemoteEntityID(), slosList, extensionsList, binding, relayState, sessionIndex, nameID, request, response, paramsMap, idpConfig); String requestIDStr = requestID.toString(); if (debug.messageEnabled()) { debug.message( "\nSPSLO.requestIDStr = " + requestIDStr + "\nbinding = " + binding); } if ((requestIDStr != null) && (requestIDStr.length() != 0) && (binding.equals(SAML2Constants.HTTP_REDIRECT) || binding.equals(SAML2Constants.HTTP_POST)) && (origLogoutRequest != null)) { IDPCache.proxySPLogoutReqCache.put(requestIDStr, origLogoutRequest); } else if ((requestIDStr != null) && (requestIDStr.length() != 0) && binding.equals(SAML2Constants.SOAP) && (msg != null)) { IDPCache.SOAPMessageByLogoutRequestID.put(requestIDStr, msg); } return requestIDStr; } /** * Gets and processes the Single LogoutResponse from IDP, * destroys the local session, checks response's issuer * and inResponseTo. * * @param request the HttpServletRequest. * @param response the HttpServletResponse. * @param samlResponse LogoutResponse in the * XML string format. * @param relayState the target URL on successful * LogoutResponse. * @throws SAML2Exception if error processing * LogoutResponse. * @throws SessionException if error processing * LogoutResponse. */ public static Map processLogoutResponse( HttpServletRequest request, HttpServletResponse response, String samlResponse, String relayState) throws SAML2Exception, SessionException { String method = "SPSingleLogout:processLogoutResponse : "; if (debug.messageEnabled()) { debug.message(method + "samlResponse : " + samlResponse); debug.message(method + "relayState : " + relayState); } String rmethod = request.getMethod(); String binding = SAML2Constants.HTTP_REDIRECT; if (rmethod.equals("POST")) { binding = SAML2Constants.HTTP_POST; } String metaAlias = SAML2MetaUtils.getMetaAliasByUri(request.getRequestURI()) ; if ((SPCache.isFedlet) && ((metaAlias == null) || (metaAlias.length() == 0))) { List spMetaAliases = sm.getAllHostedServiceProviderMetaAliases("/"); if ((spMetaAliases != null) && !spMetaAliases.isEmpty()) { // get first one metaAlias = (String) spMetaAliases.get(0); } } if ((metaAlias == null) || (metaAlias.length() == 0)) { throw new SAML2Exception( SAML2Utils.bundle.getString("nullSPEntityID")); } String realm = SAML2Utils. getRealm(SAML2MetaUtils.getRealmByMetaAlias(metaAlias)); String spEntityID = sm.getEntityByMetaAlias(metaAlias); if (!SAML2Utils.isSPProfileBindingSupported( realm, spEntityID, SAML2Constants.SLO_SERVICE, binding)) { throw new SAML2Exception( SAML2Utils.bundle.getString("unsupportedBinding")); } // Validate the RelayState URL. SAML2Utils.validateRelayStateURL(realm, spEntityID, relayState, SAML2Constants.SP_ROLE); LogoutResponse logoutRes = null; if (rmethod.equals("POST")) { logoutRes = LogoutUtil.getLogoutResponseFromPost(samlResponse, response); } else if (rmethod.equals("GET")) { String decodedStr = SAML2Utils.decodeFromRedirect(samlResponse); if (decodedStr == null) { throw new SAML2Exception(SAML2Utils.bundle.getString( "nullDecodedStrFromSamlResponse")); } logoutRes = ProtocolFactory.getInstance().createLogoutResponse(decodedStr); } if (logoutRes == null) { if (debug.messageEnabled()) { debug.message("SSingleLogout:processLogoutResponse: logoutRes " + "is null"); } return null; } String idpEntityID = logoutRes.getIssuer().getValue(); Issuer resIssuer = logoutRes.getIssuer(); String inResponseTo = logoutRes.getInResponseTo(); LogoutRequest logoutReq = (LogoutRequest) SPCache.logoutRequestIDHash.remove(inResponseTo); if (logoutReq == null) { logoutReq = (LogoutRequest) SAML2Store.getTokenFromStore(inResponseTo); } if (logoutReq == null && SAML2FailoverUtils.isSAML2FailoverEnabled()) { //check the samlFailover cache instead try { logoutReq = (LogoutRequest) SAML2FailoverUtils.retrieveSAML2Token(inResponseTo); } catch (SAML2TokenRepositoryException e) { throw new SAML2Exception(SAML2Utils.bundle.getString("LogoutRequestIDandInResponseToDoNotMatch")); } } // invoke SPAdapter preSingleLogoutProcess String userId = null; if (!SPCache.isFedlet) { userId = preSingleLogoutProcess(spEntityID, realm, request, response, null, logoutReq, logoutRes, binding); } SAML2Utils.verifyResponseIssuer( realm, spEntityID, resIssuer, inResponseTo); boolean needToVerify = SAML2Utils.getWantLogoutResponseSigned(realm, spEntityID, SAML2Constants.SP_ROLE); if (debug.messageEnabled()) { debug.message(method + "metaAlias : " + metaAlias); debug.message(method + "realm : " + realm); debug.message(method + "idpEntityID : " + idpEntityID); debug.message(method + "spEntityID : " + spEntityID); } Map infoMap = new HashMap(); infoMap.put("entityid", spEntityID); infoMap.put(SAML2Constants.REALM, realm); if (needToVerify) { boolean valid = false; if (rmethod.equals("GET")) { String queryString = request.getQueryString(); valid = SAML2Utils.verifyQueryString(queryString, realm, SAML2Constants.SP_ROLE, idpEntityID); } else { valid = LogoutUtil.verifySLOResponse(logoutRes, realm, idpEntityID, spEntityID, SAML2Constants.SP_ROLE); } if (!valid) { debug.error("SPSingleLogout.processLogoutResponse: " + "Invalid signature in SLO Response."); throw new SAML2Exception(SAML2Utils.bundle.getString( "invalidSignInResponse")); } SPSSODescriptorElement spsso = sm.getSPSSODescriptor(realm, spEntityID); String loc = getSLOResponseLocationOrLocation(spsso, binding); if (!SAML2Utils.verifyDestination(logoutRes.getDestination(), loc)) { throw new SAML2Exception( SAML2Utils.bundle.getString("invalidDestination")); } } if (inResponseTo == null || inResponseTo.length() == 0) { if (debug.messageEnabled()) { debug.message( "LogoutResponse inResponseTo is null"); } throw new SAML2Exception( SAML2Utils.bundle.getString( "nullInResponseToFromSamlResponse")); } if (logoutReq != null) { if (debug.messageEnabled()) { debug.message( "LogoutResponse inResponseTo matches "+ "LogoutRequest ID."); } } else { if (debug.messageEnabled()) { debug.message( "LogoutResponse inResponseTo does not match " + "LogoutRequest ID."); } throw new SAML2Exception( SAML2Utils.bundle.getString( "LogoutRequestIDandInResponseToDoNotMatch")); } infoMap.put("inResponseTo" , inResponseTo); infoMap.put(SAML2Constants.RELAY_STATE, relayState); // destroy session try { Object session = sessionProvider.getSession(request); if ((session != null) && sessionProvider.isValid(session)) { sessionProvider.invalidateSession(session, request, response); } } catch (SessionException se) { debug.message("SPSingleLogout.processLogoutResponse() : Unable to invalidate session: " + se.getMessage()); } if (!SPCache.isFedlet) { if (isSuccess(logoutRes)) { // invoke SPAdapter postSingleLogoutSucces postSingleLogoutSuccess(spEntityID, realm, request, response, userId, logoutReq, logoutRes, binding); } else { throw new SAML2Exception(SAML2Utils.BUNDLE_NAME, "sloFailed", null); } } else { // obtain fedlet adapter FedletAdapter fedletAdapter = SAML2Utils.getFedletAdapterClass(spEntityID, realm); if (fedletAdapter != null) { if (isSuccess(logoutRes)) { fedletAdapter.onFedletSLOSuccess( request, response, logoutReq, logoutRes, spEntityID, idpEntityID, binding); } else { fedletAdapter.onFedletSLOFailure( request, response, logoutReq, logoutRes, spEntityID, idpEntityID, binding); throw new SAML2Exception(SAML2Utils.BUNDLE_NAME, "sloFailed", null); } } } return infoMap; } static String preSingleLogoutProcess(String hostedEntityID, String realm, HttpServletRequest request, HttpServletResponse response, String userID, LogoutRequest logoutRequest, LogoutResponse logoutResponse, String binding) throws SAML2Exception { SAML2ServiceProviderAdapter spAdapter = null; try { spAdapter = SAML2Utils.getSPAdapterClass(hostedEntityID, realm); } catch (SAML2Exception e) { if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( "SPACSUtils.invokeSPAdapterForPreSLOProcess", e); } } if (spAdapter != null) { if (userID == null) { try { Object session = sessionProvider.getSession(request); if (session != null) { userID = sessionProvider.getPrincipalName(session); } } catch (SessionException ex) { if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( "SPACSUtils.invokeSPAdapterForPreSLOProcess2", ex); } } } spAdapter.preSingleLogoutProcess(hostedEntityID, realm, request, response, userID, logoutRequest, logoutResponse, binding); } return userID; } static void postSingleLogoutSuccess(String hostedEntityID, String realm, HttpServletRequest request, HttpServletResponse response, String userID, LogoutRequest logoutRequest, LogoutResponse logoutResponse, String binding) { SAML2ServiceProviderAdapter spAdapter = null; try { spAdapter = SAML2Utils.getSPAdapterClass(hostedEntityID, realm); } catch (SAML2Exception e) { if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message( "SPACSUtils.invokeSPAdapterForPostSLOProcess", e); } } if (spAdapter != null) { spAdapter.postSingleLogoutSuccess(hostedEntityID, realm, request, response, userID, logoutRequest, logoutResponse, binding); } } /** * Gets and processes the Single LogoutRequest from IDP. * * @param request the HttpServletRequest. * @param response the HttpServletResponse. * @param out the print writer for writing out presentation * @param samlRequest LogoutRequest in the * XML string format. * @param relayState the target URL on successful * LogoutRequest. * @throws SAML2Exception if error processing * LogoutRequest. * @throws SessionException if error processing * LogoutRequest. */ public static void processLogoutRequest( HttpServletRequest request, HttpServletResponse response, PrintWriter out, String samlRequest, String relayState) throws SAML2Exception, SessionException { String method = "processLogoutRequest : "; if (debug.messageEnabled()) { debug.message(method + "samlRequest : " + samlRequest); debug.message(method + "relayState : " + relayState); } String rmethod = request.getMethod(); String binding = SAML2Constants.HTTP_REDIRECT; if (rmethod.equals("POST")) { binding = SAML2Constants.HTTP_POST; } String metaAlias = SAML2MetaUtils.getMetaAliasByUri( request.getRequestURI()) ; if ((SPCache.isFedlet) && ((metaAlias == null) || (metaAlias.length() == 0))) { List spMetaAliases = sm.getAllHostedServiceProviderMetaAliases("/"); if ((spMetaAliases != null) && !spMetaAliases.isEmpty()) { // get first one metaAlias = (String) spMetaAliases.get(0); } if ((metaAlias == null) || (metaAlias.length() == 0)) { throw new SAML2Exception( SAML2Utils.bundle.getString("nullSPEntityID")); } } String realm = SAML2Utils.getRealm( SAML2MetaUtils.getRealmByMetaAlias(metaAlias)); String spEntityID = sm.getEntityByMetaAlias(metaAlias); if (!SAML2Utils.isSPProfileBindingSupported( realm, spEntityID, SAML2Constants.SLO_SERVICE, binding)) { throw new SAML2Exception( SAML2Utils.bundle.getString("unsupportedBinding")); } LogoutRequest logoutReq = null; if (rmethod.equals("POST")) { logoutReq = LogoutUtil.getLogoutRequestFromPost(samlRequest, response); } else if (rmethod.equals("GET")) { String decodedStr = SAML2Utils.decodeFromRedirect(samlRequest); if (decodedStr == null) { throw new SAML2Exception(SAML2Utils.bundle.getString( "nullDecodedStrFromSamlRequest")); } logoutReq = ProtocolFactory.getInstance().createLogoutRequest(decodedStr); } if (logoutReq == null) { if (debug.messageEnabled()) { debug.message("SPSingleLogout:processLogoutRequest: logoutReq " + "is null"); } return; } String location = null; String idpEntityID = logoutReq.getIssuer().getValue(); // invoke SPAdapter preSingleLogoutProcess : IDP initiated HTTP //String userId = preSingleLogoutProcess(spEntityID, realm, request, // response, null, logoutReq, null, SAML2Constants.HTTP_REDIRECT); boolean needToVerify = SAML2Utils.getWantLogoutRequestSigned(realm, spEntityID, SAML2Constants.SP_ROLE); if (debug.messageEnabled()) { debug.message(method + "metaAlias : " + metaAlias); debug.message(method + "realm : " + realm); debug.message(method + "idpEntityID : " + idpEntityID); debug.message(method + "spEntityID : " + spEntityID); } if (needToVerify == true) { boolean valid = false; if (rmethod.equals("POST")) { valid = LogoutUtil.verifySLORequest(logoutReq, realm, idpEntityID, spEntityID, SAML2Constants.SP_ROLE); } else { String queryString = request.getQueryString(); valid = SAML2Utils.verifyQueryString(queryString, realm, SAML2Constants.SP_ROLE, idpEntityID); } if (!valid) { debug.error("SPSingleLogout.processLogoutRequest: " + "Invalid signature in SLO Request."); throw new SAML2Exception(SAML2Utils.bundle.getString( "invalidSignInRequest")); } SPSSODescriptorElement spsso = sm.getSPSSODescriptor(realm, spEntityID); String loc = getSLOResponseLocationOrLocation(spsso, binding); if (!SAML2Utils.verifyDestination(logoutReq.getDestination(), loc)) { throw new SAML2Exception( SAML2Utils.bundle.getString("invalidDestination")); } } // get IDPSSODescriptor IDPSSODescriptorElement idpsso = sm.getIDPSSODescriptor(realm,idpEntityID); if (idpsso == null) { String[] data = {idpEntityID}; LogUtil.error(Level.INFO,LogUtil.IDP_METADATA_ERROR,data, null); throw new SAML2Exception( SAML2Utils.bundle.getString("metaDataError")); } List slosList = idpsso.getSingleLogoutService(); if (slosList == null) { String[] data = {idpEntityID}; LogUtil.error(Level.INFO,LogUtil.SLO_NOT_FOUND,data, null); throw new SAML2Exception( SAML2Utils.bundle.getString("sloServiceListNotfound")); } location = LogoutUtil.getSLOResponseServiceLocation( slosList, binding); if (location == null || location.length() == 0) { location = LogoutUtil.getSLOServiceLocation(slosList, binding); if (location == null || location.length() == 0) { debug.error( "Unable to find the IDP's single logout "+ "response service with the HTTP-Redirect binding"); throw new SAML2Exception( SAML2Utils.bundle.getString( "sloResponseServiceLocationNotfound")); } else { if (debug.messageEnabled()) { debug.message( "SP's single logout response service location = "+ location); } } } else { if (debug.messageEnabled()) { debug.message( "IDP's single logout response service location = "+ location); } } List partners = IDPProxyUtil.getSPSessionPartners(request); //IDP Proxy Case if (partners != null && !partners.isEmpty()) { LogoutResponse logoutRespon = processLogoutRequest(logoutReq, spEntityID, realm, request, response, false, false, binding, true); logoutRespon.setDestination(XMLUtils.escapeSpecialCharacters( location)); IDPProxyUtil.sendIDPInitProxyLogoutRequest(request, response, out, logoutRespon, location, spEntityID, idpEntityID, binding, realm); } else { LogoutResponse logoutRes = processLogoutRequest( logoutReq, spEntityID, realm, request, response, true, binding, true); logoutRes.setDestination(XMLUtils.escapeSpecialCharacters( location)); LogoutUtil.sendSLOResponse(response, request, logoutRes, location, relayState, realm, spEntityID, SAML2Constants.SP_ROLE, idpEntityID, binding); } } /** * Gets and processes the Single LogoutRequest from IDP * and return LogoutResponse. * * @param logoutReq LogoutRequest from IDP * @param spEntityID name of host entity ID. * @param realm name of host entity. * @param request HTTP servlet request. * @param response HTTP servlet response. * @param isLBReq true if the request is for load balancing. * @param binding value of SAML2Constants.HTTP_REDIRECT or * SAML2Constants.SOAP. * @param isVerified true if the request is verified already. * @return LogoutResponse the target URL on successful * LogoutRequest. */ public static LogoutResponse processLogoutRequest( LogoutRequest logoutReq, String spEntityID, String realm, HttpServletRequest request, HttpServletResponse response, boolean isLBReq, String binding, boolean isVerified) { return processLogoutRequest(logoutReq, spEntityID, realm, request, response, isLBReq, true, binding, isVerified); } /** * Gets and processes the Single LogoutRequest from IDP * and return LogoutResponse. * * @param logoutReq LogoutRequest from IDP * @param spEntityID name of host entity ID. * @param realm name of host entity. * @param request HTTP servlet request. * @param response HTTP servlet response. * @param isLBReq true if the request is for load balancing. * @param binding value of SAML2Constants.HTTP_REDIRECT or * SAML2Constants.SOAP. * @param isVerified true if the request is verified already. * @return LogoutResponse the target URL on successful * LogoutRequest. */ public static LogoutResponse processLogoutRequest( LogoutRequest logoutReq, String spEntityID, String realm, HttpServletRequest request, HttpServletResponse response, boolean isLBReq, boolean destroySession, String binding, boolean isVerified) { final String method = "processLogoutRequest : "; NameID nameID = null; Status status = null; Issuer issuer = null; String idpEntity = logoutReq.getIssuer().getValue(); String userId = null; try { do { // TODO: check the NotOnOrAfter attribute of LogoutRequest issuer = logoutReq.getIssuer(); String requestId = logoutReq.getID(); SAML2Utils.verifyRequestIssuer( realm, spEntityID, issuer, requestId); issuer = SAML2Utils.createIssuer(spEntityID); // get SessionIndex and NameID form LogoutRequest List siList = logoutReq.getSessionIndex(); int numSI = 0; if (siList != null) { numSI = siList.size(); if (debug.messageEnabled()) { debug.message(method + "Number of session indices in the logout request is " + numSI); } } nameID = LogoutUtil.getNameIDFromSLORequest(logoutReq, realm, spEntityID, SAML2Constants.SP_ROLE); if (nameID == null) { debug.error(method + "LogoutRequest does not contain Name ID"); status = SAML2Utils.generateStatus( SAML2Constants.RESPONDER, SAML2Utils.bundle. getString("missing_name_identifier")); break; } String infoKeyString = null; infoKeyString = (new NameIDInfoKey(nameID.getValue(), spEntityID, idpEntity)).toValueString(); if (debug.messageEnabled()) { debug.message(method + "infokey=" + infoKeyString); } if (SPCache.isFedlet) { // verify request if(!isVerified && !LogoutUtil.verifySLORequest(logoutReq, realm, idpEntity, spEntityID, SAML2Constants.SP_ROLE)) { throw new SAML2Exception( SAML2Utils.bundle.getString("invalidSignInRequest")); } // obtain fedlet adapter FedletAdapter fedletAdapter = SAML2Utils.getFedletAdapterClass(spEntityID, realm); boolean result = false; if (fedletAdapter != null) { // call adapter to do real logout result = fedletAdapter.doFedletSLO(request, response, logoutReq, spEntityID, idpEntity, siList, nameID.getValue(), binding); } if (result) { status = SUCCESS_STATUS; } else { status = SAML2Utils.generateStatus( SAML2Constants.RESPONDER, SAML2Utils.bundle.getString("appLogoutFailed")); } break; } List list = (List)SPCache.fedSessionListsByNameIDInfoKey .get(infoKeyString); if (debug.messageEnabled()) { debug.message(method + "SPFedsessions=" + list); } if ((list == null) || list.isEmpty()) { String spQ = nameID.getSPNameQualifier(); if ((spQ == null) || (spQ.length() == 0)) { infoKeyString = (new NameIDInfoKey(nameID.getValue(), spEntityID, nameID.getNameQualifier())).toValueString(); list = (List)SPCache.fedSessionListsByNameIDInfoKey .get(infoKeyString); } } boolean foundPeer = false; List remoteServiceURLs = null; if (isLBReq) { remoteServiceURLs = FSUtils.getRemoteServiceURLs(request); foundPeer = remoteServiceURLs != null && !remoteServiceURLs.isEmpty(); } if (debug.messageEnabled()) { debug.message(method + "isLBReq = " + isLBReq + ", foundPeer = " + foundPeer); } if (list == null || list.isEmpty()) { if (foundPeer) { boolean peerError = false; for(Iterator iter = remoteServiceURLs.iterator(); iter.hasNext();) { String remoteLogoutURL = getRemoteLogoutURL( (String)iter.next(), request); LogoutResponse logoutRes = LogoutUtil.forwardToRemoteServer( logoutReq, remoteLogoutURL); if ((logoutRes != null) && !isNameNotFound(logoutRes)) { if (isSuccess(logoutRes)) { if (numSI > 0) { siList = LogoutUtil.getSessionIndex(logoutRes); if (siList == null || siList.isEmpty()){ peerError = false; break; } } } else { peerError = true; } } } if (peerError || (siList != null && siList.size() > 0)) { status = PARTIAL_LOGOUT_STATUS; } else { status = SUCCESS_STATUS; } } else { debug.error(method + "invalid Name ID received"); status = SAML2Utils .generateStatus(SAML2Constants.RESPONDER, SAML2Utils.bundle .getString("invalid_name_identifier")); } break; } else { // find the session, do signature validation if(!isVerified && !LogoutUtil.verifySLORequest(logoutReq, realm, logoutReq.getIssuer().getValue(), spEntityID, SAML2Constants.SP_ROLE)) { throw new SAML2Exception( SAML2Utils.bundle.getString("invalidSignInRequest")); } // invoke SPAdapter for preSingleLogoutProcess try { String tokenId = ((SPFedSession) list.iterator().next()).spTokenID; Object token = sessionProvider.getSession(tokenId); userId = sessionProvider.getPrincipalName(token); if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message("SPSingleLogout." + "processLogoutRequest, user = " + userId); } } catch (SessionException ex) { if (SAML2Utils.debug.messageEnabled()) { SAML2Utils.debug.message("SPSingleLogout." + "processLogoutRequest", ex); } } userId = preSingleLogoutProcess(spEntityID, realm, request, response, userId, logoutReq, null, binding); } // get application logout URL BaseConfigType spConfig = SAML2Utils.getSAML2MetaManager() .getSPSSOConfig(realm, spEntityID); List appLogoutURL = (List) SAML2MetaUtils.getAttributes( spConfig).get(SAML2Constants.APP_LOGOUT_URL); if (debug.messageEnabled()) { debug.message("IDPLogoutUtil.processLogoutRequest: " + "external app logout URL= " + appLogoutURL); } if (numSI == 0) { // logout all fed sessions for this user // between this SP and the IDP List tokenIDsToBeDestroyed = new ArrayList(); synchronized (list) { Iterator iter = list.listIterator(); while (iter.hasNext()) { SPFedSession fedSession =(SPFedSession) iter.next(); tokenIDsToBeDestroyed.add(fedSession.spTokenID); iter.remove(); if ((agent != null) && agent.isRunning() && (saml2Svc != null)) { saml2Svc.setFedSessionCount( (long)SPCache. fedSessionListsByNameIDInfoKey.size()); } } } for (Iterator iter = tokenIDsToBeDestroyed.listIterator(); iter.hasNext();) { String tokenID =(String) iter.next(); Object token = null; try { token = sessionProvider.getSession(tokenID); } catch (SessionException se) { debug.error(method + "Could not create session from token ID = " + tokenID); continue; } if (debug.messageEnabled()) { debug.message(method + "destroy token " + tokenID); } // handle external application logout if configured if ((appLogoutURL != null) && (appLogoutURL.size() != 0)) { SAML2Utils.postToAppLogout(request, (String) appLogoutURL.get(0), token); } if (destroySession) { sessionProvider.invalidateSession(token, request, response); } } if (foundPeer) { boolean peerError = false; for(Iterator iter = remoteServiceURLs.iterator(); iter.hasNext();) { String remoteLogoutURL = getRemoteLogoutURL( (String)iter.next(), request); LogoutResponse logoutRes = LogoutUtil.forwardToRemoteServer(logoutReq, remoteLogoutURL); if ((logoutRes == null) || !(isSuccess(logoutRes) || isNameNotFound(logoutRes))) { peerError = true; } } if (peerError) { status = PARTIAL_LOGOUT_STATUS; } else { status = SUCCESS_STATUS; } } } else { // logout only those fed sessions specified // in logout request session list String sessionIndex = null; List siNotFound = new ArrayList(); for (int i = 0; i < numSI; i++) { sessionIndex = (String)siList.get(i); String tokenIDToBeDestroyed = null; synchronized (list) { Iterator iter = list.listIterator(); while (iter.hasNext()) { SPFedSession fedSession = (SPFedSession) iter.next(); if (sessionIndex .equals(fedSession.idpSessionIndex)) { if (debug.messageEnabled()) { debug.message(method + " found si + " + sessionIndex); } tokenIDToBeDestroyed = fedSession.spTokenID; iter.remove(); if ((agent != null) && agent.isRunning() && (saml2Svc != null)) { saml2Svc.setFedSessionCount( (long)SPCache. fedSessionListsByNameIDInfoKey. size()); } break; } } } if (tokenIDToBeDestroyed != null) { try { Object token = sessionProvider.getSession( tokenIDToBeDestroyed); if (debug.messageEnabled()) { debug.message(method + "destroy token (2) " + tokenIDToBeDestroyed); } // handle external application logout if ((appLogoutURL != null) && (appLogoutURL.size() != 0)) { SAML2Utils.postToAppLogout(request, (String) appLogoutURL.get(0), token); } if (destroySession) { sessionProvider.invalidateSession( token, request, response); } } catch (SessionException se) { debug.error(method + "Could not create " + "session from token ID = " + tokenIDToBeDestroyed); } } else { siNotFound.add(sessionIndex); } } if (isLBReq) { if (foundPeer && !siNotFound.isEmpty()) { boolean peerError = false; LogoutRequest lReq = copyAndMakeMutable(logoutReq); for(Iterator iter = remoteServiceURLs.iterator(); iter.hasNext();) { lReq.setSessionIndex(siNotFound); String remoteLogoutURL = getRemoteLogoutURL( (String)iter.next(), request); LogoutResponse logoutRes = LogoutUtil.forwardToRemoteServer(lReq, remoteLogoutURL); if ((logoutRes != null) && !isNameNotFound(logoutRes)) { if (isSuccess(logoutRes)) { siNotFound = LogoutUtil.getSessionIndex(logoutRes); } else { peerError = true; } } if (debug.messageEnabled()) { debug.message(method + "siNotFound = " + siNotFound); } if (siNotFound == null || siNotFound.isEmpty()) { peerError = false; break; } } if (peerError || (siNotFound != null && !siNotFound.isEmpty())){ status = PARTIAL_LOGOUT_STATUS; } else { status = SUCCESS_STATUS; } } else { status = SUCCESS_STATUS; } } else { if (siNotFound.isEmpty()) { status = SUCCESS_STATUS; } else { status = SAML2Utils.generateStatus( SAML2Constants.SUCCESS, SAML2Utils.bundle.getString("requestSuccess")); LogoutUtil.setSessionIndex(status, siNotFound); } } } } while (false); } catch (SessionException se) { debug.error("processLogoutRequest: ", se); status = SAML2Utils.generateStatus(SAML2Constants.RESPONDER, se.toString()); } catch (SAML2Exception e) { debug.error("processLogoutRequest: " + "failed to create response", e); status = SAML2Utils.generateStatus(SAML2Constants.RESPONDER, e.toString()); } // create LogoutResponse if (spEntityID == null) { spEntityID = nameID.getSPNameQualifier(); } LogoutResponse logResponse = LogoutUtil.generateResponse( status, logoutReq.getID(), issuer, realm, SAML2Constants.SP_ROLE, idpEntity); if (isSuccess(logResponse)) { // invoke SPAdapter for postSingleLogoutSuccess postSingleLogoutSuccess(spEntityID, realm, request, response, userId, logoutReq, logResponse, binding); } return logResponse; } static boolean isSuccess(LogoutResponse logoutRes) { return logoutRes.getStatus().getStatusCode().getValue() .equals(SAML2Constants.SUCCESS); } static boolean isNameNotFound(LogoutResponse logoutRes) { Status status = logoutRes.getStatus(); String statusMessage = status.getStatusMessage(); return (status.getStatusCode().getValue() .equals(SAML2Constants.RESPONDER) && statusMessage != null && statusMessage.equals( SAML2Utils.bundle.getString("invalid_name_identifier"))); } private static LogoutRequest copyAndMakeMutable(LogoutRequest src) { LogoutRequest dest = ProtocolFactory.getInstance() .createLogoutRequest(); try { dest.setNotOnOrAfter(src.getNotOnOrAfter()); dest.setReason(src.getReason()); dest.setEncryptedID(src.getEncryptedID()); dest.setNameID(src.getNameID()); dest.setBaseID(src.getBaseID()); dest.setSessionIndex(src.getSessionIndex()); dest.setIssuer(src.getIssuer()); dest.setExtensions(src.getExtensions()); dest.setID(src.getID()); dest.setVersion(src.getVersion()); dest.setIssueInstant(src.getIssueInstant()); dest.setDestination(XMLUtils.escapeSpecialCharacters( src.getDestination())); dest.setConsent(src.getConsent()); } catch(SAML2Exception ex) { debug.error("SPLogoutUtil.copyAndMakeMutable:", ex); } return dest; } private static String getSLOResponseLocationOrLocation( SPSSODescriptorElement spsso, String binding) { String location = null; if (spsso != null) { List sloList = spsso.getSingleLogoutService(); if (sloList != null && !sloList.isEmpty()) { location = LogoutUtil.getSLOResponseServiceLocation( sloList, binding); if (location == null || (location.length() == 0)) { location = LogoutUtil.getSLOServiceLocation( sloList, binding); } } } return location; } private static String getRemoteLogoutURL(String serverURL, HttpServletRequest request) { if (serverURL == null || request == null) { return null; } String queryString = request.getQueryString(); if (queryString == null) { return serverURL + SAML2Utils.removeDeployUri( request.getRequestURI()) + "?isLBReq=false"; } else { return serverURL + SAML2Utils.removeDeployUri( request.getRequestURI()) + "?" + queryString + "&isLBReq=false"; } } }