001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2007 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: AttributeQueryUtil.java,v 1.11 2009/07/24 22:51:48 madan_ranganath Exp $
026 *
027 * Portions copyright 2010-2017 ForgeRock AS.
028 */
029package com.sun.identity.saml2.profile;
030
031import static org.forgerock.openam.utils.Time.*;
032
033import java.util.ArrayList;
034import java.util.Date;
035import java.util.HashMap;
036import java.util.HashSet;
037import java.util.Hashtable;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Map;
041import java.util.Set;
042import java.security.PrivateKey;
043import java.security.cert.X509Certificate;
044import javax.crypto.SecretKey;
045import javax.servlet.http.HttpServletRequest;
046import javax.servlet.http.HttpServletResponse;
047import javax.xml.soap.SOAPException;
048import javax.xml.soap.SOAPMessage;
049
050import com.sun.identity.saml2.common.SOAPCommunicator;
051import org.w3c.dom.Element;
052
053import com.sun.identity.plugin.datastore.DataStoreProviderException;
054import com.sun.identity.plugin.datastore.DataStoreProvider;
055import com.sun.identity.saml.xmlsig.KeyProvider;
056import com.sun.identity.saml2.assertion.Assertion;
057import com.sun.identity.saml2.assertion.AssertionFactory;
058import com.sun.identity.saml2.assertion.Attribute;
059import com.sun.identity.saml2.assertion.AttributeStatement;
060import com.sun.identity.saml2.assertion.Conditions;
061import com.sun.identity.saml2.assertion.EncryptedAssertion;
062import com.sun.identity.saml2.assertion.Issuer;
063import com.sun.identity.saml2.assertion.NameID;
064import com.sun.identity.saml2.assertion.EncryptedID;
065import com.sun.identity.saml2.assertion.Subject;
066import com.sun.identity.saml2.common.SAML2Constants;
067import com.sun.identity.saml2.common.SAML2Exception;
068import com.sun.identity.saml2.common.SAML2Utils;
069import com.sun.identity.saml2.jaxb.assertion.AttributeElement;
070import com.sun.identity.saml2.jaxb.assertion.AttributeValueElement;
071import com.sun.identity.saml2.jaxb.entityconfig.AttributeAuthorityConfigElement;
072import com.sun.identity.saml2.jaxb.entityconfig.AttributeQueryConfigElement;
073import com.sun.identity.saml2.jaxb.entityconfig.IDPSSOConfigElement;
074import com.sun.identity.saml2.jaxb.metadata.AttributeAuthorityDescriptorElement;
075import com.sun.identity.saml2.jaxb.metadata.AttributeServiceElement;
076import com.sun.identity.saml2.jaxb.metadataextquery.AttributeQueryDescriptorElement;
077import com.sun.identity.saml2.key.EncInfo;
078import com.sun.identity.saml2.key.KeyUtil;
079import com.sun.identity.saml2.meta.SAML2MetaException;
080import com.sun.identity.saml2.meta.SAML2MetaManager;
081import com.sun.identity.saml2.meta.SAML2MetaUtils;
082import com.sun.identity.saml2.plugins.AttributeAuthorityMapper;
083import com.sun.identity.saml2.plugins.SPAttributeMapper;
084import com.sun.identity.saml2.protocol.AttributeQuery;
085import com.sun.identity.saml2.protocol.ProtocolFactory;
086import com.sun.identity.saml2.protocol.Response;
087import com.sun.identity.saml2.protocol.Status;
088import com.sun.identity.saml2.protocol.StatusCode;
089import com.sun.identity.saml2.xmlenc.EncManager;
090
091/**
092 * This class provides methods to send or process <code>AttributeQuery</code>.
093 *
094 * @supported.api
095 */
096public class AttributeQueryUtil {
097
098    private static final String DEFAULT_ATTRIBUTE_NAME_FORMAT =
099            "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified";
100    static KeyProvider keyProvider = KeyUtil.getKeyProviderInstance(); 
101    static Hashtable attrAuthorityMapperCache = new Hashtable(); 
102    static DataStoreProvider dsProvider = null;
103    static SAML2MetaManager metaManager = SAML2Utils.getSAML2MetaManager();
104
105    static {
106        try {
107            dsProvider = SAML2Utils.getDataStoreProvider(); 
108        } catch (SAML2Exception se) {
109            SAML2Utils.debug.error("AttributeQueryUtil.static:", se);
110        }
111    }
112
113    private AttributeQueryUtil() {
114    }
115
116    /**
117     * Sends the <code>AttributeQuery</code> to specified
118     * attribute authority and returns <code>Response</code> coming
119     * from the attribute authority.
120     *
121     * @param attrQuery the <code>AttributeQuery</code> object
122     * @param attrAuthorityEntityID entity ID of attribute authority
123     * @param realm the realm of hosted entity
124     * @param attrQueryProfile the attribute query profile or null to ignore
125     * @param attrProfile the attribute profile
126     * @param binding the binding
127     *
128     * @return the <code>Response</code> object
129     * @exception SAML2Exception if the operation is not successful
130     *
131     * @supported.api
132     */
133    public static Response sendAttributeQuery(AttributeQuery attrQuery,
134        String attrAuthorityEntityID, String realm, String attrQueryProfile,
135        String attrProfile, String binding) throws SAML2Exception {
136
137        AttributeAuthorityDescriptorElement aad = null;
138        try {
139             aad = metaManager.getAttributeAuthorityDescriptor(
140                realm, attrAuthorityEntityID);
141        } catch (SAML2MetaException sme) {
142            SAML2Utils.debug.error("AttributeQueryUtil.sendAttributeQuery:",
143                sme);
144            throw new SAML2Exception(
145                SAML2Utils.bundle.getString("metaDataError"));
146        }
147
148        if (aad == null) {
149            throw new SAML2Exception(
150                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
151        }
152
153        if (binding == null) {
154            throw new SAML2Exception(
155                SAML2Utils.bundle.getString("unsupportedBinding"));
156        }
157
158        String location = findLocation(aad, binding, attrQueryProfile,
159             attrProfile);
160
161        if (location == null) {
162            throw new SAML2Exception(
163                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
164        }
165
166        if (binding.equalsIgnoreCase(SAML2Constants.SOAP)) {
167            signAttributeQuery(attrQuery, realm, false);
168            return sendAttributeQuerySOAP(attrQuery, location,
169                attrAuthorityEntityID, aad);
170        } else {
171            throw new SAML2Exception(
172                SAML2Utils.bundle.getString("unsupportedBinding"));
173        }
174    }
175
176     /**
177     * Sends the <code>AttributeQuery</code> to specified
178     * attribute authority and returns <code>Response</code> coming
179     * from the attribute authority.
180     *
181     * @param attrQuery the <code>AttributeQuery</code> object
182     * @param request the HTTP Request
183     * @param  response the HTTP Response
184     * @param attrAuthorityEntityID entity ID of attribute authority
185     * @param realm the realm of hosted entity
186     * @param attrQueryProfile the attribute query profile or null to ignore
187     * @param attrProfile the attribute profile
188     * @param binding the binding
189     *
190     * @exception SAML2Exception if the operation is not successful
191     *
192     * @supported.api
193     */
194     public static void sendAttributeQuery(AttributeQuery attrQuery,
195        HttpServletRequest request, HttpServletResponse response,
196        String attrAuthorityEntityID, String realm, String attrQueryProfile,
197        String attrProfile, String binding) throws SAML2Exception {
198
199        AttributeAuthorityDescriptorElement aad = null;
200        try {
201             aad = metaManager.getAttributeAuthorityDescriptor(
202                realm, attrAuthorityEntityID);
203        } catch (SAML2MetaException sme) {
204            SAML2Utils.debug.error("AttributeQueryUtil.sendAttributeQuery:",
205                sme);
206            throw new SAML2Exception(
207                SAML2Utils.bundle.getString("metaDataError"));
208        }
209
210        if (aad == null) {
211            throw new SAML2Exception(
212                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
213        }
214
215        if (binding == null) {
216            throw new SAML2Exception(
217                SAML2Utils.bundle.getString("unsupportedBinding"));
218        }
219
220        String location = findLocation(aad, binding, attrQueryProfile,
221             attrProfile);
222
223        if (location == null) {
224            throw new SAML2Exception(
225                SAML2Utils.bundle.getString("attrAuthorityNotFound"));
226        }
227
228        if (binding.equalsIgnoreCase(SAML2Constants.HTTP_POST)) {
229            signAttributeQuery(attrQuery, realm, false);
230            String encodedReqMsg = SAML2Utils.encodeForPOST(attrQuery.toXMLString(true, true));
231            SAML2Utils.postToTarget(request, response, "SAMLRequest", encodedReqMsg, null, null, location);
232        } else {
233            throw new SAML2Exception(
234                SAML2Utils.bundle.getString("unsupportedBinding"));
235        }
236    }
237
238
239    /**
240     * Processes the <code>AttributeQuery</code> coming
241     * from a requester.
242     *
243     * @param attrQuery the <code>AttributeQuery</code> object
244     * @param request the <code>HttpServletRequest</code> object
245     * @param response the <code>HttpServletResponse</code> object
246     * @param attrAuthorityEntityID entity ID of attribute authority
247     * @param realm the realm of hosted entity
248     * @param attrQueryProfileAlias the attribute query profile alias
249     *
250     * @return the <code>Response</code> object
251     * @exception SAML2Exception if the operation is not successful
252     */
253    public static Response processAttributeQuery(AttributeQuery attrQuery,
254        HttpServletRequest request, HttpServletResponse response,
255        String attrAuthorityEntityID, String realm,
256        String attrQueryProfileAlias) throws SAML2Exception {
257
258        AttributeAuthorityMapper attrAuthorityMapper = 
259            getAttributeAuthorityMapper(realm, attrAuthorityEntityID,
260            attrQueryProfileAlias);
261
262        String attrQueryProfile = AttributeQueryUtil.getAttributeQueryProfile(
263            attrQueryProfileAlias);
264
265        try {
266            attrAuthorityMapper.authenticateRequester(request, response,
267                attrQuery, attrAuthorityEntityID, realm);
268        } catch(SAML2Exception se) {
269            if (SAML2Utils.debug.messageEnabled()) {
270                SAML2Utils.debug.message("AttributeQueryUtil." +
271                "processAttributeQuery: ", se);
272            }
273            return SAML2Utils.getErrorResponse(attrQuery,
274                SAML2Constants.REQUESTER, null, se.getMessage(), null);
275        }
276
277        try {
278            attrAuthorityMapper.validateAttributeQuery(request, response,
279                attrQuery, attrAuthorityEntityID, realm);
280        } catch(SAML2Exception se) {
281            SAML2Utils.debug.error("AttributeQueryUtil.processAttributeQuery:",
282                se);
283            return SAML2Utils.getErrorResponse(attrQuery,
284                SAML2Constants.REQUESTER, null, se.getMessage(), null);
285        }
286
287        Issuer issuer = attrQuery.getIssuer();
288        String requesterEntityID = issuer.getValue();        
289        AttributeAuthorityDescriptorElement aad = null;
290        try {
291             aad = metaManager.getAttributeAuthorityDescriptor(
292                realm, attrAuthorityEntityID);
293        } catch (SAML2MetaException sme) {
294            SAML2Utils.debug.error("AttributeQueryUtil.processAttributeQuery:",
295                sme);
296            return SAML2Utils.getErrorResponse(attrQuery,
297                SAML2Constants.RESPONDER, null,
298                SAML2Utils.bundle.getString("metaDataError"), null);
299        } 
300
301        if (aad == null) {
302            return SAML2Utils.getErrorResponse(attrQuery,
303                SAML2Constants.REQUESTER, null,
304                SAML2Utils.bundle.getString("attrAuthorityNotFound"), null);
305        }
306
307        Object identity = null;
308        try {
309            identity = attrAuthorityMapper.getIdentity(request, response,
310                attrQuery, attrAuthorityEntityID, realm);
311        } catch (SAML2Exception se) {
312            if (SAML2Utils.debug.messageEnabled()) {
313                SAML2Utils.debug.message("AttributeQueryUtil." +
314                "processAttributeQuery: ", se);
315            }
316            return SAML2Utils.getErrorResponse(attrQuery,
317                SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL,
318                se.getMessage(), null);
319        }
320
321        if (identity == null) {
322            if (SAML2Utils.debug.messageEnabled()) {
323                SAML2Utils.debug.message("AttributeQueryUtil." +
324                "processAttributeQuery: unable to find identity.");
325            }
326            return SAML2Utils.getErrorResponse(attrQuery,
327                SAML2Constants.REQUESTER, SAML2Constants.UNKNOWN_PRINCIPAL,
328                null, null);
329        }
330
331        // Addition to support changing of desired attributes list
332        List desiredAttrs = (List)request.getAttribute("AttributeQueryUtil-desiredAttrs");
333        if (desiredAttrs == null) {
334            desiredAttrs = attrQuery.getAttributes();
335        }
336        try {
337            desiredAttrs = verifyDesiredAttributes(aad.getAttribute(),
338                desiredAttrs);
339        } catch (SAML2Exception se) {
340            return SAML2Utils.getErrorResponse(attrQuery,
341                SAML2Constants.REQUESTER, 
342                SAML2Constants.INVALID_ATTR_NAME_OR_VALUE, null, null);
343        }
344
345        List attributes = attrAuthorityMapper.getAttributes(identity,
346            attrQuery, attrAuthorityEntityID, realm);
347
348        if (request.getAttribute("AttributeQueryUtil-storeAllAttributes") != null) {
349            request.setAttribute("AttributeQueryUtil-allAttributes", attributes);
350        }
351
352        attributes = filterAttributes(attributes, desiredAttrs);
353
354        ProtocolFactory protocolFactory = ProtocolFactory.getInstance();
355        Response samlResp = protocolFactory.createResponse();
356        List assertionList = new ArrayList();
357
358        Assertion assertion = null;
359        try {
360            assertion = getAssertion(attrQuery, attrAuthorityEntityID,
361                requesterEntityID, realm, attrQueryProfileAlias, attributes);
362        } catch (SAML2Exception se) {
363            if (SAML2Utils.debug.messageEnabled()) {
364                SAML2Utils.debug.message(
365                    "AttributeQueryUtil.processAttributeQuery:", se);
366            }
367            return SAML2Utils.getErrorResponse(attrQuery,
368                SAML2Constants.RESPONDER, null, se.getMessage(), null);
369        }
370
371        EncryptedID encryptedID = attrQuery.getSubject().getEncryptedID();
372        if (encryptedID != null) {
373            EncryptedAssertion encryptedAssertion = null;
374            try {
375                signAssertion(assertion, realm, attrAuthorityEntityID, false);
376                encryptedAssertion = encryptAssertion(assertion,
377                        encryptedID, attrAuthorityEntityID, requesterEntityID,
378                        realm, attrQueryProfileAlias);
379            } catch (SAML2Exception se) {
380                if (SAML2Utils.debug.messageEnabled()) {
381                        SAML2Utils.debug.message(
382                            "AttributeQueryUtil.processAttributeQuery:", se);
383                }
384                return SAML2Utils.getErrorResponse(attrQuery,
385                        SAML2Constants.RESPONDER, null, se.getMessage(), null);
386            }
387            assertionList.add(encryptedAssertion);        
388            samlResp.setEncryptedAssertion(assertionList);
389        } else {
390            assertionList.add(assertion);        
391            samlResp.setAssertion(assertionList);
392        }
393
394        samlResp.setID(SAML2Utils.generateID());
395        samlResp.setInResponseTo(attrQuery.getID());
396
397        samlResp.setVersion(SAML2Constants.VERSION_2_0);
398        samlResp.setIssueInstant(newDate());
399    
400        Status status = protocolFactory.createStatus();
401        StatusCode statusCode = protocolFactory.createStatusCode();
402        statusCode.setValue(SAML2Constants.SUCCESS);
403        status.setStatusCode(statusCode);
404        samlResp.setStatus(status);
405
406        Issuer respIssuer = AssertionFactory.getInstance().createIssuer();
407        respIssuer.setValue(attrAuthorityEntityID);
408        samlResp.setIssuer(respIssuer);
409
410        signResponse(samlResp, attrAuthorityEntityID, realm, false);
411
412        return samlResp;
413    }
414
415    /**
416     * Converts attribute query profile alias to attribute query profile.
417     *
418     * @param attrQueryProfileAlias attribute query profile alias
419     *
420     * @return attribute query profile
421     */
422    public static String getAttributeQueryProfile(
423        String attrQueryProfileAlias) {
424
425        if (attrQueryProfileAlias == null) {
426            return null;
427        } else if (attrQueryProfileAlias.equals(
428            SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
429            return  SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE;
430        } else if (attrQueryProfileAlias.equals(
431            SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
432            return  SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE;
433        }
434
435        return null;
436    }
437
438    private static void signAttributeQuery(AttributeQuery attrQuery,
439        String realm, boolean includeCert) throws SAML2Exception {
440        String requesterEntityID = attrQuery.getIssuer().getValue();
441        
442        String alias = SAML2Utils.getSigningCertAlias(realm, requesterEntityID,
443            SAML2Constants.ATTR_QUERY_ROLE);
444
445        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
446        if (signingKey == null) {
447            throw new SAML2Exception(
448                SAML2Utils.bundle.getString("missingSigningCertAlias"));
449        }
450
451        X509Certificate signingCert = null;
452        if (includeCert) {
453            signingCert = keyProvider.getX509Certificate(alias);
454        }
455        
456        if (signingKey != null) {
457            attrQuery.sign(signingKey, signingCert);
458        }
459    }
460
461    public static void validateEntityRequester(AttributeQuery attrQuery,
462        String attrAuthorityEntityID, String realm) throws SAML2Exception {
463
464        Issuer issuer = attrQuery.getIssuer();
465        String format = issuer.getFormat();
466        if ((format == null) || (format.length() == 0) ||
467            (format.equals(SAML2Constants.UNSPECIFIED)) ||
468            (format.equals(SAML2Constants.ENTITY))) {
469
470            String requestedEntityID = issuer.getValue();
471
472            if (!SAML2Utils.isSourceSiteValid(issuer, realm,
473                attrAuthorityEntityID)) {
474                throw new SAML2Exception(SAML2Utils.bundle.getString(
475                    "attrQueryIssuerInvalid"));
476            }
477        } else {
478            throw new SAML2Exception(SAML2Utils.bundle.getString(
479                "attrQueryIssuerInvalid"));
480        }
481    }
482
483    /**
484     * Checks if the attribute query signature is valid.
485     *
486     * @param attrQuery attribute query
487     * @param attrAuthorityEntityID entity ID of attribute authority
488     * @param realm the realm of hosted entity
489     *
490     * @exception SAML2Exception if the attribute query signature is not valid.
491     */
492    public static void verifyAttrQuerySignature(AttributeQuery attrQuery,
493        String attrAuthorityEntityID, String realm)
494        throws SAML2Exception {
495
496        if (!attrQuery.isSigned()) {
497            throw new SAML2Exception(SAML2Utils.bundle.getString(
498                "attrQueryNotSigned"));
499        }
500
501        String requestedEntityID = attrQuery.getIssuer().getValue();
502
503        AttributeQueryDescriptorElement attrqDesc =
504            metaManager.getAttributeQueryDescriptor(realm, requestedEntityID);
505        if (attrqDesc == null) {
506            throw new SAML2Exception(SAML2Utils.bundle.getString(
507                "attrQueryIssuerNotFound"));
508        }
509        Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(attrqDesc, requestedEntityID,
510                SAML2Constants.ATTR_QUERY_ROLE);
511
512        if (!signingCerts.isEmpty()) {
513            boolean valid = attrQuery.isSignatureValid(signingCerts);
514            if (SAML2Utils.debug.messageEnabled()) {
515                SAML2Utils.debug.message(
516                    "AttributeQueryUtil.verifyAttributeQuery: " +
517                    "Signature validity is : " + valid);
518            }
519            if (!valid) {
520                throw new SAML2Exception(SAML2Utils.bundle.getString(
521                    "invalidSignatureAttrQuery"));
522            }
523        } else {
524            throw new SAML2Exception(
525                SAML2Utils.bundle.getString("missingSigningCertAlias"));
526        }
527    }
528
529    public static String getIdentityFromDataStoreX509Subject(
530        AttributeQuery attrQuery, String attrAuthorityEntityID, String realm)
531        throws SAML2Exception {
532
533        Subject subject = attrQuery.getSubject();
534        NameID nameID = null;
535        EncryptedID encryptedID = subject.getEncryptedID();
536
537        if (encryptedID != null) {
538            nameID = encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID,
539                    SAML2Constants.ATTR_AUTH_ROLE));
540        } else {
541            nameID = subject.getNameID();
542        }
543
544        if (!SAML2Constants.X509_SUBJECT_NAME.equals(nameID.getFormat())) {
545            throw new SAML2Exception(SAML2Utils.bundle.getString(
546                "unsupportedAttrQuerySubjectNameID"));
547        }
548
549        String mappingAttrName = getAttributeValueFromAttrAuthorityConfig(
550            realm, attrAuthorityEntityID,
551            SAML2Constants.X509_SUBJECT_DATA_STORE_ATTR_NAME);
552
553        if ((mappingAttrName == null) || (mappingAttrName.length() == 0)) {
554            throw new SAML2Exception(SAML2Utils.bundle.getString(
555                "x509SubjectMappingNotConfigured"));
556        }
557
558        String x509SubjectDN = nameID.getValue();
559        Map attrMap = new HashMap();
560        Set values = new HashSet();
561        values.add(x509SubjectDN);
562        attrMap.put(mappingAttrName, values);
563
564        if (SAML2Utils.debug.messageEnabled()) {
565            SAML2Utils.debug.message(
566                "AttributeQueryUtil.getIdentityFromDataStoreX509Subject: " +
567                "mappingAttrName = " + mappingAttrName +
568                ", X509 subject DN = " + x509SubjectDN);
569        }
570
571        try {
572            return dsProvider.getUserID(realm, attrMap);
573        } catch (DataStoreProviderException dse) {
574            SAML2Utils.debug.error(
575                "AttributeQueryUtil.getIdentityFromDataStoreX509Subject:",dse);
576            throw new SAML2Exception(dse.getMessage());
577        }
578    }
579
580    public static String getIdentity(AttributeQuery attrQuery,
581        String attrAuthorityEntityID, String realm) throws SAML2Exception {
582
583        Subject subject = attrQuery.getSubject();
584        NameID nameID = null;
585        EncryptedID encryptedID = subject.getEncryptedID();
586
587        if (encryptedID != null) {
588            nameID = encryptedID.decrypt(KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID,
589                    SAML2Constants.ATTR_AUTH_ROLE));
590        } else {
591            nameID = subject.getNameID();
592        }
593
594        String nameIDFormat = nameID.getFormat();
595        // NameIDFormat is "transient"
596        if (SAML2Constants.NAMEID_TRANSIENT_FORMAT.equals(nameIDFormat)) {
597            return (String)IDPCache.userIDByTransientNameIDValue.get(
598                nameID.getValue());
599        } else  
600          // NameIDFormat is "unspecified"
601          if (SAML2Constants.UNSPECIFIED.equals(nameIDFormat)) {
602            Map userIDsSearchMap = new HashMap();
603            Set userIDValuesSet = new HashSet();
604            userIDValuesSet.add(nameID.getValue());
605            String userId = "uid";
606
607            IDPSSOConfigElement config = SAML2Utils.getSAML2MetaManager().getIDPSSOConfig(
608                    realm, attrAuthorityEntityID);
609            Map attrs = SAML2MetaUtils.getAttributes(config);
610
611            List nimAttrs = (List)attrs.get(SAML2Constants.NAME_ID_FORMAT_MAP);
612
613
614            for (Iterator i = nimAttrs.iterator(); i.hasNext(); ) {
615                String attrName = (String)i.next();
616                if (attrName != null && attrName.length()>2 && attrName.startsWith(nameIDFormat)) {
617                    int eqPos = attrName.indexOf('=');
618                    if (eqPos != -1 && eqPos<attrName.length()-2) {
619                        userId = attrName.substring(eqPos+1);
620                        SAML2Utils.debug.message("AttributeQueryUtil.getIdentity: NameID attribute from map: " + userId);
621                        break;
622                    }
623                }
624            }
625            userIDsSearchMap.put(userId, userIDValuesSet);
626            try {
627                return dsProvider.getUserID(realm, userIDsSearchMap);
628            } catch (DataStoreProviderException dse) {
629                SAML2Utils.debug.error(
630                    "AttributeQueryUtil.getIdentityFromDataStore1:", dse);
631                throw new SAML2Exception(dse.getMessage());
632            }
633        } else {
634            String requestedEntityID = attrQuery.getIssuer().getValue();
635
636            try {
637                return dsProvider.getUserID(realm, SAML2Utils.getNameIDKeyMap(
638                    nameID, attrAuthorityEntityID, requestedEntityID, realm,
639                    SAML2Constants.IDP_ROLE));
640            } catch (DataStoreProviderException dse) {
641                SAML2Utils.debug.error(
642                    "AttributeQueryUtil.getIdentityFromDataStore:", dse);
643                throw new SAML2Exception(dse.getMessage());
644            }
645        }
646    }
647
648    public static List getUserAttributes(String userId,
649        AttributeQuery attrQuery, String attrAuthorityEntityID, String realm)
650        throws SAML2Exception {
651 
652        String requestedEntityID = attrQuery.getIssuer().getValue();
653
654        Map configMap = SAML2Utils.getConfigAttributeMap(realm,
655            requestedEntityID, SAML2Constants.SP_ROLE);
656        if (SAML2Utils.debug.messageEnabled()) {
657            SAML2Utils.debug.message(
658                "AttributeQueryUtil.getUserAttributes: " +
659                "remote SP attribute map = " + configMap);
660        }
661        if (configMap == null || configMap.isEmpty()) {
662            configMap = SAML2Utils.getConfigAttributeMap(realm,
663                attrAuthorityEntityID, SAML2Constants.IDP_ROLE);
664            if (configMap == null || configMap.isEmpty()) {
665                if (SAML2Utils.debug.messageEnabled()) {
666                    SAML2Utils.debug.message(
667                        "AttributeQueryUtil.getUserAttributes:" +
668                        "Configuration map is not defined.");
669                }
670                return null;
671            }
672            if (SAML2Utils.debug.messageEnabled()) {
673                SAML2Utils.debug.message(
674                    "AttributeQueryUtil.getUserAttributes: " +
675                    "hosted IDP attribute map=" + configMap);
676            }
677        }
678
679        List attributes = new ArrayList();
680
681        Set localAttributes = new HashSet();
682        localAttributes.addAll(configMap.values());
683        Map valueMap = null;
684
685        try {
686            valueMap = dsProvider.getAttributes(userId, localAttributes);
687        } catch (DataStoreProviderException dse) {
688            if (SAML2Utils.debug.warningEnabled()) {
689                SAML2Utils.debug.warning(
690                    "AttributeQueryUtil.getUserAttributes:", dse);
691            }
692        }
693
694        Iterator iter = configMap.keySet().iterator();
695        while(iter.hasNext()) {
696            String samlAttribute = (String)iter.next();
697            String localAttribute = (String)configMap.get(samlAttribute);
698            String[] localAttributeValues = null;
699            if ((valueMap != null) && (!valueMap.isEmpty())) {
700                Set values = (Set)valueMap.get(localAttribute);
701                if ((values == null) || values.isEmpty()) {
702                    if (SAML2Utils.debug.messageEnabled()) {
703                        SAML2Utils.debug.message(
704                            "AttributeQueryUtil.getUserAttributes:" +
705                            " user profile does not have value for " +
706                            localAttribute);
707                    }
708                } else {
709                    localAttributeValues = (String[])
710                        values.toArray(new String[values.size()]);
711                }
712            }
713
714            if ((localAttributeValues == null) ||
715                (localAttributeValues.length == 0)) {
716                if (SAML2Utils.debug.messageEnabled()) {
717                    SAML2Utils.debug.message(
718                        "AttributeQueryUtil.getUserAttributes:" +
719                        " user does not have " + localAttribute);
720                }
721                continue;
722            }
723
724            Attribute attr = SAML2Utils.getSAMLAttribute(samlAttribute,
725                localAttributeValues);
726            attributes.add(attr);
727        }
728        return attributes;
729    }
730
731    public static void signResponse(Response response,
732        String attrAuthorityEntityID, String realm, boolean includeCert)
733        throws SAML2Exception {
734        
735        String alias = SAML2Utils.getSigningCertAlias(realm,
736            attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE);
737
738        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
739        if (signingKey == null) {
740            throw new SAML2Exception(
741                SAML2Utils.bundle.getString("missingSigningCertAlias"));
742        }
743
744        X509Certificate signingCert = null;
745        if (includeCert) {
746            signingCert = keyProvider.getX509Certificate(alias);
747        }
748        
749        if (signingKey != null) {
750            response.sign(signingKey, signingCert);
751        }
752    }
753
754    private static Assertion getAssertion(AttributeQuery attrQuery,
755        String attrAuthorityEntityID, String requesterEntityID, String realm,
756        String attrQueryProfileAlias, List attributes) throws SAML2Exception {
757
758        AssertionFactory assertionFactory = AssertionFactory.getInstance();
759        Assertion assertion = assertionFactory.createAssertion();
760        assertion.setID(SAML2Utils.generateID());    
761        assertion.setVersion(SAML2Constants.VERSION_2_0);
762        assertion.setIssueInstant(newDate());
763        Issuer issuer = assertionFactory.createIssuer();
764        issuer.setValue(attrAuthorityEntityID);
765        assertion.setIssuer(issuer);
766
767        Subject subjectQ = attrQuery.getSubject();
768        Subject subject = assertionFactory.createSubject();
769        subject.setEncryptedID(subjectQ.getEncryptedID());
770        subject.setNameID(subjectQ.getNameID());
771        subject.setBaseID(subjectQ.getBaseID());
772        subject.setSubjectConfirmation(subjectQ.getSubjectConfirmation());
773        assertion.setSubject(subject);
774
775        if ((attributes != null) && (!attributes.isEmpty())) {
776            AttributeStatement attrStatement =
777                assertionFactory.createAttributeStatement();
778
779            attrStatement.setAttribute(attributes);
780            List attrStatementList = new ArrayList();
781            attrStatementList.add(attrStatement);
782            assertion.setAttributeStatements(attrStatementList); 
783        }
784
785        int effectiveTime = IDPSSOUtil.getEffectiveTime(realm,
786            attrAuthorityEntityID);
787        int notBeforeSkewTime = IDPSSOUtil.getNotBeforeSkewTime(realm,
788            attrAuthorityEntityID);
789        Conditions conditions = IDPSSOUtil.getConditions(requesterEntityID, 
790            notBeforeSkewTime, effectiveTime, realm);
791        assertion.setConditions(conditions);
792
793        return assertion;
794    }
795
796    private static void signAssertion(Assertion assertion, String realm,
797        String attrAuthorityEntityID, boolean includeCert)
798        throws SAML2Exception {
799
800        String alias = SAML2Utils.getSigningCertAlias(realm,
801            attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE);
802
803        PrivateKey signingKey = keyProvider.getPrivateKey(alias);
804        X509Certificate signingCert = null;
805        if (includeCert) {
806            signingCert = keyProvider.getX509Certificate(alias);
807        }
808        
809        if (signingKey != null) {
810            assertion.sign(signingKey, signingCert);
811        }
812    }
813
814    private static EncryptedAssertion encryptAssertion(Assertion assertion, EncryptedID encryptedID,
815            String attrAuthorityEntityID, String requesterEntityID, String realm, String attrQueryProfileAlias)
816            throws SAML2Exception {
817        SecretKey secretKey = EncManager.getEncInstance().getSecretKey(encryptedID.toXMLString(true, true),
818                KeyUtil.getDecryptionKeys(realm, attrAuthorityEntityID, SAML2Constants.ATTR_AUTH_ROLE));
819
820        AttributeQueryDescriptorElement aqd =
821            metaManager.getAttributeQueryDescriptor(realm, requesterEntityID);
822        EncInfo encInfo = KeyUtil.getEncInfo(aqd, requesterEntityID,
823            SAML2Constants.ATTR_QUERY_ROLE);
824
825        Element el = EncManager.getEncInstance().encrypt(
826            assertion.toXMLString(true, true), encInfo.getWrappingKey(),
827            secretKey, encInfo.getDataEncAlgorithm(),
828            encInfo.getDataEncStrength(), requesterEntityID,
829            "EncryptedAssertion");
830
831        return AssertionFactory.getInstance().createEncryptedAssertion(el);
832    }
833
834    private static List<Attribute> verifyDesiredAttributes(List<AttributeElement> supportedAttrs,
835            List<Attribute> desiredAttrs) throws SAML2Exception {
836        if (supportedAttrs == null || supportedAttrs.isEmpty()) {
837            return desiredAttrs;
838        }
839
840        if (desiredAttrs == null || desiredAttrs.isEmpty()) {
841            return convertAttributes(supportedAttrs);
842        }
843
844        for (Attribute desiredAttr : desiredAttrs) {
845            boolean isAttrValid = false;
846            Iterator<AttributeElement> supportedAttrIterator = supportedAttrs.iterator();
847            while (supportedAttrIterator.hasNext()) {
848                AttributeElement supportedAttr = supportedAttrIterator.next();
849                if (isSameAttribute(desiredAttr, supportedAttr)) {
850                    if (isValueValid(desiredAttr, supportedAttr)) {
851                        isAttrValid = true;
852                        //By removing the attribute from the supported list we make sure that an AttributeQuery can
853                        //not request the same Attribute more than once, see SAML core 3.3.2.3.
854                        supportedAttrIterator.remove();
855                        break;
856                    } else {
857                        throw new SAML2Exception("Attribute value not supported");
858                    }
859                }
860            }
861            if (!isAttrValid) {
862                throw new SAML2Exception("Attribute name not supported");
863            }
864        }
865        return desiredAttrs;
866    }
867
868    private static List convertAttributes(List jaxbAttrs)
869        throws SAML2Exception {
870
871        List resultAttrs = new ArrayList();
872        for(Iterator iter = jaxbAttrs.iterator(); iter.hasNext(); ) {
873            AttributeElement jaxbAttr = (AttributeElement)iter.next();
874            Attribute attr = AssertionFactory.getInstance().createAttribute();
875            attr.setName(jaxbAttr.getName());
876            attr.setNameFormat(jaxbAttr.getNameFormat());
877            attr.setFriendlyName(jaxbAttr.getFriendlyName());
878
879            List jaxbValues = jaxbAttr.getAttributeValue();
880            if ((jaxbValues != null) && (!jaxbValues.isEmpty())) {
881                List newValues = new ArrayList();
882                for(Iterator iterV = jaxbValues.iterator(); iterV.hasNext();) {
883                    AttributeValueElement jaxbValeu =
884                        (AttributeValueElement)iter.next();
885                    List content = jaxbValeu.getContent();
886                    if ((content != null) && (!content.isEmpty())) {
887                        newValues.add(content.get(0));
888                    }
889                }
890                if (!newValues.isEmpty()) {
891                    attr.setAttributeValueString(newValues);
892                }
893            }
894            resultAttrs.add(attr);
895        }
896        return resultAttrs;
897    }
898
899    private static List<Attribute> filterAttributes(List<Attribute> attributes, List<Attribute> desiredAttrs) {
900
901        if (attributes == null || attributes.isEmpty()) {
902            SAML2Utils.debug.message("AttributeQueryUtil.filterAttributes: attributes are null");
903            return attributes;
904        }
905        if (desiredAttrs == null || desiredAttrs.isEmpty()) {
906            SAML2Utils.debug.message("AttributeQueryUtil.filterAttributes: desired attributes are null");
907            return attributes;
908        }
909
910        List<Attribute> returnAttributes = new ArrayList<Attribute>();
911        if (!desiredAttrs.isEmpty()) {
912            for (Attribute attrD : desiredAttrs) {
913                for (Attribute attr : attributes) {
914                    if (isSameAttribute(attr, attrD) ) {
915                        attr = filterAttributeValues(attr, attrD);
916                        if (attr != null) {
917                            //let's copy FriendlyName if exists
918                            String fName = attrD.getFriendlyName();
919                            if (fName != null && fName.length() > 0){
920                                try {
921                                    attr.setFriendlyName(fName);
922                                } catch (SAML2Exception e) {
923                                    //do nothing, attribute will be sent without
924                                    //friendlyName set
925                                }
926                            }
927                            returnAttributes.add(attr);
928                        }
929                        break;
930                    }
931                }
932            }
933        }
934        return returnAttributes;
935    }
936
937    private static boolean isSameAttribute(Attribute attribute, Attribute desired) {
938        return desired.getName().equals(attribute.getName())
939                && isNameFormatMatching(desired.getNameFormat(), attribute.getNameFormat());
940    }
941
942    private static Attribute filterAttributeValues(Attribute attr,
943        Attribute desiredAttr) {
944
945        List valuesD = desiredAttr.getAttributeValueString();
946        if ((valuesD == null) || (valuesD.isEmpty())) {
947            return attr;
948        }
949
950        List values = attr.getAttributeValueString();
951        if ((values == null) || (values.isEmpty())) {
952            return null;
953        }
954
955        List newValuesD = new ArrayList();
956        for(Iterator iter = valuesD.iterator(); iter.hasNext(); ) {
957            String valueD = (String)iter.next();
958            if (values.contains(valueD)) {
959                newValuesD.add(valueD);
960            }
961        }
962
963        if (newValuesD.isEmpty()) {
964            return null;
965        }
966
967        if (newValuesD.size() == valuesD.size()) {
968            return desiredAttr;
969        }
970
971        try {
972            Attribute newAttr =
973                AssertionFactory.getInstance().createAttribute();
974            newAttr.setName(desiredAttr.getName());
975            newAttr.setNameFormat(desiredAttr.getNameFormat());
976            newAttr.setFriendlyName(desiredAttr.getFriendlyName());
977            newAttr.setAnyAttribute(desiredAttr.getAnyAttribute());
978            newAttr.setAttributeValueString(newValuesD);
979
980            return newAttr;
981        } catch(SAML2Exception se) {
982            if (SAML2Utils.debug.messageEnabled()) {
983                SAML2Utils.debug.message(
984                    "AttributeQueryUtil.filterAttributeValues:", se);
985            }
986            return null;
987        }
988    }
989
990    private static boolean isSameAttribute(Attribute desired, AttributeElement supported) {
991        return desired.getName().equals(supported.getName())
992                && isNameFormatMatching(desired.getNameFormat(), supported.getNameFormat());
993    }
994
995    /**
996     * Determines whether the desired Attribute NameFormat matches with the available attribute's NameFormat. When
997     * the NameFormat isn't specified in the request, the
998     * <code>urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified</code> default NameFormat needs to be used (see
999     * SAML core spec 2.7.3.1).
1000     * The different attribute profiles (SAML profiles spec section 8) each determine how the attribute comparison
1001     * should be performed, however there is no clear way to actually determine which attribute profile is being used
1002     * when the Attribute Authority supports more than one profile. Because of this, the unspecified Attribute
1003     * NameFormat has been implemented as a wildcard match, much similarly to how requesting the unspecified
1004     * NameID-Format allows the IdP to choose an arbitrary NameID-Format when generating the assertion for an SP.
1005     *
1006     * @param desiredNameFormat The NameFormat of the Attribute defined in the AttributeQuery request.
1007     * @param availableNameFormat The NameFormat of the Attribute defined in the server configuration.
1008     * @return <code>true</code> if the desired NameFormat is unspecified, or if it is the same as the NameFormat
1009     * defined in the server configuration.
1010     */
1011    private static boolean isNameFormatMatching(String desiredNameFormat, String availableNameFormat) {
1012        return desiredNameFormat == null || DEFAULT_ATTRIBUTE_NAME_FORMAT.equals(desiredNameFormat)
1013                || desiredNameFormat.equals(availableNameFormat);
1014    }
1015
1016    private static boolean isValueValid(Attribute desiredAttr,
1017        AttributeElement supportedAttr) {
1018
1019        List valuesD = desiredAttr.getAttributeValueString();
1020        if ((valuesD == null) || (valuesD.isEmpty())) {
1021            return true;
1022        }
1023        List attrValuesS = supportedAttr.getAttributeValue();
1024        if ((attrValuesS == null) || (attrValuesS.isEmpty())) {
1025            return true;
1026        }
1027
1028        List valuesS = new ArrayList();
1029        for(Iterator iter = attrValuesS.iterator(); iter.hasNext(); ) {
1030            AttributeValueElement attrValueElem =
1031                (AttributeValueElement)iter.next();
1032            valuesS.addAll(attrValueElem.getContent());
1033        }
1034
1035        try {
1036            return valuesS.containsAll(valuesD);
1037        } catch (Exception ex) {
1038            if (SAML2Utils.debug.messageEnabled()) {
1039                SAML2Utils.debug.message(
1040                    "AttributeQueryUtil.isValueValid:", ex);
1041            }
1042            return false;
1043        }
1044    }
1045
1046    private static Response sendAttributeQuerySOAP(AttributeQuery attrQuery,
1047        String attributeServiceURL, String attrAuthorityEntityID,
1048        AttributeAuthorityDescriptorElement aad) throws SAML2Exception {
1049
1050        String attrQueryXMLString = attrQuery.toXMLString(true, true);
1051        if (SAML2Utils.debug.messageEnabled()) {
1052            SAML2Utils.debug.message(
1053                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
1054                "attrQueryXMLString = " + attrQueryXMLString);
1055            SAML2Utils.debug.message(
1056                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
1057                "attributeServiceURL = " + attributeServiceURL);
1058        }
1059        
1060        SOAPMessage resMsg = null;
1061        try {
1062            resMsg = SOAPCommunicator.getInstance().sendSOAPMessage(attrQueryXMLString,
1063                    attributeServiceURL, true);
1064        } catch (SOAPException se) {
1065            SAML2Utils.debug.error(
1066                "AttributeQueryUtil.sendAttributeQuerySOAP: ", se);
1067            throw new SAML2Exception(
1068                SAML2Utils.bundle.getString("errorSendingAttributeQuery"));
1069        }
1070        
1071        Element respElem = SOAPCommunicator.getInstance().getSamlpElement(resMsg, "Response");
1072        Response response =
1073            ProtocolFactory.getInstance().createResponse(respElem);
1074
1075        Status status = response.getStatus();
1076        if (!SAML2Constants.SUCCESS.equals(status.getStatusCode().getValue())) {
1077            String message = status.getStatusMessage() == null ? "" : status.getStatusMessage();
1078            String detail = status.getStatusDetail() == null ? "" : status.getStatusDetail().toXMLString();
1079
1080            SAML2Utils.debug.error(
1081                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
1082                "Non-Success status " + status.getStatusCode().getValue() +
1083                    ", message: " + message + ", detail: " + detail);
1084
1085            Object[] args = { status.getStatusCode().getValue(), message, detail };
1086            throw new SAML2Exception(SAML2Utils.BUNDLE_NAME, "failureStatusAttributeQuery", args);
1087        }
1088
1089        if (SAML2Utils.debug.messageEnabled()) {
1090            SAML2Utils.debug.message(
1091                "AttributeQueryUtil.sendAttributeQuerySOAP: " +
1092                "response = " + response.toXMLString(true, true));
1093        }
1094
1095        verifyResponse(response, attrQuery, attrAuthorityEntityID, aad);
1096
1097        return response;
1098    }
1099
1100    private static void verifyResponse(Response response,
1101        AttributeQuery attrQuery, String attrAuthorityEntityID,
1102        AttributeAuthorityDescriptorElement aad)
1103        throws SAML2Exception {
1104
1105        String attrQueryID = attrQuery.getID();
1106        if ((attrQueryID != null) &&
1107            (!attrQueryID.equals(response.getInResponseTo()))) {
1108
1109            throw new SAML2Exception(
1110                SAML2Utils.bundle.getString("invalidInResponseToAttrQuery"));
1111        }
1112
1113        Issuer respIssuer = response.getIssuer();
1114        if (respIssuer == null) {
1115            return;
1116        }
1117
1118        if (!attrAuthorityEntityID.equals(respIssuer.getValue())) {
1119            throw new SAML2Exception(SAML2Utils.bundle.getString(
1120                "responseIssuerMismatch"));
1121        }
1122
1123        if (!response.isSigned()) {
1124            throw new SAML2Exception(SAML2Utils.bundle.getString(
1125                "responseNotSigned"));
1126        }
1127
1128        Set<X509Certificate> signingCerts = KeyUtil.getVerificationCerts(aad, attrAuthorityEntityID,
1129                SAML2Constants.ATTR_AUTH_ROLE);
1130
1131        if (!signingCerts.isEmpty()) {
1132            boolean valid = response.isSignatureValid(signingCerts);
1133            if (SAML2Utils.debug.messageEnabled()) {
1134                SAML2Utils.debug.message(
1135                    "AttributeQueryUtil.verifyResponse: " +
1136                    "Signature validity is : " + valid);
1137            }
1138            if (!valid) {
1139                throw new SAML2Exception(SAML2Utils.bundle.getString(
1140                    "invalidSignatureOnResponse"));
1141            }
1142        } else {
1143            throw new SAML2Exception(
1144                    SAML2Utils.bundle.getString("missingSigningCertAlias"));
1145        }
1146
1147    }
1148
1149    private static String findLocation(
1150        AttributeAuthorityDescriptorElement aad, String binding,
1151        String attrQueryProfile, String attrProfile) {
1152        SAML2Utils.debug.message("AttributeQueryUtil.findLocation entering...");
1153        List attrProfiles = aad.getAttributeProfile();
1154        if ((attrProfiles == null) || (attrProfiles.isEmpty())) {
1155            SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfiles is null or empty");
1156            if (attrProfile != null) {
1157                SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfiles is null or empty and attrProfile is null");
1158                return null;
1159            }
1160        } else if (!attrProfiles.contains(attrProfile)) {
1161            SAML2Utils.debug.message("AttributeQueryUtil.findLocation: attrProfile not found in the attrProfiles");
1162            return null;
1163        }
1164        SAML2Utils.debug.message("AttributeQueryUtil.findLocation: entering...");
1165
1166        List attrServices = aad.getAttributeService();
1167        for(Iterator iter = attrServices.iterator(); iter.hasNext(); ) {
1168            AttributeServiceElement attrService =
1169                (AttributeServiceElement)iter.next();
1170            if (isValidAttributeService(binding, attrService,
1171                attrQueryProfile)) {
1172                SAML2Utils.debug.message("AttributeQueryUtil.findLocation: found valid service");
1173                return attrService.getLocation();
1174            }
1175        }
1176        SAML2Utils.debug.message("AttributeQueryUtil.findLocation: nothing found, leaving last line with null");
1177
1178        return null;
1179    }
1180
1181    private static boolean isValidAttributeService(String binding,
1182        AttributeServiceElement attrService, String attrQueryProfile) {
1183    
1184        if (!binding.equalsIgnoreCase(attrService.getBinding())) {
1185            return false;
1186        }
1187
1188        if (attrQueryProfile == null) {
1189            return false;
1190        }
1191
1192        return ((attrQueryProfile.equals(
1193            SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE)) ||
1194            (SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE.equals(
1195            attrQueryProfile) && attrService.isSupportsX509Query()));
1196    }
1197
1198    /** 
1199     * Returns an <code>AttributeAuthorityMapper</code>
1200     *
1201     * @param realm the realm name
1202     * @param attrAuthorityEntityID the entity id of the attribute authority
1203     * @param attrQueryProfileAlias attribute profile alias
1204     *
1205     * @return the <code>AttributeAuthorityMapper</code>
1206     * @exception SAML2Exception if the operation is not successful
1207     */
1208    static AttributeAuthorityMapper getAttributeAuthorityMapper(String realm,
1209        String attrAuthorityEntityID, String attrQueryProfileAlias)
1210        throws SAML2Exception {
1211
1212        String attrAuthorityMapperName = null;
1213        AttributeAuthorityMapper attrAuthorityMapper = null;
1214        try {
1215            attrAuthorityMapperName = getAttributeValueFromAttrAuthorityConfig(
1216                realm, attrAuthorityEntityID, attrQueryProfileAlias + "_" +
1217                SAML2Constants.ATTRIBUTE_AUTHORITY_MAPPER);
1218
1219            if (attrAuthorityMapperName == null) {
1220                attrAuthorityMapperName = 
1221                    SAML2Constants.DEFAULT_ATTRIBUTE_AUTHORITY_MAPPER_CLASS;
1222                if (SAML2Utils.debug.messageEnabled()) {
1223                    SAML2Utils.debug.message(
1224                        "AttributeQueryUtil.getAttributeAuthorityMapper: use "+
1225                        attrAuthorityMapperName);
1226                }
1227            }
1228            attrAuthorityMapper = (AttributeAuthorityMapper)
1229                attrAuthorityMapperCache.get(attrAuthorityMapperName);
1230            if (attrAuthorityMapper == null) {
1231                attrAuthorityMapper = (AttributeAuthorityMapper)
1232                    Class.forName(attrAuthorityMapperName).newInstance();
1233                attrAuthorityMapperCache.put(attrAuthorityMapperName,
1234                       attrAuthorityMapper);
1235            } else {
1236                if (SAML2Utils.debug.messageEnabled()) {
1237                    SAML2Utils.debug.message(
1238                        "AttributeQueryUtil.getAttributeAuthorityMapper: " +
1239                        "got the AttributeAuthorityMapper from cache");
1240                }
1241            }
1242        } catch (Exception ex) {
1243            SAML2Utils.debug.error(
1244                "AttributeQueryUtil.getAttributeAuthorityMapper: " +
1245                "Unable to get IDP Attribute Mapper.", ex);
1246            throw new SAML2Exception(ex);
1247        }
1248
1249        return attrAuthorityMapper;
1250    }
1251
1252    private static String getAttributeValueFromAttrAuthorityConfig(
1253        String realm, String attrAuthorityEntityID, String attrName)
1254    {
1255        try {
1256            AttributeAuthorityConfigElement config =
1257                metaManager.getAttributeAuthorityConfig(realm,
1258                attrAuthorityEntityID);
1259            Map attrs = SAML2MetaUtils.getAttributes(config);
1260            String value = null;
1261            List values = (List) attrs.get(attrName);
1262            if ((values != null) && (!values.isEmpty())) {
1263                value = ((String)values.iterator().next()).trim();
1264            }
1265            return value;
1266        } catch (SAML2MetaException sme) {
1267            if (SAML2Utils.debug.messageEnabled()) {
1268                SAML2Utils.debug.message("AttributeQueryUtil." +
1269                   "getAttributeValueFromAttrAuthorityConfig: " +
1270                   "get AttributeAuthorityConfig failed", sme);
1271            }
1272        }
1273        return null;
1274    }
1275
1276    /**
1277     * Sends the AttributeQuery to specified attribute authority,
1278     * validates the response and returns the attribute map
1279     * <code>Map&lt;String, String&gt;</code> to the Fedlet
1280     *
1281     * @param spEntityID SP entity ID
1282     * @param idpEntityID IDP entity ID
1283     * @param nameIDValue  NameID value 
1284     * @param attrsList The list of attributes whose values need to be
1285     *                  fetched from IDP
1286     * @param attrQueryProfileAlias  Attribute Query Profile Alias
1287     * @param subjectDN  Attribute name which contains X.509 subject DN
1288     *
1289     * @return the <code>Map</code> object
1290     * @exception SAML2Exception if the operation is not successful
1291     *
1292     * @deprecated Use {@link #getAttributesForFedlet(String, String, String, List, String, String)}
1293     */
1294    public static Map<String, String> getAttributeMapForFedlet(String spEntityID, String idpEntityID,
1295            String nameIDValue, List<String> attrsList, String attrQueryProfileAlias, String subjectDN)
1296            throws SAML2Exception {
1297        Map<String, Set<String>> attrMap = getAttributesForFedlet(spEntityID, idpEntityID, nameIDValue, attrsList,
1298                attrQueryProfileAlias, subjectDN);
1299
1300        Map<String, String> newAttrMap = new HashMap<String, String>();
1301        for (Map.Entry<String, Set<String>> entry : attrMap.entrySet()) {
1302            String attrName = entry.getKey();
1303            Set<String> attrValue = entry.getValue();
1304            StringBuilder pipedValue = new StringBuilder();
1305            for (String value : attrValue) {
1306                // Multiple attribute values
1307                // are seperated with "|"
1308                if (pipedValue.length() > 0) {
1309                    pipedValue.append('|');
1310                }
1311                pipedValue.append(value);
1312            }
1313            newAttrMap.put(attrName, pipedValue.toString());
1314        }
1315
1316        return newAttrMap;
1317    }
1318    
1319    /**
1320     * Sends the AttributeQuery to specified attribute authority,
1321     * validates the response and returns the attribute map
1322     * <code>Map&lt;String, Set&lt;String&gt;&gt;</code> to the Fedlet
1323     *
1324     * @param spEntityID SP entity ID
1325     * @param idpEntityID IDP entity ID
1326     * @param nameIDValue  NameID value 
1327     * @param attrsList The list of attributes whose values need to be
1328     *                  fetched from IDP
1329     * @param attrQueryProfileAlias  Attribute Query Profile Alias
1330     * @param subjectDN  Attribute name which contains X.509 subject DN
1331     *
1332     * @return the <code>Map</code> object
1333     * @exception SAML2Exception if the operation is not successful
1334     *
1335     * @supported.api
1336     */
1337    public static Map<String, Set<String>> getAttributesForFedlet(String spEntityID, String idpEntityID,
1338            String nameIDValue, List<String> attrsList, String attrQueryProfileAlias, String subjectDN)
1339            throws SAML2Exception {
1340        final String classMethod = "AttributeQueryUtil.getAttributesForFedlet: ";
1341
1342        AttributeQueryConfigElement attrQueryConfig = metaManager.getAttributeQueryConfig("/", spEntityID);
1343        if (attrQueryConfig == null) {
1344            if (SAML2Utils.debug.messageEnabled()) {
1345                SAML2Utils.debug.message(classMethod + "Attribute Query Config is null");
1346            }
1347            return null;
1348        }
1349
1350        String attrqMetaAlias = attrQueryConfig.getMetaAlias();
1351        if (attrqMetaAlias == null) {
1352            if (SAML2Utils.debug.messageEnabled()) {
1353                SAML2Utils.debug.message(classMethod + "Attribute Query MetaAlias is null");
1354            }
1355            return null;
1356        }
1357
1358        boolean wantNameIDEncrypted = SAML2Utils.getWantNameIDEncrypted("/", spEntityID,
1359                SAML2Constants.ATTR_QUERY_ROLE);
1360        
1361        AttributeQuery attrQuery = constructAttrQueryForFedlet(spEntityID, idpEntityID, nameIDValue, attrsList,
1362                attrqMetaAlias, attrQueryProfileAlias, subjectDN, wantNameIDEncrypted);
1363
1364        String attrQueryProfile = null;
1365        if (attrQueryProfileAlias.equals(SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
1366            attrQueryProfile = SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE;
1367        } else if (attrQueryProfileAlias.equals(SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
1368            attrQueryProfile = SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE;
1369        }
1370
1371        Response samlResp = sendAttributeQuery(attrQuery, idpEntityID, "/", attrQueryProfile,
1372                SAML2Constants.BASIC_ATTRIBUTE_PROFILE, SAML2Constants.SOAP);
1373
1374        // Validate the response
1375        boolean validResp = validateSAMLResponseForFedlet(samlResp, spEntityID, wantNameIDEncrypted);
1376
1377        Map<String, Set<String>> attrMap = new HashMap<String, Set<String>>();
1378        if (validResp) {
1379            // Return back the AttributeMap
1380            if (samlResp != null) {
1381                List<Object> assertions;
1382                if (wantNameIDEncrypted) {
1383                    assertions = samlResp.getEncryptedAssertion();
1384                } else {
1385                    assertions = samlResp.getAssertion();
1386                }
1387
1388                for (Object currentAssertion : assertions) {
1389                    Assertion assertion;
1390                    if (wantNameIDEncrypted) {
1391                        assertion = getDecryptedAssertion((EncryptedAssertion) currentAssertion, spEntityID);
1392                    } else {
1393                        assertion = (Assertion) currentAssertion;
1394                    }
1395                    if (assertion != null) {
1396                        List<AttributeStatement> statements = assertion.getAttributeStatements();
1397                        if (statements != null && statements.size() > 0) {
1398                            for (AttributeStatement statement : statements) {
1399                                List<Attribute> attributes = statement.getAttribute();
1400                                attrMap.putAll(mapAttributes("/", spEntityID, idpEntityID, nameIDValue, attributes));
1401                            }
1402                        } else {
1403                            if (SAML2Utils.debug.messageEnabled()) {
1404                                SAML2Utils.debug.message(classMethod + "Empty Statement present in SAML response");
1405                            }
1406                        }
1407                    } else {
1408                        if (SAML2Utils.debug.messageEnabled()) {
1409                            SAML2Utils.debug.message(classMethod + "Empty Assertion present in SAML response");
1410                        }
1411                    }
1412                }
1413                if (SAML2Utils.debug.messageEnabled()) {
1414                    SAML2Utils.debug.message(classMethod + "attributes received from Attribute Query: " + attrMap);
1415                }
1416            }
1417        } else {
1418            if (SAML2Utils.debug.messageEnabled()) {
1419                SAML2Utils.debug.message(classMethod + "Invalid response obtained from Attribute Authority");
1420            }
1421        }
1422        // Return the attribute map and to the fedlet
1423        return attrMap;
1424    }
1425
1426    private static Map<String, Set<String>> mapAttributes(String realm, String spEntityID, String idpEntityID,
1427            String userID, List<Attribute> attributes) throws SAML2Exception {
1428        SPAttributeMapper spAttributeMapper = SAML2Utils.getSPAttributeMapper(realm, spEntityID);
1429        return spAttributeMapper.getAttributes(attributes, userID, spEntityID, idpEntityID, realm);
1430    }
1431
1432    /**
1433     * Constructs the Attribute Query used by the Fedlet to retrieve the 
1434     * values from IDP
1435     *
1436     * @param samlResp saml response
1437     *
1438     * @exception SAML2Exception if the operation is not successful
1439     *
1440     * @supported.api
1441     */
1442    private static AttributeQuery constructAttrQueryForFedlet(
1443                             String spEntityID,
1444                             String idpEntityID,
1445                             String nameIDValue,
1446                             List<String> attrsList,
1447                             String attrqMetaAlias,
1448                             String attrProfileNameAlias,
1449                             String subjectDN,
1450                             boolean wantNameIDEncrypted) throws SAML2Exception
1451    {
1452        String attrqEntityID =
1453          SAML2Utils.getSAML2MetaManager().getEntityByMetaAlias(attrqMetaAlias);
1454
1455        ProtocolFactory protocolFactory = ProtocolFactory.getInstance();
1456        AssertionFactory assertionFactory = AssertionFactory.getInstance();        
1457
1458        AttributeQuery attrQuery = protocolFactory.createAttributeQuery();
1459
1460        Issuer issuer = assertionFactory.createIssuer();
1461        issuer.setValue(attrqEntityID);
1462
1463        attrQuery.setIssuer(issuer);
1464        attrQuery.setID(SAML2Utils.generateID());
1465        attrQuery.setVersion(SAML2Constants.VERSION_2_0);
1466        attrQuery.setIssueInstant(newDate());
1467
1468        List attrs = new ArrayList();
1469        for (String attributeName : attrsList) {
1470            Attribute attr = assertionFactory.createAttribute();
1471            attr.setName(attributeName);
1472            attr.setNameFormat(SAML2Constants.BASIC_NAME_FORMAT);
1473            attrs.add(attr);
1474        }        
1475        attrQuery.setAttributes(attrs);
1476
1477        Subject subject = assertionFactory.createSubject();
1478        NameID nameID = assertionFactory.createNameID();
1479        nameID.setNameQualifier(idpEntityID);
1480        nameID.setSPNameQualifier(spEntityID);
1481
1482        if (attrProfileNameAlias.equals(
1483                    SAML2Constants.DEFAULT_ATTR_QUERY_PROFILE_ALIAS)) {
1484            nameID.setFormat(SAML2Constants.NAMEID_TRANSIENT_FORMAT);
1485            nameID.setValue(nameIDValue);
1486        }
1487
1488        if (attrProfileNameAlias.equals(
1489                    SAML2Constants.X509_SUBJECT_ATTR_QUERY_PROFILE_ALIAS)) {
1490            nameID.setFormat(SAML2Constants.X509_SUBJECT_NAME);
1491            nameID.setValue(subjectDN);            
1492        }
1493
1494        if (!wantNameIDEncrypted) {
1495            subject.setNameID(nameID);
1496        } else {
1497            AttributeAuthorityDescriptorElement aad =
1498                  metaManager.getAttributeAuthorityDescriptor("/", idpEntityID);
1499
1500            EncInfo encInfo = KeyUtil.getEncInfo(aad, idpEntityID,
1501                                                 SAML2Constants.ATTR_AUTH_ROLE);            
1502
1503            EncryptedID encryptedID = nameID.encrypt(encInfo.getWrappingKey(),
1504                                                  encInfo.getDataEncAlgorithm(),
1505                                                  encInfo.getDataEncStrength(),
1506                                                  idpEntityID);
1507            subject.setEncryptedID(encryptedID);
1508        }
1509    
1510        attrQuery.setSubject(subject);
1511
1512        return attrQuery;
1513    }
1514
1515     /**
1516     * Validates the SAML response obtained from Attribute Authortity
1517     *
1518     * @param samlResp saml response
1519     *
1520     * @exception SAML2Exception if the operation is not successful
1521     *
1522     * @supported.api
1523     */
1524    private static boolean validateSAMLResponseForFedlet(
1525                              Response samlResp,
1526                              String spEntityID, 
1527                              boolean wantNameIDEncrypted) throws SAML2Exception
1528    {
1529        boolean resp = true;
1530        if (samlResp != null && samlResp.isSigned()) {
1531            List assertions = null;
1532            if (wantNameIDEncrypted) {
1533                assertions = samlResp.getEncryptedAssertion();
1534            } else {
1535                assertions = samlResp.getAssertion();
1536            }
1537            if (assertions == null) {
1538                return false;
1539            }
1540            for (Iterator asserIter = assertions.iterator();
1541                 asserIter.hasNext();) {
1542                Assertion assertion = null;
1543                if (wantNameIDEncrypted) {
1544                    assertion = getDecryptedAssertion(
1545                                         (EncryptedAssertion)asserIter.next(),
1546                                          spEntityID);
1547                } else {
1548                    assertion = (Assertion)asserIter.next();
1549                }
1550                if (assertion != null) {
1551                    Conditions conditions = assertion.getConditions();
1552                    if (conditions != null) {
1553                        List audienceRes = conditions.
1554                                               getAudienceRestrictions();
1555                        if (audienceRes.size() > 1) {
1556                                resp = false;
1557                                break;
1558                        }
1559                     }
1560                     List statements = assertion.getAttributeStatements();
1561                     if (statements.size() > 1) {
1562                         resp = false;
1563                         break;
1564                     }
1565                }
1566            }
1567        } else {
1568            resp = false;
1569        }
1570        return resp;
1571    }
1572
1573     /**
1574     * Returns the decrypted assertion
1575     *
1576     * @param samlResp saml response
1577     *
1578     * @exception SAML2Exception if the operation is not successful
1579     *
1580     * @supported.api
1581     */
1582    private static Assertion getDecryptedAssertion(
1583                                      EncryptedAssertion eAssertion,
1584                                      String spEntityID) throws SAML2Exception 
1585    {
1586        if (eAssertion != null) {
1587            return eAssertion.decrypt(KeyUtil.getDecryptionKeys("/", spEntityID, SAML2Constants.ATTR_QUERY_ROLE));
1588        }
1589        return null;
1590    }
1591}