package com.sun.identity.saml2.profile;

import com.airlock.iam.opensso.jaxb.entityconfig.BaseConfigType;
import com.airlock.iam.opensso.jaxb.metadata.EndpointType;
import com.airlock.iam.opensso.jaxb.metadata.IDPSSODescriptorType;
import com.airlock.iam.opensso.jaxb.metadata.SPSSODescriptorType;
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.Issuer;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2Repository;
import com.sun.identity.saml2.common.SAML2SDKUtils;
import com.sun.identity.saml2.common.SAML2Utils;
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.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.saml2.protocol.StatusCode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;

public class IDPSingleLogout {
   private static final int LOGOUT_SUCCEEDED_STATUS = 0;
   private static SAML2MetaManager sm;
   private static SessionProvider sessionProvider;
   private static final Status SUCCESS_STATUS = SAML2Utils.generateStatus(
      "urn:oasis:names:tc:SAML:2.0:status:Success", SAML2Utils.BUNDLE.getString("requestSuccess")
   );
   private static final Status PARTIAL_LOGOUT_STATUS = SAML2Utils.generateStatus(
      "urn:oasis:names:tc:SAML:2.0:status:Responder", SAML2Utils.BUNDLE.getString("partialLogout")
   );

   public static void initiateLogoutRequest(HttpServletRequest request, HttpServletResponse response, String binding, Map<String, String> paramsMap) throws SAML2Exception {
      if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message("in initiateLogoutRequest");
         SAML2SDKUtils.DEBUG.message("binding : " + binding);
         SAML2SDKUtils.DEBUG.message("paramsMap : " + paramsMap);
      }

      String metaAlias = paramsMap.get("IDPMetaAlias");

      try {
         Object session = sessionProvider.getSession(request);
         if (session == null) {
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("nullSSOToken"));
         }

         if (metaAlias == null) {
            String[] values = sessionProvider.getProperty(session, "IDPMetaAlias");
            if (values != null && values.length != 0) {
               metaAlias = values[0];
            }
         }

         if (metaAlias == null) {
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("nullIDPMetaAlias"));
         }

         paramsMap.put("metaAlias", metaAlias);
         String realm = SAML2Utils.getRealm(SAML2MetaUtils.getRealmByMetaAlias(metaAlias));
         String idpEntityID = sm.getEntityByMetaAlias(metaAlias);
         if (idpEntityID == null) {
            SAML2SDKUtils.DEBUG.error("Identity Provider ID is missing");
            String[] data = new String[]{idpEntityID};
            LogUtil.error(Level.INFO, "INVALID_IDP", data, null);
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("nullIDPEntityID"));
         }

         String idpSessionIndex = IDPSSOUtil.getSessionIndex(session);
         if (idpSessionIndex == null) {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("No SP session participant(s)");
            }

            SessionProvider provider = SessionManager.getProvider();
            provider.invalidateSession(session, request, response);
         } else {
            IDPSession idpSession = IDPCache.idpSessionsByIndices().get(idpSessionIndex);
            if (idpSession == null) {
               if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("IDPSLO.initiateLogoutRequest: IDP Session with session index " + idpSessionIndex + " already removed.");
               }

               try {
                  if (SAML2Utils.isSAML2FailOverEnabled()) {
                     SAML2Repository.getInstance().delete(idpSessionIndex);
                  }
               } catch (SAML2Exception e) {
                  SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
               }

               IDPCache.authnContextCache().remove(idpSessionIndex);
               SessionProvider provider = SessionManager.getProvider();
               provider.invalidateSession(session, request, response);
            } else {
               if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("idpSessionIndex=" + idpSessionIndex);
               }

               List<NameIDandSPpair> list = idpSession.getNameIDandSPpairs();
               if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("IDPSingleLogout.initiateLogoutReq: NameIDandSPpairs=" + list + ", size=" + list.size());
               }

               if (list.isEmpty()) {
                  if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                     SAML2SDKUtils.DEBUG.message("No SP session participant(s)");
                  }

                  IDPCache.idpSessionsByIndices().remove(idpSessionIndex);

                  try {
                     if (SAML2Utils.isSAML2FailOverEnabled()) {
                        SAML2Repository.getInstance().delete(idpSessionIndex);
                     }
                  } catch (SAML2Exception e) {
                     SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
                  }

                  IDPCache.authnContextCache().remove(idpSessionIndex);
                  SessionProvider provider = SessionManager.getProvider();
                  provider.invalidateSession(session, request, response);
               } else {
                  String relayState = paramsMap.get("RelayState");
                  int soapFailCount = 0;
                  int n = list.size();
                  boolean sessionRemoved = false;
                  int i = 0;

                  while (true) {
                     label189: {
                        if (i < n) {
                           NameIDandSPpair pair = switch (binding) {
                              case "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" -> (NameIDandSPpair)list.removeFirst();
                              case "urn:oasis:names:tc:SAML:2.0:bindings:SOAP" -> (NameIDandSPpair)list.get(i);
                              default -> {
                                 SAML2SDKUtils.DEBUG.error("IDPSingleLogout. unsuported binding" + binding);
                                 throw new SAML2Exception(SAML2Utils.BUNDLE.getString("unsupportedSloBinding"));
                              }
                           };
                           String spEntityID = pair.getSPEntityID();
                           if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                              SAML2SDKUtils.DEBUG.message("IDPSingleLogout.initLogoutReq: processing spEntityID " + spEntityID);
                           }

                           SPSSODescriptorType spsso = sm.getSPSSODescriptor(realm, spEntityID);
                           if (spsso == null) {
                              String[] data = new String[]{spEntityID};
                              LogUtil.error(Level.INFO, "SP_METADATA_ERROR", data, null);
                              throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
                           }

                           List<String> extensionsList = LogoutUtil.getExtensionsList(paramsMap);
                           List<EndpointType> slosList = spsso.getSingleLogoutService();
                           if (slosList == null) {
                              String[] data = new String[]{idpEntityID};
                              LogUtil.error(Level.INFO, "SLO_NOT_FOUND", data, null);
                              throw new SAML2Exception(SAML2Utils.BUNDLE.getString("sloServiceListNotfound"));
                           }

                           BaseConfigType spConfig = null;
                           if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")) {
                              spConfig = sm.getSPSSOConfig(realm, spEntityID);
                              if (i == 0) {
                                 Object session1 = idpSession.getSession();
                                 SessionProvider provider = SessionManager.getProvider();
                                 provider.invalidateSession(session1, request, response);
                                 IDPCache.idpSessionsByIndices().remove(idpSessionIndex);
                                 IDPCache.authnContextCache().remove(idpSessionIndex);
                                 sessionRemoved = true;
                              }
                           }

                           StringBuffer requestID;
                           try {
                              requestID = LogoutUtil.doLogout(
                                 metaAlias,
                                 spEntityID,
                                 slosList,
                                 extensionsList,
                                 binding,
                                 relayState,
                                 idpSessionIndex,
                                 pair.getNameID(),
                                 request,
                                 response,
                                 paramsMap,
                                 spConfig
                              );
                           } catch (SAML2Exception ex) {
                              if (!binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")) {
                                 throw ex;
                              }

                              SAML2SDKUtils.DEBUG.error("IDPSingleLogout.initiateLogoutRequest:", ex);
                              soapFailCount++;
                              break label189;
                           }

                           String requestIDStr = requestID.toString();
                           if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                              SAML2SDKUtils.DEBUG.message("\nIDPSLO.requestIDStr = " + requestIDStr + "\nbinding = " + binding);
                           }

                           if (requestIDStr.isEmpty()
                              || !binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
                                 && !binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) {
                              break label189;
                           }

                           idpSession.setPendingLogoutRequestID(requestIDStr);
                        }

                        if (!sessionRemoved) {
                           IDPCache.idpSessionsByIndices().put(idpSessionIndex, idpSession);
                        }

                        if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")) {
                           if (soapFailCount == n) {
                              throw new SAML2Exception(SAML2Utils.BUNDLE.getString("sloFailed"));
                           }

                           if (soapFailCount > 0) {
                              throw new SAML2Exception(SAML2Utils.BUNDLE.getString("partialLogout"));
                           }
                        }

                        return;
                     }

                     i++;
                  }
               }
            }
         }
      } catch (SAML2MetaException sme) {
         SAML2SDKUtils.DEBUG.error("Error retreiving metadata", sme);
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
      } catch (SessionException ssoe) {
         SAML2SDKUtils.DEBUG.error("SessionException: ", ssoe);
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
      }
   }

   public static void processLogoutRequest(HttpServletRequest request, HttpServletResponse response, String samlRequest, String relayState) throws SAML2Exception, SessionException {
      String method = "processLogoutRequest : ";
      if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message(method + "IDPSingleLogout:processLogoutRequest");
         SAML2SDKUtils.DEBUG.message(method + "samlRequest : " + samlRequest);
         SAML2SDKUtils.DEBUG.message(method + "relayState : " + relayState);
      }

      String rmethod = request.getMethod();
      String binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
      LogoutRequest logoutReq = null;
      if (rmethod.equals("POST")) {
         binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
         logoutReq = LogoutUtil.getLogoutRequestFromPost(samlRequest);
      } 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 (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("IDPSingleLogout:processLogoutRequest: logoutReq is null");
         }
      } else {
         String metaAlias = SAML2MetaUtils.getMetaAliasByUri(request.getRequestURI());
         String realm = SAML2Utils.getRealm(SAML2MetaUtils.getRealmByMetaAlias(metaAlias));
         String idpEntityID = sm.getEntityByMetaAlias(metaAlias);
         String spEntityID = logoutReq.getIssuer().getValue();
         boolean needToVerify = SAML2Utils.getWantLogoutRequestSigned(realm, idpEntityID, "IDPRole");
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message(method + "metaAlias : " + metaAlias);
            SAML2SDKUtils.DEBUG.message(method + "realm : " + realm);
            SAML2SDKUtils.DEBUG.message(method + "idpEntityID : " + idpEntityID);
            SAML2SDKUtils.DEBUG.message(method + "spEntityID : " + spEntityID);
         }

         if (needToVerify) {
            boolean valid = false;
            if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")) {
               String queryString = request.getQueryString();
               valid = SAML2Utils.verifyQueryString(queryString, realm, "IDPRole", spEntityID);
            } else {
               valid = LogoutUtil.verifySLORequest(logoutReq, realm, spEntityID, idpEntityID, "IDPRole");
            }

            if (!valid) {
               SAML2SDKUtils.DEBUG.error("Invalid signature in SLO Request.");
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidSignInRequest"));
            }

            IDPSSODescriptorType idpsso = sm.getIDPSSODescriptor(realm, idpEntityID);
            String loc = null;
            if (idpsso != null) {
               List<EndpointType> sloList = idpsso.getSingleLogoutService();
               if (sloList != null && !sloList.isEmpty()) {
                  loc = LogoutUtil.getSLOResponseServiceLocation(sloList, binding);
                  if (loc == null || loc.isEmpty()) {
                     loc = LogoutUtil.getSLOServiceLocation(sloList, binding);
                  }
               }
            }

            if (!SAML2Utils.verifyDestination(logoutReq.getDestination(), loc)) {
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidDestination"));
            }
         }

         LogoutResponse logoutRes = processLogoutRequest(logoutReq, request, response, binding, relayState, idpEntityID, realm, true);
         if (logoutRes != null) {
            String location = getSingleLogoutLocation(spEntityID, realm, binding);
            logoutRes.setDestination(location);
            updateLogoutResponse(logoutRes, 0);
            LogoutUtil.sendSLOResponse(request, response, logoutRes, location, relayState, realm, idpEntityID, "IDPRole", spEntityID, binding);
         }
      }
   }

   private static String getSingleLogoutLocation(String spEntityID, String realm, String binding) throws SAML2Exception {
      SPSSODescriptorType spsso = sm.getSPSSODescriptor(realm, spEntityID);
      if (spsso == null) {
         String[] data = new String[]{spEntityID};
         LogUtil.error(Level.INFO, "SP_METADATA_ERROR", data, null);
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
      }

      List<EndpointType> slosList = spsso.getSingleLogoutService();
      String location = LogoutUtil.getSLOResponseServiceLocation(slosList, binding);
      if (location == null || location.isEmpty()) {
         location = LogoutUtil.getSLOServiceLocation(slosList, binding);
         if (location == null || location.isEmpty()) {
            SAML2SDKUtils.DEBUG.error("Unable to find the IDP's single logout response service with the HTTP-Redirect binding");
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("sloResponseServiceLocationNotfound"));
         }

         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("SP's single logout response service location = " + location);
         }
      } else if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message("IDP's single logout response service location = " + location);
      }

      if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message("IDPSingleLogout.getSLOLocation: loc=" + location);
      }

      return location;
   }

   public static boolean processLogoutResponse(HttpServletRequest request, HttpServletResponse response, String samlResponse, String relayState) throws SAML2Exception, SessionException {
      String method = "processLogoutResponse : ";
      if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message(method + "samlResponse : " + samlResponse);
         SAML2SDKUtils.DEBUG.message(method + "relayState : " + relayState);
      }

      String rmethod = request.getMethod();
      LogoutResponse logoutRes = null;
      String binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect";
      if (rmethod.equals("POST")) {
         binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST";
         logoutRes = LogoutUtil.getLogoutResponseFromPost(samlResponse);
      } 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 (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("IDPSingleLogout:processLogoutResponse: logoutRes is null");
         }

         return false;
      } else {
         String metaAlias = SAML2MetaUtils.getMetaAliasByUri(request.getRequestURI());
         String realm = SAML2Utils.getRealm(SAML2MetaUtils.getRealmByMetaAlias(metaAlias));
         String idpEntityID = sm.getEntityByMetaAlias(metaAlias);
         String spEntityID = logoutRes.getIssuer().getValue();
         Issuer resIssuer = logoutRes.getIssuer();
         String requestId = logoutRes.getInResponseTo();
         SAML2Utils.verifyResponseIssuer(realm, idpEntityID, resIssuer, requestId);
         boolean needToVerify = SAML2Utils.getWantLogoutResponseSigned(realm, idpEntityID, "IDPRole");
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message(method + "metaAlias : " + metaAlias);
            SAML2SDKUtils.DEBUG.message(method + "realm : " + realm);
            SAML2SDKUtils.DEBUG.message(method + "idpEntityID : " + idpEntityID);
            SAML2SDKUtils.DEBUG.message(method + "spEntityID : " + spEntityID);
         }

         if (needToVerify) {
            boolean valid = false;
            if (rmethod.equals("POST")) {
               valid = LogoutUtil.verifySLOResponse(logoutRes, realm, spEntityID, idpEntityID, "IDPRole");
            } else {
               String queryString = request.getQueryString();
               valid = SAML2Utils.verifyQueryString(queryString, realm, "IDPRole", spEntityID);
            }

            if (!valid) {
               SAML2SDKUtils.DEBUG.error("Invalid signature in SLO Response.");
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidSignInResponse"));
            }

            IDPSSODescriptorType idpsso = sm.getIDPSSODescriptor(realm, idpEntityID);
            String loc = null;
            if (idpsso != null) {
               List<EndpointType> sloList = idpsso.getSingleLogoutService();
               if (sloList != null && !sloList.isEmpty()) {
                  loc = LogoutUtil.getSLOResponseServiceLocation(sloList, binding);
                  if (loc == null || loc.isEmpty()) {
                     loc = LogoutUtil.getSLOServiceLocation(sloList, binding);
                  }
               }
            }

            if (!SAML2Utils.verifyDestination(logoutRes.getDestination(), loc)) {
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidDestination"));
            }
         }

         return processLogoutResponse(request, response, logoutRes, relayState, metaAlias, idpEntityID, realm, binding);
      }
   }

   private static boolean processLogoutResponse(
      HttpServletRequest request,
      HttpServletResponse response,
      LogoutResponse logoutRes,
      String relayState,
      String metaAlias,
      String idpEntityID,
      String realm,
      String binding
   ) throws SAML2Exception, SessionException {
      Object session = sessionProvider.getSession(request);
      String idpSessionIndex = IDPSSOUtil.getSessionIndex(session);
      if (idpSessionIndex == null) {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("No SP session participant(s)");
         }

         SessionProvider provider = SessionManager.getProvider();
         provider.invalidateSession(session, request, response);
         return false;
      } else {
         IDPSession idpSession = IDPCache.idpSessionsByIndices().get(idpSessionIndex);
         if (idpSession == null) {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("IDPSLO.processLogoutResponse : IDP Session with session index " + idpSessionIndex + " already removed.");
            }

            try {
               if (SAML2Utils.isSAML2FailOverEnabled()) {
                  SAML2Repository.getInstance().delete(idpSessionIndex);
               }
            } catch (SAML2Exception e) {
               SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
            }

            IDPCache.authnContextCache().remove(idpSessionIndex);
            SessionProvider provider = SessionManager.getProvider();
            provider.invalidateSession(session, request, response);
            return false;
         } else {
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("idpSessionIndex=" + idpSessionIndex);
            }

            List<NameIDandSPpair> list = idpSession.getNameIDandSPpairs();
            SAML2SDKUtils.DEBUG.message("idpSession.getNameIDandSPpairs()=" + list);
            if (list.isEmpty()) {
               String requestID = idpSession.getPendingLogoutRequestID();
               String inResponseTo = logoutRes.getInResponseTo();
               if (inResponseTo != null && inResponseTo.equals(requestID) && SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("LogoutRespone's inResponseTo matches the previous LogoutRequest's ID.");
               }

               String originatingRequestID = idpSession.getOriginatingLogoutRequestID();
               if (originatingRequestID == null) {
                  IDPCache.idpSessionsByIndices().remove(idpSessionIndex);

                  try {
                     if (SAML2Utils.isSAML2FailOverEnabled()) {
                        SAML2Repository.getInstance().delete(idpSessionIndex);
                     }
                  } catch (SAML2Exception e) {
                     SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
                  }

                  IDPCache.authnContextCache().remove(idpSessionIndex);
                  sessionProvider.invalidateSession(idpSession.getSession(), request, response);
                  SAML2SDKUtils.DEBUG.message("IDP initiated SLO Success");
                  return false;
               } else {
                  String originatingLogoutSPEntityID = idpSession.getOriginatingLogoutSPEntityID();
                  SPSSODescriptorType spsso = sm.getSPSSODescriptor(realm, originatingLogoutSPEntityID);
                  if (spsso == null) {
                     String[] data = new String[]{originatingLogoutSPEntityID};
                     LogUtil.error(Level.INFO, "SP_METADATA_ERROR", data, null);
                     throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
                  }

                  List<EndpointType> slosList = spsso.getSingleLogoutService();
                  String location = LogoutUtil.getSLOResponseServiceLocation(slosList, binding);
                  if (location == null || location.isEmpty()) {
                     location = LogoutUtil.getSLOServiceLocation(slosList, binding);
                     if (location == null || location.isEmpty()) {
                        SAML2SDKUtils.DEBUG.error("Unable to find the IDP's single logout response service with the HTTP-Redirect binding");
                        throw new SAML2Exception(SAML2Utils.BUNDLE.getString("sloResponseServiceLocationNotfound"));
                     }

                     if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                        SAML2SDKUtils.DEBUG.message("SP's single logout response service location = " + location);
                     }
                  } else if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                     SAML2SDKUtils.DEBUG.message("IDP's single logout response service location = " + location);
                  }

                  Status status = destroyTokenAndGenerateStatus(idpSessionIndex, idpSession.getSession(), request, response, true);
                  logoutRes = LogoutUtil.generateResponse(status, originatingRequestID, SAML2Utils.createIssuer(idpEntityID));
                  if (location != null && logoutRes != null) {
                     logoutRes.setDestination(location);
                     IDPCache.idpSessionsByIndices().remove(idpSessionIndex);

                     try {
                        if (SAML2Utils.isSAML2FailOverEnabled()) {
                           SAML2Repository.getInstance().delete(idpSessionIndex);
                        }
                     } catch (SAML2Exception e) {
                        SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
                     }

                     IDPCache.authnContextCache().remove(idpSessionIndex);
                     updateLogoutResponse(logoutRes, 0);
                     SAML2SDKUtils.DEBUG.message("SLO succeeded, sending logout response to the initiating SP " + originatingLogoutSPEntityID);
                     LogoutUtil.sendSLOResponse(
                        request, response, logoutRes, location, relayState, realm, idpEntityID, "IDPRole", originatingLogoutSPEntityID, binding
                     );
                     return true;
                  } else {
                     IDPCache.idpSessionsByIndices().remove(idpSessionIndex);

                     try {
                        if (SAML2Utils.isSAML2FailOverEnabled()) {
                           SAML2Repository.getInstance().delete(idpSessionIndex);
                        }
                     } catch (SAML2Exception e) {
                        SAML2SDKUtils.DEBUG.error("Error while deleting idpSessionIndex from Persistent DB.", e);
                     }

                     IDPCache.authnContextCache().remove(idpSessionIndex);
                     return false;
                  }
               }
            } else {
               NameIDandSPpair pair = list.removeFirst();
               String spEntityID = pair.getSPEntityID();
               SPSSODescriptorType spsso = null;
               spsso = sm.getSPSSODescriptor(realm, spEntityID);
               if (spsso == null) {
                  String[] data = new String[]{spEntityID};
                  LogUtil.error(Level.INFO, "SP_METADATA_ERROR", data, null);
                  throw new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
               }

               List<EndpointType> slosList = spsso.getSingleLogoutService();
               List<String> extensionsList = LogoutUtil.getExtensionsList(request.getParameterMap());
               HashMap<String, String> paramsMap = new HashMap<>();
               request.getParameterMap().forEach((key, value) -> paramsMap.put(key, value != null && value.length > 0 ? value[0] : null));
               paramsMap.put("role", "IDPRole");
               StringBuffer requestID = LogoutUtil.doLogout(
                  metaAlias, spEntityID, slosList, extensionsList, binding, relayState, idpSessionIndex, pair.getNameID(), request, response, paramsMap, null
               );
               String requestIDStr = requestID.toString();
               if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                  SAML2SDKUtils.DEBUG.message("IDPSingleLogout.processLogoutRequest: requestIDStr = " + requestIDStr + "\nbinding = " + binding);
               }

               if (requestIDStr != null && !requestIDStr.isEmpty()) {
                  idpSession.setPendingLogoutRequestID(requestIDStr);
                  IDPCache.idpSessionsByIndices().put(idpSessionIndex, idpSession);
               }

               SAML2SDKUtils.DEBUG.message("SLO in progress, sending logout request to the next SP " + spEntityID);
               return true;
            }
         }
      }
   }

   private static LogoutResponse processLogoutRequest(
      LogoutRequest logoutReq,
      HttpServletRequest request,
      HttpServletResponse response,
      String binding,
      String relayState,
      String idpEntityID,
      String realm,
      boolean isVerified
   ) throws SAML2Exception {
      Status status = null;
      Object session = null;
      String tmpStr = request.getParameter("isLBReq");
      boolean isLBReq = tmpStr == null || !tmpStr.equals("false");

      try {
         String requestId = logoutReq.getID();
         SAML2Utils.verifyRequestIssuer(realm, idpEntityID, logoutReq.getIssuer(), requestId);
         List<String> siList = logoutReq.getSessionIndex();
         if (siList == null) {
            SAML2SDKUtils.DEBUG.error("IDPSingleLogout.processLogoutRequest: session index are null in logout request");
            status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Requester", "");
         } else {
            String sessionIndex = siList.isEmpty() ? null : siList.getFirst();
            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.processLogoutRequest: idpEntityID=" + idpEntityID + ", sessionIndex=" + sessionIndex);
            }

            if (sessionIndex == null) {
               SAML2SDKUtils.DEBUG.error("IDPLogoutUtil.processLogoutRequest: No session index in logout request");
               status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Requester", "");
            } else {
               String remoteServiceURL = null;
               if (isLBReq) {
                  String serverId = sessionIndex.substring(sessionIndex.length() - 2);
                  if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                     SAML2SDKUtils.DEBUG.message("IDPSingleLogout.processLogoutRequest: sessionIndex=" + sessionIndex + ", id=" + serverId);
                  }

                  remoteServiceURL = SAML2Utils.getRemoteServiceURL(serverId);
               }

               try {
                  Object ses = sessionProvider.getSession(request);
                  if (IDPSSOUtil.getSessionIndex(ses) == null) {
                     LogUtil.access(
                        Level.INFO, "SESSION_INDEX", new String[]{"Session does not contain the sessionIndex anymore, restoring it from the LogoutRequest."}
                     );
                     sessionProvider.setProperty(ses, "SAML2IDPSessionIndex", new String[]{sessionIndex});
                  }
               } catch (SessionException e) {
                  SAML2SDKUtils.DEBUG.message("Session does not exist anymore: " + e.getMessage());
               }

               IDPSession idpSession = IDPCache.idpSessionsByIndices().get(sessionIndex);
               if (idpSession == null && SAML2Utils.isSAML2FailOverEnabled()) {
                  IDPSessionCopy idpSessionCopy = (IDPSessionCopy)SAML2Repository.getInstance().retrieve(sessionIndex);
                  if (idpSessionCopy != null) {
                     idpSession = new IDPSession(idpSessionCopy);
                  } else {
                     SAML2SDKUtils.DEBUG.error("IDPSessionCopy is NULL!!!");
                  }
               }

               if (idpSession == null) {
                  if (remoteServiceURL != null) {
                     boolean peerError = false;
                     String remoteLogoutURL = remoteServiceURL + SAML2Utils.removeDeployUri(request.getRequestURI());
                     String queryString = request.getQueryString();
                     if (queryString == null) {
                        remoteLogoutURL = remoteLogoutURL + "?isLBReq=false";
                     } else {
                        remoteLogoutURL = remoteLogoutURL + "?" + queryString + "&isLBReq=false";
                     }

                     LogoutResponse logoutRes = LogoutUtil.forwardToRemoteServer(logoutReq, remoteLogoutURL);
                     if (logoutRes == null || isNameNotFound(logoutRes)) {
                        peerError = true;
                     } else if (isSuccess(logoutRes) && !siList.isEmpty()) {
                        siList = LogoutUtil.getSessionIndex(logoutRes);
                        if (siList == null || siList.isEmpty()) {
                           peerError = false;
                           return LogoutUtil.generateResponse(status, logoutReq.getID(), SAML2Utils.createIssuer(idpEntityID));
                        }
                     }

                     if (!peerError && (siList == null || siList.isEmpty())) {
                        status = SUCCESS_STATUS;
                     } else {
                        status = PARTIAL_LOGOUT_STATUS;
                     }
                  } else {
                     SAML2SDKUtils.DEBUG.error("IDPLogoutUtil.processLogoutRequest: IDP no longer has this session index " + sessionIndex);
                     sessionProvider.invalidateSession(sessionProvider.getSession(request), request, response);
                     status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Responder", SAML2Utils.BUNDLE.getString("invalidSessionIndex"));
                  }
               } else {
                  if (!isVerified && !LogoutUtil.verifySLORequest(logoutReq, realm, logoutReq.getIssuer().getValue(), idpEntityID, "IDPRole")) {
                     throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidSignInRequest"));
                  }

                  session = idpSession.getSession();
                  BaseConfigType idpConfig = SAML2Utils.getSAML2MetaManager().getIDPSSOConfig(realm, idpEntityID);
                  List<String> appLogoutURL = SAML2MetaUtils.getAttributes(idpConfig).get("appLogoutUrl");
                  if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                     SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.processLogoutRequest: external app logout URL= " + appLogoutURL);
                  }

                  if (appLogoutURL != null && !appLogoutURL.isEmpty()) {
                     SAML2Utils.postToAppLogout(request, appLogoutURL.getFirst(), session);
                  }

                  List<NameIDandSPpair> list = idpSession.getNameIDandSPpairs();
                  int n = list.size();
                  if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                     SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.processLogoutRequest: NameIDandSPpair for " + sessionIndex + " is " + list + ", size=" + n);
                  }

                  String spIssuer = logoutReq.getIssuer().getValue();

                  for (int i = 0; i < n; i++) {
                     NameIDandSPpair pair = list.get(i);
                     if (pair.getSPEntityID().equals(spIssuer)) {
                        list.remove(i);
                        IDPCache.idpSessionsByIndices().put(sessionIndex, idpSession);
                        break;
                     }
                  }

                  n = list.size();
                  if (list.isEmpty()) {
                     status = destroyTokenAndGenerateStatus(sessionIndex, idpSession.getSession(), request, response, true);
                     IDPCache.idpSessionsByIndices().remove(sessionIndex);
                     if (SAML2Utils.isSAML2FailOverEnabled()) {
                        SAML2Repository.getInstance().delete(sessionIndex);
                     }

                     IDPCache.authnContextCache().remove(sessionIndex);
                  } else {
                     if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
                        || binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) {
                        idpSession.setOriginatingLogoutRequestID(logoutReq.getID());
                        idpSession.setOriginatingLogoutSPEntityID(logoutReq.getIssuer().getValue());
                        IDPCache.idpSessionsByIndices().put(sessionIndex, idpSession);
                     }

                     int soapFailCount = 0;

                     for (int i = 0; i < n; i++) {
                        NameIDandSPpair pair;
                        if (!binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
                           && !binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) {
                           pair = list.get(i);
                        } else {
                           pair = list.removeFirst();
                           IDPCache.idpSessionsByIndices().put(sessionIndex, idpSession);
                        }

                        String spEntityID = pair.getSPEntityID();
                        if (SAML2SDKUtils.DEBUG.messageEnabled()) {
                           SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.processLogoutRequest: SP for " + sessionIndex + " is " + spEntityID);
                        }

                        SPSSODescriptorType sp = SAML2Utils.getSAML2MetaManager().getSPSSODescriptor(realm, spEntityID);
                        List<EndpointType> slosList = sp.getSingleLogoutService();
                        BaseConfigType spConfig = null;
                        if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")) {
                           spConfig = SAML2Utils.getSAML2MetaManager().getSPSSOConfig(realm, spEntityID);
                        }

                        String uri = request.getRequestURI();
                        String metaAlias = SAML2MetaUtils.getMetaAliasByUri(uri);
                        HashMap<String, String> paramsMap = new HashMap<>();
                        paramsMap.put("role", "IDPRole");

                        StringBuffer requestID;
                        try {
                           requestID = LogoutUtil.doLogout(
                              metaAlias,
                              spEntityID,
                              slosList,
                              null,
                              binding,
                              relayState,
                              sessionIndex,
                              pair.getNameID(),
                              request,
                              response,
                              paramsMap,
                              spConfig
                           );
                        } catch (SAML2Exception ex) {
                           if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:SOAP")) {
                              SAML2SDKUtils.DEBUG.error("IDPSingleLogout.initiateLogoutRequest:", ex);
                              soapFailCount++;
                              continue;
                           }

                           throw ex;
                        }

                        if (binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
                           || binding.equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) {
                           String requestIDStr = requestID.toString();
                           if (requestIDStr != null && !requestIDStr.isEmpty()) {
                              idpSession.setPendingLogoutRequestID(requestIDStr);
                              IDPCache.idpSessionsByIndices().put(sessionIndex, idpSession);
                           }

                           return null;
                        }
                     }

                     if (soapFailCount == n) {
                        throw new SAML2Exception(SAML2Utils.BUNDLE.getString("sloFailed"));
                     }

                     if (soapFailCount > 0) {
                        throw new SAML2Exception(SAML2Utils.BUNDLE.getString("partialLogout"));
                     }

                     status = destroyTokenAndGenerateStatus(sessionIndex, idpSession.getSession(), request, response, true);
                     IDPCache.idpSessionsByIndices().remove(sessionIndex);
                     if (SAML2Utils.isSAML2FailOverEnabled()) {
                        SAML2Repository.getInstance().delete(sessionIndex);
                     }

                     IDPCache.authnContextCache().remove(sessionIndex);
                  }
               }
            }
         }
      } catch (SessionException ssoe) {
         SAML2SDKUtils.DEBUG.error("IDPLogoutUtil : unable to get meta for ", ssoe);
         status = SAML2Utils.generateStatus(idpEntityID, ssoe.toString());
      } catch (SAML2Exception e) {
         SAML2SDKUtils.DEBUG.error("Error processing logout request", e);
         throw e;
      }

      return LogoutUtil.generateResponse(status, logoutReq.getID(), SAML2Utils.createIssuer(idpEntityID));
   }

   private static void updateLogoutResponse(LogoutResponse logRes, int retStat) throws SAML2Exception {
      if (SAML2SDKUtils.DEBUG.messageEnabled()) {
         SAML2SDKUtils.DEBUG.message("IDPSingleLogout.updateLogoutResponse: response=" + logRes.toXMLString() + "\nstatus = " + retStat);
      }

      if (retStat != 0) {
         StatusCode code = logRes.getStatus().getStatusCode();
         if (code.getValue().equals("urn:oasis:names:tc:SAML:2.0:status:Success")) {
            code.setValue("urn:oasis:names:tc:SAML:2.0:status:Responder");
         }
      }
   }

   private static Status destroyTokenAndGenerateStatus(
      String sessionIndex, Object session, HttpServletRequest request, HttpServletResponse response, boolean cleanUp
   ) throws SAML2Exception {
      Status status = null;
      if (session != null) {
         try {
            if (cleanUp) {
               SessionProvider provider = SessionManager.getProvider();
               provider.invalidateSession(session, request, response);
            }

            if (SAML2SDKUtils.DEBUG.messageEnabled()) {
               SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.destroyTAGR: Local session destroyed.");
            }

            status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Success", "");
         } catch (Exception e) {
            SAML2SDKUtils.DEBUG.error("IDPLogoutUtil.destroyTAGR: ", e);
            status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Responder", "");
         }
      } else {
         if (SAML2SDKUtils.DEBUG.messageEnabled()) {
            SAML2SDKUtils.DEBUG.message("IDPLogoutUtil.destroyTAGR: No such session with index " + sessionIndex + " exists.");
         }

         status = SAML2Utils.generateStatus("urn:oasis:names:tc:SAML:2.0:status:Success", "");
      }

      return status;
   }

   private static boolean isSuccess(LogoutResponse logoutRes) {
      return logoutRes.getStatus().getStatusCode().getValue().equals("urn:oasis:names:tc:SAML:2.0:status:Success");
   }

   private static boolean isNameNotFound(LogoutResponse logoutRes) {
      Status status = logoutRes.getStatus();
      String statusMessage = status.getStatusMessage();
      return status.getStatusCode().getValue().equals("urn:oasis:names:tc:SAML:2.0:status:Responder")
         && statusMessage != null
         && statusMessage.equals(SAML2Utils.BUNDLE.getString("invalid_name_identifier"));
   }

   static {
      try {
         sm = new SAML2MetaManager();
      } catch (SAML2MetaException sme) {
         SAML2SDKUtils.DEBUG.error("Error retrieving metadata", sme);
      }

      try {
         sessionProvider = SessionManager.getProvider();
      } catch (SessionException se) {
         SAML2SDKUtils.DEBUG.error("Error retrieving session provider.", se);
      }
   }
}
