/**
* 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
* 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: IDPSingleLogout.java,v 1.28 2009/11/25 01:20:47 madan_ranganath Exp $
*
* Portions Copyrighted 2010-2015 ForgeRock AS.
*/
/**
* This class reads the required data from HttpServletRequest and
* initiates the <code>LogoutRequest</code> from IDP to SP.
*/
public class IDPSingleLogout {
// Status elements
static {
try {
sm = new SAML2MetaManager();
} catch (SAML2MetaException sme) {
}
try {
} catch (SessionException se) {
}
}
private IDPSingleLogout() {
}
/**
* Parses the request parameters and initiates the Logout
* Request to be sent to the SP.
*
* @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.
* @throws SAML2Exception if error initiating request to SP.
*/
if (debug.messageEnabled()) {
}
boolean logoutall = false;
if ((logoutAllValue != null) &&
logoutall = true;
}
try {
throw new SAML2Exception(
}
}
}
throw new SAML2Exception(
}
if (idpEntityID == null) {
throw new SAML2Exception(
}
// clean up session index
if (idpSessionIndex == null) {
if (debug.messageEnabled()) {
}
return;
}
// If request has been misrouted and we don't have SAML2 Failover
// then send the request to the original server
if (!SAML2FailoverUtils.isSAML2FailoverEnabled() &&
return;
} else {
if (debug.messageEnabled()) {
+ "SAML2 Failover will be attempted. Be sure SFO is "
+ "properly configured or the attempt will fail");
}
}
if (idpSession == null) {
if (debug.messageEnabled()) {
+ "IDP Session with session index "
+ idpSessionIndex + " already removed.");
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
return;
}
if (debug.messageEnabled()) {
}
if (debug.messageEnabled()) {
}
if (n == 0) {
if (debug.messageEnabled()) {
}
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
return;
}
// Validate the RelayState URL.
int soapFailCount = 0;
for (int i = 0; i < n; i++) {
if (debug.messageEnabled()) {
}
// get IDP entity config in case of SOAP, for basic auth info
if (logoutall == true) {
}
SingleLogoutServiceElement logoutEndpoint = LogoutUtil.getMostAppropriateSLOServiceLocation(slosList,
if (logoutEndpoint == null) {
continue;
}
try {
} catch (SAML2Exception ex) {
"IDPSingleLogout.initiateLogoutRequest:" , ex);
continue;
} else {
throw ex;
}
}
if (debug.messageEnabled()) {
}
}
return;
}
}
//This code only runs if the logout process didn't redirect away, so either none of the SPs supported the
//requested binding, or SOAP was used for the logout (or the mixture of this two).
if (logoutall == true) {
} else {
}
}
//handling the case when the auth was initiated with HTTP-Redirect, but only SOAP or no SLO endpoint was
//available, and also the case when the whole logout process was using SOAP binding from the beginning
boolean isMultiProtocol = MultiProtocolUtils.isMultipleProtocolSession(request, SingleLogoutManager.SAML2);
//TODO: would be nice to actually return the correct message in idpSingleLogoutInit.jsp
if (soapFailCount == n) {
if (isMultiProtocol) {
}
} else if (soapFailCount > 0) {
if (isMultiProtocol) {
}
}
// processing multi-federation protocol session
if (isMultiProtocol) {
boolean isSOAPInitiated =
try {
ex);
}
if (debug.messageEnabled()) {
+ "SLOManager return status = " + retStat);
}
switch (retStat) {
throw new SAML2Exception(
throw new SAML2Exception(
default:
break;
}
}
} catch (SAML2MetaException sme) {
throw new SAML2Exception(
} catch (SessionException ssoe) {
throw new SAML2Exception(
}
}
/**
* Gets and processes the Single <code>LogoutRequest</code> from SP.
*
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
* @param out the print writer for writing out presentation
* @param samlRequest <code>LogoutRequest</code> in the
* XML string format.
* @param relayState the target URL on successful
* <code>LogoutRequest</code>.
* @throws SAML2Exception if error processing
* <code>LogoutRequest</code>.
* @throws SessionException if error processing
* <code>LogoutRequest</code>.
*/
public static void processLogoutRequest(
if (debug.messageEnabled()) {
}
}
{
" is not supported for " + idpEntityID);
throw new SAML2Exception(
}
response);
if (decodedStr == null) {
"nullDecodedStrFromSamlRequest"));
}
}
if (debug.messageEnabled()) {
"is null");
}
return;
}
boolean needToVerify =
if (debug.messageEnabled()) {
}
if (needToVerify) {
boolean valid = false;
} else {
}
if (!valid) {
throw new SAML2Exception(
}
}
}
}
loc)) {
throw new SAML2Exception(
}
}
// Get the local session, if it does not exist send a succesful
// Logout Response with a status message of "Already Logout"
try {
} catch (SessionException ssoe) {
return;
}
// If the request has been misrouted and we don't have SAML2 Failover
// then send the request to the original server
return;
} else {
if (debug.messageEnabled()) {
+ "SAML2 Failover will be attempted. Be sure SFO is "
+ "properly configured or the attempt will fail");
}
}
idpEntityID, realm, true);
// this is the case where there is more SP session participant
// and processLogoutRequest() sends LogoutRequest to one of them
// already
// through HTTP_Redirect, nothing to do here
return;
}
// this is the case where there is no more SP session
// participant
// call multi-federation protocol processing
// this is SP initiated HTTP based single logout
boolean isMultiProtocolSession = false;
try {
isMultiProtocolSession = true;
// call Multi-Federation protocol SingleLogoutManager
}
} catch (SessionException e) {
// ignore as session might not be valid
} catch (Exception e) {
}
if (!isMultiProtocolSession ||
} else {
}
}
}
private static SingleLogoutServiceElement getLogoutResponseEndpoint(String realm, String spEntityID,
LogoutUtil.getMostAppropriateSLOServiceLocation(getSPSLOServiceEndpoints(realm, spEntityID), binding);
}
}
return endpoint;
}
}
return location;
}
/**
* Returns single logout location for the service provider.
*/
throws SAML2Exception {
"Unable to find the IDP's single logout "+
"response service with the HTTP-Redirect binding");
throw new SAML2Exception(
"sloResponseServiceLocationNotfound"));
} else {
if (debug.messageEnabled()) {
"SP's single logout response service location = "+
location);
}
}
} else {
if (debug.messageEnabled()) {
"IDP's single logout response service location = "+
location);
}
}
if (debug.messageEnabled()) {
}
return location;
}
} else {
}
}
/**
* Gets and processes the Single <code>LogoutResponse</code> from SP,
* destroys the local session, checks response's issuer
* and inResponseTo.
*
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
* @param samlResponse <code>LogoutResponse</code> in the
* XML string format.
* @param relayState the target URL on successful
* <code>LogoutResponse</code>.
* @return true if jsp has sendRedirect for relayState, false otherwise
* @throws SAML2Exception if error processing
* <code>LogoutResponse</code>.
* @throws SessionException if error processing
* <code>LogoutResponse</code>.
*/
public static boolean processLogoutResponse(
if (debug.messageEnabled()) {
}
}
{
throw new SAML2Exception(
}
response);
if (decodedStr == null) {
"nullDecodedStrFromSamlResponse"));
}
}
if (debug.messageEnabled()) {
"is null");
}
return false;
}
boolean needToVerify =
if (debug.messageEnabled()) {
}
if (needToVerify) {
boolean valid = false;
} else {
}
if (!valid) {
throw new SAML2Exception(
}
}
}
}
loc)) {
throw new SAML2Exception(
}
}
binding);
// IDPProxy
return true;
}
}
return doRelayState;
}
throws SAML2Exception, SessionException {
// use the cache to figure out which session index is in question
// and then use the cache to see if any more SPs to send logout request
// if yes, send one
// if no, do local logout and send response back to original requesting
// SP (this SP name should be remembered in cache)
if (idpSessionIndex == null) {
if (debug.messageEnabled()) {
}
return false;
}
if (idpSession == null) {
if (debug.messageEnabled()) {
+ "IDP Session with session index "
+ idpSessionIndex + " already removed.");
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
return false;
}
if (debug.messageEnabled()) {
}
} else {
// send Next Requests
//When processing a logout response we must ensure that we try to use the original logout request
//binding to make sure asynchronous bindings have precedence over synchronous bindings.
SingleLogoutServiceElement logoutEndpoint = LogoutUtil.getMostAppropriateSLOServiceLocation(slosList,
if (logoutEndpoint == null) {
continue;
}
if (bindingUsed.equals(SAML2Constants.HTTP_REDIRECT) || bindingUsed.equals(SAML2Constants.HTTP_POST)) {
if (debug.messageEnabled()) {
+ "\nbinding = " + bindingUsed);
}
}
return true;
}
}
//seems like there were only SOAP endpoints left for SPs, so now we should just send back the logout
//response.
}
}
/**
* Gets and processes the Single <code>LogoutRequest</code> from SP
* and return <code>LogoutResponse</code>.
*
* @param logoutReq <code>LogoutRequest</code> from SP
* @param request the HttpServletRequest.
* @param response the HttpServletResponse.
* @param binding name of binding will be used for request processing.
* @param relayState the relay state.
* @param idpEntityID name of host entity ID.
* @param realm name of host entity.
* @param isVerified true if the request is verified already.
* @return LogoutResponse the target URL on successful
* <code>LogoutRequest</code>.
* @throws SAML2Exception if error processing
* <code>LogoutRequest</code>.
*/
try {
do {
"session index are null in logout request");
status =
break;
}
// TODO : handle list of session index
}
if (debug.messageEnabled()) {
+ sessionIndex);
}
if (sessionIndex == null) {
// this case won't happen
// according to the spec: SP has to send at least
// one sessionIndex, could be multiple (TODO: need
// to handle that above; but when IDP sends out
// logout request, it could omit sessionIndex list,
// which means all sessions on SP side, so SP side
// needs to care about this case
"No session index in logout request");
status =
break;
}
if(isLBReq) {
// server id is the last two digit of the session index
if (debug.messageEnabled()) {
}
// find out remote serice URL based on server id
}
// Read from SAML2 Token Repository
try {
} catch (SAML2TokenRepositoryException se) {
}
// Copy back to IDPSession
if (idpSessionCopy != null) {
} else {
}
}
if (idpSession == null) {
// If the IDP session does not find locally for a given
// session index and if the IDP is behind a lb with another
// peer then we have to route the request.
if (remoteServiceURL != null) {
boolean peerError = false;
if(queryString == null) {
} else {
queryString + "&isLBReq=false";
}
!isNameNotFound(logoutRes)) {
siList =
peerError = false;
break;
}
}
} else {
peerError = true;
}
if (peerError ||
break;
} else {
break;
}
} else {
"IDP no longer has this session index "+ sessionIndex);
break;
}
} else {
// If the user session exists in this IDP then verify the
// signature.
if (!isVerified &&
throw new SAML2Exception(
}
}
// handle external application logout if configured
if (debug.messageEnabled()) {
"external app logout URL= " + appLogoutURL);
}
}
if (debug.messageEnabled()) {
", size=" + n);
}
// remove sending SP from the list
for (int i=0; i<n; i++) {
break;
}
}
boolean cleanUp = true;
cleanUp = false;
}
if (n == 0) {
// this is the case where there is no other
// session participant
if (cleanUp) {
}
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
try {
} catch (SAML2TokenRepositoryException se) {
}
}
}
break;
}
//We should save the originally used request binding to make sure the response is sent back using the
//correct binding.
// there are other SPs to be logged out
}
int soapFailCount = 0;
for (int i = 0; i < n; i++) {
if (debug.messageEnabled()) {
+ spEntityID);
}
// get IDP entity config in case of SOAP,for basic auth info
if (logoutEndpoint == null) {
continue;
}
try {
} catch (SAML2Exception ex) {
"IDPSingleLogout.initiateLogoutRequest:" , ex);
continue;
} else {
throw ex;
}
}
}
return null;
}
}
if (soapFailCount == n) {
} else if (soapFailCount > 0) {
}
return null;
} else {
// binding is SOAP, generate logout response
// and send to initiating SP
if (cleanUp) {
{
(long)IDPCache.idpSessionsByIndices.
size());
}
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
try {
} catch (SAML2TokenRepositoryException se) {
}
}
}
}
} while (false);
} catch (SessionException ssoe) {
} catch (SAML2Exception e) {
// show throw exception
e.printStackTrace();
}
// process multi-federation protocol
boolean isMultiProtocol = false;
try {
isMultiProtocol = true;
}
} catch (SessionException ex) {
//ignore
}
//here we are providing null for remote entity, because it's an unused variable in the method...
if (!isMultiProtocol) {
return logRes;
} else {
try {
return logRes;
} else {
return null;
}
} catch (SessionException ex) {
"initiated SOAP logout", ex);
"initiated SOAP logout (MP)", ex);
}
}
}
int retStat) throws SAML2Exception {
if (debug.messageEnabled()) {
}
return logRes;
} else {
}
return logRes;
}
}
/**
* Destroys the Single SignOn token and generates
* the <code>Status</code>.
*
* @param sessionIndex IDP's session index.
* @param session the Single Sign On session.
*
* @return <code>Status</code>.
* @throws SAML2Exception if error generating
* <code>Status</code>.
*/
boolean cleanUp) throws SAML2Exception {
try {
if (cleanUp) {
}
if (debug.messageEnabled()) {
+ "Local session destroyed.");
}
} catch (Exception e) {
}
} else {
if (debug.messageEnabled()) {
}
// TODO : should this be success?
}
return status;
}
private static void destroyAllTokenForUser(
if (debug.messageEnabled()) {
"User to logoutAll : " + userToLogout);
}
while (keys.hasMoreElements()) {
if (idpSession != null) {
try {
{
(long)IDPCache.
}
}
} catch (SessionException e) {
continue;
}
}
} else {
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
}
}
}
}
statusMessage != null &&
}
/**
* Removes transient nameid from the cache.
*/
return;
}
nameIDValue)) {
}
}
}
/**
* Checks if a SAML2 request has been misrouted, if so, send the
* request to the original server, gets the response and redirects it
* or posts it back
*
* @param request the Servlet request
* @param response the Servlet response
* @param out the print writer for writing out presentation
* @param session the Single Sign On session.
*
* @return true if the request was misrouted and it was forwarded to
* the original server
* @throws SAML2Exception, SessionException
*/
throws SAML2Exception, SessionException {
// Check that the request has not been missrouted
if (idpSessionIndex == null) {
if (debug.messageEnabled()) {
}
return true;
}
if (debug.messageEnabled()) {
", id=" + serverId);
}
// If misrouted, route it to the proper server
if (debug.warningEnabled()) {
+ " and request is owned by " + serverId);
}
if (queryString != null) {
}
}
if (debug.messageEnabled()) {
}
// if we have a redirect then let the JSP do the redirect
if (debug.messageEnabled()) {
+ "redirect actioned by the JSP");
}
try {
} catch (IOException ex) {
}
return true;
}
// no redirect, perhaps an error page, return the content
if (debug.messageEnabled()) {
}
return true;
}
}
return false;
}
/**
* Generates a new Logout Response with Success Status saying that the user has already logged out.
*
* @param response The Servlet response.
* @param logoutReq The SAML 2.0 Logout Request.
* @param relayState The original relay state that came with the request.
* @param realm The realm where the hosted entity has been defined.
* @param idpEntityID The entity id of the hosted IdP.
* @param spEntityID The entity id of the remote SP.
* @param binding The binding that the IdP should reply with to the SP.
*
* @throws SAML2Exception If there was a problem while constructing/sending the Logout Response.
*/
private static void sendAlreadyLogedOutResp(HttpServletResponse response, HttpServletRequest request,
+ "We are already logged out. Generating success logout");
}
private static boolean sendLastResponse(IDPSession idpSession, LogoutResponse logoutRes, HttpServletRequest request,
HttpServletResponse response, String idpSessionIndex, Object session, String realm, String idpEntityID,
//resetting the binding to the original value so the response is sent back with the correct binding
if (originatingRequestID == null) {
// this is IDP initiated SLO
if (idpSession.getLogoutAll()) {
} else {
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
if (!MultiProtocolUtils.isMultipleProtocolSession(idpSession.getSession(), SingleLogoutManager.SAML2)) {
} else {
// call Multi-Federation protocol SingleLogoutManager
try {
} catch (SAML2Exception ex) {
throw ex;
}
return true;
}
}
}
return false;
}
List<SingleLogoutServiceElement> slosList = getSPSLOServiceEndpoints(realm, originatingLogoutSPEntityID);
debug.error("Unable to find the IDP's single logout response service with the HTTP-Redirect binding");
} else {
if (debug.messageEnabled()) {
}
}
} else {
if (debug.messageEnabled()) {
}
}
Status status = destroyTokenAndGenerateStatus(idpSessionIndex, idpSession.getSession(), request, response,
true);
//here we are providing null for remote entity, because it's an unused variable in the method...
logoutRes = LogoutUtil.generateResponse(status, originatingRequestID, SAML2Utils.createIssuer(idpEntityID),
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
// call multi-federation protocol processing
// this is the SP initiated HTTP binding case
boolean isMultiProtocolSession = false;
try {
isMultiProtocolSession = true;
// call Multi-Federation protocol SingleLogoutManager
}
} catch (SessionException e) {
// ignore as session might not be valid
} catch (Exception e) {
}
return true;
} else {
return false;
}
}
}
try {
if (SAML2FailoverUtils.isSAML2FailoverEnabled()) {
}
} catch (SAML2TokenRepositoryException se) {
}
return false;
}
/**
* Gets the single log out end points for the Service Provider.
*
* @param realm the realm that the service provider is configured within
* @param spEntityID the id for the service provider configuration entity
* @return a list of Single Logout Service elements
* @throws SAML2Exception if there was a problem retrieving the SP SSO Descriptor Element
*/
// get SPSSODescriptor
}
return spsso.getSingleLogoutService();
}
}