/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * https://opensso.dev.java.net/public/CDDLv1.0.html or
 * opensso/legal/CDDLv1.0.txt
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL
 * Header Notice in each file and include the License file
 * at opensso/legal/CDDLv1.0.txt.
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * $Id: AttributeQueryUtil.java,v 1.11 2009/07/24 22:51:48 madan_ranganath Exp $
 *
 * Portions copyright 2010-2016 ForgeRock AS.
 */
package com.sun.identity.saml2.profile;

import static org.forgerock.openam.utils.Time.*;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;

import com.sun.identity.saml2.common.SOAPCommunicator;
import org.w3c.dom.Element;

import com.sun.identity.plugin.datastore.DataStoreProviderException;
import com.sun.identity.plugin.datastore.DataStoreProvider;
import com.sun.identity.saml.xmlsig.KeyProvider;
import com.sun.identity.saml2.assertion.Assertion;
import com.sun.identity.saml2.assertion.AssertionFactory;
import com.sun.identity.saml2.assertion.Attribute;
import com.sun.identity.saml2.assertion.AttributeStatement;
import com.sun.identity.saml2.assertion.Conditions;
import com.sun.identity.saml2.assertion.EncryptedAssertion;
import com.sun.identity.saml2.assertion.Issuer;
import com.sun.identity.saml2.assertion.NameID;
import com.sun.identity.saml2.assertion.EncryptedID;
import com.sun.identity.saml2.assertion.Subject;
import com.sun.identity.saml2.common.SAML2Constants;
import com.sun.identity.saml2.common.SAML2Exception;
import com.sun.identity.saml2.common.SAML2Utils;
import com.sun.identity.saml2.jaxb.assertion.AttributeElement;
import com.sun.identity.saml2.jaxb.assertion.AttributeValueElement;
import com.sun.identity.saml2.jaxb.entityconfig.AttributeAuthorityConfigElement;
import com.sun.identity.saml2.jaxb.entityconfig.AttributeQueryConfigElement;
import com.sun.identity.saml2.jaxb.entityconfig.IDPSSOConfigElement;
import com.sun.identity.saml2.jaxb.metadata.AttributeAuthorityDescriptorElement;
import com.sun.identity.saml2.jaxb.metadata.AttributeServiceElement;
import com.sun.identity.saml2.jaxb.metadataextquery.AttributeQueryDescriptorElement;
import com.sun.identity.saml2.key.EncInfo;
import com.sun.identity.saml2.key.KeyUtil;
import com.sun.identity.saml2.meta.SAML2MetaException;
import com.sun.identity.saml2.meta.SAML2MetaManager;
import com.sun.identity.saml2.meta.SAML2MetaUtils;
import com.sun.identity.saml2.plugins.AttributeAuthorityMapper;
import com.sun.identity.saml2.plugins.SPAttributeMapper;
import com.sun.identity.saml2.protocol.AttributeQuery;
import com.sun.identity.saml2.protocol.ProtocolFactory;
import com.sun.identity.saml2.protocol.Response;
import com.sun.identity.saml2.protocol.Status;
import com.sun.identity.saml2.protocol.StatusCode;
import com.sun.identity.saml2.xmlenc.EncManager;

/**
 * This class provides methods to send or process <code>AttributeQuery</code>.
 *
 * @supported.api
 */
public class AttributeQueryUtil {

    private static final String DEFAULT_ATTRIBUTE_NAME_FORMAT =
            "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified";
    static KeyProvider keyProvider = KeyUtil.getKeyProviderInstance(); 
    static Hashtable attrAuthorityMapperCache = new Hashtable(); 
    static DataStoreProvider dsProvider = null;
    static SAML2MetaManager metaManager = SAML2Utils.getSAML2MetaManager();

    static {
        try {
            dsProvider = SAML2Utils.getDataStoreProvider(); 
        } catch (SAML2Exception se) {
            SAML2Utils.debug.error("AttributeQueryUtil.static:", se);
        }
    }

    private AttributeQueryUtil() {
    }

    /**
     * Sends the <code>AttributeQuery</code> to specified
     * attribute authority and returns <code>Response</code> coming
     * from the attribute authority.
     *
     * @param attrQuery the <code>AttributeQuery</code> object
     * @param attrAuthorityEntityID entity ID of attribute authority
     * @param realm the realm of hosted entity
     * @param attrQueryProfile the attribute query profile or null to ignore
     * @param attrProfile the attribute profile
     * @param binding the binding
     *
     * @return the <code>Response</code> object
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
    public static Response sendAttributeQuery(AttributeQuery attrQuery,
        String attrAuthorityEntityID, String realm, String attrQueryProfile,
        String attrProfile, String binding) throws SAML2Exception {

        AttributeAuthorityDescriptorElement aad = null;
        try {
             aad = metaManager.getAttributeAuthorityDescriptor(
                realm, attrAuthorityEntityID);
        } catch (SAML2MetaException sme) {
            SAML2Utils.debug.error("AttributeQueryUtil.sendAttributeQuery:",
                sme);
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("metaDataError"));
        }

        if (aad == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
        }

        if (binding == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("unsupportedBinding"));
        }

        String location = findLocation(aad, binding, attrQueryProfile,
             attrProfile);

        if (location == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
        }

        if (binding.equalsIgnoreCase(SAML2Constants.SOAP)) {
            signAttributeQuery(attrQuery, realm, false);
            return sendAttributeQuerySOAP(attrQuery, location,
                attrAuthorityEntityID, aad);
        } else {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("unsupportedBinding"));
        }
    }

     /**
     * Sends the <code>AttributeQuery</code> to specified
     * attribute authority and returns <code>Response</code> coming
     * from the attribute authority.
     *
     * @param attrQuery the <code>AttributeQuery</code> object
     * @param request the HTTP Request
     * @param  response the HTTP Response
     * @param attrAuthorityEntityID entity ID of attribute authority
     * @param realm the realm of hosted entity
     * @param attrQueryProfile the attribute query profile or null to ignore
     * @param attrProfile the attribute profile
     * @param binding the binding
     *
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
     public static void sendAttributeQuery(AttributeQuery attrQuery,
        HttpServletRequest request, HttpServletResponse response,
        String attrAuthorityEntityID, String realm, String attrQueryProfile,
        String attrProfile, String binding) throws SAML2Exception {

        AttributeAuthorityDescriptorElement aad = null;
        try {
             aad = metaManager.getAttributeAuthorityDescriptor(
                realm, attrAuthorityEntityID);
        } catch (SAML2MetaException sme) {
            SAML2Utils.debug.error("AttributeQueryUtil.sendAttributeQuery:",
                sme);
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("metaDataError"));
        }

        if (aad == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
        }

        if (binding == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("unsupportedBinding"));
        }

        String location = findLocation(aad, binding, attrQueryProfile,
             attrProfile);

        if (location == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
        }

        if (binding.equalsIgnoreCase(SAML2Constants.HTTP_POST)) {
            signAttributeQuery(attrQuery, realm, false);
            String encodedReqMsg = SAML2Utils.encodeForPOST(attrQuery.toXMLString(true, true));
            SAML2Utils.postToTarget(request, response, "SAMLRequest", encodedReqMsg, null, null, location);
        } else {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("unsupportedBinding"));
        }
    }


    /**
     * Processes the <code>AttributeQuery</code> coming
     * from a requester.
     *
     * @param attrQuery the <code>AttributeQuery</code> object
     * @param request the <code>HttpServletRequest</code> object
     * @param response the <code>HttpServletResponse</code> object
     * @param attrAuthorityEntityID entity ID of attribute authority
     * @param realm the realm of hosted entity
     * @param attrQueryProfileAlias the attribute query profile alias
     *
     * @return the <code>Response</code> object
     * @exception SAML2Exception if the operation is not successful
     */
    public static Response processAttributeQuery(AttributeQuery attrQuery,
        HttpServletRequest request, HttpServletResponse response,
        String attrAuthorityEntityID, String realm,
        String attrQueryProfileAlias) throws SAML2Exception {

        AttributeAuthorityMapper attrAuthorityMapper = 
            getAttributeAuthorityMapper(realm, attrAuthorityEntityID,
            attrQueryProfileAlias);

        String attrQueryProfile = AttributeQueryUtil.getAttributeQueryProfile(
            attrQueryProfileAlias);

        try {
            attrAuthorityMapper.authenticateRequester(request, response,
                attrQuery, attrAuthorityEntityID, realm);
        } catch(SAML2Exception se) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message("AttributeQueryUtil." +
                "processAttributeQuery: ", se);
            }
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, null, se.getMessage(), null);
        }

        try {
            attrAuthorityMapper.validateAttributeQuery(request, response,
                attrQuery, attrAuthorityEntityID, realm);
        } catch(SAML2Exception se) {
            SAML2Utils.debug.error("AttributeQueryUtil.processAttributeQuery:",
                se);
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, null, se.getMessage(), null);
        }

        Issuer issuer = attrQuery.getIssuer();
        String requesterEntityID = issuer.getValue();        
        AttributeAuthorityDescriptorElement aad = null;
        try {
             aad = metaManager.getAttributeAuthorityDescriptor(
                realm, attrAuthorityEntityID);
        } catch (SAML2MetaException sme) {
            SAML2Utils.debug.error("AttributeQueryUtil.processAttributeQuery:",
                sme);
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.RESPONDER, null,
                SAML2Utils.bundle.getString("metaDataError"), null);
        } 

        if (aad == null) {
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, null,
                SAML2Utils.bundle.getString("attrAuthorityNotFound"), null);
        }

        Object identity = null;
        try {
            identity = attrAuthorityMapper.getIdentity(request, response,
                attrQuery, attrAuthorityEntityID, realm);
        } catch (SAML2Exception se) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message("AttributeQueryUtil." +
                "processAttributeQuery: ", se);
            }
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL,
                se.getMessage(), null);
        }

        if (identity == null) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message("AttributeQueryUtil." +
                "processAttributeQuery: unable to find identity.");
            }
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL,
                null, null);
        }

        // Addition to support changing of desired attributes list
        List desiredAttrs = (List)request.getAttribute("AttributeQueryUtil-desiredAttrs");
        if (desiredAttrs == null) {
            desiredAttrs = attrQuery.getAttributes();
        }
        try {
            desiredAttrs = verifyDesiredAttributes(aad.getAttribute(),
                desiredAttrs);
        } catch (SAML2Exception se) {
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.REQUESTER, 
                SAML2Constants.INVALID_ATTR_NAME_OR_VALUE, null, null);
        }

        List attributes = attrAuthorityMapper.getAttributes(identity,
            attrQuery, attrAuthorityEntityID, realm);

        if (request.getAttribute("AttributeQueryUtil-storeAllAttributes") != null) {
            request.setAttribute("AttributeQueryUtil-allAttributes", attributes);
        }

        attributes = filterAttributes(attributes, desiredAttrs);

        ProtocolFactory protocolFactory = ProtocolFactory.getInstance();
        Response samlResp = protocolFactory.createResponse();
        List assertionList = new ArrayList();

        Assertion assertion = null;
        try {
            assertion = getAssertion(attrQuery, attrAuthorityEntityID,
                requesterEntityID, realm, attrQueryProfileAlias, attributes);
        } catch (SAML2Exception se) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.processAttributeQuery:", se);
            }
            return SAML2Utils.getErrorResponse(attrQuery,
                SAML2Constants.RESPONDER, null, se.getMessage(), null);
        }

        EncryptedID encryptedID = attrQuery.getSubject().getEncryptedID();
        if (encryptedID != null) {
            EncryptedAssertion encryptedAssertion = null;
            try {
                signAssertion(assertion, realm, attrAuthorityEntityID, false);
                encryptedAssertion = encryptAssertion(assertion,
                        encryptedID, attrAuthorityEntityID, requesterEntityID,
                        realm, attrQueryProfileAlias);
            } catch (SAML2Exception se) {
                if (SAML2Utils.debug.messageEnabled()) {
                        SAML2Utils.debug.message(
                            "AttributeQueryUtil.processAttributeQuery:", se);
                }
                return SAML2Utils.getErrorResponse(attrQuery,
                        SAML2Constants.RESPONDER, null, se.getMessage(), null);
            }
            assertionList.add(encryptedAssertion);        
            samlResp.setEncryptedAssertion(assertionList);
        } else {
            assertionList.add(assertion);        
            samlResp.setAssertion(assertionList);
        }

        samlResp.setID(SAML2Utils.generateID());
        samlResp.setInResponseTo(attrQuery.getID());

        samlResp.setVersion(SAML2Constants.VERSION_2_0);
        samlResp.setIssueInstant(newDate());
    
        Status status = protocolFactory.createStatus();
        StatusCode statusCode = protocolFactory.createStatusCode();
        statusCode.setValue(SAML2Constants.SUCCESS);
        status.setStatusCode(statusCode);
        samlResp.setStatus(status);

        Issuer respIssuer = AssertionFactory.getInstance().createIssuer();
        respIssuer.setValue(attrAuthorityEntityID);
        samlResp.setIssuer(respIssuer);

        signResponse(samlResp, attrAuthorityEntityID, realm, false);

        return samlResp;
    }

    /**
     * Converts attribute query profile alias to attribute query profile.
     *
     * @param attrQueryProfileAlias attribute query profile alias
     *
     * @return attribute query profile
     */
    public static String getAttributeQueryProfile(
        String attrQueryProfileAlias) {

        if (attrQueryProfileAlias == null) {
            return null;
        } else if (attrQueryProfileAlias.equals(
            SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
            return  SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE;
        } else if (attrQueryProfileAlias.equals(
            SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
            return  SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE;
        }

        return null;
    }

    private static void signAttributeQuery(AttributeQuery attrQuery,
        String realm, boolean includeCert) throws SAML2Exception {
        String requesterEntityID = attrQuery.getIssuer().getValue();
        
        String alias = SAML2Utils.getSigningCertAlias(realm, requesterEntityID,
            SAML2Constants.ATTR_QUERY_ROLE);

        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
        if (signingKey == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("missingSigningCertAlias"));
        }

        X509Certificate signingCert = null;
        if (includeCert) {
            signingCert = keyProvider.getX509Certificate(alias);
        }
        
        if (signingKey != null) {
            attrQuery.sign(signingKey, signingCert);
        }
    }

    public static void validateEntityRequester(AttributeQuery attrQuery,
        String attrAuthorityEntityID, String realm) throws SAML2Exception {

        Issuer issuer = attrQuery.getIssuer();
        String format = issuer.getFormat();
        if ((format == null) || (format.length() == 0) ||
            (format.equals(SAML2Constants.UNSPECIFIED)) ||
            (format.equals(SAML2Constants.ENTITY))) {

            String requestedEntityID = issuer.getValue();

            if (!SAML2Utils.isSourceSiteValid(issuer, realm,
                attrAuthorityEntityID)) {
                throw new SAML2Exception(SAML2Utils.bundle.getString(
                    "attrQueryIssuerInvalid"));
            }
        } else {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "attrQueryIssuerInvalid"));
        }
    }

    /**
     * Checks if the attribute query signature is valid.
     *
     * @param attrQuery attribute query
     * @param attrAuthorityEntityID entity ID of attribute authority
     * @param realm the realm of hosted entity
     *
     * @exception SAML2Exception if the attribute query signature is not valid.
     */
    public static void verifyAttrQuerySignature(AttributeQuery attrQuery,
        String attrAuthorityEntityID, String realm)
        throws SAML2Exception {

        if (!attrQuery.isSigned()) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "attrQueryNotSigned"));
        }

        String requestedEntityID = attrQuery.getIssuer().getValue();

        AttributeQueryDescriptorElement attrqDesc =
            metaManager.getAttributeQueryDescriptor(realm, requestedEntityID);
        if (attrqDesc == null) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "attrQueryIssuerNotFound"));
        }
        Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(attrqDesc, requestedEntityID,
                SAML2Constants.ATTR_QUERY_ROLE);

        if (!signingCerts.isEmpty()) {
            boolean valid = attrQuery.isSignatureValid(signingCerts);
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.verifyAttributeQuery: " +
                    "Signature validity is : " + valid);
            }
            if (!valid) {
                throw new SAML2Exception(SAML2Utils.bundle.getString(
                    "invalidSignatureAttrQuery"));
            }
        } else {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("missingSigningCertAlias"));
        }
    }

    public static String getIdentityFromDataStoreX509Subject(
        AttributeQuery attrQuery, String attrAuthorityEntityID, String realm)
        throws SAML2Exception {

        Subject subject = attrQuery.getSubject();
        NameID nameID = null;
        EncryptedID encryptedID = subject.getEncryptedID();

        if (encryptedID != null) {
            nameID = encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID,
                    SAML2Constants.ATTR_AUTH_ROLE));
        } else {
            nameID = subject.getNameID();
        }

        if (!SAML2Constants.X509_SUBJECT_NAME.equals(nameID.getFormat())) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "unsupportedAttrQuerySubjectNameID"));
        }

        String mappingAttrName = getAttributeValueFromAttrAuthorityConfig(
            realm, attrAuthorityEntityID,
            SAML2Constants.X509_SUBJECT_DATA_STORE_ATTR_NAME);

        if ((mappingAttrName == null) || (mappingAttrName.length() == 0)) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "x509SubjectMappingNotConfigured"));
        }

        String x509SubjectDN = nameID.getValue();
        Map attrMap = new HashMap();
        Set values = new HashSet();
        values.add(x509SubjectDN);
        attrMap.put(mappingAttrName, values);

        if (SAML2Utils.debug.messageEnabled()) {
            SAML2Utils.debug.message(
                "AttributeQueryUtil.getIdentityFromDataStoreX509Subject: " +
                "mappingAttrName = " + mappingAttrName +
                ", X509 subject DN = " + x509SubjectDN);
        }

        try {
            return dsProvider.getUserID(realm, attrMap);
        } catch (DataStoreProviderException dse) {
            SAML2Utils.debug.error(
                "AttributeQueryUtil.getIdentityFromDataStoreX509Subject:",dse);
            throw new SAML2Exception(dse.getMessage());
        }
    }

    public static String getIdentity(AttributeQuery attrQuery,
        String attrAuthorityEntityID, String realm) throws SAML2Exception {

        Subject subject = attrQuery.getSubject();
        NameID nameID = null;
        EncryptedID encryptedID = subject.getEncryptedID();

        if (encryptedID != null) {
            nameID = encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID,
                    SAML2Constants.ATTR_AUTH_ROLE));
        } else {
            nameID = subject.getNameID();
        }

        String nameIDFormat = nameID.getFormat();
        // NameIDFormat is "transient"
        if (SAML2Constants.NAMEID_TRANSIENT_FORMAT.equals(nameIDFormat)) {
            return (String)IDPCache.userIDByTransientNameIDValue.get(
                nameID.getValue());
        } else  
          // NameIDFormat is "unspecified"
          if (SAML2Constants.UNSPECIFIED.equals(nameIDFormat)) {
            Map userIDsSearchMap = new HashMap();
            Set userIDValuesSet = new HashSet();
            userIDValuesSet.add(nameID.getValue());
            String userId = "uid";

            IDPSSOConfigElement config = SAML2Utils.getSAML2MetaManager().getIDPSSOConfig(
                    realm, attrAuthorityEntityID);
            Map attrs = SAML2MetaUtils.getAttributes(config);

            List nimAttrs = (List)attrs.get(SAML2Constants.NAME_ID_FORMAT_MAP);


            for (Iterator i = nimAttrs.iterator(); i.hasNext(); ) {
                String attrName = (String)i.next();
                if (attrName != null && attrName.length()>2 && attrName.startsWith(nameIDFormat)) {
                    int eqPos = attrName.indexOf('=');
                    if (eqPos != -1 && eqPos<attrName.length()-2) {
                        userId = attrName.substring(eqPos+1);
                        SAML2Utils.debug.message("AttributeQueryUtil.getIdentity: NameID attribute from map: " + userId);
                        break;
                    }
                }
            }
            userIDsSearchMap.put(userId, userIDValuesSet);
            try {
                return dsProvider.getUserID(realm, userIDsSearchMap);
            } catch (DataStoreProviderException dse) {
                SAML2Utils.debug.error(
                    "AttributeQueryUtil.getIdentityFromDataStore1:", dse);
                throw new SAML2Exception(dse.getMessage());
            }
        } else {
            String requestedEntityID = attrQuery.getIssuer().getValue();

            try {
                return dsProvider.getUserID(realm, SAML2Utils.getNameIDKeyMap(
                    nameID, attrAuthorityEntityID, requestedEntityID, realm,
                    SAML2Constants.IDP_ROLE));
            } catch (DataStoreProviderException dse) {
                SAML2Utils.debug.error(
                    "AttributeQueryUtil.getIdentityFromDataStore:", dse);
                throw new SAML2Exception(dse.getMessage());
            }
        }
    }

    public static List getUserAttributes(String userId,
        AttributeQuery attrQuery, String attrAuthorityEntityID, String realm)
        throws SAML2Exception {
 
        String requestedEntityID = attrQuery.getIssuer().getValue();

        Map configMap = SAML2Utils.getConfigAttributeMap(realm,
            requestedEntityID, SAML2Constants.SP_ROLE);
        if (SAML2Utils.debug.messageEnabled()) {
            SAML2Utils.debug.message(
                "AttributeQueryUtil.getUserAttributes: " +
                "remote SP attribute map = " + configMap);
        }
        if (configMap == null || configMap.isEmpty()) {
            configMap = SAML2Utils.getConfigAttributeMap(realm,
                attrAuthorityEntityID, SAML2Constants.IDP_ROLE);
            if (configMap == null || configMap.isEmpty()) {
                if (SAML2Utils.debug.messageEnabled()) {
                    SAML2Utils.debug.message(
                        "AttributeQueryUtil.getUserAttributes:" +
                        "Configuration map is not defined.");
                }
                return null;
            }
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.getUserAttributes: " +
                    "hosted IDP attribute map=" + configMap);
            }
        }

        List attributes = new ArrayList();

        Set localAttributes = new HashSet();
        localAttributes.addAll(configMap.values());
        Map valueMap = null;

        try {
            valueMap = dsProvider.getAttributes(userId, localAttributes);
        } catch (DataStoreProviderException dse) {
            if (SAML2Utils.debug.warningEnabled()) {
                SAML2Utils.debug.warning(
                    "AttributeQueryUtil.getUserAttributes:", dse);
            }
        }

        Iterator iter = configMap.keySet().iterator();
        while(iter.hasNext()) {
            String samlAttribute = (String)iter.next();
            String localAttribute = (String)configMap.get(samlAttribute);
            String[] localAttributeValues = null;
            if ((valueMap != null) && (!valueMap.isEmpty())) {
                Set values = (Set)valueMap.get(localAttribute);
                if ((values == null) || values.isEmpty()) {
                    if (SAML2Utils.debug.messageEnabled()) {
                        SAML2Utils.debug.message(
                            "AttributeQueryUtil.getUserAttributes:" +
                            " user profile does not have value for " +
                            localAttribute);
                    }
                } else {
                    localAttributeValues = (String[])
                        values.toArray(new String[values.size()]);
                }
            }

            if ((localAttributeValues == null) ||
                (localAttributeValues.length == 0)) {
                if (SAML2Utils.debug.messageEnabled()) {
                    SAML2Utils.debug.message(
                        "AttributeQueryUtil.getUserAttributes:" +
                        " user does not have " + localAttribute);
                }
                continue;
            }

            Attribute attr = SAML2Utils.getSAMLAttribute(samlAttribute,
                localAttributeValues);
            attributes.add(attr);
        }
        return attributes;
    }

    public static void signResponse(Response response,
        String attrAuthorityEntityID, String realm, boolean includeCert)
        throws SAML2Exception {
        
        String alias = SAML2Utils.getSigningCertAlias(realm,
            attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE);

        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
        if (signingKey == null) {
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("missingSigningCertAlias"));
        }

        X509Certificate signingCert = null;
        if (includeCert) {
            signingCert = keyProvider.getX509Certificate(alias);
        }
        
        if (signingKey != null) {
            response.sign(signingKey, signingCert);
        }
    }

    private static Assertion getAssertion(AttributeQuery attrQuery,
        String attrAuthorityEntityID, String requesterEntityID, String realm,
        String attrQueryProfileAlias, List attributes) throws SAML2Exception {

        AssertionFactory assertionFactory = AssertionFactory.getInstance();
        Assertion assertion = assertionFactory.createAssertion();
        assertion.setID(SAML2Utils.generateID());    
        assertion.setVersion(SAML2Constants.VERSION_2_0);
        assertion.setIssueInstant(newDate());
        Issuer issuer = assertionFactory.createIssuer();
        issuer.setValue(attrAuthorityEntityID);
        assertion.setIssuer(issuer);

        Subject subjectQ = attrQuery.getSubject();
        Subject subject = assertionFactory.createSubject();
        subject.setEncryptedID(subjectQ.getEncryptedID());
        subject.setNameID(subjectQ.getNameID());
        subject.setBaseID(subjectQ.getBaseID());
        subject.setSubjectConfirmation(subjectQ.getSubjectConfirmation());
        assertion.setSubject(subject);

        if ((attributes != null) && (!attributes.isEmpty())) {
            AttributeStatement attrStatement =
                assertionFactory.createAttributeStatement();

            attrStatement.setAttribute(attributes);
            List attrStatementList = new ArrayList();
            attrStatementList.add(attrStatement);
            assertion.setAttributeStatements(attrStatementList); 
        }

        int effectiveTime = IDPSSOUtil.getEffectiveTime(realm,
            attrAuthorityEntityID);
        int notBeforeSkewTime = IDPSSOUtil.getNotBeforeSkewTime(realm,
            attrAuthorityEntityID);
        Conditions conditions = IDPSSOUtil.getConditions(requesterEntityID, 
            notBeforeSkewTime, effectiveTime);
        assertion.setConditions(conditions);

        return assertion;
    }

    private static void signAssertion(Assertion assertion, String realm,
        String attrAuthorityEntityID, boolean includeCert)
        throws SAML2Exception {

        String alias = SAML2Utils.getSigningCertAlias(realm,
            attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE);

        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
        X509Certificate signingCert = null;
        if (includeCert) {
            signingCert = keyProvider.getX509Certificate(alias);
        }
        
        if (signingKey != null) {
            assertion.sign(signingKey, signingCert);
        }
    }

    private static EncryptedAssertion encryptAssertion(Assertion assertion, EncryptedID encryptedID,
            String attrAuthorityEntityID, String requesterEntityID, String realm, String attrQueryProfileAlias)
            throws SAML2Exception {
        SecretKey secretKey = EncManager.getEncInstance().getSecretKey(encryptedID.toXMLString(true, true),
                KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE));

        AttributeQueryDescriptorElement aqd =
            metaManager.getAttributeQueryDescriptor(realm, requesterEntityID);
        EncInfo encInfo = KeyUtil.getEncInfo(aqd, requesterEntityID,
            SAML2Constants.ATTR_QUERY_ROLE);

        Element el = EncManager.getEncInstance().encrypt(
            assertion.toXMLString(true, true), encInfo.getWrappingKey(),
            secretKey, encInfo.getDataEncAlgorithm(),
            encInfo.getDataEncStrength(), requesterEntityID,
            "EncryptedAssertion");

        return AssertionFactory.getInstance().createEncryptedAssertion(el);
    }

    private static List<Attribute> verifyDesiredAttributes(List<AttributeElement> supportedAttrs,
            List<Attribute> desiredAttrs) throws SAML2Exception {
        if (supportedAttrs == null || supportedAttrs.isEmpty()) {
            return desiredAttrs;
        }

        if (desiredAttrs == null || desiredAttrs.isEmpty()) {
            return convertAttributes(supportedAttrs);
        }

        for (Attribute desiredAttr : desiredAttrs) {
            boolean isAttrValid = false;
            Iterator<AttributeElement> supportedAttrIterator = supportedAttrs.iterator();
            while (supportedAttrIterator.hasNext()) {
                AttributeElement supportedAttr = supportedAttrIterator.next();
                if (isSameAttribute(desiredAttr, supportedAttr)) {
                    if (isValueValid(desiredAttr, supportedAttr)) {
                        isAttrValid = true;
                        //By removing the attribute from the supported list we make sure that an AttributeQuery can
                        //not request the same Attribute more than once, see SAML core 3.3.2.3.
                        supportedAttrIterator.remove();
                        break;
                    } else {
                        throw new SAML2Exception("Attribute value not supported");
                    }
                }
            }
            if (!isAttrValid) {
                throw new SAML2Exception("Attribute name not supported");
            }
        }
        return desiredAttrs;
    }

    private static List convertAttributes(List jaxbAttrs)
        throws SAML2Exception {

        List resultAttrs = new ArrayList();
        for(Iterator iter = jaxbAttrs.iterator(); iter.hasNext(); ) {
            AttributeElement jaxbAttr = (AttributeElement)iter.next();
            Attribute attr = AssertionFactory.getInstance().createAttribute();
            attr.setName(jaxbAttr.getName());
            attr.setNameFormat(jaxbAttr.getNameFormat());
            attr.setFriendlyName(jaxbAttr.getFriendlyName());

            List jaxbValues = jaxbAttr.getAttributeValue();
            if ((jaxbValues != null) && (!jaxbValues.isEmpty())) {
                List newValues = new ArrayList();
                for(Iterator iterV = jaxbValues.iterator(); iterV.hasNext();) {
                    AttributeValueElement jaxbValeu =
                        (AttributeValueElement)iter.next();
                    List content = jaxbValeu.getContent();
                    if ((content != null) && (!content.isEmpty())) {
                        newValues.add(content.get(0));
                    }
                }
                if (!newValues.isEmpty()) {
                    attr.setAttributeValueString(newValues);
                }
            }
            resultAttrs.add(attr);
        }
        return resultAttrs;
    }

    private static List<Attribute> filterAttributes(List<Attribute> attributes, List<Attribute> desiredAttrs) {

        if (attributes == null || attributes.isEmpty()) {
            SAML2Utils.debug.message("AttributeQueryUtil.filterAttributes: attributes are null");
            return attributes;
        }
        if (desiredAttrs == null || desiredAttrs.isEmpty()) {
            SAML2Utils.debug.message("AttributeQueryUtil.filterAttributes: desired attributes are null");
            return attributes;
        }

        List<Attribute> returnAttributes = new ArrayList<Attribute>();
        if (!desiredAttrs.isEmpty()) {
            for (Attribute attrD : desiredAttrs) {
                for (Attribute attr : attributes) {
                    if (isSameAttribute(attr, attrD) ) {
                        attr = filterAttributeValues(attr, attrD);
                        if (attr != null) {
                            //let's copy FriendlyName if exists
                            String fName = attrD.getFriendlyName();
                            if (fName != null && fName.length() > 0){
                                try {
                                    attr.setFriendlyName(fName);
                                } catch (SAML2Exception e) {
                                    //do nothing, attribute will be sent without
                                    //friendlyName set
                                }
                            }
                            returnAttributes.add(attr);
                        }
                        break;
                    }
                }
            }
        }
        return returnAttributes;
    }

    private static boolean isSameAttribute(Attribute attribute, Attribute desired) {
        return desired.getName().equals(attribute.getName())
                && isNameFormatMatching(desired.getNameFormat(), attribute.getNameFormat());
    }

    private static Attribute filterAttributeValues(Attribute attr,
        Attribute desiredAttr) {

        List valuesD = desiredAttr.getAttributeValueString();
        if ((valuesD == null) || (valuesD.isEmpty())) {
            return attr;
        }

        List values = attr.getAttributeValueString();
        if ((values == null) || (values.isEmpty())) {
            return null;
        }

        List newValuesD = new ArrayList();
        for(Iterator iter = valuesD.iterator(); iter.hasNext(); ) {
            String valueD = (String)iter.next();
            if (values.contains(valueD)) {
                newValuesD.add(valueD);
            }
        }

        if (newValuesD.isEmpty()) {
            return null;
        }

        if (newValuesD.size() == valuesD.size()) {
            return desiredAttr;
        }

        try {
            Attribute newAttr =
                AssertionFactory.getInstance().createAttribute();
            newAttr.setName(desiredAttr.getName());
            newAttr.setNameFormat(desiredAttr.getNameFormat());
            newAttr.setFriendlyName(desiredAttr.getFriendlyName());
            newAttr.setAnyAttribute(desiredAttr.getAnyAttribute());
            newAttr.setAttributeValueString(newValuesD);

            return newAttr;
        } catch(SAML2Exception se) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.filterAttributeValues:", se);
            }
            return null;
        }
    }

    private static boolean isSameAttribute(Attribute desired, AttributeElement supported) {
        return desired.getName().equals(supported.getName())
                && isNameFormatMatching(desired.getNameFormat(), supported.getNameFormat());
    }

    /**
     * Determines whether the desired Attribute NameFormat matches with the available attribute's NameFormat. When
     * the NameFormat isn't specified in the request, the
     * <code>urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</code> default NameFormat needs to be used (see
     * SAML core spec 2.7.3.1).
     * The different attribute profiles (SAML profiles spec section 8) each determine how the attribute comparison
     * should be performed, however there is no clear way to actually determine which attribute profile is being used
     * when the Attribute Authority supports more than one profile. Because of this, the unspecified Attribute
     * NameFormat has been implemented as a wildcard match, much similarly to how requesting the unspecified
     * NameID-Format allows the IdP to choose an arbitrary NameID-Format when generating the assertion for an SP.
     *
     * @param desiredNameFormat The NameFormat of the Attribute defined in the AttributeQuery request.
     * @param availableNameFormat The NameFormat of the Attribute defined in the server configuration.
     * @return <code>true</code> if the desired NameFormat is unspecified, or if it is the same as the NameFormat
     * defined in the server configuration.
     */
    private static boolean isNameFormatMatching(String desiredNameFormat, String availableNameFormat) {
        return desiredNameFormat == null || DEFAULT_ATTRIBUTE_NAME_FORMAT.equals(desiredNameFormat)
                || desiredNameFormat.equals(availableNameFormat);
    }

    private static boolean isValueValid(Attribute desiredAttr,
        AttributeElement supportedAttr) {

        List valuesD = desiredAttr.getAttributeValueString();
        if ((valuesD == null) || (valuesD.isEmpty())) {
            return true;
        }
        List attrValuesS = supportedAttr.getAttributeValue();
        if ((attrValuesS == null) || (attrValuesS.isEmpty())) {
            return true;
        }

        List valuesS = new ArrayList();
        for(Iterator iter = attrValuesS.iterator(); iter.hasNext(); ) {
            AttributeValueElement attrValueElem =
                (AttributeValueElement)iter.next();
            valuesS.addAll(attrValueElem.getContent());
        }

        try {
            return valuesS.containsAll(valuesD);
        } catch (Exception ex) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.isValueValid:", ex);
            }
            return false;
        }
    }

    private static Response sendAttributeQuerySOAP(AttributeQuery attrQuery,
        String attributeServiceURL, String attrAuthorityEntityID,
        AttributeAuthorityDescriptorElement aad) throws SAML2Exception {

        String attrQueryXMLString = attrQuery.toXMLString(true, true);
        if (SAML2Utils.debug.messageEnabled()) {
            SAML2Utils.debug.message(
                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
                "attrQueryXMLString = " + attrQueryXMLString);
            SAML2Utils.debug.message(
                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
                "attributeServiceURL = " + attributeServiceURL);
        }
        
        SOAPMessage resMsg = null;
        try {
            resMsg = SOAPCommunicator.getInstance().sendSOAPMessage(attrQueryXMLString,
                    attributeServiceURL, true);
        } catch (SOAPException se) {
            SAML2Utils.debug.error(
                "AttributeQueryUtil.sendAttributeQuerySOAP: ", se);
            throw new SAML2Exception(
                SAML2Utils.bundle.getString("errorSendingAttributeQuery"));
        }
        
        Element respElem = SOAPCommunicator.getInstance().getSamlpElement(resMsg, "Response");
        Response response =
            ProtocolFactory.getInstance().createResponse(respElem);

        Status status = response.getStatus();
        if (!SAML2Constants.SUCCESS.equals(status.getStatusCode().getValue())) {
            String message = status.getStatusMessage() == null ? "" : status.getStatusMessage();
            String detail = status.getStatusDetail() == null ? "" : status.getStatusDetail().toXMLString();

            SAML2Utils.debug.error(
                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
                "Non-Success status " + status.getStatusCode().getValue() +
                    ", message: " + message + ", detail: " + detail);

            Object[] args = { status.getStatusCode().getValue(), message, detail };
            throw new SAML2Exception(SAML2Utils.BUNDLE_NAME, "failureStatusAttributeQuery", args);
        }

        if (SAML2Utils.debug.messageEnabled()) {
            SAML2Utils.debug.message(
                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
                "response = " + response.toXMLString(true, true));
        }

        verifyResponse(response, attrQuery, attrAuthorityEntityID, aad);

        return response;
    }

    private static void verifyResponse(Response response,
        AttributeQuery attrQuery, String attrAuthorityEntityID,
        AttributeAuthorityDescriptorElement aad)
        throws SAML2Exception {

        String attrQueryID = attrQuery.getID();
        if ((attrQueryID != null) &&
            (!attrQueryID.equals(response.getInResponseTo()))) {

            throw new SAML2Exception(
                SAML2Utils.bundle.getString("invalidInResponseToAttrQuery"));
        }

        Issuer respIssuer = response.getIssuer();
        if (respIssuer == null) {
            return;
        }

        if (!attrAuthorityEntityID.equals(respIssuer.getValue())) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "responseIssuerMismatch"));
        }

        if (!response.isSigned()) {
            throw new SAML2Exception(SAML2Utils.bundle.getString(
                "responseNotSigned"));
        }

        Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(aad, attrAuthorityEntityID,
                SAML2Constants.ATTR_AUTH_ROLE);

        if (!signingCerts.isEmpty()) {
            boolean valid = response.isSignatureValid(signingCerts);
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(
                    "AttributeQueryUtil.verifyResponse: " +
                    "Signature validity is : " + valid);
            }
            if (!valid) {
                throw new SAML2Exception(SAML2Utils.bundle.getString(
                    "invalidSignatureOnResponse"));
            }
        } else {
            throw new SAML2Exception(
                    SAML2Utils.bundle.getString("missingSigningCertAlias"));
        }

    }

    private static String findLocation(
        AttributeAuthorityDescriptorElement aad, String binding,
        String attrQueryProfile, String attrProfile) {
        SAML2Utils.debug.message("AttributeQueryUtil.findLocation entering...");
        List attrProfiles = aad.getAttributeProfile();
        if ((attrProfiles == null) || (attrProfiles.isEmpty())) {
            SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfiles is null or empty");
            if (attrProfile != null) {
                SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfiles is null or empty and attrProfile is null");
                return null;
            }
        } else if (!attrProfiles.contains(attrProfile)) {
            SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfile not found in the attrProfiles");
            return null;
        }
        SAML2Utils.debug.message("AttributeQueryUtil.findLocation: entering...");

        List attrServices = aad.getAttributeService();
        for(Iterator iter = attrServices.iterator(); iter.hasNext(); ) {
            AttributeServiceElement attrService =
                (AttributeServiceElement)iter.next();
            if (isValidAttributeService(binding, attrService,
                attrQueryProfile)) {
                SAML2Utils.debug.message("AttributeQueryUtil.findLocation: found valid service");
                return attrService.getLocation();
            }
        }
        SAML2Utils.debug.message("AttributeQueryUtil.findLocation: nothing found, leaving last line with null");

        return null;
    }

    private static boolean isValidAttributeService(String binding,
        AttributeServiceElement attrService, String attrQueryProfile) {
    
        if (!binding.equalsIgnoreCase(attrService.getBinding())) {
            return false;
        }

        if (attrQueryProfile == null) {
            return false;
        }

        return ((attrQueryProfile.equals(
            SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE)) ||
            (SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE.equals(
            attrQueryProfile) && attrService.isSupportsX509Query()));
    }

    /** 
     * Returns an <code>AttributeAuthorityMapper</code>
     *
     * @param realm the realm name
     * @param attrAuthorityEntityID the entity id of the attribute authority
     * @param attrQueryProfileAlias attribute profile alias
     *
     * @return the <code>AttributeAuthorityMapper</code>
     * @exception SAML2Exception if the operation is not successful
     */
    static AttributeAuthorityMapper getAttributeAuthorityMapper(String realm,
        String attrAuthorityEntityID, String attrQueryProfileAlias)
        throws SAML2Exception {

        String attrAuthorityMapperName = null;
        AttributeAuthorityMapper attrAuthorityMapper = null;
        try {
            attrAuthorityMapperName = getAttributeValueFromAttrAuthorityConfig(
                realm, attrAuthorityEntityID, attrQueryProfileAlias + "_" +
                SAML2Constants.ATTRIBUTE_AUTHORITY_MAPPER);

            if (attrAuthorityMapperName == null) {
                attrAuthorityMapperName = 
                    SAML2Constants.DEFAULT_ATTRIBUTE_AUTHORITY_MAPPER_CLASS;
                if (SAML2Utils.debug.messageEnabled()) {
                    SAML2Utils.debug.message(
                        "AttributeQueryUtil.getAttributeAuthorityMapper: use "+
                        attrAuthorityMapperName);
                }
            }
            attrAuthorityMapper = (AttributeAuthorityMapper)
                attrAuthorityMapperCache.get(attrAuthorityMapperName);
            if (attrAuthorityMapper == null) {
                attrAuthorityMapper = (AttributeAuthorityMapper)
                    Class.forName(attrAuthorityMapperName).newInstance();
                attrAuthorityMapperCache.put(attrAuthorityMapperName,
                       attrAuthorityMapper);
            } else {
                if (SAML2Utils.debug.messageEnabled()) {
                    SAML2Utils.debug.message(
                        "AttributeQueryUtil.getAttributeAuthorityMapper: " +
                        "got the AttributeAuthorityMapper from cache");
                }
            }
        } catch (Exception ex) {
            SAML2Utils.debug.error(
                "AttributeQueryUtil.getAttributeAuthorityMapper: " +
                "Unable to get IDP Attribute Mapper.", ex);
            throw new SAML2Exception(ex);
        }

        return attrAuthorityMapper;
    }

    private static String getAttributeValueFromAttrAuthorityConfig(
        String realm, String attrAuthorityEntityID, String attrName)
    {
        try {
            AttributeAuthorityConfigElement config =
                metaManager.getAttributeAuthorityConfig(realm,
                attrAuthorityEntityID);
            Map attrs = SAML2MetaUtils.getAttributes(config);
            String value = null;
            List values = (List) attrs.get(attrName);
            if ((values != null) && (!values.isEmpty())) {
                value = ((String)values.iterator().next()).trim();
            }
            return value;
        } catch (SAML2MetaException sme) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message("AttributeQueryUtil." +
                   "getAttributeValueFromAttrAuthorityConfig: " +
                   "get AttributeAuthorityConfig failed", sme);
            }
        }
        return null;
    }

    /**
     * Sends the AttributeQuery to specified attribute authority,
     * validates the response and returns the attribute map
     * <code>Map&lt;String, String&gt;</code> to the Fedlet
     *
     * @param spEntityID SP entity ID
     * @param idpEntityID IDP entity ID
     * @param nameIDValue  NameID value 
     * @param attrsList The list of attributes whose values need to be
     *                  fetched from IDP
     * @param attrQueryProfileAlias  Attribute Query Profile Alias
     * @param subjectDN  Attribute name which contains X.509 subject DN
     *
     * @return the <code>Map</code> object
     * @exception SAML2Exception if the operation is not successful
     *
     * @deprecated Use {@link #getAttributesForFedlet(String, String, String, List, String, String)}
     */
    public static Map<String, String> getAttributeMapForFedlet(String spEntityID, String idpEntityID,
            String nameIDValue, List<String> attrsList, String attrQueryProfileAlias, String subjectDN)
            throws SAML2Exception {
        Map<String, Set<String>> attrMap = getAttributesForFedlet(spEntityID, idpEntityID, nameIDValue, attrsList,
                attrQueryProfileAlias, subjectDN);

        Map<String, String> newAttrMap = new HashMap<String, String>();
        for (Map.Entry<String, Set<String>> entry : attrMap.entrySet()) {
            String attrName = entry.getKey();
            Set<String> attrValue = entry.getValue();
            StringBuilder pipedValue = new StringBuilder();
            for (String value : attrValue) {
                // Multiple attribute values
                // are seperated with "|"
                if (pipedValue.length() > 0) {
                    pipedValue.append('|');
                }
                pipedValue.append(value);
            }
            newAttrMap.put(attrName, pipedValue.toString());
        }

        return newAttrMap;
    }
    
    /**
     * Sends the AttributeQuery to specified attribute authority,
     * validates the response and returns the attribute map
     * <code>Map&lt;String, Set&lt;String&gt;&gt;</code> to the Fedlet
     *
     * @param spEntityID SP entity ID
     * @param idpEntityID IDP entity ID
     * @param nameIDValue  NameID value 
     * @param attrsList The list of attributes whose values need to be
     *                  fetched from IDP
     * @param attrQueryProfileAlias  Attribute Query Profile Alias
     * @param subjectDN  Attribute name which contains X.509 subject DN
     *
     * @return the <code>Map</code> object
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
    public static Map<String, Set<String>> getAttributesForFedlet(String spEntityID, String idpEntityID,
            String nameIDValue, List<String> attrsList, String attrQueryProfileAlias, String subjectDN)
            throws SAML2Exception {
        final String classMethod = "AttributeQueryUtil.getAttributesForFedlet: ";

        AttributeQueryConfigElement attrQueryConfig = metaManager.getAttributeQueryConfig("/", spEntityID);
        if (attrQueryConfig == null) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(classMethod + "Attribute Query Config is null");
            }
            return null;
        }

        String attrqMetaAlias = attrQueryConfig.getMetaAlias();
        if (attrqMetaAlias == null) {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(classMethod + "Attribute Query MetaAlias is null");
            }
            return null;
        }

        boolean wantNameIDEncrypted = SAML2Utils.getWantNameIDEncrypted("/", spEntityID,
                SAML2Constants.ATTR_QUERY_ROLE);
        
        AttributeQuery attrQuery = constructAttrQueryForFedlet(spEntityID, idpEntityID, nameIDValue, attrsList,
                attrqMetaAlias, attrQueryProfileAlias, subjectDN, wantNameIDEncrypted);

        String attrQueryProfile = null;
        if (attrQueryProfileAlias.equals(SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
            attrQueryProfile = SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE;
        } else if (attrQueryProfileAlias.equals(SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
            attrQueryProfile = SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE;
        }

        Response samlResp = sendAttributeQuery(attrQuery, idpEntityID, "/", attrQueryProfile,
                SAML2Constants.BASIC_ATTRIBUTE_PROFILE, SAML2Constants.SOAP);

        // Validate the response
        boolean validResp = validateSAMLResponseForFedlet(samlResp, spEntityID, wantNameIDEncrypted);

        Map<String, Set<String>> attrMap = new HashMap<String, Set<String>>();
        if (validResp) {
            // Return back the AttributeMap
            if (samlResp != null) {
                List<Object> assertions;
                if (wantNameIDEncrypted) {
                    assertions = samlResp.getEncryptedAssertion();
                } else {
                    assertions = samlResp.getAssertion();
                }

                for (Object currentAssertion : assertions) {
                    Assertion assertion;
                    if (wantNameIDEncrypted) {
                        assertion = getDecryptedAssertion((EncryptedAssertion) currentAssertion, spEntityID);
                    } else {
                        assertion = (Assertion) currentAssertion;
                    }
                    if (assertion != null) {
                        List<AttributeStatement> statements = assertion.getAttributeStatements();
                        if (statements != null && statements.size() > 0) {
                            for (AttributeStatement statement : statements) {
                                List<Attribute> attributes = statement.getAttribute();
                                attrMap.putAll(mapAttributes("/", spEntityID, idpEntityID, nameIDValue, attributes));
                            }
                        } else {
                            if (SAML2Utils.debug.messageEnabled()) {
                                SAML2Utils.debug.message(classMethod + "Empty Statement present in SAML response");
                            }
                        }
                    } else {
                        if (SAML2Utils.debug.messageEnabled()) {
                            SAML2Utils.debug.message(classMethod + "Empty Assertion present in SAML response");
                        }
                    }
                }
                if (SAML2Utils.debug.messageEnabled()) {
                    SAML2Utils.debug.message(classMethod + "attributes received from Attribute Query: " + attrMap);
                }
            }
        } else {
            if (SAML2Utils.debug.messageEnabled()) {
                SAML2Utils.debug.message(classMethod + "Invalid response obtained from Attribute Authority");
            }
        }
        // Return the attribute map and to the fedlet
        return attrMap;
    }

    private static Map<String, Set<String>> mapAttributes(String realm, String spEntityID, String idpEntityID,
            String userID, List<Attribute> attributes) throws SAML2Exception {
        SPAttributeMapper spAttributeMapper = SAML2Utils.getSPAttributeMapper(realm, spEntityID);
        return spAttributeMapper.getAttributes(attributes, userID, spEntityID, idpEntityID, realm);
    }

    /**
     * Constructs the Attribute Query used by the Fedlet to retrieve the 
     * values from IDP
     *
     * @param samlResp saml response
     *
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
    private static AttributeQuery constructAttrQueryForFedlet(
                             String spEntityID,
                             String idpEntityID,
                             String nameIDValue,
                             List<String> attrsList,
                             String attrqMetaAlias,
                             String attrProfileNameAlias,
                             String subjectDN,
                             boolean wantNameIDEncrypted) throws SAML2Exception
    {
        String attrqEntityID =
          SAML2Utils.getSAML2MetaManager().getEntityByMetaAlias(attrqMetaAlias);

        ProtocolFactory protocolFactory = ProtocolFactory.getInstance();
        AssertionFactory assertionFactory = AssertionFactory.getInstance();        

        AttributeQuery attrQuery = protocolFactory.createAttributeQuery();

        Issuer issuer = assertionFactory.createIssuer();
        issuer.setValue(attrqEntityID);

        attrQuery.setIssuer(issuer);
        attrQuery.setID(SAML2Utils.generateID());
        attrQuery.setVersion(SAML2Constants.VERSION_2_0);
        attrQuery.setIssueInstant(newDate());

        List attrs = new ArrayList();
        for (String attributeName : attrsList) {
            Attribute attr = assertionFactory.createAttribute();
            attr.setName(attributeName);
            attr.setNameFormat(SAML2Constants.BASIC_NAME_FORMAT);
            attrs.add(attr);
        }        
        attrQuery.setAttributes(attrs);

        Subject subject = assertionFactory.createSubject();
        NameID nameID = assertionFactory.createNameID();
        nameID.setNameQualifier(idpEntityID);
        nameID.setSPNameQualifier(spEntityID);

        if (attrProfileNameAlias.equals(
                    SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
            nameID.setFormat(SAML2Constants.NAMEID_TRANSIENT_FORMAT);
            nameID.setValue(nameIDValue);
        }

        if (attrProfileNameAlias.equals(
                    SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
            nameID.setFormat(SAML2Constants.X509_SUBJECT_NAME);
            nameID.setValue(subjectDN);            
        }

        if (!wantNameIDEncrypted) {
            subject.setNameID(nameID);
        } else {
            AttributeAuthorityDescriptorElement aad =
                  metaManager.getAttributeAuthorityDescriptor("/", idpEntityID);

            EncInfo encInfo = KeyUtil.getEncInfo(aad, idpEntityID,
                                                 SAML2Constants.ATTR_AUTH_ROLE);            

            EncryptedID encryptedID = nameID.encrypt(encInfo.getWrappingKey(),
                                                  encInfo.getDataEncAlgorithm(),
                                                  encInfo.getDataEncStrength(),
                                                  idpEntityID);
            subject.setEncryptedID(encryptedID);
        }
    
        attrQuery.setSubject(subject);

        return attrQuery;
    }

     /**
     * Validates the SAML response obtained from Attribute Authortity
     *
     * @param samlResp saml response
     *
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
    private static boolean validateSAMLResponseForFedlet(
                              Response samlResp,
                              String spEntityID, 
                              boolean wantNameIDEncrypted) throws SAML2Exception
    {
        boolean resp = true;
        if (samlResp != null && samlResp.isSigned()) {
            List assertions = null;
            if (wantNameIDEncrypted) {
                assertions = samlResp.getEncryptedAssertion();
            } else {
                assertions = samlResp.getAssertion();
            }
            if (assertions == null) {
                return false;
            }
            for (Iterator asserIter = assertions.iterator();
                 asserIter.hasNext();) {
                Assertion assertion = null;
                if (wantNameIDEncrypted) {
                    assertion = getDecryptedAssertion(
                                         (EncryptedAssertion)asserIter.next(),
                                          spEntityID);
                } else {
                    assertion = (Assertion)asserIter.next();
                }
                if (assertion != null) {
                    Conditions conditions = assertion.getConditions();
                    if (conditions != null) {
                        List audienceRes = conditions.
                                               getAudienceRestrictions();
                        if (audienceRes.size() > 1) {
                                resp = false;
                                break;
                        }
                     }
                     List statements = assertion.getAttributeStatements();
                     if (statements.size() > 1) {
                         resp = false;
                         break;
                     }
                }
            }
        } else {
            resp = false;
        }
        return resp;
    }

     /**
     * Returns the decrypted assertion
     *
     * @param samlResp saml response
     *
     * @exception SAML2Exception if the operation is not successful
     *
     * @supported.api
     */
    private static Assertion getDecryptedAssertion(
                                      EncryptedAssertion eAssertion,
                                      String spEntityID) throws SAML2Exception 
    {
        if (eAssertion != null) {
            return eAssertion.decrypt(KeyUtil.getDecryptionKeys("/", spEntityID, SAML2Constants.ATTR_QUERY_ROLE));
        }
        return null;
    }
}
