Now with SAML 1 attribute query goodness (though not yet tested)
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractSAML2ProfileHandler.java
1 /*
2  * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.profile.saml2;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.servlet.ServletRequest;
25 import javax.servlet.ServletResponse;
26
27 import org.apache.log4j.Logger;
28 import org.joda.time.DateTime;
29 import org.opensaml.common.SAMLObjectBuilder;
30 import org.opensaml.common.SAMLVersion;
31 import org.opensaml.common.impl.SAMLObjectContentReference;
32 import org.opensaml.common.xml.SAMLConstants;
33 import org.opensaml.log.Level;
34 import org.opensaml.saml2.core.Advice;
35 import org.opensaml.saml2.core.Assertion;
36 import org.opensaml.saml2.core.Audience;
37 import org.opensaml.saml2.core.AudienceRestriction;
38 import org.opensaml.saml2.core.AuthnRequest;
39 import org.opensaml.saml2.core.Conditions;
40 import org.opensaml.saml2.core.Issuer;
41 import org.opensaml.saml2.core.NameID;
42 import org.opensaml.saml2.core.ProxyRestriction;
43 import org.opensaml.saml2.core.RequestAbstractType;
44 import org.opensaml.saml2.core.Response;
45 import org.opensaml.saml2.core.Statement;
46 import org.opensaml.saml2.core.Status;
47 import org.opensaml.saml2.core.StatusCode;
48 import org.opensaml.saml2.core.StatusMessage;
49 import org.opensaml.saml2.core.StatusResponseType;
50 import org.opensaml.saml2.core.Subject;
51 import org.opensaml.saml2.core.SubjectConfirmation;
52 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
53 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
54 import org.opensaml.saml2.metadata.NameIDFormat;
55 import org.opensaml.saml2.metadata.PDPDescriptor;
56 import org.opensaml.saml2.metadata.RoleDescriptor;
57 import org.opensaml.saml2.metadata.SPSSODescriptor;
58 import org.opensaml.saml2.metadata.SSODescriptor;
59 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
60 import org.opensaml.xml.XMLObjectBuilder;
61 import org.opensaml.xml.security.credential.Credential;
62 import org.opensaml.xml.signature.Signature;
63 import org.opensaml.xml.signature.Signer;
64 import org.opensaml.xml.util.DatatypeHelper;
65
66 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
67 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
68 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
69 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
70 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
71 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
72 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
73 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
74 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
75 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
76
77 /**
78  * Common implementation details for profile handlers.
79  */
80 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
81
82     /** SAML Version for this profile handler. */
83     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
84
85     /** Class logger. */
86     private Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
87
88     /** For building response. */
89     private SAMLObjectBuilder<Response> responseBuilder;
90
91     /** For building status. */
92     private SAMLObjectBuilder<Status> statusBuilder;
93
94     /** For building statuscode. */
95     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
96
97     /** For building StatusMessages. */
98     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
99
100     /** For building assertion. */
101     private SAMLObjectBuilder<Assertion> assertionBuilder;
102
103     /** For building issuer. */
104     private SAMLObjectBuilder<Issuer> issuerBuilder;
105
106     /** For building subject. */
107     private SAMLObjectBuilder<Subject> subjectBuilder;
108
109     /** For builder subject confirmation. */
110     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
111
112     /** For building conditions. */
113     private SAMLObjectBuilder<Conditions> conditionsBuilder;
114
115     /** For building audience restriction. */
116     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
117
118     /** For building proxy retrictions. */
119     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
120
121     /** For building audience. */
122     private SAMLObjectBuilder<Audience> audienceBuilder;
123
124     /** For building advice. */
125     private SAMLObjectBuilder<Advice> adviceBuilder;
126
127     /** For building signature. */
128     private XMLObjectBuilder<Signature> signatureBuilder;
129
130     /** Constructor. */
131     @SuppressWarnings("unchecked")
132     protected AbstractSAML2ProfileHandler() {
133         super();
134
135         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
136         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
137         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
138                 StatusCode.DEFAULT_ELEMENT_NAME);
139         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
140                 StatusMessage.DEFAULT_ELEMENT_NAME);
141         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
142         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
143                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
144         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
145         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
146                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
147         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
148                 Conditions.DEFAULT_ELEMENT_NAME);
149         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
150                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
151         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
152                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
153         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
154         adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
155         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
156     }
157
158     /**
159      * Convenience method for getting the SAML 2 advice builder.
160      * 
161      * @return SAML 2 advice builder
162      */
163     public SAMLObjectBuilder<Advice> getAdviceBuilder() {
164         return adviceBuilder;
165     }
166
167     /**
168      * Convenience method for getting the SAML 2 assertion builder.
169      * 
170      * @return SAML 2 assertion builder
171      */
172     public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
173         return assertionBuilder;
174     }
175
176     /**
177      * Convenience method for getting the SAML 2 audience builder.
178      * 
179      * @return SAML 2 audience builder
180      */
181     public SAMLObjectBuilder<Audience> getAudienceBuilder() {
182         return audienceBuilder;
183     }
184
185     /**
186      * Convenience method for getting the SAML 2 audience restriction builder.
187      * 
188      * @return SAML 2 audience restriction builder
189      */
190     public SAMLObjectBuilder<AudienceRestriction> getAudienceRestrictionBuilder() {
191         return audienceRestrictionBuilder;
192     }
193
194     /**
195      * Convenience method for getting the SAML 2 conditions builder.
196      * 
197      * @return SAML 2 conditions builder
198      */
199     public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
200         return conditionsBuilder;
201     }
202
203     /**
204      * Convenience method for getting the SAML 2 Issuer builder.
205      * 
206      * @return SAML 2 Issuer builder
207      */
208     public SAMLObjectBuilder<Issuer> getIssuerBuilder() {
209         return issuerBuilder;
210     }
211
212     /**
213      * Convenience method for getting the SAML 2 proxy restriction builder.
214      * 
215      * @return SAML 2 proxy restriction builder
216      */
217     public SAMLObjectBuilder<ProxyRestriction> getProxyRestrictionBuilder() {
218         return proxyRestrictionBuilder;
219     }
220
221     /**
222      * Convenience method for getting the SAML 2 response builder.
223      * 
224      * @return SAML 2 response builder
225      */
226     public SAMLObjectBuilder<Response> getResponseBuilder() {
227         return responseBuilder;
228     }
229
230     /**
231      * Convenience method for getting the Signature builder.
232      * 
233      * @return signature builder
234      */
235     public XMLObjectBuilder<Signature> getSignatureBuilder() {
236         return signatureBuilder;
237     }
238
239     /**
240      * Convenience method for getting the SAML 2 status builder.
241      * 
242      * @return SAML 2 status builder
243      */
244     public SAMLObjectBuilder<Status> getStatusBuilder() {
245         return statusBuilder;
246     }
247
248     /**
249      * Convenience method for getting the SAML 2 status code builder.
250      * 
251      * @return SAML 2 status code builder
252      */
253     public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
254         return statusCodeBuilder;
255     }
256
257     /**
258      * Convenience method for getting the SAML 2 status message builder.
259      * 
260      * @return SAML 2 status message builder
261      */
262     public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
263         return statusMessageBuilder;
264     }
265
266     /**
267      * Convenience method for getting the SAML 2 subject builder.
268      * 
269      * @return SAML 2 subject builder
270      */
271     public SAMLObjectBuilder<Subject> getSubjectBuilder() {
272         return subjectBuilder;
273     }
274
275     /**
276      * Convenience method for getting the SAML 2 subject confirmation builder.
277      * 
278      * @return SAML 2 subject confirmation builder
279      */
280     public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
281         return subjectConfirmationBuilder;
282     }
283
284     /**
285      * Builds a response to the attribute query within the request context.
286      * 
287      * @param requestContext current request context
288      * @param assertionSubject subject of the assertion within the response
289      * @param statements the statements to include in the response
290      * 
291      * @return the built response
292      * 
293      * @throws ProfileException thrown if there is a problem creating the SAML response
294      * @throws AttributeRequestException thrown if there is a problem resolving attributes
295      */
296     protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
297             List<Statement> statements) throws ProfileException, AttributeRequestException {
298
299         DateTime issueInstant = new DateTime();
300
301         // create the assertion and add the attribute statement
302         Assertion assertion = buildAssertion(requestContext, issueInstant);
303         assertion.setSubject(assertionSubject);
304         if (statements != null) {
305             assertion.getStatements().addAll(statements);
306         }
307
308         // create the SAML response and add the assertion
309         Response samlResponse = getResponseBuilder().buildObject();
310         samlResponse.setIssueInstant(issueInstant);
311         populateStatusResponse(requestContext, samlResponse);
312
313         samlResponse.getAssertions().add(assertion);
314
315         // sign the assertion if it should be signed
316         signAssertion(requestContext, assertion);
317
318         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
319         samlResponse.setStatus(status);
320
321         return samlResponse;
322     }
323
324     /**
325      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
326      * 
327      * @param requestContext current request context
328      * @param issueInstant time to use as assertion issue instant
329      * 
330      * @return the built assertion
331      */
332     protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
333         Assertion assertion = getAssertionBuilder().buildObject();
334         assertion.setID(getIdGenerator().generateIdentifier());
335         assertion.setIssueInstant(issueInstant);
336         assertion.setVersion(SAMLVersion.VERSION_20);
337         assertion.setIssuer(buildEntityIssuer(requestContext));
338
339         Conditions conditions = buildConditions(requestContext, issueInstant);
340         assertion.setConditions(conditions);
341
342         return assertion;
343     }
344
345     /**
346      * Creates an {@link Issuer} populated with information about the relying party.
347      * 
348      * @param requestContext current request context
349      * 
350      * @return the built issuer
351      */
352     protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
353         Issuer issuer = getIssuerBuilder().buildObject();
354         issuer.setFormat(Issuer.ENTITY);
355         issuer.setValue(requestContext.getRelyingPartyId());
356
357         return issuer;
358     }
359
360     /**
361      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
362      * restrictions, and proxy restrictions.
363      * 
364      * @param requestContext current request context
365      * @param issueInstant timestamp the assertion was created
366      * 
367      * @return constructed conditions
368      */
369     protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
370         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
371
372         Conditions conditions = getConditionsBuilder().buildObject();
373         conditions.setNotBefore(issueInstant);
374         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
375
376         Collection<String> audiences;
377
378         // add audience restrictions
379         audiences = profileConfig.getAssertionAudiences();
380         if (audiences != null && audiences.size() > 0) {
381             AudienceRestriction audienceRestriction = getAudienceRestrictionBuilder().buildObject();
382             for (String audienceUri : audiences) {
383                 Audience audience = getAudienceBuilder().buildObject();
384                 audience.setAudienceURI(audienceUri);
385                 audienceRestriction.getAudiences().add(audience);
386             }
387             conditions.getAudienceRestrictions().add(audienceRestriction);
388         }
389
390         // add proxy restrictions
391         audiences = profileConfig.getProxyAudiences();
392         if (audiences != null && audiences.size() > 0) {
393             ProxyRestriction proxyRestriction = getProxyRestrictionBuilder().buildObject();
394             Audience audience;
395             for (String audienceUri : audiences) {
396                 audience = getAudienceBuilder().buildObject();
397                 audience.setAudienceURI(audienceUri);
398                 proxyRestriction.getAudiences().add(audience);
399             }
400
401             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
402             conditions.getConditions().add(proxyRestriction);
403         }
404
405         return conditions;
406     }
407
408     /**
409      * Populates the response's id, in response to, issue instant, version, and issuer properties.
410      * 
411      * @param requestContext current request context
412      * @param response the response to populate
413      */
414     protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
415         response.setID(getIdGenerator().generateIdentifier());
416         if (requestContext.getSamlRequest() != null) {
417             response.setInResponseTo(requestContext.getSamlRequest().getID());
418         }
419         response.setVersion(SAMLVersion.VERSION_20);
420         response.setIssuer(buildEntityIssuer(requestContext));
421     }
422
423     /**
424      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
425      * signing credentials.
426      * 
427      * @param requestContext current request context
428      * @param assertion assertion to sign
429      * 
430      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
431      *             required, if a signing credential is not configured
432      */
433     protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
434             throws ProfileException {
435         if (log.isDebugEnabled()) {
436             log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
437                     + " should be signed");
438         }
439
440         boolean signAssertion = false;
441
442         RoleDescriptor relyingPartyRole;
443         try {
444             relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
445                     requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
446         } catch (MetadataProviderException e) {
447             throw new ProfileException("Unable to lookup entity metadata for relying party "
448                     + requestContext.getRelyingPartyId());
449         }
450         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
451
452         if (relyingPartyRole instanceof SPSSODescriptor) {
453             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
454             if (ssoDescriptor.getWantAssertionsSigned() != null) {
455                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
456                 if (log.isDebugEnabled()) {
457                     log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
458                             + " indicates to sign assertions: " + signAssertion);
459                 }
460             }
461         } else if (profileConfig.getSignAssertions()) {
462             signAssertion = true;
463             log.debug("IdP relying party configuration "
464                     + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
465                     + " indicates to sign assertions: " + signAssertion);
466         }
467
468         if (!signAssertion) {
469             return;
470         }
471
472         if (log.isDebugEnabled()) {
473             log.debug("Determining signing credntial for assertion to relying party "
474                     + requestContext.getRelyingPartyId());
475         }
476         Credential signatureCredential = profileConfig.getSigningCredential();
477         if (signatureCredential == null) {
478             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
479         }
480
481         if (signatureCredential == null) {
482             throw new ProfileException("No signing credential is specified for relying party configuration "
483                     + requestContext.getRelyingPartyConfiguration().getProviderId()
484                     + " or it's SAML2 attribute query profile configuration");
485         }
486
487         if (log.isDebugEnabled()) {
488             log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
489         }
490         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
491         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
492         signature.getContentReferences().add(contentRef);
493         assertion.setSignature(signature);
494
495         Signer.signObject(signature);
496     }
497
498     /**
499      * Build a status message, with an optional second-level failure message.
500      * 
501      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
502      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
503      *            null, no second-level Status element will be set.
504      * @param secondLevelFailureMessage An optional second-level failure message
505      * 
506      * @return a Status object.
507      */
508     protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
509         Status status = getStatusBuilder().buildObject();
510
511         StatusCode statusCode = getStatusCodeBuilder().buildObject();
512         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
513         status.setStatusCode(statusCode);
514
515         if (secondLevelCode != null) {
516             StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
517             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
518             statusCode.setStatusCode(secondLevelStatusCode);
519         }
520
521         if (secondLevelFailureMessage != null) {
522             StatusMessage msg = getStatusMessageBuilder().buildObject();
523             msg.setMessage(secondLevelFailureMessage);
524             status.setStatusMessage(msg);
525         }
526
527         return status;
528     }
529
530     /**
531      * Builds the SAML subject for the user for the service provider.
532      * 
533      * @param requestContext current request context
534      * @param confirmationMethod subject confirmation method used for the subject
535      * 
536      * @return SAML subject for the user for the service provider
537      * 
538      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
539      *             name ID attribute or because there are no supported name formats
540      */
541     protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
542             throws ProfileException {
543         NameID nameID = buildNameId(requestContext);
544         requestContext.setSubjectNameID(nameID);
545         // TODO handle encryption
546
547         SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
548         subjectConfirmation.setMethod(confirmationMethod);
549
550         Subject subject = getSubjectBuilder().buildObject();
551         subject.setNameID(nameID);
552         subject.getSubjectConfirmations().add(subjectConfirmation);
553
554         return subject;
555     }
556
557     /**
558      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
559      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
560      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
561      * the principals attributes are inspected for an attribtue supported an attribute encoder whose category is one of
562      * the supported name formats.
563      * 
564      * @param requestContext current request context
565      * 
566      * @return the NameID appropriate for this request
567      * 
568      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
569      *             name ID attribute or because there are no supported name formats
570      */
571     protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
572         if (log.isDebugEnabled()) {
573             log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
574                     + "/" + requestContext.getRelyingPartyId());
575         }
576         Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
577         List<String> supportedNameFormats = getNameFormats(requestContext);
578
579         if (log.isDebugEnabled()) {
580             log.debug("Supported NameID formats: " + supportedNameFormats);
581         }
582
583         if (principalAttributes != null && supportedNameFormats != null) {
584             try {
585                 AttributeEncoder<NameID> nameIdEncoder = null;
586                 for (BaseAttribute attribute : principalAttributes.values()) {
587                     for (String nameFormat : supportedNameFormats) {
588                         nameIdEncoder = attribute.getEncoderByCategory(nameFormat);
589                         if (nameIdEncoder != null) {
590                             if (log.isDebugEnabled()) {
591                                 log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
592                                         + nameFormat + " to create the NameID for principal "
593                                         + requestContext.getPrincipalName());
594                             }
595                             return nameIdEncoder.encode(attribute);
596                         }
597                     }
598                 }
599             } catch (AttributeEncodingException e) {
600                 throw new ProfileException("Unable to encode NameID attribute", e);
601             }
602         }
603
604         throw new ProfileException("No principal attributes support NameID construction");
605     }
606
607     /**
608      * Gets the NameID format to use when creating NameIDs for the relying party.
609      * 
610      * @param requestContext current request context
611      * 
612      * @return list of nameID formats that may be used with the relying party
613      * 
614      * @throws ProfileException thrown if there is a problem determing the NameID format to use
615      */
616     protected List<String> getNameFormats(SAML2ProfileRequestContext requestContext) throws ProfileException {
617         ArrayList<String> nameFormats = new ArrayList<String>();
618
619         try {
620             RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
621                     requestContext.getAssertingPartyRole(), SAMLConstants.SAML20P_NS);
622             List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
623
624             String nameFormat = null;
625             if (requestContext.getSamlRequest() instanceof AuthnRequest) {