Policy based NameID format and assertion signing code
[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.log.Level;
33 import org.opensaml.saml2.core.Advice;
34 import org.opensaml.saml2.core.Assertion;
35 import org.opensaml.saml2.core.Audience;
36 import org.opensaml.saml2.core.AudienceRestriction;
37 import org.opensaml.saml2.core.AuthnRequest;
38 import org.opensaml.saml2.core.Conditions;
39 import org.opensaml.saml2.core.Issuer;
40 import org.opensaml.saml2.core.NameID;
41 import org.opensaml.saml2.core.ProxyRestriction;
42 import org.opensaml.saml2.core.RequestAbstractType;
43 import org.opensaml.saml2.core.Response;
44 import org.opensaml.saml2.core.Statement;
45 import org.opensaml.saml2.core.Status;
46 import org.opensaml.saml2.core.StatusCode;
47 import org.opensaml.saml2.core.StatusMessage;
48 import org.opensaml.saml2.core.StatusResponseType;
49 import org.opensaml.saml2.core.Subject;
50 import org.opensaml.saml2.core.SubjectConfirmation;
51 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
52 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
53 import org.opensaml.saml2.metadata.NameIDFormat;
54 import org.opensaml.saml2.metadata.PDPDescriptor;
55 import org.opensaml.saml2.metadata.RoleDescriptor;
56 import org.opensaml.saml2.metadata.SPSSODescriptor;
57 import org.opensaml.saml2.metadata.SSODescriptor;
58 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
59 import org.opensaml.xml.XMLObjectBuilder;
60 import org.opensaml.xml.security.credential.Credential;
61 import org.opensaml.xml.signature.Signature;
62 import org.opensaml.xml.signature.Signer;
63 import org.opensaml.xml.util.DatatypeHelper;
64
65 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
66 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
67 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
68 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
69 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
70 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
71 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
72 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
73 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
74 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
75
76 /**
77  * Common implementation details for profile handlers.
78  */
79 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
80
81     /** SAML Version for this profile handler. */
82     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
83
84     /** URI for the SAML 2 protocol. */
85     public static final String SAML20_PROTOCOL_URI = "urn:oasis:names:tc:SAML:2.0:protocol";
86
87     /** Class logger. */
88     private Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
89
90     /** For building response. */
91     private SAMLObjectBuilder<Response> responseBuilder;
92
93     /** For building status. */
94     private SAMLObjectBuilder<Status> statusBuilder;
95
96     /** For building statuscode. */
97     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
98
99     /** For building StatusMessages. */
100     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
101
102     /** For building assertion. */
103     private SAMLObjectBuilder<Assertion> assertionBuilder;
104
105     /** For building issuer. */
106     private SAMLObjectBuilder<Issuer> issuerBuilder;
107
108     /** For building subject. */
109     private SAMLObjectBuilder<Subject> subjectBuilder;
110
111     /** For builder subject confirmation. */
112     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
113
114     /** For building conditions. */
115     private SAMLObjectBuilder<Conditions> conditionsBuilder;
116
117     /** For building audience restriction. */
118     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
119
120     /** For building proxy retrictions. */
121     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
122
123     /** For building audience. */
124     private SAMLObjectBuilder<Audience> audienceBuilder;
125
126     /** For building advice. */
127     private SAMLObjectBuilder<Advice> adviceBuilder;
128
129     /** For building signature. */
130     private XMLObjectBuilder<Signature> signatureBuilder;
131
132     /** Constructor. */
133     @SuppressWarnings("unchecked")
134     protected AbstractSAML2ProfileHandler() {
135         super();
136
137         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
138         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
139         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
140                 StatusCode.DEFAULT_ELEMENT_NAME);
141         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
142                 StatusMessage.DEFAULT_ELEMENT_NAME);
143         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
144         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
145                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
146         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
147         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
148                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
149         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
150                 Conditions.DEFAULT_ELEMENT_NAME);
151         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
152                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
153         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
154                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
155         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
156         adviceBuilder = (SAMLObjectBuilder<Advice>) getBuilderFactory().getBuilder(Advice.DEFAULT_ELEMENT_NAME);
157         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
158     }
159
160     /**
161      * Convenience method for getting the SAML 2 advice builder.
162      * 
163      * @return SAML 2 advice builder
164      */
165     public SAMLObjectBuilder<Advice> getAdviceBuilder() {
166         return adviceBuilder;
167     }
168
169     /**
170      * Convenience method for getting the SAML 2 assertion builder.
171      * 
172      * @return SAML 2 assertion builder
173      */
174     public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
175         return assertionBuilder;
176     }
177
178     /**
179      * Convenience method for getting the SAML 2 audience builder.
180      * 
181      * @return SAML 2 audience builder
182      */
183     public SAMLObjectBuilder<Audience> getAudienceBuilder() {
184         return audienceBuilder;
185     }
186
187     /**
188      * Convenience method for getting the SAML 2 audience restriction builder.
189      * 
190      * @return SAML 2 audience restriction builder
191      */
192     public SAMLObjectBuilder<AudienceRestriction> getAudienceRestrictionBuilder() {
193         return audienceRestrictionBuilder;
194     }
195
196     /**
197      * Convenience method for getting the SAML 2 conditions builder.
198      * 
199      * @return SAML 2 conditions builder
200      */
201     public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
202         return conditionsBuilder;
203     }
204
205     /**
206      * Convenience method for getting the SAML 2 Issuer builder.
207      * 
208      * @return SAML 2 Issuer builder
209      */
210     public SAMLObjectBuilder<Issuer> getIssuerBuilder() {
211         return issuerBuilder;
212     }
213
214     /**
215      * Convenience method for getting the SAML 2 proxy restriction builder.
216      * 
217      * @return SAML 2 proxy restriction builder
218      */
219     public SAMLObjectBuilder<ProxyRestriction> getProxyRestrictionBuilder() {
220         return proxyRestrictionBuilder;
221     }
222
223     /**
224      * Convenience method for getting the SAML 2 response builder.
225      * 
226      * @return SAML 2 response builder
227      */
228     public SAMLObjectBuilder<Response> getResponseBuilder() {
229         return responseBuilder;
230     }
231
232     /**
233      * Convenience method for getting the Signature builder.
234      * 
235      * @return signature builder
236      */
237     public XMLObjectBuilder<Signature> getSignatureBuilder() {
238         return signatureBuilder;
239     }
240
241     /**
242      * Convenience method for getting the SAML 2 status builder.
243      * 
244      * @return SAML 2 status builder
245      */
246     public SAMLObjectBuilder<Status> getStatusBuilder() {
247         return statusBuilder;
248     }
249
250     /**
251      * Convenience method for getting the SAML 2 status code builder.
252      * 
253      * @return SAML 2 status code builder
254      */
255     public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
256         return statusCodeBuilder;
257     }
258
259     /**
260      * Convenience method for getting the SAML 2 status message builder.
261      * 
262      * @return SAML 2 status message builder
263      */
264     public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
265         return statusMessageBuilder;
266     }
267
268     /**
269      * Convenience method for getting the SAML 2 subject builder.
270      * 
271      * @return SAML 2 subject builder
272      */
273     public SAMLObjectBuilder<Subject> getSubjectBuilder() {
274         return subjectBuilder;
275     }
276
277     /**
278      * Convenience method for getting the SAML 2 subject confirmation builder.
279      * 
280      * @return SAML 2 subject confirmation builder
281      */
282     public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
283         return subjectConfirmationBuilder;
284     }
285
286     /**
287      * Builds a response to the attribute query within the request context.
288      * 
289      * @param requestContext current request context
290      * @param assertionSubject subject of the assertion within the response
291      * @param statements the statements to include in the response
292      * 
293      * @return the built response
294      * 
295      * @throws ProfileException thrown if there is a problem creating the SAML response
296      * @throws AttributeRequestException thrown if there is a problem resolving attributes
297      */
298     protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
299             List<Statement> statements) throws ProfileException, AttributeRequestException {
300
301         DateTime issueInstant = new DateTime();
302
303         // create the assertion and add the attribute statement
304         Assertion assertion = buildAssertion(requestContext, issueInstant);
305         assertion.setSubject(assertionSubject);
306         if (statements != null) {
307             assertion.getStatements().addAll(statements);
308         }
309
310         // create the SAML response and add the assertion
311         Response samlResponse = getResponseBuilder().buildObject();
312         samlResponse.setIssueInstant(issueInstant);
313         populateStatusResponse(requestContext, samlResponse);
314
315         samlResponse.getAssertions().add(assertion);
316
317         // sign the assertion if it should be signed
318         signAssertion(requestContext, assertion);
319
320         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
321         samlResponse.setStatus(status);
322
323         return samlResponse;
324     }
325
326     /**
327      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
328      * 
329      * @param requestContext current request context
330      * @param issueInstant time to use as assertion issue instant
331      * 
332      * @return the built assertion
333      */
334     protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
335         Assertion assertion = getAssertionBuilder().buildObject();
336         assertion.setID(getIdGenerator().generateIdentifier());
337         assertion.setIssueInstant(issueInstant);
338         assertion.setVersion(SAMLVersion.VERSION_20);
339         assertion.setIssuer(buildEntityIssuer(requestContext));
340
341         Conditions conditions = buildConditions(requestContext, issueInstant);
342         assertion.setConditions(conditions);
343
344         return assertion;
345     }
346
347     /**
348      * Creates an {@link Issuer} populated with information about the relying party.
349      * 
350      * @param requestContext current request context
351      * 
352      * @return the built issuer
353      */
354     protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
355         Issuer issuer = getIssuerBuilder().buildObject();
356         issuer.setFormat(Issuer.ENTITY);
357         issuer.setValue(requestContext.getRelyingPartyId());
358
359         return issuer;
360     }
361
362     /**
363      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
364      * restrictions, and proxy restrictions.
365      * 
366      * @param requestContext current request context
367      * @param issueInstant timestamp the assertion was created
368      * 
369      * @return constructed conditions
370      */
371     protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
372         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
373
374         Conditions conditions = getConditionsBuilder().buildObject();
375         conditions.setNotBefore(issueInstant);
376         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
377
378         Collection<String> audiences;
379
380         // add audience restrictions
381         audiences = profileConfig.getAssertionAudiences();
382         if (audiences != null && audiences.size() > 0) {
383             AudienceRestriction audienceRestriction = getAudienceRestrictionBuilder().buildObject();
384             for (String audienceUri : audiences) {
385                 Audience audience = getAudienceBuilder().buildObject();
386                 audience.setAudienceURI(audienceUri);
387                 audienceRestriction.getAudiences().add(audience);
388             }
389             conditions.getAudienceRestrictions().add(audienceRestriction);
390         }
391
392         // add proxy restrictions
393         audiences = profileConfig.getProxyAudiences();
394         if (audiences != null && audiences.size() > 0) {
395             ProxyRestriction proxyRestriction = getProxyRestrictionBuilder().buildObject();
396             Audience audience;
397             for (String audienceUri : audiences) {
398                 audience = getAudienceBuilder().buildObject();
399                 audience.setAudienceURI(audienceUri);
400                 proxyRestriction.getAudiences().add(audience);
401             }
402
403             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
404             conditions.getConditions().add(proxyRestriction);
405         }
406
407         return conditions;
408     }
409
410     /**
411      * Populates the response's id, in response to, issue instant, version, and issuer properties.
412      * 
413      * @param requestContext current request context
414      * @param response the response to populate
415      */
416     protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
417         response.setID(getIdGenerator().generateIdentifier());
418         if (requestContext.getSamlRequest() != null) {
419             response.setInResponseTo(requestContext.getSamlRequest().getID());
420         }
421         response.setVersion(SAMLVersion.VERSION_20);
422         response.setIssuer(buildEntityIssuer(requestContext));
423     }
424
425     /**
426      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
427      * signing credentials.
428      * 
429      * @param requestContext current request context
430      * @param assertion assertion to sign
431      * 
432      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
433      *             required, if a signing credential is not configured
434      */
435     protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
436             throws ProfileException {
437         if (log.isDebugEnabled()) {
438             log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
439                     + " should be signed");
440         }
441
442         boolean signAssertion = false;
443
444         RoleDescriptor relyingPartyRole;
445         try {
446             relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
447                     requestContext.getRelyingPartyRole(), SAML20_PROTOCOL_URI);
448         } catch (MetadataProviderException e) {
449             throw new ProfileException("Unable to lookup entity metadata for relying party "
450                     + requestContext.getRelyingPartyId());
451         }
452         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
453
454         if (relyingPartyRole instanceof SPSSODescriptor) {
455             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
456             if (ssoDescriptor.getWantAssertionsSigned() != null) {
457                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
458                 if (log.isDebugEnabled()) {
459                     log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
460                             + " indicates to sign assertions: " + signAssertion);
461                 }
462             }
463         } else if (profileConfig.getSignAssertions()) {
464             signAssertion = true;
465             log.debug("IdP relying party configuration "
466                     + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
467                     + " indicates to sign assertions: " + signAssertion);
468         }
469
470         if (!signAssertion) {
471             return;
472         }
473
474         if (log.isDebugEnabled()) {
475             log.debug("Determining signing credntial for assertion to relying party "
476                     + requestContext.getRelyingPartyId());
477         }
478         Credential signatureCredential = profileConfig.getSigningCredential();
479         if (signatureCredential == null) {
480             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
481         }
482
483         if (signatureCredential == null) {
484             throw new ProfileException("No signing credential is specified for relying party configuration "
485                     + requestContext.getRelyingPartyConfiguration().getProviderId()
486                     + " or it's SAML2 attribute query profile configuration");
487         }
488
489         if (log.isDebugEnabled()) {
490             log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
491         }
492         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
493         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
494         signature.getContentReferences().add(contentRef);
495         assertion.setSignature(signature);
496
497         Signer.signObject(signature);
498     }
499
500     /**
501      * Build a status message, with an optional second-level failure message.
502      * 
503      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
504      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
505      *            null, no second-level Status element will be set.
506      * @param secondLevelFailureMessage An optional second-level failure message
507      * 
508      * @return a Status object.
509      */
510     protected Status buildStatus(String topLevelCode, String secondLevelCode, String secondLevelFailureMessage) {
511         Status status = getStatusBuilder().buildObject();
512
513         StatusCode statusCode = getStatusCodeBuilder().buildObject();
514         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
515         status.setStatusCode(statusCode);
516
517         if (secondLevelCode != null) {
518             StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
519             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
520             statusCode.setStatusCode(secondLevelStatusCode);
521         }
522
523         if (secondLevelFailureMessage != null) {
524             StatusMessage msg = getStatusMessageBuilder().buildObject();
525             msg.setMessage(secondLevelFailureMessage);
526             status.setStatusMessage(msg);
527         }
528
529         return status;
530     }
531
532     /**
533      * Builds the SAML subject for the user for the service provider.
534      * 
535      * @param requestContext current request context
536      * @param confirmationMethod subject confirmation method used for the subject
537      * 
538      * @return SAML subject for the user for the service provider
539      * 
540      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
541      *             name ID attribute or because there are no supported name formats
542      */
543     protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
544             throws ProfileException {
545         NameID nameID = buildNameId(requestContext);
546         requestContext.setSubjectNameID(nameID);
547         // TODO handle encryption
548
549         SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
550         subjectConfirmation.setMethod(confirmationMethod);
551
552         Subject subject = getSubjectBuilder().buildObject();
553         subject.setNameID(nameID);
554         subject.getSubjectConfirmations().add(subjectConfirmation);
555
556         return subject;
557     }
558
559     /**
560      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
561      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
562      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
563      * the principals attributes are inspected for an attribtue supported an attribute encoder whose category is one of
564      * the supported name formats.
565      * 
566      * @param requestContext current request context
567      * 
568      * @return the NameID appropriate for this request
569      * 
570      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
571      *             name ID attribute or because there are no supported name formats
572      */
573     protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
574         if (log.isDebugEnabled()) {
575             log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
576                     + "/" + requestContext.getRelyingPartyId());
577         }
578         Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
579         List<String> supportedNameFormats = getNameIDFormat(requestContext);
580
581         if (log.isDebugEnabled()) {
582             log.debug("Supported NameID formats: " + supportedNameFormats);
583         }
584
585         if (principalAttributes != null && supportedNameFormats != null) {
586             try {
587                 AttributeEncoder<NameID> nameIdEncoder = null;
588                 for (BaseAttribute attribute : principalAttributes.values()) {
589                     for (String nameFormat : supportedNameFormats) {
590                         nameIdEncoder = attribute.getEncoderByCategory(nameFormat);
591                         if (nameIdEncoder != null) {
592                             if (log.isDebugEnabled()) {
593                                 log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
594                                         + nameFormat + " to create the NameID for principal "
595                                         + requestContext.getPrincipalName());
596                             }
597                             return nameIdEncoder.encode(attribute);
598                         }
599                     }
600                 }
601             } catch (AttributeEncodingException e) {
602                 throw new ProfileException("Unable to encode NameID attribute", e);
603             }
604         }
605
606         throw new ProfileException("No principal attributes support NameID construction");
607     }
608
609     /**
610      * Gets the NameID format to use when creating NameIDs for the relying party.
611      * 
612      * @param requestContext current request context
613      * 
614      * @return list of nameID formats that may be used with the relying party
615      * 
616      * @throws ProfileException thrown if there is a problem determing the NameID format to use
617      */
618     protected List<String> getNameIDFormat(SAML2ProfileRequestContext requestContext) throws ProfileException {
619         ArrayList<String> nameFormats = new ArrayList<String>();
620
621         try {
622             RoleDescriptor assertingPartyRole = getMetadataProvider().getRole(requestContext.getAssertingPartyId(),
623                     requestContext.getAssertingPartyRole(), SAML20_PROTOCOL_URI);
624             List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
625
626             String nameFormat = null;
627             if (requestContext.getSamlRequest() instanceof AuthnRequest) {
628                 AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
629                 if (authnRequest.getNameIDPolicy() != null) {
630                     nameFormat = authnRequest.getNameIDPolicy().getFormat();
631                     if (assertingPartySupportedFormats.contains(nameFormat)) {
632                         nameFormats.add(nameFormat);
633                     } else {
634                         throw new ProfileException("NameID format required by relying party is not supported");
635                     }
636                 }
637             }
638
639             if (nameFormats.isEmpty()) {
640                 RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
641                         requestContext.getRelyingPartyRole(), SAML20_PROTOCOL_URI);
642                 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
643
644                 assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
645                 nameFormats.addAll(assertingPartySupportedFormats);
646             }
647             if (nameFormats.isEmpty()) {
648                 nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
649             }
650
651             return nameFormats;
652
653         } catch (MetadataProviderException e) {
654             throw new ProfileException("Unable to determine lookup entity metadata", e);
655         }
656     }
657
658     /**
659      * Gets the list of NameID formats supported for a given role.
660      * 
661      * @param role the role to get the list of supported NameID formats
662      * 
663      * @return list of supported NameID formats
664      */
665     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
666         List<NameIDFormat> nameIDFormats = null;
667
668         if (role instanceof SSODescriptor) {
669             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
670         } else if (role instanceof AuthnAuthorityDescriptor) {
671             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
672         } else if (role instanceof PDPDescriptor) {
673             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
674         } else if (role instanceof AttributeAuthorityDescriptor) {
675             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
676         }
677
678         ArrayList<String> supportedFormats = new ArrayList<String>();
679         if (nameIDFormats != null) {
680             for (NameIDFormat format : nameIDFormats) {
681                 supportedFormats.add(format.getFormat());
682             }
683         }
684
685         return supportedFormats;
686     }
687
688     /**
689      * Constructs an SAML response message carrying a request error.
690      * 
691      * @param requestContext current request context
692      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
693      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
694      *            null, no second-level Status element will be set.
695      * @param secondLevelFailureMessage An optional second-level failure message
696      * 
697      * @return the constructed error response
698      */
699     protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext, String topLevelCode,
700             String secondLevelCode, String secondLevelFailureMessage) {
701         Response samlResponse = getResponseBuilder().buildObject();
702         samlResponse.setIssueInstant(new DateTime());
703         populateStatusResponse(requestContext, samlResponse);
704
705         Status status = buildStatus(topLevelCode, secondLevelCode, secondLevelFailureMessage);
706         samlResponse.setStatus(status);
707
708         return samlResponse;
709     }
710
711     /**
712      * Writes an aduit log entry indicating the successful response to the attribute request.
713      * 
714      * @param context current request context
715      */
716     protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
717         AuditLogEntry auditLogEntry = new AuditLogEntry();
718         auditLogEntry.setMessageProfile(getProfileId());
719         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
720         auditLogEntry.setPrincipalName(context.getPrincipalName());
721         auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
722         auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
723         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
724         auditLogEntry.setRequestId(context.getSamlRequest().getID());
725         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
726         auditLogEntry.setResponseId(context.getSamlResponse().getID());
727         getAduitLog().log(Level.CRITICAL, auditLogEntry);
728     }
729
730     /**
731      * Contextual object used to accumlate information as profile requests are being processed.
732      * 
733      * @param <RequestType> type of SAML 2 request
734      * @param <ResponseType> type of SAML 2 response
735      * @param <ProfileConfigurationType> configuration type for this profile
736      */
737     protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
738             extends SAMLProfileRequestContext {
739
740         /** SAML request message. */
741         private RequestType samlRequest;
742
743         /** SAML response message. */
744         private ResponseType samlResponse;
745
746         /** Request profile configuration. */
747         private ProfileConfigurationType profileConfiguration;
748
749         /** The NameID of the subject of this request. */
750         private NameID subjectNameID;
751
752         /**
753          * Constructor.
754          * 
755          * @param request current profile request
756          * @param response current profile response
757          */
758         public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
759                 ProfileResponse<ServletResponse> response) {
760             super(request, response);
761         }
762
763         /**
764          * Gets the NameID of the subject of this request.
765          * 
766          * @return NameID of the subject of this request
767          */
768         public NameID getSubjectNameID() {
769             return subjectNameID;
770         }
771
772         /**
773          * Sets the NameID of the subject of this request.
774          * 
775          * @param nameID NameID of the subject of this request
776          */
777         public void setSubjectNameID(NameID nameID) {
778             subjectNameID = nameID;
779         }
780
781         /**
782          * Gets the profile configuration for this request.
783          * 
784          * @return profile configuration for this request
785          */
786         public ProfileConfigurationType getProfileConfiguration() {
787             return profileConfiguration;
788         }
789
790         /**
791          * Sets the profile configuration for this request.
792          * 
793          * @param configuration profile configuration for this request
794          */
795         public void setProfileConfiguration(ProfileConfigurationType configuration) {
796             profileConfiguration = configuration;
797         }
798
799         /**
800          * Gets the SAML request message.
801          * 
802          * @return SAML request message
803          */
804         public RequestType getSamlRequest() {
805             return samlRequest;
806         }
807
808         /**
809          * Sets the SAML request message.
810          * 
811          * @param request SAML request message
812          */
813         public void setSamlRequest(RequestType request) {
814             samlRequest = request;
815         }
816
817         /**
818          * Gets the SAML response message.
819          * 
820          * @return SAML response message
821          */
822         public ResponseType getSamlResponse() {
823             return samlResponse;
824         }
825
826         /**
827          * Sets the SAML response message.
828          * 
829          * @param response SAML response message
830          */
831         public void setSamlResponse(ResponseType response) {
832             samlResponse = response;
833         }
834     }
835 }