package com.sun.identity.saml2.profile;

import com.airlock.iam.openfedlib.saml2.XjcHelper;
import com.airlock.iam.openfedlib.saml2.state.Saml2DataMapProvider;
import com.airlock.iam.opensso.jaxb.entityconfig.BaseConfigType;
import com.airlock.iam.opensso.jaxb.metadata.AffiliationDescriptorType;
import com.airlock.iam.opensso.jaxb.metadata.IDPSSODescriptorType;
import com.airlock.iam.opensso.jaxb.metadata.IndexedEndpointType;
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.saml.common.SAMLUtils;
import com.sun.identity.saml.xmlsig.KeyProvider;
import com.sun.identity.saml2.assertion.Advice;
import com.sun.identity.saml2.assertion.Assertion;
import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.Attribute;
import com.sun.identity.saml2.assertion.AttributeStatement;
import com.sun.identity.saml2.assertion.EncryptedAttribute;
import com.sun.identity.saml2.assertion.EncryptedID;
import com.sun.identity.saml2.assertion.Issuer;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.assertion.Subject;
import com.sun.identity.saml2.common.AccountUtils;
import com.sun.identity.saml2.common.NameIDInfo;
import com.sun.identity.saml2.common.NameIDInfoKey;
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.key.KeyUtil;
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.SAML2ServiceProviderAdapter;
import com.sun.identity.saml2.plugins.SPAccountMapper;
import com.sun.identity.saml2.plugins.SPAttributeMapper;
import com.sun.identity.saml2.protocol.Artifact;
import com.sun.identity.saml2.protocol.ArtifactResolve;
import com.sun.identity.saml2.protocol.ArtifactResponse;
import com.sun.identity.saml2.protocol.AuthnRequest;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.Response;
import com.sun.identity.saml2.protocol.Status;
import com.sun.identity.shared.encode.Base64;
import com.sun.identity.shared.encode.URLEncDec;
import com.sun.identity.shared.xml.XMLUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.xml.soap.SOAPConnection;
import jakarta.xml.soap.SOAPException;
import jakarta.xml.soap.SOAPMessage;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.util.logging.Level;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class SPACSUtils {
   private SPACSUtils() {
   }

   public static ResponseInfo getResponse(
      HttpServletRequest request, HttpServletResponse response, String orgName, String hostEntityId, SAML2MetaManager metaManager
   ) throws SAML2Exception, IOException {
      ResponseInfo respInfo = null;
      String method = request.getMethod();
      if (method.equals("GET")) {
         respInfo = getResponseFromGet(request, response, orgName, hostEntityId, metaManager);
      } else {
         if (!method.equals("POST")) {
            SAMLUtils.sendError(response, "notSupportedHTTPMethod", SAML2Utils.BUNDLE.getString("notSupportedHTTPMethod"));
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("notSupportedHTTPMethod"));
         }

         respInfo = getResponseFromPost(request, response, orgName, hostEntityId, metaManager);
      }

      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message("SPACSUtils.getResponse: got response=" + respInfo.getResponse().toXMLString(true, true));
      }

      return respInfo;
   }

   private static ResponseInfo getResponseFromGet(
      HttpServletRequest request, HttpServletResponse response, String orgName, String hostEntityId, SAML2MetaManager metaManager
   ) throws SAML2Exception, IOException {
      ResponseInfo respInfo = null;
      String resID = request.getParameter("resID");
      if (resID != null && !resID.isEmpty()) {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message("SPACSUtils.getResponseFromGet: resID=" + resID);
         }

         if (respInfo == null) {
            if (SAML2Utils.DEBUG.messageEnabled()) {
               SAML2Utils.DEBUG.message("SPACSUtils.getResponseFromGet: couldn't find Response from resID.");
            }

            String[] data = new String[]{resID};
            LogUtil.error(Level.INFO, "RESPONSE_NOT_FOUND_FROM_CACHE", data, null);
            SAMLUtils.sendError(response, "SSOFailed", SAML2Utils.BUNDLE.getString("SSOFailed"));
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("SSOFailed"));
         } else {
            return respInfo;
         }
      } else {
         String samlArt = request.getParameter("SAMLart");
         if (samlArt != null && !samlArt.trim().isEmpty()) {
            return new ResponseInfo(
               getResponseFromArtifact(samlArt, hostEntityId, request, response, orgName, metaManager),
               "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact",
               null
            );
         }

         SAML2Utils.DEBUG.error("SPACSUtils.getResponseFromGet: Artifact string is empty.");
         LogUtil.error(Level.INFO, "MISSING_ARTIFACT", null, null);
         SAMLUtils.sendError(response, "missingArtifact", SAML2Utils.BUNDLE.getString("missingArtifact"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("missingArtifact"));
      }
   }

   private static Response getResponseFromArtifact(
      String samlArt, String hostEntityId, HttpServletRequest request, HttpServletResponse response, String orgName, SAML2MetaManager sm
   ) throws SAML2Exception, IOException {
      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message("SPACSUtils.getResponseFromArtifact: samlArt = " + samlArt);
      }

      Artifact art = null;

      try {
         art = ProtocolFactory.getInstance().createArtifact(samlArt.trim());
         String[] data = new String[]{samlArt.trim()};
         LogUtil.access(Level.INFO, "RECEIVED_ARTIFACT", data, null);
      } catch (SAML2Exception se) {
         SAML2Utils.DEBUG.error("SPACSUtils.getResponseFromArtifact: Unable to decode and parse artifact string:" + samlArt);
         SAMLUtils.sendError(response, "errorObtainArtifact", SAML2Utils.BUNDLE.getString("errorObtainArtifact"));
         throw se;
      }

      String idpEntityID = getIDPEntityID(art, response, orgName, sm);
      IDPSSODescriptorType idp = null;

      try {
         idp = sm.getIDPSSODescriptor(orgName, idpEntityID);
      } catch (SAML2MetaException se) {
         String[] data = new String[]{orgName, idpEntityID};
         LogUtil.error(Level.INFO, "IDP_META_NOT_FOUND", data, null);
         SAMLUtils.sendError(response, "failedToGetIDPSSODescriptor", se.getMessage());
         throw se;
      }

      String location = getIDPArtifactResolutionServiceUrl(art.getEndpointIndex(), idpEntityID, idp, response);
      ArtifactResolve resolve = null;
      SOAPMessage resMsg = null;

      try {
         resolve = ProtocolFactory.getInstance().createArtifactResolve();
         resolve.setID(SAML2Utils.generateID());
         resolve.setVersion("2.0");
         resolve.setIssueInstant(new Date());
         resolve.setArtifact(art);
         resolve.setDestination(location);
         Issuer issuer = AssertionFactory.getInstance().createIssuer();
         issuer.setValue(hostEntityId);
         resolve.setIssuer(issuer);
         String needArtiResolveSigned = SAML2Utils.getAttributeValueFromSSOConfig(orgName, idpEntityID, "IDPRole", "wantArtifactResolveSigned");
         if (needArtiResolveSigned != null && needArtiResolveSigned.equals("true")) {
            String signAlias = getAttributeValueFromSPSSOConfig(orgName, hostEntityId, sm, "signingCertAlias");
            if (signAlias == null) {
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("missingSigningCertAlias"));
            }

            KeyProvider kp = KeyUtil.getKeyProviderInstance();
            if (kp == null) {
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("nullKeyProvider"));
            }

            resolve.sign(kp.getPrivateKey(signAlias), kp.getX509Certificate(signAlias));
         }

         String resolveString = resolve.toXMLString(true, true);
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message("SPACSUtils.getResponseFromArtifact: ArtifactResolve=" + resolveString);
         }

         SOAPConnection con = SAML2Utils.CONNECTION_FACTORY.createConnection();
         SOAPMessage msg = SAML2Utils.createSOAPMessage(resolveString, true);
         BaseConfigType config = null;
         config = sm.getIDPSSOConfig(orgName, idpEntityID);
         location = SAML2Utils.fillInBasicAuthInfo(config, location);
         resMsg = con.call(msg, location);
      } catch (SAML2Exception s2e) {
         SAML2Utils.DEBUG.error("SPACSUtils.getResponseFromArtifact: couldn't create ArtifactResolve:", s2e);
         String[] data = new String[]{hostEntityId, art.getArtifactValue()};
         LogUtil.error(Level.INFO, "CANNOT_CREATE_ARTIFACT_RESOLVE", data, null);
         SAMLUtils.sendError(response, "errorCreateArtifactResolve", SAML2Utils.BUNDLE.getString("errorCreateArtifactResolve"));
         throw s2e;
      } catch (SOAPException se) {
         SAML2Utils.DEBUG.error("SPACSUtils.getResponseFromGet: couldn't get ArtifactResponse. SOAP error:", se);
         String[] data = new String[]{hostEntityId, location};
         LogUtil.error(Level.INFO, "CANNOT_GET_SOAP_RESPONSE", data, null);
         SAMLUtils.sendError(response, "errorInSOAPCommunication", SAML2Utils.BUNDLE.getString("errorInSOAPCommunication"));
         throw new SAML2Exception(se.getMessage());
      }

      Response result = getResponseFromSOAP(resMsg, resolve, response, idpEntityID, idp, orgName, hostEntityId, sm);
      String[] data = new String[]{hostEntityId, idpEntityID, art.getArtifactValue(), ""};
      if (LogUtil.isAccessLoggable(Level.FINE)) {
         data[3] = result.toXMLString();
      }

      LogUtil.access(Level.INFO, "GOT_RESPONSE_FROM_ARTIFACT", data, null);
      return result;
   }

   private static String getIDPEntityID(Artifact art, HttpServletResponse response, String orgName, SAML2MetaManager metaManager) throws SAML2Exception, IOException {
      String sourceID = art.getSourceID();
      String idpEntityID = null;

      try {
         List<String> allIdps = new ArrayList<>(10);
         allIdps.addAll(metaManager.getAllRemoteIdentityProviderEntities(orgName));
         allIdps.addAll(metaManager.getAllHostedIdentityProviderEntities(orgName));

         for (String allIdp : allIdps) {
            idpEntityID = allIdp;
            String tmpSourceID = SAML2Utils.generateSourceID(idpEntityID);
            if (sourceID.equals(tmpSourceID)) {
               break;
            }

            idpEntityID = null;
         }

         if (idpEntityID == null) {
            SAML2Utils.DEBUG.error("SPACSUtils.getResponseFromGet: Unable to find the IDP based on the SourceID in the artifact");
            String[] data = new String[]{art.getArtifactValue(), orgName};
            LogUtil.error(Level.INFO, "IDP_NOT_FOUND", data, null);
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("cannotFindIDP"));
         } else {
            return idpEntityID;
         }
      } catch (SAML2Exception se) {
         String[] data = new String[]{art.getArtifactValue(), orgName};
         LogUtil.error(Level.INFO, "IDP_NOT_FOUND", data, null);
         SAMLUtils.sendError(response, "cannotFindIDP", se.getMessage());
         throw se;
      }
   }

   private static String getIDPArtifactResolutionServiceUrl(int endpointIndex, String idpEntityID, IDPSSODescriptorType idp, HttpServletResponse response) throws SAML2Exception, IOException {
      List<IndexedEndpointType> arsList = idp.getArtifactResolutionService();
      IndexedEndpointType ars = null;
      String location = null;
      String defaultLocation = null;
      String firstLocation = null;
      boolean isDefault = false;

      for (int i = 0; i < arsList.size(); i++) {
         ars = arsList.get(i);
         location = ars.getLocation();
         int index = ars.getIndex();
         isDefault = XjcHelper.mapBooleanWrapperToPrimitiveWithFalseAsDefault(ars.isIsDefault());
         if (index == endpointIndex) {
            break;
         }

         if (isDefault) {
            defaultLocation = location;
         }

         if (i == 0) {
            firstLocation = location;
         }

         location = null;
      }

      if (location == null || location.isEmpty()) {
         location = defaultLocation;
         if (location == null || location.isEmpty()) {
            location = firstLocation;
            if (location == null || location.isEmpty()) {
               SAML2Utils.DEBUG.error("SPACSUtils: Unable to get the location of artifact resolution service for " + idpEntityID);
               String[] data = new String[]{idpEntityID};
               LogUtil.error(Level.INFO, "ARTIFACT_RESOLUTION_URL_NOT_FOUND", data, null);
               SAMLUtils.sendError(response, "cannotFindArtifactResolutionUrl", SAML2Utils.BUNDLE.getString("cannotFindArtifactResolutionUrl"));
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("cannotFindArtifactResolutionUrl"));
            }
         }
      }

      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message("SPACSUtils: IDP artifact resolution service url =" + location);
      }

      return location;
   }

   private static Response getResponseFromSOAP(
      SOAPMessage resMsg,
      ArtifactResolve resolve,
      HttpServletResponse response,
      String idpEntityID,
      IDPSSODescriptorType idp,
      String orgName,
      String hostEntityId,
      SAML2MetaManager sm
   ) throws SAML2Exception {
      String method = "SPACSUtils.getResponseFromSOAP:";
      Element resElem = null;

      try {
         resElem = SAML2Utils.getSamlpElement(resMsg, "ArtifactResponse");
      } catch (SAML2Exception se) {
         String[] data = new String[]{idpEntityID};
         LogUtil.error(Level.INFO, "SOAP_ERROR", data, null);
         SAMLUtils.sendError(response, "soapError", se.getMessage());
         throw se;
      }

      ArtifactResponse artiResp = null;

      try {
         artiResp = ProtocolFactory.getInstance().createArtifactResponse(resElem);
      } catch (SAML2Exception se) {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message(method + "Couldn't create ArtifactResponse:", se);
         }

         String[] data = new String[]{idpEntityID};
         LogUtil.error(Level.INFO, "CANNOT_INSTANTIATE_ARTIFACT_RESPONSE", data, null);
         SAMLUtils.sendError(response, "failedToCreateArtifactResponse", se.getMessage());
         throw se;
      }

      if (artiResp == null) {
         String[] data = new String[]{idpEntityID};
         LogUtil.error(Level.INFO, "MISSING_ARTIFACT_RESPONSE", data, null);
         SAMLUtils.sendError(response, "missingArtifactResponse", SAML2Utils.BUNDLE.getString("missingArtifactResponse"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("missingArtifactResponse"));
      }

      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message(method + "Received ArtifactResponse:" + artiResp.toXMLString(true, true));
      }

      String wantArtiRespSigned = getAttributeValueFromSPSSOConfig(orgName, hostEntityId, sm, "wantArtifactResponseSigned");
      if (wantArtiRespSigned != null && wantArtiRespSigned.equals("true")) {
         List<X509Certificate> certs = KeyUtil.getVerificationCerts(idp, idpEntityID, "IDPRole");
         if (!artiResp.isSigned() || !artiResp.isSignatureValid(certs)) {
            if (SAML2Utils.DEBUG.messageEnabled()) {
               SAML2Utils.DEBUG.message(method + "ArtifactResponse's signature is invalid.");
            }

            String[] data = new String[]{idpEntityID};
            LogUtil.error(Level.INFO, "ARTIFACT_RESPONSE_INVALID_SIGNATURE", data, null);
            SAMLUtils.sendError(response, "invalidSignature", SAML2Utils.BUNDLE.getString("invalidSignature"));
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidSignature"));
         }
      }

      String inResponseTo = artiResp.getInResponseTo();
      if (inResponseTo != null && inResponseTo.equals(resolve.getID())) {
         Issuer idpIssuer = artiResp.getIssuer();
         if (idpIssuer != null && idpIssuer.getValue().equals(idpEntityID)) {
            Status status = artiResp.getStatus();
            if (status != null && status.getStatusCode().getValue().equals("urn:oasis:names:tc:SAML:2.0:status:Success")) {
               try {
                  return ProtocolFactory.getInstance().createResponse(artiResp.getAny());
               } catch (SAML2Exception se) {
                  if (SAML2Utils.DEBUG.messageEnabled()) {
                     SAML2Utils.DEBUG.message(method + "couldn't instantiate Response:", se);
                  }

                  String[] data = new String[]{idpEntityID};
                  LogUtil.error(Level.INFO, "CANNOT_INSTANTIATE_RESPONSE_ARTIFACT", data, null);
                  SAMLUtils.sendError(response, "failedToCreateResponse", se.getMessage());
                  throw se;
               }
            } else {
               String statusCode = status == null ? "" : status.getStatusCode().getValue();
               if (SAML2Utils.DEBUG.messageEnabled()) {
                  SAML2Utils.DEBUG.message(method + "ArtifactResponse's status code is not success." + statusCode);
               }

               String[] data = new String[]{idpEntityID, ""};
               if (LogUtil.isErrorLoggable(Level.FINE)) {
                  data[1] = statusCode;
               }

               LogUtil.error(Level.INFO, "ARTIFACT_RESPONSE_INVALID_STATUS_CODE", data, null);
               SAMLUtils.sendError(response, "invalidStatusCode", SAML2Utils.BUNDLE.getString("invalidStatusCode"));
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidStatusCode"));
            }
         } else {
            if (SAML2Utils.DEBUG.messageEnabled()) {
               SAML2Utils.DEBUG.message(method + "ArtifactResponse's Issuer is invalid.");
            }

            String[] data = new String[]{idpEntityID};
            LogUtil.error(Level.INFO, "ARTIFACT_RESPONSE_INVALID_ISSUER", data, null);
            SAMLUtils.sendError(response, "invalidIssuer", SAML2Utils.BUNDLE.getString("invalidIssuer"));
            throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidIssuer"));
         }
      } else {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message(method + "ArtifactResponse's InResponseTo is invalid.");
         }

         String[] data = new String[]{idpEntityID};
         LogUtil.error(Level.INFO, "ARTIFACT_RESPONSE_INVALID_INRESPONSETO", data, null);
         SAMLUtils.sendError(response, "invalidInResponseTo", SAML2Utils.BUNDLE.getString("invalidInResponseTo"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidInResponseTo"));
      }
   }

   private static ResponseInfo getResponseFromPost(
      HttpServletRequest request, HttpServletResponse response, String orgName, String hostEntityId, SAML2MetaManager metaManager
   ) throws SAML2Exception, IOException {
      String classMethod = "SPACSUtils:getResponseFromPost";
      SAML2Utils.DEBUG.message("SPACSUtils:getResponseFromPost");
      String samlArt = request.getParameter("SAMLart");
      if (samlArt != null && !samlArt.trim().isEmpty()) {
         return new ResponseInfo(
            getResponseFromArtifact(samlArt, hostEntityId, request, response, orgName, metaManager), "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact", null
         );
      }

      String samlResponse = request.getParameter("SAMLResponse");
      if (samlResponse == null) {
         LogUtil.error(Level.INFO, "MISSING_SAML_RESPONSE_FROM_POST", null, null);
         SAMLUtils.sendError(response, "missingSAMLResponse", SAML2Utils.BUNDLE.getString("missingSAMLResponse"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("missingSAMLResponse"));
      }

      Response resp = null;
      ByteArrayInputStream bis = null;

      try {
         byte[] raw = Base64.decode(samlResponse);
         if (raw != null) {
            bis = new ByteArrayInputStream(raw);
            Document doc = XMLUtils.toDOMDocument(bis, SAML2Utils.DEBUG);
            if (doc != null) {
               resp = ProtocolFactory.getInstance().createResponse(doc.getDocumentElement());
            }
         }
      } catch (SAML2Exception se) {
         SAML2Utils.DEBUG.error("SPACSUtils.getResponse: Exception when instantiating SAMLResponse:", se);
         LogUtil.error(Level.INFO, "CANNOT_INSTANTIATE_RESPONSE_POST", null, null);
         SAMLUtils.sendError(response, "errorObtainResponse", SAML2Utils.BUNDLE.getString("errorObtainResponse"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("errorObtainResponse"));
      } catch (Exception e) {
         SAML2Utils.DEBUG.error("SPACSUtils.getResponse: Exception when decoding SAMLResponse:", e);
         LogUtil.error(Level.INFO, "CANNOT_DECODE_RESPONSE", null, null);
         SAMLUtils.sendError(response, "errorDecodeResponse", SAML2Utils.BUNDLE.getString("errorDecodeResponse"));
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("errorDecodeResponse"));
      } finally {
         if (bis != null) {
            try {
               bis.close();
            } catch (Exception ie) {
               if (SAML2Utils.DEBUG.messageEnabled()) {
                  SAML2Utils.DEBUG.message("SPACSUtils.getResponse: Exception when close the input stream:", ie);
               }
            }
         }
      }

      if (resp != null) {
         boolean needPOSTResponseSigned = SAML2Utils.wantPOSTResponseSigned(orgName, hostEntityId, "SPRole");
         String idpEntityID = resp.getIssuer().getValue();
         IDPSSODescriptorType idp = null;

         try {
            idp = metaManager.getIDPSSODescriptor(orgName, idpEntityID);
         } catch (SAML2MetaException se) {
            String[] data = new String[]{orgName, hostEntityId, idpEntityID};
            LogUtil.error(Level.INFO, "IDP_META_NOT_FOUND", data, null);
            SAMLUtils.sendError(response, "failedToGetIDPSSODescriptor", se.getMessage());
            throw se;
         }

         if (needPOSTResponseSigned) {
            List<X509Certificate> certs = KeyUtil.getVerificationCerts(idp, idpEntityID, "IDPRole");
            if (!resp.isSigned() || !resp.isSignatureValid(certs)) {
               SAML2Utils.DEBUG.error(classMethod + " Signature in Response is invalid ");
               String[] data = new String[]{orgName, hostEntityId, idpEntityID};
               LogUtil.error(Level.INFO, "POST_RESPONSE_INVALID_SIGNATURE", data, null);
               SAMLUtils.sendError(response, "invalidSignature", SAML2Utils.BUNDLE.getString("invalidSignature"));
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("invalidSignInResponse"));
            }
         }

         String[] data = new String[]{""};
         if (LogUtil.isAccessLoggable(Level.FINE)) {
            data[0] = resp.toXMLString();
         }

         LogUtil.access(Level.INFO, "GOT_RESPONSE_FROM_POST", data, null);
         return new ResponseInfo(resp, "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", null);
      } else {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message("SPACSUtils.getResponse: Decoded response, resp is null");
         }

         return null;
      }
   }

   private static Object processResponse(
      HttpServletRequest request,
      HttpServletResponse response,
      String metaAlias,
      Object session,
      ResponseInfo respInfo,
      String realm,
      String hostEntityId,
      SAML2MetaManager metaManager
   ) throws SAML2Exception {
      String classMethod = "SPACSUtils.processResponse: ";
      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message(classMethod + "Response : " + respInfo.getResponse());
      }

      Map<String, Object> smap = null;

      try {
         smap = SAML2Utils.verifyResponse(request, response, respInfo.getResponse(), realm, hostEntityId, respInfo.getProfileBinding());
      } catch (SAML2Exception se) {
         invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 1, se);
         throw se;
      }

      Subject assertionSubject = (Subject)smap.get("Subject");
      NameID nameId = assertionSubject.getNameID();
      EncryptedID encId = assertionSubject.getEncryptedID();
      Assertion authnAssertion = (Assertion)smap.get("assertion");
      String sessionIndex = (String)smap.get("SessionIndex");
      respInfo.setSessionIndex(sessionIndex);
      Integer authLevel = (Integer)smap.get("AuthLevel");
      String inRespToResp = (String)smap.get("inResponseTo");
      List<Assertion> assertions = (List<Assertion>)smap.get("assertions");
      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message(classMethod + "Assertions : " + assertions);
      }

      BaseConfigType spssoconfig = metaManager.getSPSSOConfig(realm, hostEntityId);
      Map<String, List<String>> attributes = SAML2MetaUtils.getAttributes(spssoconfig);
      SPAccountMapper acctMapper = getSPAccountMapper(attributes);
      SPAttributeMapper attrMapper = getSPAttributeMapper(attributes);
      boolean needAttributeEncrypted = false;
      boolean needNameIDEncrypted = false;
      String assertionEncryptedAttr = SAML2Utils.getAttributeValueFromSPSSOConfig(spssoconfig, "wantAssertionEncrypted");
      if (assertionEncryptedAttr == null || !assertionEncryptedAttr.equals("true")) {
         String attrEncryptedStr = SAML2Utils.getAttributeValueFromSPSSOConfig(spssoconfig, "wantAttributeEncrypted");
         if (attrEncryptedStr != null && attrEncryptedStr.equals("true")) {
            needAttributeEncrypted = true;
         }

         String idEncryptedStr = SAML2Utils.getAttributeValueFromSPSSOConfig(spssoconfig, "wantNameIDEncrypted");
         if (idEncryptedStr != null && idEncryptedStr.equals("true")) {
            needNameIDEncrypted = true;
         }
      }

      PrivateKey decryptionKey = KeyUtil.getDecryptionKey(spssoconfig);
      if (needNameIDEncrypted && encId == null) {
         SAML2Utils.DEBUG.error(classMethod + "process: NameID was not encrypted.");
         SAML2Exception se = new SAML2Exception(SAML2Utils.BUNDLE.getString("nameIDNotEncrypted"));
         invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 1, se);
         throw se;
      }

      if (encId != null) {
         try {
            nameId = encId.decrypt(decryptionKey);
         } catch (SAML2Exception se) {
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 1, se);
            throw se;
         }
      }

      respInfo.setNameId(nameId);
      SPSSODescriptorType spDesc = null;

      try {
         spDesc = metaManager.getSPSSODescriptor(realm, hostEntityId);
      } catch (SAML2MetaException ex) {
         SAML2Utils.DEBUG.error(classMethod, ex);
      }

      if (spDesc == null) {
         SAML2Exception se = new SAML2Exception(SAML2Utils.BUNDLE.getString("metaDataError"));
         invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 11, se);
         throw se;
      }

      String nameIDFormat = nameId.getFormat();
      if (nameIDFormat != null) {
         List<String> spNameIDFormatList = spDesc.getNameIDFormat();
         if (spNameIDFormatList != null && !spNameIDFormatList.isEmpty() && !spNameIDFormatList.contains(nameIDFormat)) {
            Object[] args = new Object[]{nameIDFormat};
            SAML2Exception se = new SAML2Exception("libSAML2", "unsupportedNameIDFormatSP", args);
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 1, se);
            throw se;
         }
      }

      String existUserName = null;
      SessionProvider sessionProvider = null;

      try {
         sessionProvider = SessionManager.getProvider();
      } catch (SessionException se) {
         SAML2Exception se2 = new SAML2Exception(se);
         invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 4, se2);
         throw se2;
      }

      if (session != null) {
         try {
            existUserName = sessionProvider.getPrincipalName(session);
         } catch (SessionException se) {
            SAML2Exception se2 = new SAML2Exception(se);
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 4, se2);
            throw se2;
         }
      }

      String userName;
      try {
         userName = acctMapper.getIdentity(authnAssertion, hostEntityId, realm);
      } catch (SAML2Exception se) {
         invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 6, se);
         throw se;
      }

      if (userName == null) {
         userName = existUserName;
      }

      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message(classMethod + "process: userName =[" + userName + "]");
      }

      List<Attribute> attrs = null;
      String remoteHostId = null;

      for (Assertion assertion : assertions) {
         remoteHostId = assertion.getIssuer().getValue();
         List<Attribute> origAttrs = getSAMLAttributes(assertion, needAttributeEncrypted, decryptionKey);
         if (origAttrs != null && !origAttrs.isEmpty()) {
            if (attrs == null) {
               attrs = new ArrayList<>();
            }

            attrs.addAll(origAttrs);
         }
      }

      Map<String, Set<String>> attrMap = null;
      if (attrs != null) {
         try {
            attrMap = attrMapper.getAttributes(attrs, userName, hostEntityId, remoteHostId, realm);
         } catch (SAML2Exception se) {
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 5, se);
            throw se;
         }
      }

      if (SAML2Utils.DEBUG.messageEnabled()) {
         SAML2Utils.DEBUG.message(classMethod + "process: remoteHostId = " + remoteHostId);
         SAML2Utils.DEBUG.message(classMethod + "process: attrMap = " + attrMap);
      }

      respInfo.setAttributeMap(attrMap);
      if (userName != null && !userName.isEmpty()) {
         boolean isTransient = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient".equals(nameId.getFormat());
         boolean writeFedInfo = !isTransient && !SAML2Utils.isFedInfoExists(userName, hostEntityId, remoteHostId, nameId);
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message(classMethod + "userName : " + userName);
            SAML2Utils.DEBUG.message(classMethod + "writeFedInfo : " + writeFedInfo);
         }

         AuthnRequest authnRequest = null;
         if (smap != null) {
            authnRequest = (AuthnRequest)smap.get("AuthnRequest");
         }

         if (inRespToResp != null && !inRespToResp.isEmpty()) {
            SPCache.requestHash().remove(inRespToResp);
         }

         Map<String, String> sessionInfoMap = new HashMap<>();
         sessionInfoMap.put("realm", realm);
         sessionInfoMap.put("principalName", userName);
         String clientAddr = request.getRemoteAddr();
         sessionInfoMap.put("Host", clientAddr);
         sessionInfoMap.put("HostName", clientAddr);
         sessionInfoMap.put("AuthLevel", String.valueOf(authLevel));

         try {
            session = sessionProvider.createSession(sessionInfoMap, request, response, null);
         } catch (SessionException se) {
            int failureCode = 10;
            int sessCode = se.getErrCode();
            if (sessCode == 1) {
               failureCode = 7;
            } else if (sessCode == 2) {
               failureCode = 8;
            } else if (sessCode == 3) {
               failureCode = 9;
            }

            if (SAML2Utils.DEBUG.messageEnabled()) {
               SAML2Utils.DEBUG.message("SPACSUtils.processResponse : error code=" + sessCode, se);
            }

            SAML2Exception se2 = new SAML2Exception(se);
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, failureCode, se2);
            throw se2;
         }

         String[] values = new String[]{metaAlias};

         try {
            setAttrMapInSession(sessionProvider, attrMap, session);
            setDiscoBootstrapCredsInSSOToken(sessionProvider, authnAssertion, session);
            sessionProvider.setProperty(session, "spMetaAlias", values);
         } catch (SessionException se) {
            SAML2Exception se2 = new SAML2Exception(se);
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 4, se2);
            throw se2;
         }

         NameIDInfo info = null;
         String affiID = nameId.getSPNameQualifier();
         AffiliationDescriptorType affiDesc = metaManager.getAffiliationDescriptor(realm, affiID);
         boolean isDualRole = SAML2Utils.isDualRole(hostEntityId, realm);
         if (affiDesc != null) {
            if (!affiDesc.getAffiliateMember().contains(hostEntityId)) {
               throw new SAML2Exception(SAML2Utils.BUNDLE.getString("spNotAffiliationMember"));
            }

            if (isDualRole) {
               info = new NameIDInfo(affiID, remoteHostId, nameId, "DualRole", true);
            } else {
               info = new NameIDInfo(affiID, remoteHostId, nameId, "SPRole", true);
            }
         } else if (isDualRole) {
            info = new NameIDInfo(hostEntityId, remoteHostId, nameId, "DualRole", false);
         } else {
            info = new NameIDInfo(hostEntityId, remoteHostId, nameId, "SPRole", false);
         }

         Map<String, String> props = new HashMap<>();
         String nameIDValueString = info.getNameIDValue();
         props.put("NameID", info.getNameIDValue());

         try {
            userName = sessionProvider.getPrincipalName(session);
         } catch (SessionException se) {
            SAML2Exception se2 = new SAML2Exception(se);
            invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 4, se2);
            throw se2;
         }

         String[] data1 = new String[]{userName, nameIDValueString};
         LogUtil.access(Level.INFO, "SUCCESS_FED_SSO", data1, session, props);
         if (writeFedInfo) {
            try {
               AccountUtils.setAccountFederation(info, userName);
            } catch (SAML2Exception se) {
               invokeSPAdapterForSSOFailure(hostEntityId, realm, request, response, smap, respInfo, 3, se);
               throw se;
            }

            String[] data = new String[]{userName, ""};
            if (LogUtil.isAccessLoggable(Level.FINE)) {
               data[1] = info.toValueString();
            }

            LogUtil.access(Level.INFO, "FED_INFO_WRITTEN", data, session, props);
         }

         saveInfoInMemory(sessionProvider, session, sessionIndex, metaAlias, info, isTransient);
         SAML2ServiceProviderAdapter spAdapter = SAML2Utils.getSPAdapterClass(hostEntityId, realm);
         if (spAdapter != null) {
            boolean redirected = spAdapter.postSingleSignOnSuccess(
               hostEntityId, realm, request, response, session, authnRequest, respInfo.getResponse(), respInfo.getProfileBinding(), writeFedInfo
            );
            String[] value = null;
            if (redirected) {
               value = new String[]{"true"};
            } else {
               value = new String[]{"false"};
            }

            try {
               sessionProvider.setProperty(session, "SAML2ResponseRedirected", value);
            } catch (SessionException | UnsupportedOperationException ex) {
               SAML2Utils.DEBUG.warning("SPSingleLogout.processResp", ex);
            }
         }

         String assertionID = authnAssertion.getID();
         if (respInfo.getProfileBinding().equals("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST")) {
            SPCache.assertionByIDCache().put(assertionID, "ONE");

            try {
               if (SAML2Utils.isSAML2FailOverEnabled()) {
                  SAML2Repository.getInstance().save(assertionID, "ONE", (Long)smap.get("NotOnOrAfter"), null);
               }
            } catch (SAML2Exception e) {
               SAML2Utils.DEBUG.error(classMethod + "DB error!", e);
            }
         }

         respInfo.setAssertion(authnAssertion);
         return session;
      } else {
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("noUserMapping"));
      }
   }

   private static void invokeSPAdapterForSSOFailure(
      String hostEntityId,
      String realm,
      HttpServletRequest request,
      HttpServletResponse response,
      Map<String, Object> smap,
      ResponseInfo respInfo,
      int errorCode,
      SAML2Exception se
   ) {
      SAML2ServiceProviderAdapter spAdapter = null;

      try {
         spAdapter = SAML2Utils.getSPAdapterClass(hostEntityId, realm);
      } catch (SAML2Exception e) {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message("SPACSUtils.invokeSPAdapterForSSOFailure", e);
         }
      }

      if (spAdapter != null) {
         AuthnRequest authnRequest = null;
         if (smap != null) {
            authnRequest = (AuthnRequest)smap.get("AuthnRequest");
         }

         boolean redirected = spAdapter.postSingleSignOnFailure(
            hostEntityId, realm, request, response, authnRequest, respInfo.getResponse(), respInfo.getProfileBinding(), errorCode
         );
         se.setRedirectionDone(redirected);
      }
   }

   private static SPAccountMapper getSPAccountMapper(Map<String, List<String>> attributes) throws SAML2Exception {
      SPAccountMapper acctMapper = null;
      List<String> acctMapperList = attributes.get("spAccountMapper");
      if (acctMapperList != null) {
         try {
            acctMapper = (SPAccountMapper)Class.forName(acctMapperList.getFirst()).getDeclaredConstructor().newInstance();
            if (SAML2Utils.DEBUG.messageEnabled()) {
               SAML2Utils.DEBUG.message("SPACSUtils.getSPAccountMapper: mapper = " + acctMapperList.getFirst());
            }
         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException e) {
            throw new SAML2Exception(e);
         }
      }

      if (acctMapper == null) {
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("failedAcctMapper"));
      } else {
         return acctMapper;
      }
   }

   private static SPAttributeMapper getSPAttributeMapper(Map<String, List<String>> attributes) throws SAML2Exception {
      SPAttributeMapper attrMapper = null;
      List<String> attrMapperList = attributes.get("spAttributeMapper");
      if (attrMapperList != null) {
         try {
            attrMapper = (SPAttributeMapper)Class.forName(attrMapperList.getFirst()).getDeclaredConstructor().newInstance();
         } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException | NoSuchMethodException cfe) {
            throw new SAML2Exception(cfe);
         }
      }

      if (attrMapper == null) {
         throw new SAML2Exception(SAML2Utils.BUNDLE.getString("failedAttrMapper"));
      } else {
         return attrMapper;
      }
   }

   private static void saveInfoInMemory(
      SessionProvider sessionProvider, Object session, String sessionIndex, String metaAlias, NameIDInfo info, boolean isTransient
   ) throws SAML2Exception {
      String infoKeyString = new NameIDInfoKey(info.getNameIDValue(), info.getHostEntityID(), info.getRemoteEntityID()).toValueString();
      String infoKeyAttribute = AccountUtils.getNameIDInfoKeyAttribute();
      String[] fromToken = null;

      try {
         fromToken = sessionProvider.getProperty(session, infoKeyAttribute);
         if (fromToken != null && fromToken.length != 0 && fromToken[0] != null && !fromToken[0].isEmpty()) {
            if (!fromToken[0].contains(infoKeyString)) {
               String[] values = new String[]{fromToken[0] + ";" + infoKeyString};
               sessionProvider.setProperty(session, infoKeyAttribute, values);
            }
         } else {
            String[] values = new String[]{infoKeyString};
            sessionProvider.setProperty(session, infoKeyAttribute, values);
         }

         if (isTransient) {
            String nameIDInfoStr = info.toValueString();
            String infoAttribute = AccountUtils.getNameIDInfoAttribute();
            String[] nameIDInfoStrs = sessionProvider.getProperty(session, infoAttribute);
            if (nameIDInfoStrs == null) {
               nameIDInfoStrs = new String[]{nameIDInfoStr};
            } else {
               Set<String> nameIDInfoStrSet = new HashSet<>();
               Collections.addAll(nameIDInfoStrSet, nameIDInfoStrs);
               nameIDInfoStrSet.add(nameIDInfoStr);
               nameIDInfoStrs = nameIDInfoStrSet.toArray(new String[0]);
            }

            sessionProvider.setProperty(session, infoAttribute, nameIDInfoStrs);
         }
      } catch (SessionException sessE) {
         throw new SAML2Exception(sessE);
      }

      String tokenID = sessionProvider.getSessionID(session);
      synchronized (Saml2DataMapProvider.DataMapId.SP_FED_SESSION_LISTS_BY_NAME_ID) {
         List<SPFedSession> fedSessions = SPCache.fedSessionListsByNameIDInfoKey().get(infoKeyString);
         if (fedSessions == null) {
            fedSessions = new ArrayList<>();
            fedSessions.add(new SPFedSession(sessionIndex, tokenID, info, metaAlias));
            SPCache.fedSessionListsByNameIDInfoKey().put(infoKeyString, fedSessions);
         } else {
            boolean found = false;

            for (SPFedSession temp : fedSessions) {
               String idpSessionIndex = null;
               if (temp != null) {
                  idpSessionIndex = temp.idpSessionIndex;
               }

               if (idpSessionIndex != null && idpSessionIndex.equals(sessionIndex)) {
                  temp.spTokenID = tokenID;
                  temp.info = info;
                  found = true;
                  break;
               }
            }

            if (!found) {
               fedSessions.add(new SPFedSession(sessionIndex, tokenID, info, metaAlias));
               SPCache.fedSessionListsByNameIDInfoKey().put(infoKeyString, fedSessions);
            }
         }
      }

      try {
         sessionProvider.addListener(session, new SPSessionListener(infoKeyString, tokenID));
      } catch (SessionException e) {
         SAML2Utils.DEBUG.error("SPACSUtils.saveInfoInMemory: Unable to add session listener.");
      }
   }

   private static void setAttrMapInSession(SessionProvider sessionProvider, Map<String, Set<String>> attrMap, Object session) throws SessionException {
      if (attrMap != null && !attrMap.isEmpty()) {
         for (Entry<String, Set<String>> entry : attrMap.entrySet()) {
            String attrName = entry.getKey();
            Set<String> attrValues = entry.getValue();
            if (attrValues != null && !attrValues.isEmpty()) {
               sessionProvider.setProperty(session, attrName, attrValues.toArray(new String[0]));
               if (SAML2Utils.DEBUG.messageEnabled()) {
                  SAML2Utils.DEBUG.message("SPACSUtils.setAttrMapInSessioin: AttrMap:" + attrName + " , " + attrValues);
               }
            }
         }
      }
   }

   private static void setDiscoBootstrapCredsInSSOToken(SessionProvider sessionProvider, Assertion assertion, Object session) throws SessionException {
      if (assertion != null) {
         Set<String> discoBootstrapCreds = null;
         Advice advice = assertion.getAdvice();
         if (advice != null) {
            List<String> creds = advice.getAdditionalInfo();
            if (creds != null && !creds.isEmpty()) {
               if (discoBootstrapCreds == null) {
                  discoBootstrapCreds = new HashSet<>();
               }

               discoBootstrapCreds.addAll(creds);
            }
         }

         if (discoBootstrapCreds != null) {
            sessionProvider.setProperty(session, "DiscoveryBootstrapCrendentials", discoBootstrapCreds.toArray(new String[0]));
         }
      }
   }

   public static String getRelayState(String relayStateID, String orgName, String hostEntityId, SAML2MetaManager sm) {
      String relayStateUrl = null;
      if (relayStateID != null && !relayStateID.trim().isEmpty()) {
         relayStateUrl = SPCache.relayStateHash().remove(relayStateID);
         if (relayStateUrl == null || relayStateUrl.trim().isEmpty()) {
            relayStateUrl = relayStateID;
         }
      }

      if (relayStateUrl == null || relayStateUrl.trim().isEmpty()) {
         relayStateUrl = getAttributeValueFromSPSSOConfig(orgName, hostEntityId, sm, "defaultRelayState");
      }

      return relayStateUrl;
   }

   private static String getIntermediateURL(String orgName, String hostEntityId, SAML2MetaManager sm) {
      return getAttributeValueFromSPSSOConfig(orgName, hostEntityId, sm, "intermediateUrl");
   }

   private static String getAttributeValueFromSPSSOConfig(String orgName, String hostEntityId, SAML2MetaManager sm, String attrName) {
      String result = null;

      try {
         BaseConfigType config = sm.getSPSSOConfig(orgName, hostEntityId);
         if (config == null) {
            return null;
         }

         Map<String, List<String>> attrs = SAML2MetaUtils.getAttributes(config);
         List<String> value = attrs.get(attrName);
         if (value != null && !value.isEmpty()) {
            result = value.getFirst().trim();
         }
      } catch (SAML2MetaException sme) {
         if (SAML2Utils.DEBUG.messageEnabled()) {
            SAML2Utils.DEBUG.message("SPACSUtils.getAttributeValueFromSPSSOConfig:", sme);
         }

         result = null;
      }

      return result;
   }

   private static List<Attribute> getSAMLAttributes(Assertion assertion, boolean needAttributeEncrypted, PrivateKey decryptionKey) {
      List<Attribute> attrList = null;
      if (assertion != null) {
         List<AttributeStatement> statements = assertion.getAttributeStatements();
         if (statements != null && !statements.isEmpty()) {
            for (AttributeStatement statement : statements) {
               List<Attribute> attributes = statement.getAttribute();
               if (needAttributeEncrypted && attributes != null && !attributes.isEmpty()) {
                  SAML2Utils.DEBUG.error("Attribute not encrypted.");
                  return null;
               }

               if (attributes != null) {
                  if (attrList == null) {
                     attrList = new ArrayList<>();
                  }

                  attrList.addAll(attributes);
               }

               List<EncryptedAttribute> encAttrs = statement.getEncryptedAttribute();
               if (encAttrs != null) {
                  Iterator<EncryptedAttribute> encIter = encAttrs.iterator();

                  while (encIter.hasNext()) {
                     if (attrList == null) {
                        attrList = new ArrayList<>();
                     }

                     try {
                        attrList.add(encIter.next().decrypt(decryptionKey));
                     } catch (SAML2Exception se) {
                        SAML2Utils.DEBUG.error("Decryption error:", se);
                        return null;
                     }
                  }
               }
            }
         }
      }

      return attrList;
   }

   public static Map<String, Object> processResponseForFedlet(HttpServletRequest request, HttpServletResponse response) throws SAML2Exception, IOException, SessionException, ServletException {
      if (request != null && response != null) {
         String requestURL = request.getRequestURL().toString();
         SAML2MetaManager metaManager = new SAML2MetaManager();
         if (metaManager == null) {
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("errorMetaManager"));
         }

         String metaAlias = SAML2MetaUtils.getMetaAliasByUri(requestURL);
         if (metaAlias == null || metaAlias.isEmpty()) {
            List<String> spMetaAliases = metaManager.getAllHostedServiceProviderMetaAliases("/");
            if (spMetaAliases != null && !spMetaAliases.isEmpty()) {
               metaAlias = spMetaAliases.getFirst();
            }

            if (metaAlias == null || metaAlias.isEmpty()) {
               throw new ServletException(SAML2SDKUtils.BUNDLE.getString("nullSPEntityID"));
            }
         }

         String hostEntityId = null;

         try {
            hostEntityId = metaManager.getEntityByMetaAlias(metaAlias);
         } catch (SAML2MetaException sme) {
            SAML2SDKUtils.DEBUG.error("SPACSUtils.processResponseForFedlet", sme);
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("metaDataError"));
         }

         if (hostEntityId == null) {
            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("metaDataError"));
         }

         String orgName = "/";
         String relayState = request.getParameter("RelayState");
         SessionProvider sessionProvider = null;
         ResponseInfo respInfo = null;

         try {
            sessionProvider = SessionManager.getProvider();
         } catch (SessionException se) {
            SAML2SDKUtils.DEBUG.error("SPACSUtils.processResponseForFedlet", se);
            throw new SAML2Exception(se);
         }

         respInfo = getResponse(request, response, orgName, hostEntityId, metaManager);
         Object newSession = null;

         try {
            newSession = processResponse(request, response, metaAlias, null, respInfo, orgName, hostEntityId, metaManager);
         } catch (SAML2Exception se) {
            SAML2SDKUtils.DEBUG.message("SPACSUtils.processResponseForFedlet", se);
            if (se.isRedirectionDone()) {
               return createMapForFedlet(respInfo, null, hostEntityId);
            }

            throw new SAML2Exception(SAML2SDKUtils.BUNDLE.getString("SSOFailed"));
         }

         SAML2SDKUtils.DEBUG.message("SSO SUCCESS");
         String[] redirected = sessionProvider.getProperty(newSession, "SAML2ResponseRedirected");
         if (redirected != null && redirected.length != 0 && redirected[0].equals("true")) {
            SAML2SDKUtils.DEBUG.message("Already redirected in SPAdapter.");
            return createMapForFedlet(respInfo, null, hostEntityId);
         }

         String finalUrl = getRelayState(relayState, orgName, hostEntityId, metaManager);
         String realFinalUrl = finalUrl;
         if (finalUrl != null && !finalUrl.isEmpty()) {
            try {
               realFinalUrl = sessionProvider.rewriteURL(newSession, finalUrl);
            } catch (SessionException se) {
               SAML2SDKUtils.DEBUG.message("SPACSUtils.processRespForFedlet", se);
               realFinalUrl = finalUrl;
            }
         }

         String redirectUrl = getIntermediateURL(orgName, hostEntityId, metaManager);
         String realRedirectUrl = null;
         if (redirectUrl == null || redirectUrl.isEmpty()) {
            realRedirectUrl = finalUrl;
         } else if (realFinalUrl != null && !realFinalUrl.isEmpty()) {
            if (redirectUrl.contains("?")) {
               redirectUrl = redirectUrl + "&goto=";
            } else {
               redirectUrl = redirectUrl + "?goto=";
            }

            redirectUrl = redirectUrl + URLEncDec.encode(realFinalUrl);

            try {
               realRedirectUrl = sessionProvider.rewriteURL(newSession, redirectUrl);
            } catch (SessionException se) {
               SAML2SDKUtils.DEBUG.message("SPACSUtils.processRespForFedlet: rewriting failed.", se);
               realRedirectUrl = redirectUrl;
            }
         } else {
            realRedirectUrl = redirectUrl;
         }

         return createMapForFedlet(respInfo, realRedirectUrl, hostEntityId);
      } else {
         throw new ServletException(SAML2SDKUtils.BUNDLE.getString("nullInput"));
      }
   }

   private static Map<String, Object> createMapForFedlet(ResponseInfo respInfo, String relayUrl, String hostedEntityId) {
      Map<String, Object> map = new HashMap<>();
      if (relayUrl != null) {
         map.put("RelayState", relayUrl);
      }

      Response samlResp = respInfo.getResponse();
      map.put("Response", samlResp);
      Assertion assertion = respInfo.getAssertion();
      map.put("Assertion", assertion);
      map.put("Subject", assertion.getSubject());
      map.put("idpEntityID", assertion.getIssuer().getValue());
      map.put("spEntityID", hostedEntityId);
      map.put("NameID", respInfo.getNameId());
      map.put("attributeMap", respInfo.getAttributeMap());
      map.put("SessionIndex", respInfo.getSessionIndex());
      return map;
   }
}
