org.wso2.carbon.appmgt.gateway.handlers.security.saml2.SAML2AuthenticationHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.appmgt.gateway.handlers.security.saml2.SAML2AuthenticationHandler.java

Source

/*
*  Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you 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.appmgt.gateway.handlers.security.saml2;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axis2.AxisFault;
import org.apache.axis2.Constants;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpStatus;
import org.apache.synapse.*;
import org.apache.synapse.core.SynapseEnvironment;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.core.axis2.Axis2Sender;
import org.apache.synapse.rest.AbstractHandler;
import org.apache.synapse.rest.RESTConstants;
import org.apache.synapse.transport.nhttp.NhttpConstants;
import org.apache.synapse.transport.passthru.util.RelayUtils;
import org.joda.time.DateTime;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import org.opensaml.Configuration;
import org.opensaml.DefaultBootstrap;
import org.opensaml.common.SAMLVersion;
import org.opensaml.saml2.core.*;
import org.opensaml.saml2.core.impl.*;
import org.opensaml.xml.ConfigurationException;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.Marshaller;
import org.opensaml.xml.io.MarshallingException;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.XMLHelper;
import org.w3c.dom.Element;
import org.wso2.carbon.appmgt.api.AppManagementException;
import org.wso2.carbon.appmgt.api.model.AuthenticatedIDP;
import org.wso2.carbon.appmgt.gateway.handlers.Utils;
import org.wso2.carbon.appmgt.gateway.handlers.security.APISecurityConstants;
import org.wso2.carbon.appmgt.gateway.handlers.security.APISecurityException;
import org.wso2.carbon.appmgt.gateway.handlers.security.Authenticator;
import org.wso2.carbon.appmgt.gateway.handlers.security.oauth.OAuthAuthenticator;
import org.wso2.carbon.appmgt.gateway.internal.ServiceReferenceHolder;
import org.wso2.carbon.appmgt.impl.AppMConstants;
import org.wso2.carbon.appmgt.impl.dao.AppMDAO;
import org.wso2.carbon.appmgt.impl.dto.SAMLTokenInfoDTO;
import org.wso2.carbon.appmgt.impl.dto.VerbInfoDTO;
import org.wso2.carbon.appmgt.impl.dto.WebAppInfoDTO;
import org.wso2.carbon.appmgt.impl.utils.AppContextCacheUtil;
import org.wso2.carbon.appmgt.impl.utils.NamedMatchList;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.sso.saml.util.SAMLSSOUtil;

import javax.cache.Cache;
import javax.cache.Caching;
import javax.xml.namespace.QName;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;

public class SAML2AuthenticationHandler extends AbstractHandler implements ManagedLifecycle {

    private static final Log log = LogFactory.getLog(SAML2AuthenticationHandler.class);

    // Names of the attributes which IDP sends back after authentication takes place.
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE = "SAMLResponse";
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_AUTHENTICATED_IDPS = "AuthenticatedIdPs";
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION = "Assertion";
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION_NOT_ON_OR_AFTER = "NotOnOrAfter";

    // The element name which IDP uses when it issues an SLO request to the SP.
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_SAML_REQUEST = "SAMLRequest";

    // Names of the attributes which IDP sends back containing the relay state
    private static final String IDP_CALLBACK_ATTRIBUTE_NAME_RELAY_STATE = "RelayState";

    private volatile Authenticator authenticator;
    private volatile SAML2Authenticator saml2Authenticator;
    private volatile WebAppInfoDTO webAppInfoDTO;

    public void init(SynapseEnvironment synapseEnvironment) {
        if (log.isDebugEnabled()) {
            log.debug("Initializing WebApp authentication handler instance");
        }
        String authenticatorType = ServiceReferenceHolder.getInstance().getAPIManagerConfiguration()
                .getFirstProperty(APISecurityConstants.API_SECURITY_AUTHENTICATOR);
        if (authenticatorType == null) {
            authenticatorType = OAuthAuthenticator.class.getName();
        } else if ("SAML2".equals(authenticatorType)) {
            authenticatorType = SAML2Authenticator.class.getName();
        }
        try {
            authenticator = (Authenticator) Class.forName(OAuthAuthenticator.class.getName()).newInstance();
            saml2Authenticator = (SAML2Authenticator) Class.forName(authenticatorType).newInstance();
        } catch (Exception e) {
            // Just throw it here - Synapse will handle it
            throw new SynapseException("Error while initializing authenticator of " + "type: " + authenticatorType);
        }

        //Initialize the context cache by calling AppContextCacheUtil to pre-load cache
        AppContextCacheUtil.getTenantContextVersionUrlMap();

        authenticator.init(synapseEnvironment);
        saml2Authenticator.init(synapseEnvironment);
    }

    /**
     * Applies SAML 2 authentication if SSO is enabled.
     * @param messageContext
     * @return
     */
    public boolean handleRequest(MessageContext messageContext) {

        // application context
        String webAppContext = (String) messageContext.getProperty(RESTConstants.REST_API_CONTEXT);
        // application version
        String webAppVersion = (String) messageContext.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);
        // http verb (eg: GET,POST)
        String httpVerb = (String) ((Axis2MessageContext) messageContext).getAxis2MessageContext()
                .getProperty("HTTP_METHOD");
        // request full path (eg: /context/version/pattern)
        String fullReqPath = (String) messageContext.getProperty(RESTConstants.REST_FULL_REQUEST_PATH);

        try {
            // Get App Info related to SSO handling.
            if (webAppInfoDTO == null) {
                WebAppInfoDTO webAppInfoDTO = getSSOInfoForApp(webAppContext, webAppVersion);
                constructAndSetFullyQualifiedSamlIssuerId(messageContext, webAppInfoDTO);
                this.webAppInfoDTO = webAppInfoDTO;
            }

            // If this is an SLO request we need to respond to the client (IDP) without continuing the flow. 
            if (isSLORequestFromIDP(messageContext)) {
                handleSLORequest();
                sendSLOResponse(messageContext);
                return false;
            }

            // check if anonymous mode allowed for the entire app
            boolean isAllowAnonymousApp = isAllowAnonymousApplication();
            // write to messageContext so then the same value can be accessed as a
            // property in other handlers
            messageContext.setProperty(AppMConstants.API_OVERVIEW_ALLOW_ANONYMOUS, isAllowAnonymousApp);
            if (isAllowAnonymousApp) {
                // if anonymous mode is allowed move to next handler
                return true;
            }

            // check if anonymous mode allowed for particular URL pattern
            boolean isAllowAnonymousUrl = isAllowAnonymousUrlPattern(httpVerb, fullReqPath);
            // write to messageContext so then the same value can be accessed as a
            // property in other handlers
            messageContext.setProperty(AppMConstants.API_URI_ALLOW_ANONYMOUS, isAllowAnonymousUrl);
            if (isAllowAnonymousUrl) {
                // if anonymous mode is allowed move to next handler
                return true;
            }

            // If SSO is not enabled skip this authentication handler.
            if (!isSSOEnabled()) {
                return true;
            }

            boolean isAuthorized = false;
            boolean isResourceAccessible = false;

            if (shouldAuthenticateWithCookie(messageContext)) {
                messageContext.setProperty(AppMConstants.APPM_SAML2_CACHE_HIT, 1);
                isAuthorized = handleSecurityUsingCookie(messageContext);
            } else if (shouldAuthenticateWithSAMLResponse(messageContext)) {

                if (log.isDebugEnabled()) {
                    log.debug("Processing SAML response");
                }

                messageContext.setProperty(AppMConstants.APPM_SAML2_CACHE_HIT, 0);

                isAuthorized = handleAuthorizationUsingSAMLResponse(messageContext);

                if (isAuthorized) {

                    //if a relay state is available, redirect to the relay state path
                    if (!redirectToRelayState(messageContext)) {
                        return false;
                    }

                    //Note: When user authenticated, IdP sends the SAMLResponse to gateway as a POST request.
                    //We validate this SAMLResponse and allow request to go to backend.
                    //This is the first request goes to access the web-app which need to go as a GET request
                    //and we need to drop the SAMLResponse goes in the request body as well. Bellow code
                    //segment is to set the HTTP_METHOD as GET and set empty body in request.

                    getAxis2MessageContext(messageContext).setProperty("HTTP_METHOD", "GET");
                    try {
                        SOAPEnvelope env = OMAbstractFactory.getSOAP12Factory().createSOAPEnvelope();
                        env.addChild(OMAbstractFactory.getSOAP12Factory().createSOAPBody());
                        getAxis2MessageContext(messageContext).setEnvelope(env);
                    } catch (AxisFault axisFault) {
                        String msg = "Error occurred while constructing SOAPEnvelope for "
                                + messageContext.getProperty("REST_API_CONTEXT") + "/"
                                + messageContext.getProperty("SYNAPSE_REST_API_VERSION");
                        log.error(msg, axisFault);
                        throw new SynapseException(msg, axisFault);
                    }
                }
            }

            if (isAuthorized) {
                if (!isLogoutRequest(messageContext)) {

                    if (checkResourceAccessible(messageContext)) {
                        setAppmSamlSsoCookie(messageContext);
                    } else {
                        handleAuthFailure(messageContext, new APISecurityException(
                                APISecurityConstants.API_AUTH_FORBIDDEN, "You have no access to this Resource"));
                        return false;
                    }

                    //Include appmSamlSsoCookie to "Cookie" header before request send to backend
                }

                return true;
            } else {
                redirectToIDPLogin(messageContext);
                return false;
            }
        } catch (AppManagementException e) {
            String errorMessage = "Error while handling authentication.";
            log.error(errorMessage);
            throw new SynapseException(errorMessage, e);
        }
    }

    /**
     * redirect the page to relay state location
     *
     * @param messageContext
     * @return if yes : true else false
     */
    private boolean redirectToRelayState(MessageContext messageContext) {
        org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();

        String appmSamlSsoCookie = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);
        Map<String, Object> headers = (Map<String, Object>) axis2MC
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        String cookieString = (String) headers.get(HTTPConstants.HEADER_SET_COOKIE);

        if (cookieString == null) {
            cookieString = AppMConstants.APPM_SAML2_COOKIE + "=" + appmSamlSsoCookie + "; " + "path=" + "/";
        } else {
            cookieString = cookieString + " ;" + "\nSet-Cookie:" + AppMConstants.APPM_SAML2_COOKIE + "="
                    + appmSamlSsoCookie + ";" + " Path=" + "/";
        }
        headers.put(HTTPConstants.HEADER_SET_COOKIE, cookieString);
        messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);

        SOAPBody soapBody = messageContext.getEnvelope().getBody();
        boolean hasRelayState = false; //check if a relay state is available
        String relayStateLocation = ""; //contains replay state location where it should be redirected to
        if (soapBody != null) {
            if (soapBody.getChildren().hasNext()) {
                // Check whether there is a SAML request in the SOAP body.
                relayStateLocation = ((OMElement) ((OMElement) (soapBody.getChildren().next()))
                        .getChildrenWithName(new QName(IDP_CALLBACK_ATTRIBUTE_NAME_RELAY_STATE)).next()).getText();

                if (!"null".equals(relayStateLocation)) {
                    hasRelayState = true;
                }
            }
        }

        // if relay state is available, redirect the request
        if (hasRelayState) {
            axis2MC.setProperty(NhttpConstants.HTTP_SC, "302");
            messageContext.setResponse(true);
            messageContext.setProperty("RESPONSE", "true");
            messageContext.setTo(null);
            axis2MC.removeProperty("NO_ENTITY_BODY");
            String method = (String) axis2MC.getProperty(Constants.Configuration.HTTP_METHOD);

            if (method.matches("^(?!.*(POST|PUT)).*$")) {
                /* If the request was not an entity enclosing request, send a XML response back */
                axis2MC.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/xml");
            }

            /* Always remove the ContentType - Let the formatter do its thing */
            axis2MC.removeProperty(Constants.Configuration.CONTENT_TYPE);
            Map headerMap = (Map) axis2MC.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

            //read value from cache and set location
            headerMap.put("Location", getSAML2RelayStateCache().get(relayStateLocation));

            if (messageContext.getProperty("error_message_type") != null && messageContext
                    .getProperty("error_message_type").toString().equalsIgnoreCase("application/json")) {
                axis2MC.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/json");
            }

            headerMap.remove(HttpHeaders.HOST);
            SOAPEnvelope env = OMAbstractFactory.getSOAP12Factory().createSOAPEnvelope();
            env.addChild(OMAbstractFactory.getSOAP12Factory().createSOAPBody());
            try {
                axis2MC.setEnvelope(env);
            } catch (AxisFault axisFault) {
                axisFault.printStackTrace();
            }

            Axis2Sender.sendBack(messageContext);
            return false;
        }
        return true;
    }

    /**
      * Check if the Anonymous Access is allowed for the overall app
      *
      * @return result : boolean result relevant to registry value
      */
    public boolean isAllowAnonymousApplication() {
        return webAppInfoDTO.getAllowAnonymous();
    }

    /**
     * Check if the Anonymous Access is allowed for the particular URL pattern
     *
     * @return boolean result either anonymous access allowed or not
     * @throws AppManagementException 
     */
    public boolean isAllowAnonymousUrlPattern(String httpVerb, String requestPath) throws AppManagementException {

        // Get App URL Pattern Info
        VerbInfoDTO verbInfoDTO = getVerbInfoForApp(webAppInfoDTO.getContext(), webAppInfoDTO.getVersion());

        if (verbInfoDTO != null && verbInfoDTO.mapAllowAnonymousUrl != null) {

            NamedMatchList<String> matcher = new NamedMatchList<String>();

            for (String pattern : verbInfoDTO.mapAllowAnonymousUrl.keySet()) {
                matcher.add(pattern, pattern);
            }

            String httpVerbAndRequestPath = httpVerb + requestPath;

            String matchedPattern = matcher.match(httpVerbAndRequestPath);

            Boolean allowAnnoymous = verbInfoDTO.mapAllowAnonymousUrl.get(matchedPattern);

            if (allowAnnoymous != null) {
                return allowAnnoymous;
            }

        }

        return false;

    }

    public boolean handleResponse(MessageContext messageContext) {

        try {
            if (isAllowAnonymousApplication()
                    || isAllowAnonymousUrlPattern((String) messageContext.getProperty("REST_METHOD"),
                            (String) messageContext.getProperty(RESTConstants.REST_FULL_REQUEST_PATH))) {
                return true;
            }

            String appmSamlSsoCookie = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);

            if (log.isDebugEnabled()) {
                log.debug("Reading AppMConstants.APPM_SAML2_COOKIE from msg context");
                log.debug(AppMConstants.APPM_SAML2_COOKIE + " : " + appmSamlSsoCookie);
            }

            org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                    .getAxis2MessageContext();
            Map<String, Object> headers = (Map<String, Object>) axis2MC
                    .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
            String cookieString = (String) headers.get(HTTPConstants.HEADER_SET_COOKIE);

            if (log.isDebugEnabled()) {
                log.debug("Exisiting set cookie string in transport headers : " + cookieString);
            }

            if (cookieString == null) {
                cookieString = AppMConstants.APPM_SAML2_COOKIE + "=" + appmSamlSsoCookie + "; " + "path=" + "/";
            } else {
                cookieString = cookieString + " ;" + "\nSet-Cookie:" + AppMConstants.APPM_SAML2_COOKIE + "="
                        + appmSamlSsoCookie + ";" + " Path=" + "/";
            }
            if (log.isDebugEnabled()) {
                log.debug("Updated set cookie string in transport headers : " + cookieString);
            }
            headers.put(HTTPConstants.HEADER_SET_COOKIE, cookieString);
            messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);

            return true;
        } catch (AppManagementException e) {
            String errorMessage = "Error while handling authentication.";
            log.error(errorMessage);
            throw new SynapseException(errorMessage, e);
        }
    }

    public void destroy() {
        if (log.isDebugEnabled()) {
            log.debug("Destroying WebApp authentication handler instance");
        }
    }

    private void sendSLOResponse(MessageContext messageContext) {

        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        Object headers = axis2MessageContext.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

        if (headers != null && headers instanceof Map) {

            @SuppressWarnings("unchecked")
            Map<String, Object> headersMap = (Map<String, Object>) headers;

            headersMap.clear();
            axis2MessageContext.setProperty("HTTP_SC", "200");
            axis2MessageContext.setProperty("NO_ENTITY_BODY", new Boolean("true"));
            messageContext.setProperty("RESPONSE", "true");
            messageContext.setTo(null);
            Axis2Sender.sendBack(messageContext);
        }
    }

    private void handleSLORequest() {
        // TODO Auto-generated method stub

    }

    private boolean isSLORequestFromIDP(MessageContext messageContext) {

        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();

        try {
            RelayUtils.buildMessage(axis2MessageContext);
        } catch (Exception e) {
            String errorMessage = "Error while building the message.";
            log.error(errorMessage, e);
            throw new SynapseException(errorMessage, e);
        }

        SOAPBody soapBody = messageContext.getEnvelope().getBody();

        if (soapBody != null) {

            // Try to get the SAML request in the SOAP body.
            // The expected structure is <body><mediate><SamlRequest></SamlRequest></mediate></body>

            if (soapBody.getChildren().hasNext()) {
                // Check whether there is a SAML request in the SOAP body.
                Iterator possibleSAMLRequestElements = ((OMElement) (soapBody.getChildren().next()))
                        .getChildrenWithName(new QName(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_REQUEST));
                return possibleSAMLRequestElements.hasNext();
            }
        }

        return false;
    }

    /**
     * Checks whether the request should be authenticated using the cookie.
     *
     * @param messageContext
     * @return true if the the request should be authenticated using the cookie, false otherwise.
     */
    private boolean shouldAuthenticateWithCookie(MessageContext messageContext) {

        // Cookie should be in the request and there should be an entry in the cache.
        String cookie = getSAMLCookie(messageContext);

        // Check the availability of cookie in cache.
        if (cookie != null && isSamlResponseInCache(cookie)) {
            return true;
        } else {
            return false;
        }

    }

    /**
     * Checks whether the request should be authenticated using the SAML response from the IDP.
     * @param messageContext
     * @return true if the request should be authenticated using the SAML response from the IDP, false otherwise.
     */
    private boolean shouldAuthenticateWithSAMLResponse(MessageContext messageContext) {
        Map<String, String> idpResponseAttributes = getIDPResponseAttributes(messageContext);
        if (log.isDebugEnabled()) {
            log.debug("shouldAuthenticateWithSAMLResponse" + messageContext);
            log.debug("idpResponseAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE) : "
                    + idpResponseAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE));
        }
        return idpResponseAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE) != null;
    }

    /**
     * Returns the cookie string of the request.
     * @param messageContext
     * @return The cookie string of the request.
     */
    private String getCookieString(MessageContext messageContext) {
        Map<String, String> headers = getTransportHeaders(messageContext);
        return headers.get(HTTPConstants.COOKIE_STRING);
    }

    /**
     * Returns the SAML cookie value.
     * @param messageContext
     * @return SAML cookie value.
     */
    private String getSAMLCookie(MessageContext messageContext) {
        String cookieString = getCookieString(messageContext);
        if (log.isDebugEnabled()) {
            log.debug("Requesting cookie : " + AppMConstants.APPM_SAML2_COOKIE + " value : " + cookieString
                    + " getCookieValue() : " + getCookieValue(cookieString, AppMConstants.APPM_SAML2_COOKIE));
        }
        return getCookieValue(cookieString, AppMConstants.APPM_SAML2_COOKIE);
    }

    /**
     * Checks whether the SAML response is in cache.
     * @param samlResponseKey
     * @return true if the SAML response is in cache, false otherwise.
     */
    private boolean isSamlResponseInCache(String samlResponseKey) {
        return getCachedSAMLResponse(samlResponseKey) != null;
    }

    /**
     * Returns SAML response from cache.
     * @param cacheKey value of appmSamlSsoTokenId cookie value
     * @return The SAML response for the given key if the result Map contains the samlResponse of
     * webApp in cache , null otherwise.
     */
    public String getCachedSAMLResponse(String cacheKey) {
        Object response = getSAML2ConfigCache().get(cacheKey);
        if (response != null) {
            Map<String, SAMLTokenInfoDTO> samlResponsesMap = (HashMap<String, SAMLTokenInfoDTO>) response;
            String samlResponseOfWebApp = null;
            SAMLTokenInfoDTO samlTokenInfoDTO = samlResponsesMap.get(webAppInfoDTO.getSaml2SsoIssuer());

            if (samlTokenInfoDTO != null) {
                samlResponseOfWebApp = samlTokenInfoDTO.getEncodedSamlToken();
            }
            if (samlResponseOfWebApp != null) {
                return samlResponseOfWebApp;
            }
        }

        return null;
    }

    /**
     * Check whether saml token saved in cached is expired.
     *
     * @param cacheKey value of appmSamlSsoTokenId cookie value
     * @return Returns true if saml token is expired. Returns false if saml token is valid.
     */
    public boolean isSamlTokenExpired(String cacheKey) {
        Object response = getSAML2ConfigCache().get(cacheKey);
        if (response != null) {
            Map<String, SAMLTokenInfoDTO> samlResponsesMap = (HashMap<String, SAMLTokenInfoDTO>) response;
            DateTime samlTokenValidity = samlResponsesMap.get(webAppInfoDTO.getSaml2SsoIssuer()).getNotOnOrAfter();

            if (samlTokenValidity != null && samlTokenValidity.compareTo(new DateTime()) < 1) {
                // notOnOrAfter is an expired timestamp
                log.debug("NotOnOrAfter is having an expired timestamp in the cache for the SAML issuer = "
                        + webAppInfoDTO.getSaml2SsoIssuer());
                return true;
            }
        }

        return false;
    }

    /**
     * Returns already cached userRoles.
     * @param cacheKey
     * @return The SAML response for the given key if the response is in cache, null otherwise.
     */
    public String getCachedUserRoles(String cacheKey) {
        Object response = getSAML2ConfigCache().get(cacheKey);
        if (response != null) {
            return (String) response;
        }

        return null;
    }

    /**
     * Returns the user from cache.
     * @param cacheKey
     * @return The cached user for the given key if the user is in cache, null otherwise.
     */
    private String getCachedLoggedInUser(String cacheKey) {
        return (String) saml2Authenticator.getKeyCache().get(cacheKey);
    }

    /**
     * Checks whether the given SAML response is authenticated.
     * @param samlAttributes
     * @return
     */
    public boolean isSAMLResponseAuthenticated(Map<String, Object> samlAttributes) {

        if (samlAttributes != null) {
            return samlAttributes.get(APISecurityConstants.SUBJECT) != null;
        }

        return false;
    }

    /**
     * Returns the Axis2 message context from the Synapse message context.
     * @param messageContext
     * @return Axis2 message context.
     */
    private org.apache.axis2.context.MessageContext getAxis2MessageContext(MessageContext messageContext) {
        return ((Axis2MessageContext) messageContext).getAxis2MessageContext();
    }

    /**
     * Checks whether the request is a logout request.
     * @param messageContext
     * @return true if the request is a logout request, false otherwise.
     */
    private boolean isLogoutRequest(MessageContext messageContext) {

        String inURL = getAxis2MessageContext(messageContext).getProperty("TransportInURL").toString();

        String logoutUrl = webAppInfoDTO.getLogoutUrl();
        if (logoutUrl != null && logoutUrl.endsWith(inURL)) {
            if (log.isDebugEnabled()) {
                log.debug("Logout URL Encountered");
            }
            return true;
        } else {
            return false;
        }
    }

    /**
     * Checks whether SSO is enabled in App Manager config.
     * @return true if SSO is enabled, false otherwise.
     */
    private boolean isSSOEnabled() {
        String idpUrl = getIDPUrl();
        return idpUrl != null;
    }

    /**
     * Returns SSO info related to the web app.
     *
     * @param webAppContext : Application Context
     * @param webAppVersion : Application Version
     * @return SSO info of the app.
     * @throws AppManagementException when an error returns while fetching data from database
     */
    private WebAppInfoDTO getSSOInfoForApp(String webAppContext, String webAppVersion)
            throws AppManagementException {
        try {
            return AppMDAO.getSAML2SSOConfigInfo(webAppContext, webAppVersion);
            // TODO: Add webAppInfoDTO to cache
        } catch (AppManagementException e) {
            //TODO: Handle exceptions
            return null;
        }

    }

    /**
     * Check the context/version and fetch the url pattern related data for the given url
     *
     * @param webAppContext : Application Context
     * @param webAppVersion : Application Version
     * @return VerbInfoDTO class object
     * @throws AppManagementException when an error returns while fetching data from database
     */
    private VerbInfoDTO getVerbInfoForApp(String webAppContext, String webAppVersion)
            throws AppManagementException {
        try {
            return AppMDAO.getVerbConfigInfo(webAppContext, webAppVersion);
        } catch (AppManagementException e) {
            throw e;
        }
    }

    /**
     * Checks whether JWT is enabled in App Manager config.
     * @return true if JWT is enabled in App Manager config.
     */
    private boolean isJWTEnabled() {
        return AppMDAO.jwtGenerator != null;
    }

    /**
     * Checks whether the signed in user is subscribed to the requested app.
     * @param messageContext
     * @return true if the signed in user is subscribed to the requested app.
     * @throws APISecurityException if there is an error in subscription check.
     */
    private boolean isSubscribed(MessageContext messageContext) throws APISecurityException {
        return saml2Authenticator.authenticate(messageContext);
    }

    /**
     * Adds cached JWT for signed in user, to the transport headers.
     * @param messageContext
     */
    private void addCachedJWTToTransportHeaders(MessageContext messageContext) {

        String jwtCacheKey = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);

        String jwt = null;

        Cache jwtCache = Caching.getCacheManager(AppMConstants.API_MANAGER_CACHE_MANAGER)
                .getCache(AppMConstants.JWT_CACHE_NAME);
        jwt = (String) jwtCache.get(jwtCacheKey);

        //Add JWT Token into transport headers
        if (jwt != null) {
            Map<String, String> headers = getTransportHeaders(messageContext);
            headers.put(authenticator.getSecurityContextHeader(), jwt);
        }
    }

    /**
     * Generate, caches and add the JWT to transport headers.
     * @param messageContext
     * @param samlAttributes SAML attributes extracted from the SAML response. Even though it can be extracted again
     * using the passed message context, It's better to pass already extracted SAML attributes for performance's sake.
     * @throws org.wso2.carbon.appmgt.api.AppManagementException when there is an error in generating JWT.
     */
    private void generateJWTAndAddToTransportHeaders(MessageContext messageContext,
            Map<String, Object> samlAttributes) throws AppManagementException {

        String jwtCacheKey = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);

        // Generate Token and update Cache.
        Cache jwtCache = Caching.getCacheManager(AppMConstants.API_MANAGER_CACHE_MANAGER)
                .getCache(AppMConstants.JWT_CACHE_NAME);

        String webAppContext = (String) messageContext.getProperty(RESTConstants.REST_API_CONTEXT);
        String webAppVersion = (String) messageContext.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);

        String jwtToken = AppMDAO.jwtGenerator.generateToken(samlAttributes, webAppContext, webAppVersion);
        jwtCache.put(jwtCacheKey, jwtToken);

        //Add JWT Token into transport headers.
        Map<String, String> headers = getTransportHeaders(messageContext);

        headers.put(authenticator.getSecurityContextHeader(), jwtToken);
        //        messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);
    }

    /**
     * Handles authentication and authorization using cookie.
     * @param messageContext
     * @return true if the user is authorized to access the resource, false otherwise.
     */
    private boolean handleSecurityUsingCookie(MessageContext messageContext) {

        Map<String, String> headers = getTransportHeaders(messageContext);

        String samlCookieValue = getSAMLCookie(messageContext);

        // If the SAML response is available in cache.
        if (isSamlResponseInCache(samlCookieValue)) {

            //Check whether saml token has expired
            if (isSamlTokenExpired(samlCookieValue)) {
                getSAML2ConfigCache().remove(samlCookieValue);
                return false;
            }

            String loggedInUser = getCachedLoggedInUser(samlCookieValue);
            AuthenticatedIDP[] authenticatedIDP = getCachedAuthenticatedIDP(samlCookieValue);

            try {

                messageContext.setProperty(APISecurityConstants.SUBJECT, loggedInUser);
                messageContext.setProperty(APISecurityConstants.AUTHENTICATED_IDP, authenticatedIDP);
                messageContext.setProperty(AppMConstants.APPM_SAML2_COOKIE, samlCookieValue);

                if (isLogoutRequest(messageContext)) {
                    handleLogoutRequest(messageContext);
                    return true;
                } else if (isSubscribed(messageContext)) { // Has subscribed
                    //TODO: check the expiration time

                    //TODO: Handle if JWT Cache expires while saml2Cookie active
                    if (isJWTEnabled()) { //JWT has enabled
                        addCachedJWTToTransportHeaders(messageContext);
                    }

                    if (shouldAddSAMLResponseAsTransportHeader()) {
                        headers.put(AppMConstants.APPM_SAML2_RESPONSE, getCachedSAMLResponse(samlCookieValue));
                        messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS,
                                headers);
                    }

                    return true;
                } else { // Not subscribed.
                    return false;
                }
            } catch (APISecurityException e) {
                handleAuthFailure(messageContext, new APISecurityException(APISecurityConstants.API_AUTH_FORBIDDEN,
                        "You have not subscribe to this Application"));
            }

        }

        return false;
    }

    /**
     * Gets the SamlResponce and return the userRoles According to the Resource Required.
     * @return true if the user is allowed to access the resource.
     * @throws org.wso2.carbon.appmgt.api.AppManagementException
     * Role claim should be passed by default
     */

    private boolean checkResourceAccessible(MessageContext synapseMessageContext) {

        Map<String, String> idpResponseAttributes = null;
        Map<String, Object> samlAttributes = null;
        String inUrl = getAxis2MessageContext(synapseMessageContext).getProperty("TransportInURL").toString();
        String webAppContext = (String) synapseMessageContext.getProperty(RESTConstants.REST_API_CONTEXT);
        String webAppVersion = (String) synapseMessageContext.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);

        String roles = getCachedUserRoles(AppMConstants.USER_ROLES_CACHE_KEY);

        org.apache.axis2.context.MessageContext axis2MsgContext;
        axis2MsgContext = ((Axis2MessageContext) synapseMessageContext).getAxis2MessageContext();

        String httpVerb = (String) axis2MsgContext.getProperty(Constants.Configuration.HTTP_METHOD);

        int appID = webAppInfoDTO.getAppID();

        ArrayList<String> mappingList = new ArrayList<String>();
        ArrayList<String> mapperList = new ArrayList<String>();
        AppMDAO appMDAO = new AppMDAO();

        try {

            mappingList = appMDAO.getInUrlMappingById(appID);

            for (String mapping : mappingList) {
                String mapperUrl = webAppContext + "/" + webAppVersion + mapping;
                mapperList.add(mapperUrl);
            }
            Collections.sort(mapperList);
            Collections.reverse(mapperList);

            String matched = getMatchedURLPattern(mapperList, inUrl);
            if (matched != null) {
                String urlMapping = matched.substring((webAppContext + "/" + webAppVersion).length(),
                        matched.length());

                // Set the relevant synapse properties to let the entitlement handler do its job properly.
                synapseMessageContext.setProperty(AppMConstants.MATCHED_URL_PATTERN_PROERTY_NAME, urlMapping);
                synapseMessageContext.setProperty(AppMConstants.MATCHED_APP_ID_PROERTY_NAME, appID);

                if (checkResourseAccessibleByRole(urlMapping, roles, appID, httpVerb)) {
                    return true;
                } else {
                    return false;
                }
            } else {
                return true;
            }

        } catch (AppManagementException e) {
            log.error("Failed to check resources for user");
            return false;
        }
    }

    private String getUserRolesFromTheSAMLResponse(Map<String, Object> samlAttributes) {
        if (samlAttributes != null) {
            if (samlAttributes.get(APISecurityConstants.CLAIM_ROLES) != null) {
                return (String) samlAttributes.get(APISecurityConstants.CLAIM_ROLES);
            } else {
                return "";
            }
        }

        return "";
    }

    /**
     * Check weather the mapping url has the roles
     * @param urlMapping
     * @param roles
     * @return true if the user is permitted to access the resource.
     * @throws org.wso2.carbon.appmgt.api.AppManagementException
     */
    private boolean checkResourseAccessibleByRole(String urlMapping, String roles, int appId, String httpVerb)
            throws AppManagementException {

        AppMDAO appMDAO = new AppMDAO();
        String toBeMatchedRoles = appMDAO.getInUrlMappingRoles(appId, urlMapping, httpVerb);
        if (roles == null || roles.equalsIgnoreCase("")) {
            return true;
        }
        if (toBeMatchedRoles == null || toBeMatchedRoles.equals("")) {
            return true;
        }

        String toBeMatchedRole[] = getDelimitedRoles(toBeMatchedRoles);
        String contextRoles[] = getDelimitedRoles(roles);

        for (int i = 0; i < contextRoles.length; i++) {
            for (int j = 0; j < toBeMatchedRole.length; j++) {
                if (contextRoles[i].equalsIgnoreCase(toBeMatchedRole[j])) {
                    return true;
                }
            }
        }
        return false;
    }

    private String[] getDelimitedRoles(String input) {

        String[] arr = input.split(",");
        return arr;
    }

    /**
     * Check weather the mapping url has the roles
     * @param patternList
     * @param inUrl
     * @return true if the user is permitted to access the resource.
     * @throws org.wso2.carbon.appmgt.api.AppManagementException
     */

    private String getMatchedURLPattern(ArrayList<String> patternList, String inUrl) {
        NamedMatchList<String> matcher = new NamedMatchList<String>();

        for (String pattern : patternList) {
            matcher.add(pattern, pattern);
        }

        String matched = matcher.match(inUrl);
        return matched;

    }

    /**
     * Handles authentication and authorization using SAML response.
     * @param messageContext
     * @return true if the user is authorized for the requested resource.
     * @throws org.wso2.carbon.appmgt.api.AppManagementException
     */
    private boolean handleAuthorizationUsingSAMLResponse(MessageContext messageContext) {

        Map<String, String> headers = getTransportHeaders(messageContext);

        Map<String, String> idpResponseAttributes = getIDPResponseAttributes(messageContext);

        Map<String, Object> samlAttributes = getAttributesOfSAMLResponse(idpResponseAttributes);

        if (isSAMLResponseAuthenticated(samlAttributes)) {
            // Set the cookie value.
            String samlCookieValue = getSAMLCookie(messageContext);
            String samlResponse = idpResponseAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE);

            SAMLTokenInfoDTO samlTokenInfoDTO = new SAMLTokenInfoDTO();
            samlTokenInfoDTO.setEncodedSamlToken(samlResponse);
            samlTokenInfoDTO.setNotOnOrAfter(
                    (DateTime) samlAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION_NOT_ON_OR_AFTER));

            if (samlCookieValue == null) {
                samlCookieValue = UUID.randomUUID().toString();
                if (log.isDebugEnabled()) {
                    log.debug("generating samlCookieValue : " + samlCookieValue);
                }
                messageContext.setProperty(AppMConstants.APPM_SAML2_COOKIE, samlCookieValue);

                //Cache SAML response. Different apps may configured to have different claim values which result in different
                //SAML responses for each app. Map is required to store SAML responses of apps being invoked. Then set the cache
                //key as value of 'appmSamlSsoTokenId' cookie and this Map has the value and put it to cache.
                Map<String, SAMLTokenInfoDTO> samlResponsesMap = new HashMap<String, SAMLTokenInfoDTO>();
                samlResponsesMap.put(webAppInfoDTO.getSaml2SsoIssuer(), samlTokenInfoDTO);
                getSAML2ConfigCache().put(samlCookieValue, samlResponsesMap);
            } else {
                if (log.isDebugEnabled()) {
                    log.debug("samlCookie already exists : " + samlCookieValue);
                }
                //This logic get executed when accessing an app that haven't accessed previously in the same browser
                Map<String, SAMLTokenInfoDTO> samlResponsesMap = (HashMap<String, SAMLTokenInfoDTO>) getSAML2ConfigCache()
                        .get(samlCookieValue);
                String samlIssuer = webAppInfoDTO.getSaml2SsoIssuer();

                if (samlResponsesMap != null && !samlResponsesMap.containsKey(samlIssuer)) {
                    samlResponsesMap.put(samlIssuer, samlTokenInfoDTO);
                    getSAML2ConfigCache().put(samlCookieValue, samlResponsesMap);
                } else { //when accessing though my-subscriptions page
                    samlResponsesMap = new HashMap<String, SAMLTokenInfoDTO>();
                    samlResponsesMap.put(samlIssuer, samlTokenInfoDTO);
                    getSAML2ConfigCache().put(samlCookieValue, samlResponsesMap);
                }
                messageContext.setProperty(AppMConstants.APPM_SAML2_COOKIE, samlCookieValue);
            }

            //APISecurityConstants.SUBJECT maps to authenticated userName
            messageContext.setProperty(APISecurityConstants.SUBJECT,
                    samlAttributes.get(APISecurityConstants.SUBJECT));

            // Get the user roles and cache them.
            String roles = getUserRolesFromTheSAMLResponse(samlAttributes);
            getSAML2ConfigCache().put(AppMConstants.USER_ROLES_CACHE_KEY, roles);

            // Get the authenticated IDP if there is one.
            AuthenticatedIDP[] authenticatedIDP = getAuthenticatedIDP(idpResponseAttributes, samlAttributes);

            if (authenticatedIDP != null) {

                cacheAuthenticatedIDP(samlCookieValue, authenticatedIDP);

                // Set the authenticated IDP in message context, if there is one.
                messageContext.setProperty(APISecurityConstants.AUTHENTICATED_IDP, authenticatedIDP);
            }

            try {
                if (isLogoutRequest(messageContext)) {
                    handleLogoutRequest(messageContext);
                    return true;
                } else if (isSubscribed(messageContext)) { //check for subscription
                    if (isJWTEnabled()) {
                        generateJWTAndAddToTransportHeaders(messageContext, samlAttributes);

                    }
                    if (shouldAddSAMLResponseAsTransportHeader()) {
                        headers.put("AppMgtSAML2Response", samlResponse);
                        messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS,
                                headers);
                    }
                    return true;
                } else {
                    handleAuthFailure(messageContext, new APISecurityException(
                            APISecurityConstants.API_AUTH_FORBIDDEN, "You have not subscribe to this Application"));
                }
            } catch (AppManagementException e) {
                e.printStackTrace();
            } catch (APISecurityException e) {
                log.error("WebApp authentication failure", e);
                handleAuthFailure(messageContext, e);
                return false;
            }
        }

        return false;

    }

    /**
     * Caches the given authenticated IDP.
     * @param key
     * @param authenticatedIDPs
     */
    private void cacheAuthenticatedIDP(String key, AuthenticatedIDP[] authenticatedIDPs) {

        Cache cache = Caching.getCacheManager(AppMConstants.AUTHENTICATED_IDP_CACHE_MANAGER)
                .getCache(AppMConstants.AUTHENTICATED_IDP_CACHE);

        cache.put(key, authenticatedIDPs);
    }

    /**
     * Return the cached authenticated IDP for the given key.
     * @param key
     * @return Cached authenticated IDP if there is one, null otherwise.
     */
    private AuthenticatedIDP[] getCachedAuthenticatedIDP(String key) {

        Cache cache = Caching.getCacheManager(AppMConstants.AUTHENTICATED_IDP_CACHE_MANAGER)
                .getCache(AppMConstants.AUTHENTICATED_IDP_CACHE);

        Object cachedObject = cache.get(key);

        if (cachedObject != null && cachedObject instanceof AuthenticatedIDP[]) {
            return (AuthenticatedIDP[]) cachedObject;
        } else {
            return null;
        }
    }

    /**
     * Returns transport headers of the message context.
     * @param messageContext
     * @return Transport headers.
     */
    private Map<String, String> getTransportHeaders(MessageContext messageContext) {
        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        return (Map<String, String>) axis2MessageContext
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
    }

    /**
     * Returns the attributes + subject on the given IDP response attributes.
     * @param idpResponseAttributes A Map which contains the elements which IDP sends. It contains the SAML response and authenticated IDP.
     * @return
     */
    private Map<String, Object> getAttributesOfSAMLResponse(Map<String, String> idpResponseAttributes) {

        String samlResponse = idpResponseAttributes.get(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE);
        String decodedSAMLResponse = new String(Base64.decode(samlResponse));

        XMLObject responseXmlObj = null;
        try {
            responseXmlObj = SAMLSSOUtil.unmarshall(decodedSAMLResponse);

        } catch (IdentityException e) {
            e.printStackTrace();
        }
        return getResult(responseXmlObj);
    }

    /**
     * Handles the logout request for an app.
     * @param messageContext
     * @throws SynapseException
     */
    private void handleLogoutRequest(MessageContext messageContext) throws SynapseException {

        String appmSaml2CookieValue = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);

        messageContext.setProperty("isLogoutRequest", true);
        String encodedSAMLResponse = getCachedSAMLResponse(appmSaml2CookieValue);

        if (encodedSAMLResponse != null) {
            try {
                //                String decodedSAMLResponse = URLDecoder.decode(encodedSAMLResponse , "UTF-8");
                String decodedSAMLResponse = new String(Base64.decode(encodedSAMLResponse));
                String samlAssertion = getSamlAssetionString(decodedSAMLResponse);
                XMLObject responseXmlObj = SAMLSSOUtil.unmarshall(samlAssertion);

                Assertion assertion = (Assertion) responseXmlObj;

                if (assertion != null) {
                    String subject = assertion.getSubject().getNameID().getValue();

                    AuthnStatement authnStatement = assertion.getAuthnStatements().get(0);
                    String sessionIndex = authnStatement.getSessionIndex();

                    LogoutRequest logoutReq = buildLogoutRequest(subject, sessionIndex,
                            webAppInfoDTO.getSaml2SsoIssuer());

                    String encodedSamlLogOutRequest = encodeRequestMessage(logoutReq);

                    if (encodedSamlLogOutRequest != null) {
                        getSAML2ConfigCache().remove(appmSaml2CookieValue);
                        sendSAMLRequestToIdP(messageContext, encodedSamlLogOutRequest);
                    } else {
                        throw new SynapseException("Error while sending logout request to IDP.");
                    }
                }

            } catch (IdentityException e) {
                e.printStackTrace();
            }
        }
    }

    private String encodeRequestMessage(RequestAbstractType requestMessage) {

        try {
            DefaultBootstrap.bootstrap();
        } catch (ConfigurationException e) {
            log.error("Error while initializing opensaml library", e);
            return null;
        }
        Marshaller marshaller = Configuration.getMarshallerFactory().getMarshaller(requestMessage);
        Element authDOM = null;
        try {
            authDOM = marshaller.marshall(requestMessage);

            /* Compress the message */
            Deflater deflater = new Deflater(Deflater.DEFLATED, true);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater);
            StringWriter rspWrt = new StringWriter();
            XMLHelper.writeNode(authDOM, rspWrt);
            deflaterOutputStream.write(rspWrt.toString().getBytes());
            deflaterOutputStream.close();

            /* Encoding the compressed message */
            String encodedRequestMessage = Base64.encodeBytes(byteArrayOutputStream.toByteArray(),
                    Base64.DONT_BREAK_LINES);
            return URLEncoder.encode(encodedRequestMessage, "UTF-8").trim();

        } catch (MarshallingException e) {
            log.error("Error occurred while encoding SAML request", e);
        } catch (UnsupportedEncodingException e) {
            log.error("Error occurred while encoding SAML request", e);
        } catch (IOException e) {
            log.error("Error occurred while encoding SAML request", e);
        }
        return null;
    }

    /**
     * Get the authenticated IDP for this request. User identity is retrieved from samlAttributes and the IDP name is retrieved from idpResponseAttributes.
     * @param idpResponseAttributes
     * @param samlAttributes
     * @return
     */
    private AuthenticatedIDP[] getAuthenticatedIDP(Map<String, String> idpResponseAttributes,
            Map<String, Object> samlAttributes) {

        // Get the identity
        String identity = (String) samlAttributes.get(APISecurityConstants.CLAIM_EMAIL);

        // Get the name of the IDP.
        String idpName = null;

        String encodedAuthenticatedIDPsString = idpResponseAttributes
                .get(IDP_CALLBACK_ATTRIBUTE_NAME_AUTHENTICATED_IDPS);

        if (encodedAuthenticatedIDPsString != null) {

            String authenticatedIDPJson = encodedAuthenticatedIDPsString.split("\\.")[1];

            // Sample JSON : {"iss":"wso2","exp":14051608961853000,"iat":1405160896185,
            //                  "idps":[{"idp":"enterprise1","authenticator":"GoogleOpenIDAuthenticator"}]}

            try {
                authenticatedIDPJson = URLDecoder.decode(authenticatedIDPJson, "UTF-8");
                authenticatedIDPJson = new String(Base64.decode(authenticatedIDPJson));

                JSONObject parsedJson = (JSONObject) JSONValue.parse(authenticatedIDPJson);
                JSONArray idps = (JSONArray) parsedJson.get("idps");
                AuthenticatedIDP[] authenticatedIDPs = new AuthenticatedIDP[idps.size()];

                for (int i = 0; i < idps.size(); i++) {
                    JSONObject authenticatedIDPJSON = (JSONObject) idps.get(i);
                    idpName = authenticatedIDPJSON.get("idp").toString();

                    AuthenticatedIDP authenticatedIDP = new AuthenticatedIDP();
                    authenticatedIDP.setIdentity(identity);
                    authenticatedIDP.setIdpName(idpName);

                    authenticatedIDPs[i] = authenticatedIDP;
                }

                return authenticatedIDPs;

            } catch (Exception e) { // Catches parsing errors
                log.error(String.format("Error while decoding 'AuthenticatedIdps' string value : %s",
                        authenticatedIDPJson), e);
                return null;
            }
        }

        return null;
    }

    /*
    * Process the response and returns the results
    */
    private Map<String, Object> getResult(XMLObject responseXmlObj) {
        if (responseXmlObj.getDOM().getNodeName().equals("saml2p:LogoutResponse")) {
            return null;
        }

        Response response = (Response) responseXmlObj;
        Assertion assertion = response.getAssertions().get(0);
        Map<String, Object> results = new HashMap<String, Object>();

        /*
           * If the request has failed, the IDP shouldn't send an assertion.
           * SSO profile spec 4.1.4.2 <Response> Usage
           */
        if (assertion != null) {
            String subject = assertion.getSubject().getNameID().getValue();
            results.put(APISecurityConstants.SUBJECT, subject); // get the subject

            //get saml token validity period
            if (assertion.getConditions() != null && assertion.getConditions().getNotOnOrAfter() != null) {
                results.put(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION_NOT_ON_OR_AFTER,
                        assertion.getConditions().getNotOnOrAfter());
            }

            List<AttributeStatement> attributeStatementList = assertion.getAttributeStatements();

            if (attributeStatementList != null) {
                // we have received attributes of user
                Iterator<AttributeStatement> attribStatIter = attributeStatementList.iterator();
                while (attribStatIter.hasNext()) {
                    AttributeStatement statement = attribStatIter.next();
                    List<Attribute> attributesList = statement.getAttributes();
                    Iterator<Attribute> attributesIter = attributesList.iterator();
                    while (attributesIter.hasNext()) {
                        Attribute attrib = attributesIter.next();
                        Element value = attrib.getAttributeValues().get(0).getDOM();
                        String attribValue = value.getTextContent();
                        results.put(attrib.getName(), attribValue);
                    }
                }
            }
        }
        return results;
    }

    private Cache getSAML2ConfigCache() {
        return Caching.getCacheManager(AppMConstants.SAML2_CONFIG_CACHE_MANAGER)
                .getCache(AppMConstants.SAML2_CONFIG_CACHE);
    }

    private Cache getAppContextVersionConfigCache() {
        return Caching.getCacheManager(AppMConstants.APP_CONTEXT_VERSION_CACHE_MANAGER)
                .getCache(AppMConstants.APP_CONTEXT_VERSION_CONFIG_CACHE);
    }

    private Cache getUserRolesCacheConfig() {
        return Caching.getCacheManager(AppMConstants.USER_ROLES_CACHE_MANAGER)
                .getCache(AppMConstants.USER_ROLES_CONFIG_CACHE);
    }

    private Cache getSAML2RelayStateCache() {
        return Caching.getCacheManager(AppMConstants.SAML2_RELAY_STATE_CACHE_MANAGER)
                .getCache(AppMConstants.SAML2_RELAY_STATE_CACHE);
    }

    private void redirectToIDPLogin(MessageContext messageContext) {
        RequestAbstractType authnRequest = buildAuthnRequestObject(messageContext);
        String encodedSamlRequest = encodeRequestMessage(authnRequest);
        sendSAMLRequestToIdP(messageContext, encodedSamlRequest);
    }

    private AuthnRequest buildAuthnRequestObject(MessageContext messageContext) {

        /* Building Issuer object */
        IssuerBuilder issuerBuilder = new IssuerBuilder();
        Issuer issuerOb = issuerBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "Issuer", "samlp");
        issuerOb.setValue(webAppInfoDTO.getSaml2SsoIssuer());

        /* NameIDPolicy */
        NameIDPolicyBuilder nameIdPolicyBuilder = new NameIDPolicyBuilder();
        NameIDPolicy nameIdPolicy = nameIdPolicyBuilder.buildObject();
        nameIdPolicy.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent");
        nameIdPolicy.setSPNameQualifier("Issuer");
        nameIdPolicy.setAllowCreate(true);

        /* AuthnContextClass */
        AuthnContextClassRefBuilder authnContextClassRefBuilder = new AuthnContextClassRefBuilder();
        AuthnContextClassRef authnContextClassRef = authnContextClassRefBuilder
                .buildObject("urn:oasis:names:tc:SAML:2.0:assertion", "AuthnContextClassRef", "saml");
        authnContextClassRef
                .setAuthnContextClassRef("urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport");

        /* AuthnContex */
        RequestedAuthnContextBuilder requestedAuthnContextBuilder = new RequestedAuthnContextBuilder();
        RequestedAuthnContext requestedAuthnContext = requestedAuthnContextBuilder.buildObject();
        requestedAuthnContext.setComparison(AuthnContextComparisonTypeEnumeration.EXACT);
        requestedAuthnContext.getAuthnContextClassRefs().add(authnContextClassRef);

        DateTime issueInstant = new DateTime();
        String authReqRandomId = Integer.toHexString(new Double(Math.random()).intValue());

        /* Creation of AuthRequestObject */
        AuthnRequestBuilder authRequestBuilder = new AuthnRequestBuilder();
        AuthnRequest authRequest = authRequestBuilder.buildObject("urn:oasis:names:tc:SAML:2.0:protocol",
                "AuthnRequest", "samlp");
        authRequest.setForceAuthn(false);
        authRequest.setIsPassive(false);
        authRequest.setIssueInstant(issueInstant);
        authRequest.setProtocolBinding("urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST");
        authRequest.setAssertionConsumerServiceURL(constructAssertionConsumerUrl(messageContext));
        authRequest.setIssuer(issuerOb);
        authRequest.setNameIDPolicy(nameIdPolicy);
        authRequest.setRequestedAuthnContext(requestedAuthnContext);
        authRequest.setID(authReqRandomId);
        authRequest.setDestination(getIDPUrl());
        authRequest.setVersion(SAMLVersion.VERSION_20);

        return authRequest;
    }

    /**
     * get Relay State if available
     *
     * @param messageContext
     * @return relay state path
     */
    private String getRelayState(MessageContext messageContext) {
        String replayState = "";
        String fullRequest = (String) messageContext.getProperty(RESTConstants.REST_FULL_REQUEST_PATH);
        String context = (String) messageContext.getProperty(RESTConstants.REST_API_CONTEXT);
        String version = (String) messageContext.getProperty(RESTConstants.SYNAPSE_REST_API_VERSION);

        if (!context.endsWith("/")) {
            context += "/";
        }

        if (!version.endsWith("/")) {
            version += "/";
        }

        getSAML2RelayStateCache().put(AppMConstants.SAML2_RELAY_STATE_CACHE_KEY, fullRequest);

        if (!(context + version).equals(fullRequest)) {
            replayState = "&" + IDP_CALLBACK_ATTRIBUTE_NAME_RELAY_STATE + "="
                    + AppMConstants.SAML2_RELAY_STATE_CACHE_KEY;
        }
        return replayState;
    }

    private void sendSAMLRequestToIdP(MessageContext messageContext, String request) {

        org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        axis2MC.setProperty(NhttpConstants.HTTP_SC, "302");

        messageContext.setResponse(true);
        messageContext.setProperty("RESPONSE", "true");
        messageContext.setTo(null);
        axis2MC.removeProperty("NO_ENTITY_BODY");
        String method = (String) axis2MC.getProperty(Constants.Configuration.HTTP_METHOD);

        if (method.matches("^(?!.*(POST|PUT)).*$")) {
            /* If the request was not an entity enclosing request, send a XML response back */
            axis2MC.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/xml");
        }

        /* Always remove the ContentType - Let the formatter do its thing */
        axis2MC.removeProperty(Constants.Configuration.CONTENT_TYPE);
        Map headerMap = (Map) axis2MC.getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

        headerMap.put("Location", getIDPUrl() + "?SAMLRequest=" + request + getRelayState(messageContext));

        if (messageContext.getProperty("error_message_type") != null && messageContext
                .getProperty("error_message_type").toString().equalsIgnoreCase("application/json")) {
            axis2MC.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/json");
        }

        headerMap.remove(HttpHeaders.HOST);
        Axis2Sender.sendBack(messageContext);
    }

    private LogoutRequest buildLogoutRequest(String user, String sessionIdx, String saml2Issuer) {

        LogoutRequest logoutReq = new LogoutRequestBuilder().buildObject();

        logoutReq.setID(UUID.randomUUID().toString());
        logoutReq.setDestination(getIDPUrl());

        DateTime issueInstant = new DateTime();
        logoutReq.setIssueInstant(issueInstant);
        logoutReq.setNotOnOrAfter(new DateTime(issueInstant.getMillis() + 5 * 60 * 1000));

        IssuerBuilder issuerBuilder = new IssuerBuilder();
        Issuer issuer = issuerBuilder.buildObject();
        issuer.setValue(saml2Issuer);
        logoutReq.setIssuer(issuer);

        NameID nameId = new NameIDBuilder().buildObject();
        nameId.setFormat("urn:oasis:names:tc:SAML:2.0:nameid-format:entity");
        nameId.setValue(user);
        logoutReq.setNameID(nameId);

        SessionIndex sessionIndex = new SessionIndexBuilder().buildObject();
        sessionIndex.setSessionIndex(sessionIdx);
        logoutReq.getSessionIndexes().add(sessionIndex);

        logoutReq.setReason("Single Logout");

        return logoutReq;
    }

    private String getCookieValue(String cookieString, String cookieName) {
        if (cookieString != null && !cookieString.isEmpty()) {
            int cStart = cookieString.indexOf(cookieName + "=");
            int cEnd;
            if (cStart != -1) {
                cStart = cStart + cookieName.length() + 1;
                cEnd = cookieString.indexOf(";", cStart);
                if (cEnd == -1) {
                    cEnd = cookieString.length();
                }
                return cookieString.substring(cStart, cEnd);
            }
        }
        return null;
    }

    private void handleAuthFailure(MessageContext messageContext, APISecurityException e) {
        messageContext.setProperty(SynapseConstants.ERROR_CODE, e.getErrorCode());
        messageContext.setProperty(SynapseConstants.ERROR_MESSAGE,
                APISecurityConstants.getAuthenticationFailureMessage(e.getErrorCode()));
        messageContext.setProperty(SynapseConstants.ERROR_EXCEPTION, e);

        Mediator sequence = messageContext.getSequence(APISecurityConstants.API_AUTH_FAILURE_HANDLER);
        // Invoke the custom error handler specified by the user
        if (sequence != null && !sequence.mediate(messageContext)) {
            // If needed user should be able to prevent the rest of the fault handling
            // logic from getting executed
            return;
        }

        // By default we send a 401 response back
        org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        axis2MC.setProperty(Constants.Configuration.MESSAGE_TYPE, "application/soap+xml");

        int status;
        if (e.getErrorCode() == APISecurityConstants.API_AUTH_GENERAL_ERROR) {
            status = HttpStatus.SC_INTERNAL_SERVER_ERROR;
        } else if (e.getErrorCode() == APISecurityConstants.API_AUTH_INCORRECT_API_RESOURCE
                || e.getErrorCode() == APISecurityConstants.API_AUTH_FORBIDDEN) {
            status = HttpStatus.SC_FORBIDDEN;
        } else {
            status = HttpStatus.SC_UNAUTHORIZED;
            Map<String, String> headers = new HashMap<String, String>();
            headers.put(HttpHeaders.WWW_AUTHENTICATE, authenticator.getChallengeString());
            axis2MC.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);
        }

        if (messageContext.isDoingPOX() || messageContext.isDoingGET()) {
            Utils.setFaultPayload(messageContext, getFaultPayload(e));
        } else {
            Utils.setSOAPFault(messageContext, "Client", "Authentication Failure", e.getMessage());
        }
        if (Utils.isCORSEnabled()) {
            /* For CORS support adding required headers to the fault response */
            Map<String, String> headers = (Map) axis2MC
                    .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
            headers.put(AppMConstants.CORSHeaders.ACCESS_CONTROL_ALLOW_ORIGIN,
                    Utils.getAllowedOrigin(authenticator.getRequestOrigin()));
            headers.put(AppMConstants.CORSHeaders.ACCESS_CONTROL_ALLOW_METHODS, Utils.getAllowedMethods());
            headers.put(AppMConstants.CORSHeaders.ACCESS_CONTROL_ALLOW_HEADERS, Utils.getAllowedHeaders());
            axis2MC.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);
        }

        Utils.sendFault(messageContext, status);
    }

    private OMElement getFaultPayload(APISecurityException e) {
        OMFactory fac = OMAbstractFactory.getOMFactory();
        OMNamespace ns = fac.createOMNamespace(APISecurityConstants.API_SECURITY_NS,
                APISecurityConstants.API_SECURITY_NS_PREFIX);
        OMElement payload = fac.createOMElement("fault", ns);

        OMElement errorCode = fac.createOMElement("code", ns);
        errorCode.setText(String.valueOf(e.getErrorCode()));
        OMElement errorMessage = fac.createOMElement("message", ns);
        errorMessage.setText(APISecurityConstants.getAuthenticationFailureMessage(e.getErrorCode()));
        OMElement errorDetail = fac.createOMElement("description", ns);
        errorDetail.setText(e.getMessage());

        payload.addChild(errorCode);
        payload.addChild(errorMessage);
        payload.addChild(errorDetail);
        return payload;
    }

    private String getSamlAssetionString(String samlResponse) {
        return samlResponse.substring(samlResponse.indexOf("<saml2:Assertion"),
                samlResponse.indexOf("</saml2:Assertion>")) + "</saml2:Assertion>";

    }

    /**
     * Returns the URL of the IDP.
     * @return
     */
    private String getIDPUrl() {
        return ServiceReferenceHolder.getInstance().getAPIManagerConfiguration()
                .getFirstProperty(AppMConstants.SSO_CONFIGURATION_IDENTITY_PROVIDER_URL);
    }

    /**
     * Returns the status of enabledsaml sso configuraion.
     * @return
     */
    private boolean getSamlSSOConfiguration() {
        return Boolean.parseBoolean(ServiceReferenceHolder.getInstance().getAPIManagerConfiguration()
                .getFirstProperty(AppMConstants.SSO_CONFIGURATION_ENABLE_SSO_CONFIGURATION));
    }

    /**
     * Checks whether the SAML response should be added as a transport header.
     * @return
     */
    private boolean shouldAddSAMLResponseAsTransportHeader() {
        return Boolean.parseBoolean(ServiceReferenceHolder.getInstance().getAPIManagerConfiguration()
                .getFirstProperty(AppMConstants.API_CONSUMER_AUTHENTICATION_ADD_SAML_RESPONSE_HEADER_TO_OUT_MSG));
    }

    /**
     * Returns decoded  IDP response attributes.
     * @param messageContext
     * @return A Map which contains the elements which IDP sends. It contains the SAML response and authenticated IDP.
     */
    private Map<String, String> getIDPResponseAttributes(MessageContext messageContext) {

        org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();

        Map<String, String> idpResponseAttributes = new HashMap<String, String>();

        try {
            RelayUtils.buildMessage(axis2MessageContext);
        } catch (Exception e) {
            log.error("Error while retrieving IDP response attributes.", e);
            return idpResponseAttributes;
        }

        Iterator iterator = messageContext.getEnvelope().getBody().getChildElements();
        while (iterator.hasNext()) {
            OMElement bodyElement = (OMElement) iterator.next();

            // Get SAML response.
            Iterator children = bodyElement
                    .getChildrenWithName(new QName(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE));
            while (children.hasNext()) {
                OMElement ele = (OMElement) children.next();
                idpResponseAttributes.put(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_RESPONSE, ele.getText());
            }

            //Get SAML Assertion
            children = bodyElement.getChildrenWithName(new QName(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION));
            while (children.hasNext()) {
                OMElement ele = (OMElement) children.next();
                idpResponseAttributes.put(IDP_CALLBACK_ATTRIBUTE_NAME_SAML_ASSERTION, ele.getText());
            }

            // Get authenticated IDPs.
            children = bodyElement.getChildrenWithName(new QName(IDP_CALLBACK_ATTRIBUTE_NAME_AUTHENTICATED_IDPS));
            while (children.hasNext()) {
                OMElement ele = (OMElement) children.next();
                idpResponseAttributes.put(IDP_CALLBACK_ATTRIBUTE_NAME_AUTHENTICATED_IDPS, ele.getText());
            }

            if (idpResponseAttributes.size() == 2) {
                break;
            }

        }

        return idpResponseAttributes;
    }

    private String getTenantDomainFromGatewayUrl(String gatewayUrl) {
        String tenantDomain = "";
        //"http://10.100.1.85:8280/t/you.com/tommy/1.0.0/";
        if (gatewayUrl.contains("/t/")) {
            String tmp;
            tmp = gatewayUrl.substring(gatewayUrl.indexOf("/t/") + 3);
            tenantDomain = tmp.substring(0, tmp.indexOf("/"));
        }
        return tenantDomain;
    }

    private void constructAndSetFullyQualifiedSamlIssuerId(MessageContext messageContext,
            WebAppInfoDTO webAppInfoDTO) {
        String assertionConsumerUrl = constructAssertionConsumerUrl(messageContext);
        String tenantDomain = getTenantDomainFromGatewayUrl(assertionConsumerUrl);
        String version = (String) messageContext.getProperty("SYNAPSE_REST_API_VERSION");

        String fullyQualifiedIssuer = webAppInfoDTO.getSaml2SsoIssuer();

        if (!tenantDomain.equals("")) {
            fullyQualifiedIssuer = fullyQualifiedIssuer + "-" + tenantDomain;
        }

        //Append version to SP name
        fullyQualifiedIssuer = fullyQualifiedIssuer + "-" + version;

        webAppInfoDTO.setSaml2SsoIssuer(fullyQualifiedIssuer);
    }

    private String constructAssertionConsumerUrl(MessageContext messageContext) {
        org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        Map<String, String> headers = (Map<String, String>) axis2MC
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

        String servicePrefix = axis2MC.getProperty("SERVICE_PREFIX").toString();
        //Note: Do not change to construct the assertionConsumerUrl directly using servicePrefix instead of headers.get("HOST").
        //It always gives IP for host which cause invalid assertionConsumerUrl
        String assertionConsumerUrl = servicePrefix.substring(0, servicePrefix.indexOf("/") + 2)
                + headers.get("HOST") + messageContext.getProperty("REST_API_CONTEXT") + "/";

        return assertionConsumerUrl;
    }

    /**
     * This method is used to set the "appmSamlSsoCookie" in the request going to backend app.
     * Note: This method cannot reuse in the response path, since we updating the "Cookie" header.
     * "Set-Cookie" header should use in response path to add new cookies to browser.
     * @param messageContext
     */
    private void setAppmSamlSsoCookie(MessageContext messageContext) {
        String appmSamlSsoCookie = (String) messageContext.getProperty(AppMConstants.APPM_SAML2_COOKIE);
        org.apache.axis2.context.MessageContext axis2MC = ((Axis2MessageContext) messageContext)
                .getAxis2MessageContext();
        Map<String, Object> headers = (Map<String, Object>) axis2MC
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
        String cookieString = (String) headers.get(HTTPConstants.HEADER_COOKIE);

        if (log.isDebugEnabled()) {
            log.debug("Exisiting cookie string in transport headers : " + cookieString);
        }
        if (cookieString == null) {
            cookieString = AppMConstants.APPM_SAML2_COOKIE + "=" + appmSamlSsoCookie + "; " + "path=" + "/";
        } else {
            cookieString = cookieString + " ;" + AppMConstants.APPM_SAML2_COOKIE + "=" + appmSamlSsoCookie + ";"
                    + " Path=" + "/";
        }
        if (log.isDebugEnabled()) {
            log.debug("Updated cookie string in transport headers : " + cookieString);
        }

        headers.put(HTTPConstants.HEADER_COOKIE, cookieString);
        messageContext.setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);
    }

}