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) {
626                 AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
627                 if (authnRequest.getNameIDPolicy() != null) {
628                     nameFormat = authnRequest.getNameIDPolicy().getFormat();
629                     if (assertingPartySupportedFormats.contains(nameFormat)) {
630                         nameFormats.add(nameFormat);
631                     } else {
632                         throw new ProfileException("NameID format required by relying party is not supported");
633                     }
634                 }
635             }
636
637             if (nameFormats.isEmpty()) {
638                 RoleDescriptor relyingPartyRole = getMetadataProvider().getRole(requestContext.getRelyingPartyId(),
639                         requestContext.getRelyingPartyRole(), SAMLConstants.SAML20P_NS);
640                 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
641
642                 assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
643                 nameFormats.addAll(assertingPartySupportedFormats);
644             }
645             if (nameFormats.isEmpty()) {
646                 nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
647             }
648
649             return nameFormats;
650
651         } catch (MetadataProviderException e) {
652             throw new ProfileException("Unable to determine lookup entity metadata", e);
653         }
654     }
655
656     /**
657      * Gets the list of NameID formats supported for a given role.
658      * 
659      * @param role the role to get the list of supported NameID formats
660      * 
661      * @return list of supported NameID formats
662      */
663     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
664         List<NameIDFormat> nameIDFormats = null;
665
666         if (role instanceof SSODescriptor) {
667             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
668         } else if (role instanceof AuthnAuthorityDescriptor) {
669             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
670         } else if (role instanceof PDPDescriptor) {
671             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
672         } else if (role instanceof AttributeAuthorityDescriptor) {
673             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
674         }
675
676         ArrayList<String> supportedFormats = new ArrayList<String>();
677         if (nameIDFormats != null) {
678             for (NameIDFormat format : nameIDFormats) {
679                 supportedFormats.add(format.getFormat());
680             }
681         }
682
683         return supportedFormats;
684     }
685
686     /**
687      * Constructs an SAML response message carrying a request error.
688      * 
689      * @param requestContext current request context
690      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
691      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
692      *            null, no second-level Status element will be set.
693      * @param secondLevelFailureMessage An optional second-level failure message
694      * 
695      * @return the constructed error response
696      */
697     protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext, String topLevelCode,
698             String secondLevelCode, String secondLevelFailureMessage) {
699         Response samlResponse = getResponseBuilder().buildObject();
700         samlResponse.setIssueInstant(new DateTime());
701         populateStatusResponse(requestContext, samlResponse);
702
703         Status status = buildStatus(topLevelCode, secondLevelCode, secondLevelFailureMessage);
704         samlResponse.setStatus(status);
705
706         return samlResponse;
707     }
708
709     /**
710      * Writes an aduit log entry indicating the successful response to the attribute request.
711      * 
712      * @param context current request context
713      */
714     protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
715         AuditLogEntry auditLogEntry = new AuditLogEntry();
716         auditLogEntry.setMessageProfile(getProfileId());
717         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
718         auditLogEntry.setPrincipalName(context.getPrincipalName());
719         auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
720         auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
721         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
722         auditLogEntry.setRequestId(context.getSamlRequest().getID());
723         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
724         auditLogEntry.setResponseId(context.getSamlResponse().getID());
725         getAduitLog().log(Level.CRITICAL, auditLogEntry);
726     }
727
728     /**
729      * Contextual object used to accumlate information as profile requests are being processed.
730      * 
731      * @param <RequestType> type of SAML 2 request
732      * @param <ResponseType> type of SAML 2 response
733      * @param <ProfileConfigurationType> configuration type for this profile
734      */
735     protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
736             extends SAMLProfileRequestContext {
737
738         /** SAML request message. */
739         private RequestType samlRequest;
740
741         /** SAML response message. */
742         private ResponseType samlResponse;
743
744         /** Request profile configuration. */
745         private ProfileConfigurationType profileConfiguration;
746
747         /** The NameID of the subject of this request. */
748         private NameID subjectNameID;
749
750         /**
751          * Constructor.
752          * 
753          * @param request current profile request
754          * @param response current profile response
755          */
756         public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
757                 ProfileResponse<ServletResponse> response) {
758             super(request, response);
759         }
760
761         /**
762          * Gets the NameID of the subject of this request.
763          * 
764          * @return NameID of the subject of this request
765          */
766         public NameID getSubjectNameID() {
767             return subjectNameID;
768         }
769
770         /**
771          * Sets the NameID of the subject of this request.
772          * 
773          * @param nameID NameID of the subject of this request
774          */
775         public void setSubjectNameID(NameID nameID) {
776             subjectNameID = nameID;
777         }
778
779         /**
780          * Gets the profile configuration for this request.
781          * 
782          * @return profile configuration for this request
783          */
784         public ProfileConfigurationType getProfileConfiguration() {
785             return profileConfiguration;
786         }
787
788         /**
789          * Sets the profile configuration for this request.
790          * 
791          * @param configuration profile configuration for this request
792          */
793         public void setProfileConfiguration(ProfileConfigurationType configuration) {
794             profileConfiguration = configuration;
795         }
796
797         /**
798          * Gets the SAML request message.
799          * 
800          * @return SAML request message
801          */
802         public RequestType getSamlRequest() {
803             return samlRequest;
804         }
805
806         /**
807          * Sets the SAML request message.
808          * 
809          * @param request SAML request message
810          */
811         public void setSamlRequest(RequestType request) {
812             samlRequest = request;
813         }
814
815         /**
816          * Gets the SAML response message.
817          * 
818          * @return SAML response message
819          */
820         public ResponseType getSamlResponse() {
821             return samlResponse;
822         }
823
824         /**
825          * Sets the SAML response message.
826          * 
827          * @param response SAML response message
828          */
829         public void setSamlResponse(ResponseType response) {
830             samlResponse = response;
831         }
832     }
833 }