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