/** * 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: FSProcessLogoutServlet.java,v 1.7 2008/12/19 06:50:47 exu Exp $ * */ package com.sun.identity.federation.services.logout; import java.io.IOException; import java.security.cert.X509Certificate; import java.util.List; import java.util.logging.Level; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServlet; import com.sun.identity.federation.common.FSUtils; import com.sun.identity.federation.common.IFSConstants; import com.sun.identity.federation.common.LogUtil; import com.sun.identity.federation.common.FSException; import com.sun.identity.federation.jaxb.entityconfig.BaseConfigType; import com.sun.identity.federation.key.KeyUtil; import com.sun.identity.federation.message.FSLogoutNotification; import com.sun.identity.federation.message.common.FSMsgException; import com.sun.identity.federation.meta.IDFFMetaException; import com.sun.identity.federation.meta.IDFFMetaManager; import com.sun.identity.federation.meta.IDFFMetaUtils; import com.sun.identity.federation.plugins.FederationSPAdapter; import com.sun.identity.federation.services.util.FSServiceUtils; import com.sun.identity.federation.services.util.FSSignatureUtil; import com.sun.identity.federation.services.FSServiceManager; import com.sun.identity.liberty.ws.meta.jaxb.ProviderDescriptorType; 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.saml.common.SAMLResponderException; import com.sun.identity.saml.common.SAMLException; /** * Handles ID-FF Single Logout request. */ public class FSProcessLogoutServlet extends HttpServlet { private static IDFFMetaManager metaManager = null; /** * Initializes the servlet. * @param config the ServletConfig object that contains * configutation information for this servlet. * @exception ServletException if an exception occurs that interrupts * the servlet's normal operation. */ public void init(ServletConfig config) throws ServletException { super.init(config); FSUtils.debug.message("FSProcessLogoutServlet Initializing..."); metaManager = FSUtils.getIDFFMetaManager(); } /** * Handles the HTTP GET request. * * @param request an HttpServletRequest object that contains * the request the client has made of the servlet. * @param response an HttpServletResponse object that contains * the response the servlet sends to the client. * @exception ServletException if an input or output error is detected when * the servlet handles the GET request * @exception IOException if the request for the GET could not be handled */ public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGetPost(request, response); } /** * Handles the HTTP POST request. * * @param request an HttpServletRequest object that contains * the request the client has made of the servlet. * @param response an HttpServletResponse object that contains * the response the servlet sends to the client. * @exception ServletException if an input or output error is detected when * the servlet handles the POST request * @exception IOException if the request for the POST could not be handled */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGetPost(request, response); } /** * Handles single logout request. * @param request an HttpServletRequest object that contains * the request the client has made of the servlet. * @param response an HttpServletResponse object that contains * the response the servlet sends to the client. * @exception ServletException if an input or output error is detected when * the servlet handles the request * @exception IOException if the request could not be handled */ private void doGetPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { FSUtils.debug.message("FSProcessLogoutServlet doGetPost..."); // Alias processing String providerAlias = request.getParameter(IFSConstants.META_ALIAS); if (providerAlias == null || providerAlias.length() == 0) { providerAlias = FSServiceUtils.getMetaAlias(request); } if (providerAlias == null || providerAlias.length() < 1) { FSUtils.debug.error("Unable to retrieve alias, Hosted Provider. " + "Cannot process request"); response.sendError(response.SC_INTERNAL_SERVER_ERROR, FSUtils.bundle.getString("aliasNotFound")); return; } if (metaManager == null) { FSUtils.debug.error("Cannot retrieve hosted descriptor. " + "Cannot process request"); response.sendError(response.SC_INTERNAL_SERVER_ERROR, FSUtils.bundle.getString( IFSConstants.FAILED_HOSTED_DESCRIPTOR)); return; } String realm = IDFFMetaUtils.getRealmByMetaAlias(providerAlias); ProviderDescriptorType hostedProviderDesc = null; BaseConfigType hostedConfig = null; String hostedRole = null; String hostedEntityId = null; try { hostedRole = metaManager.getProviderRoleByMetaAlias(providerAlias); hostedEntityId = metaManager.getEntityIDByMetaAlias(providerAlias); if (hostedRole != null) { if (hostedRole.equalsIgnoreCase(IFSConstants.IDP)) { hostedProviderDesc = metaManager.getIDPDescriptor( realm, hostedEntityId); hostedConfig = metaManager.getIDPDescriptorConfig( realm, hostedEntityId); } else if (hostedRole.equalsIgnoreCase(IFSConstants.SP)) { hostedProviderDesc = metaManager.getSPDescriptor( realm, hostedEntityId); hostedConfig = metaManager.getSPDescriptorConfig( realm, hostedEntityId); } } if (hostedProviderDesc == null){ throw new IDFFMetaException((String)null); } } catch (IDFFMetaException eam) { FSUtils.debug.error("Unable to find Hosted Provider. " + "not process request", eam); response.sendError(response.SC_INTERNAL_SERVER_ERROR, FSUtils.bundle.getString( IFSConstants.FAILED_HOSTED_DESCRIPTOR)); return; } String logoutDoneURL = FSServiceUtils.getLogoutDonePageURL( request, hostedConfig, providerAlias); String commonErrorPage = FSServiceUtils.getErrorPageURL( request, hostedConfig, providerAlias); if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("logoutDoneURL : " + logoutDoneURL + "\ncommonErrorPage : " + commonErrorPage); } String sourceCheck = (String) request.getAttribute("logoutSource"); if (sourceCheck == null) { sourceCheck = request.getParameter("logoutSource"); } Object ssoToken = getValidToken(request); String userID = null; if (ssoToken == null) { if (sourceCheck != null) { if (sourceCheck.equalsIgnoreCase("local")) { // need to redirect to LogoutDone.jsp with // status=noSession if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("FSProcessLogoutServlet, " + "control where Source is local"); } FSServiceUtils.returnLocallyAfterOperation( response, logoutDoneURL, false, IFSConstants.LOGOUT_SUCCESS, IFSConstants.LOGOUT_NO_SESSION); return; } else if (sourceCheck.equalsIgnoreCase("remote")){ // logout return if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "Control where Source is remote - not from app" + "link but from other provider"); } FSServiceUtils.returnLocallyAfterOperation( response, logoutDoneURL, true, IFSConstants.LOGOUT_SUCCESS, IFSConstants.LOGOUT_FAILURE); return; } else if (sourceCheck.equalsIgnoreCase("logoutGet")){ // logout Get profile if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "Control where Source is Http Get action - " + "not from app link "); } FSServiceUtils.returnLocallyAfterOperation( response, logoutDoneURL, true, IFSConstants.LOGOUT_SUCCESS, IFSConstants.LOGOUT_FAILURE); return; } } } else { try { userID = SessionManager.getProvider().getPrincipalName(ssoToken); } catch (SessionException ssoExp) { if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("Couldn't get user object:", ssoExp); } } if (sourceCheck != null) { if (sourceCheck.equalsIgnoreCase("local")) { // initiate logout FSUtils.debug.message( "Control where Source is local - from applink"); doLogoutInitiation(request, response, hostedProviderDesc, hostedConfig, realm, hostedEntityId, hostedRole, providerAlias, ssoToken, logoutDoneURL, sourceCheck); return; } else if (sourceCheck.equalsIgnoreCase("remote")){ // logout return if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "Control where Source is remote - not from app" + "link but from other provider. Token valid"); } doLogoutInitiation(request, response, hostedProviderDesc, hostedConfig, realm, hostedEntityId, hostedRole, providerAlias, ssoToken, logoutDoneURL, sourceCheck); return; } else if (sourceCheck.equalsIgnoreCase("logoutGet")){ // logout Get profile if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "Control where Source is Http Get action - not from" + " applink. Initiation will take care in " + "preLogouthandler "); } doLogoutInitiation(request, response, hostedProviderDesc, hostedConfig, realm, hostedEntityId, hostedRole, providerAlias, ssoToken, logoutDoneURL, sourceCheck); return; } } } // received logout request from remote provider FSLogoutNotification logoutObj = null; try { logoutObj = FSLogoutNotification.parseURLEncodedRequest(request); } catch (FSMsgException e) { // FSMsgException would mean that the request does not have the // FSLogoutNotification message, so show error page FSUtils.debug.message( "Bad Logout request. calling showErrorPage"); FSServiceUtils.showErrorPage(response, commonErrorPage, IFSConstants.LOGOUT_REQUEST_IMPROPER, IFSConstants.LOGOUT_FAILED); return; } if (logoutObj == null) { FSUtils.debug.message( "Bad Logout request. calling showErrorPage"); FSServiceUtils.showErrorPage( response, commonErrorPage, IFSConstants.LOGOUT_REQUEST_IMPROPER, IFSConstants.LOGOUT_FAILED); } else { doRequestProcessing( request, response, hostedProviderDesc, hostedConfig, hostedRole, realm, hostedEntityId, providerAlias, logoutObj, commonErrorPage, userID, ssoToken); } return; } /** * Retrieves valid session from HTTP Request. * @param request HTTP request object * @return session if the session is valid; null * otherwise. */ private Object getValidToken(HttpServletRequest request) { FSUtils.debug.message( "Entered FSProcessLogoutServlet::getValidToken"); try { SessionProvider sessionProvider = SessionManager.getProvider(); Object ssoToken = sessionProvider.getSession(request); if ((ssoToken == null) || (!sessionProvider.isValid(ssoToken))) { FSUtils.debug.message( "session is not valid, redirecting for authentication"); return null; } return ssoToken; } catch (SessionException e){ if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("SessionException caught: " + e); } return null; } } /** * Initiates logout request processing. It is called when a logout request * is received from a remote provider. * @param request HTTPServletRequest object received via a * HTTP Redirect * @param response HTTPServletResponse object to be sent back * to user agent * @param hostedDescriptor the provider for whom request is received * @param hostedConfig hosted provider's extended meta config * @param hostedRole hosted provider's role * @param realm the realm in which the entity resides * @param hostedEntityId hosted provider's entity id * @param metaAlias hosted provider's meta alias * @param reqLogout the single logout request * @param commonErrorPage where to go if an error occurred * @param userID user id * @param ssoToken user session object */ private void doRequestProcessing( HttpServletRequest request, HttpServletResponse response, ProviderDescriptorType hostedDescriptor, BaseConfigType hostedConfig, String hostedRole, String realm, String hostedEntityId, String metaAlias, FSLogoutNotification reqLogout, String commonErrorPage, String userID, Object ssoToken) { FSUtils.debug.message( "Entered FSProcessLogoutServlet::doRequestProcessing"); int minorVersion = reqLogout.getMinorVersion(); String remoteEntityId = reqLogout.getProviderId(); ProviderDescriptorType remoteDesc = null; boolean isIDP = false; try { if (hostedRole != null) { if (hostedRole.equalsIgnoreCase(IFSConstants.IDP)) { remoteDesc = metaManager.getSPDescriptor( realm, remoteEntityId); } else if (hostedRole.equalsIgnoreCase(IFSConstants.SP)) { remoteDesc = metaManager.getIDPDescriptor( realm, remoteEntityId); isIDP = true; } } if (remoteDesc == null) { throw new IDFFMetaException((String) null); } } catch(IDFFMetaException e) { FSUtils.debug.error("Remote provider metadata not found."); String[] data = { remoteEntityId, realm }; LogUtil.error(Level.INFO,LogUtil.INVALID_PROVIDER,data, ssoToken); FSLogoutUtil.returnToSource(response, remoteDesc, IFSConstants.SAML_RESPONDER, commonErrorPage, minorVersion, hostedConfig, hostedEntityId, userID); return; } boolean bVerify = true; if (FSServiceUtils.isSigningOn()) { try { FSUtils.debug.message("Calling verifyLogoutSignature"); bVerify = verifyLogoutSignature( request, remoteDesc, remoteEntityId, isIDP); } catch(FSException e) { FSUtils.debug.error( "FSProcessLogoutServlet::doRequestProcessing " + "Signature on Logout request is invalid" + "Cannot proceed federation Logout"); String[] data = { userID }; LogUtil.error(Level.INFO, LogUtil.INVALID_SIGNATURE,data, ssoToken); FSLogoutUtil.returnToSource(response, remoteDesc, IFSConstants.SAML_REQUESTER, commonErrorPage, minorVersion, hostedConfig, hostedEntityId, userID); return; } catch(SAMLException e) { FSUtils.debug.error( "FSProcessLogoutServlet::doRequestProcessing(SAML) " + "Signature on Logout request is invalid" + "Cannot proceed federation Logout"); String[] data = { userID }; LogUtil.error(Level.INFO, LogUtil.INVALID_SIGNATURE,data, ssoToken); FSLogoutUtil.returnToSource(response, remoteDesc, IFSConstants.SAML_REQUESTER, commonErrorPage, minorVersion, hostedConfig, hostedEntityId, userID); return; } } String errorStatus = IFSConstants.SAML_RESPONDER; if (bVerify) { // Check if trusted provider if (metaManager.isTrustedProvider( realm, hostedEntityId,remoteEntityId)) { //Object ssoToken = getValidToken(request); if (ssoToken != null) { // session is valid, start single logout // Invoke Messaging APIs to get providerid from request FSServiceManager instSManager = FSServiceManager.getInstance(); if (instSManager != null) { FSUtils.debug.message( "FSServiceManager Instance not null"); // Call SP Adapter preSingleLogoutProcess // for IDP/HTTP case callPreSingleLogoutProcess(request, response, hostedRole, hostedConfig, hostedEntityId, userID, reqLogout); FSPreLogoutHandler handlerObj = instSManager.getPreLogoutHandler(); if (handlerObj != null) { handlerObj.setLogoutRequest(reqLogout); handlerObj.setHostedDescriptor( hostedDescriptor); handlerObj.setHostedDescriptorConfig( hostedConfig); handlerObj.setRealm(realm); handlerObj.setHostedEntityId(hostedEntityId); handlerObj.setHostedProviderRole(hostedRole); handlerObj.setMetaAlias(metaAlias); handlerObj.setRemoteEntityId(remoteEntityId); handlerObj.setRemoteDescriptor(remoteDesc); handlerObj.processHttpSingleLogoutRequest( request, response, ssoToken); return; } } else { if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "FSServiceManager Instance null. Cannot" + " continue logout"); } String[] data = { userID }; LogUtil.error( Level.INFO,LogUtil.LOGOUT_FAILED, data, ssoToken); FSLogoutUtil.returnToSource( response, remoteDesc, IFSConstants.SAML_RESPONDER, commonErrorPage, minorVersion, hostedConfig, hostedEntityId, userID); return; } } else { // ssoToken is null if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "Invalid session in request processing. " + "Nothing to logout"); } //Verify request,getUserDNcall destroyPrincipalSession userID = FSLogoutUtil.getUserFromRequest( reqLogout, realm, hostedEntityId, hostedRole, hostedConfig, metaAlias); if (userID != null) { FSLogoutUtil.destroyPrincipalSession( userID, metaAlias, reqLogout.getSessionIndex(), request, response); // Here we need to send back to source // provider's return URL FSLogoutUtil.returnToSource( response, remoteDesc, IFSConstants.SAML_RESPONDER, commonErrorPage, minorVersion, hostedConfig, hostedEntityId, userID); return; } } } else { FSUtils.debug.error("Remote provider not in trusted list"); } } else { FSUtils.debug.error( "FSProcessLogoutServlet::doRequestProcesing " + "Signature on Logout request is invalid" + "Cannot proceed federation Logout"); String[] data = { userID }; LogUtil.error(Level.INFO,LogUtil.INVALID_SIGNATURE, data, ssoToken); errorStatus = IFSConstants.SAML_REQUESTER; } FSLogoutUtil.returnToSource( response, remoteDesc, errorStatus, commonErrorPage, minorVersion, hostedConfig, hostedEntityId,userID); return; } /** * Initiates logout request processing. Called when a logout is to be * initiated or when returned from a remote provider. * @param request HTTPServletRequest object received via a * HTTP Redirect * @param response HTTPServletResponse object to be sent back * to user agent * @param hostedDescriptor the provider for whom request is received * @param hostedConfig hosted provider's extended meta config * @param realm the realm in which the provider resides * @param hostedEntityId hosted provider's entity id * @param metaAlias hosted provider's meta alias * @param ssoToken session token of the user * @param logoutDoneURL where to go when logout is done * @param sourceCheck source check string. Possible value: * local : single logout initiated from local host * remote : single logout initiated from remmote host * logoutGet : Http Get action. */ private void doLogoutInitiation( HttpServletRequest request, HttpServletResponse response, ProviderDescriptorType hostedDescriptor, BaseConfigType hostedConfig, String realm, String hostedEntityId, String hostedRole, String metaAlias, Object ssoToken, String logoutDoneURL, String sourceCheck) { FSUtils.debug.message("FSProcessLogoutServlet::doLogoutInitiation"); FSServiceManager instSManager = FSServiceManager.getInstance(); String relayState = request.getParameter(IFSConstants.LOGOUT_RELAY_STATE); if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("FSProcessLogoutServlet.doLogoutInit: relay=" + relayState); } if (instSManager != null) { FSUtils.debug.message("FSServiceManager Instance not null"); FSPreLogoutHandler handlerObj = instSManager.getPreLogoutHandler(); if (handlerObj != null) { handlerObj.setHostedDescriptor(hostedDescriptor); handlerObj.setHostedDescriptorConfig(hostedConfig); handlerObj.setRealm(realm); handlerObj.setHostedEntityId(hostedEntityId); handlerObj.setHostedProviderRole(hostedRole); handlerObj.setMetaAlias(metaAlias); handlerObj.setRelayState(relayState); handlerObj.handleSingleLogout( request, response, ssoToken, sourceCheck); return; } else { FSUtils.debug.error( "FSPreLogoutHandler is null.Cannot continue logout"); String[] data = { logoutDoneURL }; LogUtil.error(Level.INFO, LogUtil.LOGOUT_FAILED_INVALID_HANDLER,data,ssoToken); } } else { FSUtils.debug.message( "FSServiceManager Instance null. Cannot continue logout"); } FSServiceUtils.returnLocallyAfterOperation( response, logoutDoneURL, false, IFSConstants.LOGOUT_SUCCESS, IFSConstants.LOGOUT_FAILURE); return; } /** * Verifies logout request signature received from the remote end. * @param request HttpServletRequest object containing the * signed Logout request * @param remoteDescriptor the remote Provider descriptor. Used to get cert * @param remoteEntity Id the remote provider's entity id * @return true if the signature is valid; false * otherwise. * @exception SAMLException, FSException if an error occurred during the * process */ private boolean verifyLogoutSignature( HttpServletRequest request, ProviderDescriptorType remoteDescriptor, String remoteEntityId, boolean isIDP ) throws SAMLException, FSException { FSUtils.debug.message( "Entered FSProcessLogoutServlet::verifyLogoutSignature"); // Verify the signature on the request X509Certificate cert = KeyUtil.getVerificationCert( remoteDescriptor, remoteEntityId, isIDP); if (cert == null) { if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message( "FSProcessLogoutServlet.verifyLogoutSignature: " + "couldn't obtain this site's cert."); } throw new SAMLResponderException( FSUtils.bundle.getString(IFSConstants.NO_CERT)); } boolean isValidSign = FSSignatureUtil.verifyRequestSignature(request, cert); if (!isValidSign) { FSUtils.debug.error("Logout request is not properly signed"); return false; } else { FSUtils.debug.message("Logout request is properly signed"); return true; } } private void callPreSingleLogoutProcess( HttpServletRequest request, HttpServletResponse response, String hostedRole, BaseConfigType hostedConfig, String hostedEntityId, String userID, FSLogoutNotification reqLogout) { // Call SP Adapter preSingleLogout for remote IDP initated HTTP request if (hostedRole != null && hostedRole.equalsIgnoreCase(IFSConstants.SP)) { FederationSPAdapter spAdapter = FSServiceUtils.getSPAdapter(hostedEntityId, hostedConfig); if (spAdapter != null) { if (FSUtils.debug.messageEnabled()) { FSUtils.debug.message("FSProcessLogoutServlet, " + "call preSingleLogoutProcess"); } try { spAdapter.preSingleLogoutProcess( hostedEntityId, request, response, userID, reqLogout, null, IFSConstants.LOGOUT_IDP_REDIRECT_PROFILE); } catch (Exception e) { // ignore adapter exception FSUtils.debug.error("preSingleLogoutProcess.IDP/HTTP", e); } } } } } // FSProcessLogoutServlet