org.wso2.carbon.hostobjects.sso.SAMLSSORelyingPartyObject.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.hostobjects.sso.SAMLSSORelyingPartyObject.java

Source

/*
* Copyright (c) 2011, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.wso2.carbon.hostobjects.sso;

import org.apache.axis2.clustering.ClusteringAgent;
import org.apache.axis2.clustering.ClusteringFault;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaggeryjs.hostobjects.web.SessionHostObject;
import org.joda.time.DateTime;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.ScriptableObject;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.*;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.signature.Signature;
import org.w3c.dom.NodeList;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.hostobjects.sso.internal.SSOConstants;
import org.wso2.carbon.hostobjects.sso.internal.SSOHostObjectDataHolder;
import org.wso2.carbon.hostobjects.sso.internal.SessionInfo;
import org.wso2.carbon.hostobjects.sso.internal.builder.AuthReqBuilder;
import org.wso2.carbon.hostobjects.sso.internal.builder.LogoutRequestBuilder;
import org.wso2.carbon.hostobjects.sso.internal.util.Util;
import org.wso2.carbon.utils.xml.StringUtils;

import javax.script.ScriptException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * This class wrap up the operations needed to write a saml relying party for sso use case.
 */
public class SAMLSSORelyingPartyObject extends ScriptableObject {
    private static final Log log = LogFactory.getLog(SAMLSSORelyingPartyObject.class);
    //stores sso properties like, identity server url,keystore path, alias, keystore password, issuerId
    private Properties ssoConfigProperties = new Properties();
    protected static volatile SAMLSSORelyingPartyObject ssho;

    // relay state,requested uri
    private static Map<String, String> relayStateMap = new HashMap<String, String>();

    // issuerId, relyingPartyObject .this is to provide sso functionality to multiple jaggery apps.
    private static volatile Map<String, SAMLSSORelyingPartyObject> ssoRelyingPartyMap = new HashMap<String, SAMLSSORelyingPartyObject>();

    // sessionId, sessionIndex. this is to map current session with session index sent from Identity server.
    // When log out request come from identity server,we need to invalidate the current session.
    private static volatile Map<String, SessionInfo> sessionIdMap = new ConcurrentHashMap<String, SessionInfo>();
    private static volatile Map<String, Set<SessionHostObject>> sessionIndexMap = new ConcurrentHashMap<String, Set<SessionHostObject>>();
    //private static volatile Set<SessionHostObject> sho = new HashSet<SessionHostObject>();
    //used store logged in user name until put into jaggery session
    private String loggedInUserName;
    private static long maxInactiveInterval = 1800000; //default 30min

    @Override
    public String getClassName() {
        return "SSORelyingParty";
    }

    public static void jsFunction_addSession(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {

        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof SessionHostObject)) {
            throw new ScriptException("Invalid argument. Session is missing.");
        }
        SessionHostObject sho = (SessionHostObject) args[0];
        maxInactiveInterval = sho.jsGet_maxInactive() * 1000; // convert to milliseconds
        //SAMLSSORelyingPartyObject.sho.add(sho);
        SAMLSSORelyingPartyObject.ssho = (SAMLSSORelyingPartyObject) thisObj;
    }

    /**
     * @param cx
     * @param args      - args[0]-issuerId, this issuer need to be registered in Identity server.
     * @param ctorObj
     * @param inNewExpr
     * @return
     * @throws ScriptException
     */
    public static Scriptable jsConstructor(Context cx, Object[] args, Function ctorObj, boolean inNewExpr)
            throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid arguments!, IssuerId is missing in parameters.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = ssoRelyingPartyMap.get((String) args[0]);
        if (relyingPartyObject == null) {
            synchronized (SAMLSSORelyingPartyObject.class) {
                if (relyingPartyObject == null) {
                    relyingPartyObject = new SAMLSSORelyingPartyObject();
                    relyingPartyObject.setSSOProperty(SSOConstants.ISSUER_ID, (String) args[0]);
                    ssoRelyingPartyMap.put((String) args[0], relyingPartyObject);
                }
            }
        }
        return relyingPartyObject;
    }

    /**
     * @param cx
     * @param thisObj
     * @param args    -args[0]- SAML response xml
     * @param funObj
     * @return
     * @throws Exception
     */
    public static boolean jsFunction_validateSignature(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {

        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. SAML response is missing.");
        }

        String decodedString = Util.decode((String) args[0]);

        XMLObject samlObject = Util.unmarshall(decodedString);
        String tenantDomain = Util.getDomainName(samlObject);

        int tenantId = Util.getRealmService().getTenantManager().getTenantId(tenantDomain);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;
            SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
            Signature signature = samlResponse.getSignature();
            if (signature == null) {
                log.error(
                        "SAMLResponse signing is enabled, but signature element not found in SAML Response element.");
                return false;
            }
            boolean sigValid = false;
            try {
                //Try and validate the signature using the super tenant key store.
                sigValid = Util.validateSignature(signature,
                        relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                        relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                        relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                        MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

            } catch (SignatureVerificationFailure e) {
                //do nothing at this point since we want to verify signature using the tenant key-store as well.
                if (log.isDebugEnabled()) {
                    log.debug("Signature verification failed with Super-Tenant Key Store", e);
                }
            }
            //If not success, try and validate the signature using tenant key store.
            if (!sigValid && !MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
                try {
                    sigValid = Util.validateSignature(signature,
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                            relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS), tenantId, tenantDomain);

                } catch (SignatureVerificationFailure e) {
                    log.error("Signature Verification Failed using super tenant and tenant key stores", e);
                    return false;
                }
            }
            return sigValid;
        }
        if (log.isWarnEnabled()) {
            log.warn("SAML response in signature validation is not a SAML Response.");
        }
        return false;
    }

    /**
     * @param cx
     * @param thisObj
     * @param args    -args[0]-Logout request xml as a string.
     * @param funObj
     * @return
     * @throws Exception
     */
    public static boolean jsFunction_isLogoutRequest(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Logout request xml is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String logoutRequest = StringEscapeUtils.unescapeXml((String) args[0]);
        String decodedString = isEncoded ? Util.decode(logoutRequest) : logoutRequest;
        XMLObject samlObject = Util.unmarshall(decodedString);
        if (log.isDebugEnabled() && samlObject instanceof LogoutRequest) {
            log.debug("Request is a logout request and request is " + args[0]);
        }

        return samlObject instanceof LogoutRequest;

    }

    /**
     * @param cx
     * @param thisObj
     * @param args-args[0]- Logout response xml as a string
     * @param funObj
     * @return
     * @throws Exception
     */
    public static boolean jsFunction_isLogoutResponse(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Logout response xml is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);
        if (log.isDebugEnabled() && samlObject instanceof LogoutResponse) {
            log.debug("Response is a logout response and response is " + args[0]);
        }
        return samlObject instanceof LogoutResponse;

    }

    /**
     * Checking whether the response is for passiveAuth SAML request or not.
     *
     * @param cx
     * @param thisObj
     * @param args    - args[0] response for passiveAuth required as XML
     * @param funObj
     * @return
     * @throws Exception
     */

    public static boolean jsFunction_isPassiveAuthResponse(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Logout response xml is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;

            if (samlResponse.getStatus() != null && samlResponse.getStatus().getStatusCode() != null
                    && samlResponse.getStatus().getStatusCode().getValue()
                            .equals("urn:oasis:names:tc:SAML:2.0:status:Responder")
                    && samlResponse.getStatus().getStatusCode().getStatusCode() != null
                    && samlResponse.getStatus().getStatusCode().getStatusCode().getValue()
                            .equals("urn:oasis:names:tc:SAML:2.0:status:NoPassive")) {
                return true;
            }

        }
        return false;

    }

    /**
     * Compressing and Encoding the response
     *
     * @param cx
     * @param thisObj
     * @param args    -args[0]- string to be encoded.
     * @param funObj
     * @return
     * @throws Exception
     */
    public static String jsFunction_encode(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. String to be encoded is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String deflate = getSSOSamlDeflateProperty(relyingPartyObject);
        boolean shouldDeflate = true;
        if (deflate != null) {
            try {
                shouldDeflate = Boolean.parseBoolean(deflate);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_DEFLATE + " " + deflate);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("Response string to be encoded is " + args[0]);
        }

        return shouldDeflate ? Util.deflateAndEncode((String) args[0]) : Util.encode((String) args[0]);

    }

    public static String jsFunction_getSAMLToken(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Session Id is missing.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        SessionInfo sessionInfo = relyingPartyObject.getSessionInfo((String) args[0]);
        if (sessionInfo != null) {
            if (log.isDebugEnabled()) {
                log.debug("SAML token of relying party object is " + sessionInfo.getSamlToken());
            }
            //Here the samlToken is encoded. So no need to encode that again
            return sessionInfo.getSamlToken();
        }
        return null;
    }

    /**
     * Decoding and deflating the encoded AuthReq
     *
     * @param cx
     * @param thisObj
     * @param args    -args[0]-String to be decoded
     * @param funObj
     * @return
     * @throws Exception
     */
    public static String jsFunction_decode(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. String to be decoded is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("AuthReq string to be decoded is " + args[0]);
        }

        return isEncoded ? Util.decode((String) args[0]) : (String) args[0];
    }

    /**
     * generate a UUID
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws Exception
     */
    public static String jsFunction_getUUID(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws Exception {
        return UUID.randomUUID().toString();

    }

    /**
     * Get SAML authentication request build with given issuer
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws Exception
     */

    public static String jsFunction_getSAMLAuthRequest(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;

        int argLength = args.length;
        String acsUrl = argLength >= 1 ? (String) args[0] : null; //set as the value of 1st arg if exists
        boolean isPassiveAuthRequired = argLength >= 2 ? (Boolean) args[1] : false; //set as 2nd arg if exists
        String nameIdPolicy = relyingPartyObject.getSSOProperty(SSOConstants.NAME_ID_POLICY);

        //check if request signing is required
        if (Boolean.parseBoolean(relyingPartyObject.getSSOProperty(SSOConstants.SIGN_REQUESTS))) {
            //builds a signed authentication request
            return Util.marshall(new AuthReqBuilder().buildSignedAuthRequest(
                    relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID),
                    relyingPartyObject.getSSOProperty(SSOConstants.IDP_URL), acsUrl, isPassiveAuthRequired,
                    MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME,
                    nameIdPolicy));
        } else {
            //builds an unsigned authentication request
            return Util.marshall(new AuthReqBuilder().buildAuthenticationRequest(
                    relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID), acsUrl, isPassiveAuthRequired,
                    nameIdPolicy));
        }
    }

    /**
     * Get SAML logout request build.
     *
     * @param cx
     * @param thisObj
     * @param args-args[0]-the user to be logout
     * @param funObj
     * @return
     * @throws Exception
     */
    public static String jsFunction_getSAMLLogoutRequest(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 2 || !(args[0] instanceof String) && (args[1] instanceof String)) {
            throw new ScriptException("Invalid argument. The user to be logout is missing.");
        }
        if (log.isDebugEnabled()) {
            log.debug("The user to be logged out is " + args[0]);
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String nameIdFormat = relyingPartyObject.getSSOProperty(SSOConstants.NAME_ID_POLICY);
        log.debug("SAMLLogoutRequest is going to get session details");
        if (relyingPartyObject.getSessionInfo((String) args[1]) != null) {
            String idpLogoutURL = relyingPartyObject.getSSOProperty(SSOConstants.IDP_LOGOUT_URL);
            if (StringUtils.isEmpty(idpLogoutURL)) {
                idpLogoutURL = relyingPartyObject.getSSOProperty(SSOConstants.IDP_URL);
            }
            String sessionIndexId = relyingPartyObject.getSessionInfo((String) args[1]).getSessionIndex();
            if (sessionIndexId != null && sessionIndexId.length() > 0) {
                if (!Boolean.parseBoolean(relyingPartyObject.getSSOProperty(SSOConstants.SIGN_REQUESTS))) {
                    return Util.marshall(new LogoutRequestBuilder().buildLogoutRequest((String) args[0],
                            sessionIndexId, SSOConstants.LOGOUT_USER,
                            relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID), nameIdFormat));
                } else {
                    return Util.marshall(new LogoutRequestBuilder().buildSignedLogoutRequest((String) args[0],
                            sessionIndexId, SSOConstants.LOGOUT_USER,
                            relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID),
                            MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME,
                            idpLogoutURL, nameIdFormat));
                }
            } else {
                if (!Boolean.parseBoolean(relyingPartyObject.getSSOProperty(SSOConstants.SIGN_REQUESTS))) {
                    return Util.marshall(new LogoutRequestBuilder().buildLogoutRequest((String) args[0],
                            SSOConstants.LOGOUT_USER, relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID),
                            nameIdFormat));
                } else {
                    return Util.marshall(new LogoutRequestBuilder().buildSignedLogoutRequest((String) args[0],
                            SSOConstants.LOGOUT_USER, relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID),
                            MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME,
                            idpLogoutURL, nameIdFormat));
                }
            }
        }
        log.debug("Session Information not found");
        return null;
    }

    /**
     * Validate the saml response and its contained assertion-check the response element count and assertion count
     * is 1 and check the assertion validity period
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws Exception
     */
    public static boolean jsFunction_validateSAMLResponseSchema(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. The SAML response is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;

            // Check for duplicate saml:Response
            NodeList list = samlResponse.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20P_NS, "Response");
            if (list.getLength() > 0) {
                log.error("Invalid schema for the SAML2 response.");
                return false;
            }

            //Check for duplicate saml:assertions
            NodeList assertionList = samlResponse.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20_NS,
                    "Assertion");
            if (assertionList == null || assertionList.getLength() > 1) {
                log.error("Invalid schema for the SAML2 response. Invalid number of assertions  detected.");
                return false;
            }
            return true;

        }
        return false;
    }

    /**
     * Extract the name of authenticated user from SAML response.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws Exception
     */
    public static String jsFunction_getSAMLResponseNameId(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. The SAML response is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);
        String username = null;
        String isAssertionEncryptionEnabled = relyingPartyObject
                .getSSOProperty(SSOConstants.ASSERTIONENCRYPTIONENABLED);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;

            Assertion assertion = null;

            if ("true".equals(isAssertionEncryptionEnabled)) {
                List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
                EncryptedAssertion encryptedAssertion = null;
                if (encryptedAssertions != null && encryptedAssertions.size() == 1) {
                    encryptedAssertion = encryptedAssertions.get(0);
                    try {
                        assertion = Util.getDecryptedAssertion(encryptedAssertion,
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                                relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                                MultitenantConstants.SUPER_TENANT_ID,
                                MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

                        username = Util.getUsernameFromAssertion(assertion,
                                relyingPartyObject.getSSOProperty(SSOConstants.LOGIN_USERNAME_ATTRIBUTE));

                    } catch (Exception e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Assertion decryption failure : ", e);
                        }
                        throw new Exception("Unable to decrypt the SAML2 Assertion");
                    }
                } else {
                    log.error("SAML Response contains invalid number of assertions.");
                }

            } else {
                List<Assertion> assertions = samlResponse.getAssertions();

                // extract the username
                if (assertions != null && assertions.size() == 1) {
                    username = Util.getUsernameFromAssertion(assertions.get(0),
                            relyingPartyObject.getSSOProperty(SSOConstants.LOGIN_USERNAME_ATTRIBUTE));
                } else {
                    log.error("SAML Response contains invalid number of assertions.");
                }
            }

        }
        if (username == null) {
            throw new Exception("Failed to get subject assertion from SAML response.");
        }
        return username;
    }

    /**
     * Set SSO Configuration key,values
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @throws ScriptException
     */
    public static void jsFunction_setProperty(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        int argLength = args.length;
        if (argLength != 2 || !(args[0] instanceof String) || !(args[1] instanceof String)) {
            throw new ScriptException("Invalid arguments when setting sso configuration values.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        synchronized (SAMLSSORelyingPartyObject.class) {
            if (StringUtils.isEmpty(relyingPartyObject.getSSOProperty((String) args[0]))) {
                relyingPartyObject.setSSOProperty((String) args[0], (String) args[1]);
                if (log.isDebugEnabled()) {
                    log.debug("Configured SSO relying party object with key : " + args[0] + " value : " + args[1]
                            + " for issuer : " + relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID));
                }
            }
        }

    }

    /**
     * Check if the browser session is valid. If user is log out from any sso service provider,
     * user session is invalidated.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws ScriptException
     */
    public static boolean jsFunction_isSessionAuthenticated(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Session id is missing.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        if (relyingPartyObject.isSessionIdExists((String) args[0])) {
            if (log.isDebugEnabled()) {
                log.debug("Browser session is valid..");
            }
            return true;
        }
        return false;

    }

    public static String jsFunction_getIdentitySessionId(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws ScriptException {
        String identitySession = null;
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Session id is missing.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        SessionInfo sessionInfo = relyingPartyObject.getSessionInfo((String) args[0]);
        if (sessionInfo != null) {
            identitySession = sessionInfo.getSessionId();
        }
        return identitySession;
    }

    public static String jsFunction_getLoggedInUser(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Session id is missing.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        SessionInfo sessionInfo = relyingPartyObject.getSessionInfo((String) args[0]);
        String loggedInUser = null;
        if (sessionInfo != null && sessionInfo.getLoggedInUser() != null) {
            loggedInUser = sessionInfo.getLoggedInUser();
        }
        if (log.isDebugEnabled()) {
            log.debug("Logged in user is" + loggedInUser);
        }
        return loggedInUser;

    }

    /**
     * Invalidate current browser authenticated session based on SAML log out request session index value.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @throws Exception
     */
    public static void jsFunction_invalidateSessionBySAMLResponse(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. SAML log out request is missing.");
        }
        if (log.isDebugEnabled()) {
            log.debug(
                    "jsFunction_invalidateSessionBySAMLResponse===================Invalidating the authenticated session ");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String logoutRequest = StringEscapeUtils.unescapeXml((String) args[0]);
        String decodedString = isEncoded ? Util.decode(logoutRequest) : logoutRequest;
        XMLObject samlObject = Util.unmarshall(decodedString);
        String sessionIndex = null;
        if (samlObject instanceof LogoutRequest) {
            // if log out request
            LogoutRequest samlLogoutRequest = (LogoutRequest) samlObject;
            List<SessionIndex> sessionIndexes = samlLogoutRequest.getSessionIndexes();
            if (sessionIndexes != null && sessionIndexes.size() > 0) {
                sessionIndex = sessionIndexes.get(0).getSessionIndex();
            }
        }

        if (sessionIndex == null) {
            log.debug("No session index found in authentication statement in SAML response.");
        } else {
            relyingPartyObject.handleLogout(sessionIndex);
        }

    }

    /**
     * Invalidate current browser authenticated session based on session id.
     * Session will be invalidated after user log out request get succeeded.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @throws Exception
     */
    public static void jsFunction_invalidateSessionBySessionId(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Session id is missing.");
        }
        if (log.isDebugEnabled()) {
            log.debug(
                    "jsFunction_invalidateSessionBySessionId===================Invalidating the authenticated session ");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String sessionId = (String) args[0];
        String sessionIndex = relyingPartyObject.getSessionIndex(sessionId);
        if (sessionIndex != null) {
            relyingPartyObject.handleLogout(sessionIndex);
        } else {
            relyingPartyObject.handleLogoutBySessionId(sessionId);
        }
    }

    /**
     * Set the current session as authenticated by mapping with current session id to session index.
     *
     * @param cx
     * @param thisObj
     * @param args    -args[0]- current session id, args[1]-SAML response
     * @param funObj
     * @throws Exception
     */
    public static void jsFunction_setSessionAuthenticated(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 3 || !(args[0] instanceof String) || !(args[1] instanceof String)
                || !(args[2] instanceof SessionHostObject)) {
            throw new ScriptException(
                    "Invalid argument. Current session id, SAML response and Session are missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[1]) : (String) args[1];
        XMLObject samlObject = Util.unmarshall(decodedString);
        String sessionIndex = null;
        String username = null;
        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;
            String isAssertionEncryptionEnabled = relyingPartyObject
                    .getSSOProperty(SSOConstants.ASSERTIONENCRYPTIONENABLED);
            Assertion assertion = null;

            if ("true".equals(isAssertionEncryptionEnabled)) {
                List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
                EncryptedAssertion encryptedAssertion = null;
                if (encryptedAssertions != null && encryptedAssertions.size() == 1) {
                    encryptedAssertion = encryptedAssertions.get(0);
                    try {
                        assertion = Util.getDecryptedAssertion(encryptedAssertion,
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                                relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                                MultitenantConstants.SUPER_TENANT_ID,
                                MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

                    } catch (Exception e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Assertion decryption failure : ", e);
                        }
                        throw new Exception("Unable to decrypt the SAML2 Assertion");
                    }
                }

            } else {
                List<Assertion> assertions = samlResponse.getAssertions();
                // extract the session index
                if (assertions != null && assertions.size() == 1) {
                    assertion = assertions.get(0);
                } else {
                    throw new ScriptException("SAML Response contains invalid number of assertions.");
                }

            }
            List<AuthnStatement> authenticationStatements = assertion.getAuthnStatements();
            AuthnStatement authnStatement = authenticationStatements.get(0);
            if (authnStatement != null) {
                if (authnStatement.getSessionIndex() != null) {
                    sessionIndex = authnStatement.getSessionIndex();
                }
            }
            username = Util.getUsernameFromAssertion(assertion,
                    relyingPartyObject.getSSOProperty(SSOConstants.LOGIN_USERNAME_ATTRIBUTE));

        }
        if (sessionIndex == null) {
            log.debug("No session index found in authentication statement in SAML response.");
        }
        if (username == null) {
            throw new Exception("Failed to get subject assertion from SAML response.");
        }

        SessionInfo sessionInfo = new SessionInfo((String) args[0]);
        if (sessionIndex != null) {
            sessionInfo.setSessionIndex(sessionIndex);
        }
        sessionInfo.setLoggedInUser(username);
        if (log.isDebugEnabled()) {
            log.debug("Encoded SAML token that is set on session info is " + args[1]);
        }
        sessionInfo.setSamlToken((String) args[1]);//We expect an encoded SamlToken here.
        SessionHostObject sho = (SessionHostObject) args[2];
        sessionInfo.setSessionHostObject(sho);
        relyingPartyObject.addSessionInfo(sessionInfo);
        //relyingPartyObject.addSessionInfo(sessionIndex, SAMLSSORelyingPartyObject.sho);

        if (sessionIndex != null) {
            relyingPartyObject.addSessionToSessionIndexMap(sessionIndex, sho);
        }
    }

    /**
     * Get SSO configuration properties.
     *
     * @param cx
     * @param thisObj
     * @param args    -args[0]-configuration key
     * @param funObj
     * @return
     * @throws ScriptException
     */
    public static String jsFunction_getProperty(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. SSO configuratin key is missing.");
        }
        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        return relyingPartyObject.getSSOProperty((String) args[0]);

    }

    /**
     * Set relay state property with requested uri.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @throws ScriptException
     */
    public static void jsFunction_setRelayStateProperty(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws ScriptException {
        int argLength = args.length;
        if (argLength != 2 || !(args[0] instanceof String) || !(args[1] instanceof String)) {
            throw new ScriptException("Invalid argument. RelayState and requested URI are missing.");
        }
        if (log.isDebugEnabled()) {
            log.debug("Added relay state properties" + (String) args[0] + ":" + (String) args[1]);
        }
        relayStateMap.put((String) args[0], (String) args[1]);

    }

    /**
     * Get requested URI for relay state. And relay state value is removed, as relay state is unique and onetime value.
     *
     * @param cx
     * @param thisObj
     * @param args
     * @param funObj
     * @return
     * @throws ScriptException
     */
    public static String jsFunction_getRelayStateProperty(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws ScriptException {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Relay state value is missing.");
        }
        String requestedURI = relayStateMap.get((String) args[0]);
        if (log.isDebugEnabled()) {
            log.debug("Requested URI:" + relayStateMap.get((String) args[0]));
        }
        relayStateMap.remove((String) args[0]);
        return requestedURI;

    }

    public static String jsFunction_xmlDecode(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Relay state value is missing.");
        }
        String xmlString = (String) args[0];
        xmlString = xmlString.replaceAll("&gt;", ">").replaceAll("&lt;", "<");
        //                .replaceAll("&apos;", "'").replaceAll("&quot;", "\"").replaceAll("&amp;", "&");

        return xmlString;

    }

    public static String jsFunction_xmlEncode(Context cx, Scriptable thisObj, Object[] args, Function funObj)
            throws ScriptException {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. Relay state value is missing.");
        }
        String xmlString = (String) args[0];
        xmlString = xmlString.replaceAll(">", "&gt;").replaceAll("<", "&lt;");
        //                .replaceAll("'","&apos;").replaceAll("\"","&quot;").replaceAll("&","&amp;");

        return xmlString;

    }

    private String getSSOProperty(String key) {
        return ssoConfigProperties.getProperty(key);
    }

    private void setSSOProperty(String key, String value) {

        if (key.equalsIgnoreCase(SSOConstants.SAML_DEFLATE) && value == null) {
            ssoConfigProperties.put(key, SSOConstants.DEFAULT_DEFLATE_VALUE);
        } else if (key.equalsIgnoreCase(SSOConstants.SAML_ENCODED) && value == null) {
            ssoConfigProperties.put(key, SSOConstants.DEFAULT_ENCODED_VALUE);
        } else {
            ssoConfigProperties.put(key, value);
        }
    }

    /**
     * Decode xml
     *
     * @param xmlString
     * @return
     */
    public static String decode(String xmlString) {
        xmlString = xmlString.replaceAll("&gt;", ">").replaceAll("&lt;", "<").replaceAll("&apos;", "'")
                .replaceAll("&quot;", "\"").replaceAll("&amp;", "&");

        return xmlString;
    }

    /**
     * Add current browser session with session index.
     */
    private void addSessionInfo(SessionInfo sessionInfo) {
        sessionIdMap.put(sessionInfo.getSessionId(), sessionInfo);
    }

    /**
     * Remove current browser session(s) mapped with session index given.
     *
     * @param sessionIndex
     */
    private void invalidateSessionBySessionIndex(String sessionIndex) {
        if (sessionIndex != null) {
            for (Map.Entry entry : sessionIdMap.entrySet()) {
                if (entry.getValue() instanceof SessionInfo) {
                    SessionInfo sessionInfo = (SessionInfo) entry.getValue();
                    if (sessionInfo != null && sessionIndex.equals(sessionInfo.getSessionIndex())) {
                        if (log.isDebugEnabled()) {
                            log.debug("Session Index exists,thus invalidating session by session index"
                                    + sessionInfo.getSessionIndex());
                        }
                        sessionIdMap.remove(entry.getKey());
                    }
                }
            }
        }
    }

    private void invalidateSessionBySessionId(String sessionId) {
        if (log.isDebugEnabled()) {
            log.debug("Session Id exists,thus invalidating it " + sessionId);
        }
        sessionIdMap.remove(sessionId);
    }

    private boolean isSessionIdExists(String sessionId) throws Exception {
        for (Map.Entry<String, Set<SessionHostObject>> entry : sessionIndexMap.entrySet()) {
            Set<SessionHostObject> sessions = new HashSet<SessionHostObject>(entry.getValue());
            for (SessionHostObject session : sessions) {
                Object[] args = new Object[0];
                if (session != null
                        && sessionId.equals(SessionHostObject.jsFunction_getId(null, session, args, null))) {
                    if (log.isDebugEnabled()) {
                        log.debug("Session Id exists:"
                                + SessionHostObject.jsFunction_getId(null, session, args, null));
                    }
                    return true;
                }
            }
        }
        log.debug("Session Id does not exist in sessionIndexMap. Now searching in sessionIdMap.");
        if (getSessionInfo(sessionId) != null) {
            log.debug("Session Id exists in sessionIdMap : " + sessionId);
            return true;
        }

        log.debug("Session Id does not exist in sessionIdMap : " + sessionId);
        return false;
    }

    private String getSessionIndex(String sessionId) throws Exception {
        Iterator<Map.Entry<String, Set<SessionHostObject>>> iterator = sessionIndexMap.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Set<SessionHostObject>> entry = iterator.next();
            Set<SessionHostObject> sessions = new HashSet<SessionHostObject>(entry.getValue());
            for (SessionHostObject session : sessions) {
                Object[] args = new Object[0];
                if (session != null
                        && sessionId.equals(SessionHostObject.jsFunction_getId(null, session, args, null))) {
                    if (log.isDebugEnabled()) {
                        log.debug("Get corresponding session index " + entry.getKey() + "by passing session id"
                                + sessionId);
                    }
                    return entry.getKey();
                }
            }
            //}
        }
        return null;
    }

    protected SessionInfo getSessionInfo(String sessionId) {
        if (sessionIdMap != null) {
            return sessionIdMap.get(sessionId);
        }
        return null;
    }

    private static String getSSOSamlEncodingProperty(SAMLSSORelyingPartyObject relyingPartyObject) {

        if (relyingPartyObject.getSSOProperty(SSOConstants.SAML_ENCODED) == null) {
            return SSOConstants.DEFAULT_ENCODED_VALUE;
        } else {
            return relyingPartyObject.getSSOProperty(SSOConstants.SAML_ENCODED);
        }
    }

    private static String getSSOSamlDeflateProperty(SAMLSSORelyingPartyObject relyingPartyObject) {

        if (relyingPartyObject.getSSOProperty(SSOConstants.SAML_DEFLATE) == null) {
            return SSOConstants.DEFAULT_DEFLATE_VALUE;
        } else {
            return relyingPartyObject.getSSOProperty(SSOConstants.SAML_DEFLATE);
        }
    }

    private void addSessionToSessionIndexMap(String sessionIndex, SessionHostObject sho) {
        if (log.isDebugEnabled()) {
            log.debug("Added session index:" + sessionIndex);
        }
        if (sessionIndexMap.containsKey(sessionIndex)) {
            Set<SessionHostObject> sessionSet = sessionIndexMap.get(sessionIndex);
            sessionSet.add(sho);
        } else {
            Set<SessionHostObject> sessionSet = new HashSet<SessionHostObject>();
            sessionSet.add(sho);
            sessionIndexMap.put(sessionIndex, sessionSet);
        }
    }

    /**
     * Remove current browser session(s) mapped with session index given.
     *
     * @param sessionId
     */
    private void invalidateSessionById(String sessionId) throws Exception {
        Iterator<Map.Entry<String, Set<SessionHostObject>>> iterator = sessionIndexMap.entrySet().iterator();
        while (iterator.hasNext()) {
            boolean remove = false;
            Map.Entry<String, Set<SessionHostObject>> entry = iterator.next();
            Set<SessionHostObject> sessions = new HashSet<SessionHostObject>(entry.getValue());
            for (SessionHostObject session : sessions) {
                Object[] args = new Object[0];
                if (session != null
                        && sessionId.equals(SessionHostObject.jsFunction_getId(null, session, args, null))) {
                    if (log.isDebugEnabled()) {
                        log.debug("Session Id exists.Invalidating that value"
                                + SessionHostObject.jsFunction_getId(null, session, args, null));
                    }
                    remove = true;
                }
                if (remove) {
                    sessionIndexMap.remove(entry.getKey());
                    remove = false;
                }
            }
        }
    }

    public void removeSession(String sessionIndex) {
        if (sessionIndexMap != null && sessionIndex != null) {
            if (sessionIndexMap.containsKey(sessionIndex)) {
                if (log.isDebugEnabled()) {
                    log.debug("Remove session index from SessionIndexMap:" + sessionIndex);
                }
                sessionIndexMap.remove(sessionIndex);
            }
        }
    }

    public void clearSessionsSet() {

        Date now = new Date();
        long lastAccessTime;
        boolean hasExpired;
        Iterator<Map.Entry<String, Set<SessionHostObject>>> iterator = sessionIndexMap.entrySet().iterator();
        while (iterator.hasNext()) {
            hasExpired = true;
            lastAccessTime = 0;

            Map.Entry<String, Set<SessionHostObject>> entry = iterator.next();
            Set<SessionHostObject> sessions = new HashSet<SessionHostObject>(entry.getValue());
            if (log.isDebugEnabled()) {
                log.debug("Cleanup: Checking session object status for  " + entry.getKey());
            }
            //check atleast one hostobject is still active.
            for (SessionHostObject session : sessions) {
                Object[] args = new Object[0];
                try {
                    lastAccessTime = SessionHostObject.jsFunction_getLastAccessedTime(null, session, new Object[0],
                            null);
                    //createdTime = SessionHostObject.jsFunction_getCreationTime(null, session, new Object[0], null);
                } catch (Exception ignored) {

                }
                if (lastAccessTime + maxInactiveInterval > now.getTime()) {
                    if (log.isDebugEnabled()) {
                        log.debug("Cleanup: Contains active session hostobject");
                    }
                    hasExpired = false;
                    break;
                }
            }
            if (hasExpired) {
                if (log.isDebugEnabled()) {
                    log.debug("Cleanup: Removing expired session info for " + entry.getKey());
                }
                removeSession(entry.getKey());
                sessionIdMap.remove(entry.getKey());
            }

        }
    }

    public SessionHostObject getSession(String sessionIndex) {
        if (sessionIndexMap != null) {
            return (SessionHostObject) sessionIndexMap.get(sessionIndex);
        }
        return null;
    }

    public void handleLogout(String sessionIndex) {
        if (log.isDebugEnabled()) {
            log.debug("session index map value:" + sessionIndexMap);
            if (sessionIndexMap != null) {
                log.debug("session index map size:" + sessionIndexMap.size());
                log.debug("session index map :" + sessionIndexMap);
                log.debug("session index :" + sessionIndex);
                // log.debug("Is session index map contains session index:"+sessionIndexMap.containsKey(sessionIndex));
            }
        }
        if ((sessionIndexMap != null && sessionIndex != null) && (!sessionIndexMap.containsKey(sessionIndex))) {
            //send cluster message
            sendSessionInvalidationClusterMessage(sessionIndex);
            return;
        }

        clearSessionData(sessionIndex);

        if (log.isDebugEnabled()) {
            log.debug("Cleared authenticated session index:" + sessionIndex + "in handle logout method");
        }
    }

    public void handleLogoutBySessionId(String sessionId) {
        clearSessionDataFromSessionId(sessionId);

        if (log.isDebugEnabled()) {
            log.debug("Cleared authenticated session id:" + sessionId + "in handle logout method");
        }
    }

    public void handleClusterLogout(String sessionIndex) {
        if (log.isDebugEnabled()) {
            log.debug("session index map value:" + sessionIndexMap);
            if (sessionIndexMap != null) {
                log.debug("session index map size:" + sessionIndexMap.size());
                log.debug("session index map :" + sessionIndexMap);
                log.debug("session index :" + sessionIndex);
                // log.debug("Is session index map contains session index:"+sessionIndexMap.containsKey(sessionIndex));
            }
        }
        if ((sessionIndexMap != null && sessionIndex != null) && (!sessionIndexMap.containsKey(sessionIndex))) {
            return;
        }

        clearSessionData(sessionIndex);

        if (log.isDebugEnabled()) {
            log.debug("Cleared authenticated session index:" + sessionIndex + "in handle logout method");
        }
    }

    private void clearSessionData(String sessionIndex) {
        try {
            ssho.invalidateSessionBySessionIndex(sessionIndex);

            if (sessionIndexMap != null && sessionIndex != null) {
                Set<SessionHostObject> sessionList = sessionIndexMap.get(sessionIndex);
                Object[] args = new Object[0];
                if (sessionList != null) {
                    for (SessionHostObject session : sessionList) {
                        if (SessionHostObject.jsFunction_getId(null, session, args, null) != null) {
                            try {
                                SessionHostObject.jsFunction_invalidate(null, session, args, null);
                            } catch (Exception ex) {
                                if (ex.getMessage().contains("Session already invalidated")) { // can be ignored
                                    log.info(ex.getMessage());
                                } else {
                                    throw ex;
                                }
                            }
                        }
                    }
                }
            }
            removeSession(sessionIndex);
            clearSessionsSet();
        } catch (Exception ignored) {
            if (log.isDebugEnabled()) {
                log.debug(ignored.getMessage());
            }
            removeSession(sessionIndex);
            clearSessionsSet();
        }
    }

    private void clearSessionDataFromSessionId(String sessionIdToClear) {
        SessionInfo sessionInfo = getSessionInfo(sessionIdToClear);
        if (sessionInfo != null) {
            SessionHostObject sessionHostObject = sessionInfo.getSessionHostObject();
            if (sessionHostObject != null) {
                try {
                    Object[] args = new Object[0];
                    SessionHostObject.jsFunction_invalidate(null, sessionHostObject, args, null);
                } catch (Exception ex) {
                    //catches generic exception since we do not need to stop the
                    // process due to invalidating one single session
                    if (ex.getMessage().contains("Session already invalidated")) { // can be ignored
                        log.info(ex.getMessage());
                    } else {
                        //log the error and continues since this does not brake the flow.
                        log.error("Error while invalidating the session " + sessionIdToClear, ex);
                    }
                }
            }
        }
        ssho.invalidateSessionBySessionId(sessionIdToClear);
        if (log.isDebugEnabled()) {
            log.debug("Cleared authenticated session index:" + sessionIdToClear + "in handle logout method");
        }
    }

    public void sendSessionInvalidationClusterMessage(String sessionIndex) {

        SessionClusterMessage clusterMessage = new SessionClusterMessage();
        clusterMessage.setMessageId(UUID.randomUUID());
        clusterMessage.setSessionIndex(sessionIndex);
        ClusteringAgent clusteringAgent = SSOHostObjectDataHolder.getInstance().getConfigurationContextService()
                .getServerConfigContext().getAxisConfiguration().getClusteringAgent();
        if (clusteringAgent != null) {
            int numberOfRetries = 0;
            while (numberOfRetries < 60) {
                try {
                    clusteringAgent.sendMessage(clusterMessage, true);
                    log.info("Sent [" + clusterMessage + "]");
                    break;
                } catch (ClusteringFault e) {
                    numberOfRetries++;
                    if (numberOfRetries < 60) {
                        log.warn(
                                "Could not send SSOSessionInvalidationClusterMessage. Retry will be attempted in 2s. Request: "
                                        + clusterMessage,
                                e);
                    } else {
                        log.error(
                                "Could not send SSOSessionInvalidationClusterMessage. Several retries failed. Request:"
                                        + clusterMessage,
                                e);
                    }
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        }
    }

    /**
     * Validates the 'Not Before' and 'Not On Or After' conditions of the SAML Assertion
     *
     * @param assertion SAML Assertion element
     * @throws ScriptException
     */
    private boolean validateAssertionValidityPeriod(Assertion assertion, int timeStampSkewInSeconds)
            throws ScriptException {
        DateTime validFrom = assertion.getConditions().getNotBefore();
        DateTime validTill = assertion.getConditions().getNotOnOrAfter();

        if (validFrom != null && validFrom.minusSeconds(timeStampSkewInSeconds).isAfterNow()) {
            log.error("Failed to meet SAML Assertion Condition 'Not Before'");
            return false;
        }

        if (validTill != null && validTill.plusSeconds(timeStampSkewInSeconds).isBeforeNow()) {
            log.error("Failed to meet SAML Assertion Condition 'Not On Or After'");
            return false;
        }

        if (validFrom != null && validTill != null && validFrom.isAfter(validTill)) {
            log.error(
                    "SAML Assertion Condition 'Not Before' must be less than the " + "value of 'Not On Or After'");
            return false;
        }

        return true;
    }

    /**
     * Validate the AudienceRestriction of SAML2 Response
     *
     * @param assertion SAML2 Assertion
     * @return validity
     */
    private boolean validateAudienceRestriction(Assertion assertion, String issuerId) throws ScriptException {

        if (assertion != null) {
            Conditions conditions = assertion.getConditions();
            if (conditions != null) {
                List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
                if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
                    for (AudienceRestriction audienceRestriction : audienceRestrictions) {
                        if (CollectionUtils.isNotEmpty(audienceRestriction.getAudiences())) {
                            boolean audienceFound = false;
                            for (Audience audience : audienceRestriction.getAudiences()) {
                                if (issuerId.equals(audience.getAudienceURI())) {
                                    audienceFound = true;
                                    break;
                                }
                            }
                            if (!audienceFound) {
                                log.error("SAML Assertion Audience Restriction validation failed.");
                                return false;
                            }
                        } else {
                            log.error("SAML Response's AudienceRestriction doesn't contain Audiences.");
                            return false;
                        }
                    }
                } else {
                    log.error("SAML Response doesn't contain AudienceRestrictions.");

                    return false;
                }
            } else {
                log.error("SAML Response doesn't contain Conditions.");

                return false;
            }
        }
        return true;
    }

    /**
     * @param cx
     * @param thisObj
     * @param args    -args[0]- SAML response xml
     * @param funObj
     * @return
     * @throws Exception
     */
    public static boolean jsFunction_validateAssertionSignature(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {

        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. SAML response is missing.");
        }

        String decodedString = Util.decode((String) args[0]);

        XMLObject samlObject = Util.unmarshall(decodedString);
        String tenantDomain = Util.getDomainName(samlObject);

        int tenantId = Util.getRealmService().getTenantManager().getTenantId(tenantDomain);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;
            SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;

            Assertion assertion = null;
            String isAssertionEncryptionEnabled = relyingPartyObject
                    .getSSOProperty(SSOConstants.ASSERTIONENCRYPTIONENABLED);

            if ("true".equals(isAssertionEncryptionEnabled)) {
                List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
                EncryptedAssertion encryptedAssertion = null;
                if (encryptedAssertions != null && encryptedAssertions.size() == 1) {
                    encryptedAssertion = encryptedAssertions.get(0);
                    try {
                        assertion = Util.getDecryptedAssertion(encryptedAssertion,
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                                relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                                MultitenantConstants.SUPER_TENANT_ID,
                                MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

                    } catch (Exception e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Assertion decryption failure : ", e);
                        }
                        throw new Exception("Unable to decrypt the SAML2 Assertion");
                    }
                }
            } else {
                List<Assertion> assertions = samlResponse.getAssertions();
                if (assertions != null && assertions.size() == 1) {
                    assertion = assertions.get(0);
                } else {
                    throw new ScriptException("SAML Response contains invalid number of assertions.");
                }
            }

            boolean sigValid = false;
            // validate the assertion signature
            Signature assertionSignature = assertion.getSignature();
            try {
                //Try and validate the signature using the super tenant key store.

                if (assertionSignature == null) {
                    log.error("SAMLAssertion signing is enabled, but signature element not found in "
                            + "SAML Assertion element.");

                    return false;
                }
                sigValid = Util.validateSignature(assertionSignature,
                        relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                        relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                        relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                        MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

            } catch (SignatureVerificationFailure e) {
                //do nothing at this point since we want to verify signature using the tenant key-store as well.
                if (log.isDebugEnabled()) {
                    log.debug("Signature verification failed with Super-Tenant Key Store", e);
                }
            } //
              //If not success, try and validate the signature using tenant key store.
            if (!sigValid && !MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) {
                try {
                    sigValid = Util.validateSignature(assertionSignature,
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                            relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS), tenantId, tenantDomain);

                } catch (SignatureVerificationFailure e) {
                    log.error("Signature Verification Failed using super tenant and tenant key stores", e);
                    return false;
                }
            }

            return sigValid;
        }
        if (log.isWarnEnabled()) {
            log.warn("SAML response in signature validation is not a SAML Response.");
        }
        return false;

    }

    /* Validate the audience restrictions values in SAML response.
            *
            * @param cx
    * @param thisObj
    * @param args
    * @param funObj
    * @return
           * @throws Exception
    */
    public static boolean jsFunction_validateAudienceRestrictions(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. The SAML response is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;
            Assertion assertion = null;
            String isAssertionEncryptionEnabled = relyingPartyObject
                    .getSSOProperty(SSOConstants.ASSERTIONENCRYPTIONENABLED);
            String issuerId = relyingPartyObject.getSSOProperty(SSOConstants.ISSUER_ID);
            if ("true".equals(isAssertionEncryptionEnabled)) {
                List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
                EncryptedAssertion encryptedAssertion = null;
                // Validate the audience restrictions
                if (encryptedAssertions != null && encryptedAssertions.size() == 1) {
                    encryptedAssertion = encryptedAssertions.get(0);
                    try {
                        assertion = Util.getDecryptedAssertion(encryptedAssertion,
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                                relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                                relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                                MultitenantConstants.SUPER_TENANT_ID,
                                MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

                    } catch (Exception e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Assertion decryption failure : ", e);
                        }
                        throw new Exception("Unable to decrypt the SAML2 Assertion");
                    }
                    return relyingPartyObject.validateAudienceRestriction(assertion, issuerId);
                } else {
                    throw new ScriptException("SAML Response contains invalid number of assertions.");
                }
            }
            List<Assertion> assertions = samlResponse.getAssertions();

            // Validate the audience restrictions
            if (assertions != null && assertions.size() == 1) {
                return relyingPartyObject.validateAudienceRestriction(assertions.get(0), issuerId);
            } else {
                throw new ScriptException("SAML Response contains invalid number of assertions.");
            }

        }
        return false;
    }

    /* Validate the assertion validity period in SAML response.
          *
          * @param cx
      * @param thisObj
      * @param args
      * @param funObj
      * @return
         * @throws Exception
      */
    public static boolean jsFunction_validateAssertionValidityPeriod(Context cx, Scriptable thisObj, Object[] args,
            Function funObj) throws Exception {
        int argLength = args.length;
        if (argLength != 1 || !(args[0] instanceof String)) {
            throw new ScriptException("Invalid argument. The SAML response is missing.");
        }

        SAMLSSORelyingPartyObject relyingPartyObject = (SAMLSSORelyingPartyObject) thisObj;
        String encoded = getSSOSamlEncodingProperty(relyingPartyObject);
        boolean isEncoded = true;
        if (encoded != null) {
            try {
                isEncoded = Boolean.parseBoolean(encoded);
            } catch (Exception e) {
                throw new ScriptException(
                        "Invalid property value found for " + "" + SSOConstants.SAML_ENCODED + " " + encoded);
            }
        }

        String decodedString = isEncoded ? Util.decode((String) args[0]) : (String) args[0];
        XMLObject samlObject = Util.unmarshall(decodedString);
        String timestampSkewString = relyingPartyObject.getSSOProperty(SSOConstants.TIMESTAMP_SKEW_IN_SECONDS);
        int timestampSkew;
        if (timestampSkewString != null && !timestampSkewString.isEmpty()) {
            timestampSkew = Integer.parseInt(timestampSkewString);
        } else {
            timestampSkew = 300;
        }

        if (samlObject instanceof Response) {
            Response samlResponse = (Response) samlObject;
            Assertion assertion = null;
            String isAssertionEncryptionEnabled = relyingPartyObject
                    .getSSOProperty(SSOConstants.ASSERTIONENCRYPTIONENABLED);
            if ("true".equals(isAssertionEncryptionEnabled)) {
                List<EncryptedAssertion> encryptedAssertions = samlResponse.getEncryptedAssertions();
                EncryptedAssertion encryptedAssertion;
                encryptedAssertion = encryptedAssertions.get(0);
                try {
                    assertion = Util.getDecryptedAssertion(encryptedAssertion,
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_NAME),
                            relyingPartyObject.getSSOProperty(SSOConstants.KEY_STORE_PASSWORD),
                            relyingPartyObject.getSSOProperty(SSOConstants.IDP_ALIAS),
                            MultitenantConstants.SUPER_TENANT_ID, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME);

                } catch (Exception e) {
                    if (log.isDebugEnabled()) {
                        log.debug("Assertion decryption failure : ", e);
                    }
                    throw new Exception("Unable to decrypt the SAML2 Assertion");
                }
            } else {
                List<Assertion> assertions = samlResponse.getAssertions();
                assertion = assertions.get(0);

            }
            //Validate assertion validity period
            return relyingPartyObject.validateAssertionValidityPeriod(assertion, timestampSkew);

        }
        return false;
    }

}