allow message decoder to be null as is the case in Shib SSO requests
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml1 / AbstractSAML1ProfileHandler.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.saml1;
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 import javax.xml.namespace.QName;
27
28 import org.apache.log4j.Logger;
29 import org.joda.time.DateTime;
30 import org.opensaml.common.SAMLObject;
31 import org.opensaml.common.SAMLObjectBuilder;
32 import org.opensaml.common.SAMLVersion;
33 import org.opensaml.common.impl.SAMLObjectContentReference;
34 import org.opensaml.log.Level;
35 import org.opensaml.saml1.core.Assertion;
36 import org.opensaml.saml1.core.AttributeQuery;
37 import org.opensaml.saml1.core.AttributeStatement;
38 import org.opensaml.saml1.core.Audience;
39 import org.opensaml.saml1.core.AudienceRestrictionCondition;
40 import org.opensaml.saml1.core.Conditions;
41 import org.opensaml.saml1.core.ConfirmationMethod;
42 import org.opensaml.saml1.core.NameIdentifier;
43 import org.opensaml.saml1.core.Request;
44 import org.opensaml.saml1.core.RequestAbstractType;
45 import org.opensaml.saml1.core.Response;
46 import org.opensaml.saml1.core.ResponseAbstractType;
47 import org.opensaml.saml1.core.Statement;
48 import org.opensaml.saml1.core.Status;
49 import org.opensaml.saml1.core.StatusCode;
50 import org.opensaml.saml1.core.StatusMessage;
51 import org.opensaml.saml1.core.Subject;
52 import org.opensaml.saml1.core.SubjectConfirmation;
53 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
54 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
55 import org.opensaml.saml2.metadata.NameIDFormat;
56 import org.opensaml.saml2.metadata.PDPDescriptor;
57 import org.opensaml.saml2.metadata.RoleDescriptor;
58 import org.opensaml.saml2.metadata.SPSSODescriptor;
59 import org.opensaml.saml2.metadata.SSODescriptor;
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
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.attribute.encoding.SAML1NameIdentifierEncoder;
70 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
71 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
72 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
73 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
74 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
75 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AbstractSAML1ProfileConfiguration;
77 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
78 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
79 import edu.internet2.middleware.shibboleth.idp.session.Session;
80
81 /** Common implementation details for profile handlers. */
82 public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHandler {
83
84     /** SAML Version for this profile handler. */
85     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_11;
86
87     /** Class logger. */
88     private static Logger log = Logger.getLogger(AbstractSAML1ProfileHandler.class);
89
90     /** Builder of Response objects. */
91     private SAMLObjectBuilder<Response> responseBuilder;
92
93     /** Builder of Assertion objects. */
94     private SAMLObjectBuilder<Assertion> assertionBuilder;
95
96     /** Builder of Conditions objects. */
97     private SAMLObjectBuilder<Conditions> conditionsBuilder;
98
99     /** Builder of AudienceRestrictionCondition objects. */
100     private SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionConditionBuilder;
101
102     /** Builder of AudienceRestrictionCondition objects. */
103     private SAMLObjectBuilder<Audience> audienceBuilder;
104
105     /** Builder of NameIdentifier objects. */
106     private SAMLObjectBuilder<NameIdentifier> nameIdBuilder;
107
108     /** Builder of SubjectConfirmation objects. */
109     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
110
111     /** Builder of ConfirmationMethod objects. */
112     private SAMLObjectBuilder<ConfirmationMethod> confirmationMethodBuilder;
113
114     /** Builder of Subject objects. */
115     private SAMLObjectBuilder<Subject> subjectBuilder;
116
117     /** Builder for Status objects. */
118     private SAMLObjectBuilder<Status> statusBuilder;
119
120     /** Builder for StatusCode objects. */
121     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
122
123     /** Builder for StatusMessage objects. */
124     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
125
126     /** For building signature. */
127     private XMLObjectBuilder<Signature> signatureBuilder;
128
129     /**
130      * Default constructor.
131      */
132     @SuppressWarnings("unchecked")
133     public AbstractSAML1ProfileHandler() {
134         super();
135         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
136         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
137                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
138         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
139                 Conditions.DEFAULT_ELEMENT_NAME);
140         audienceRestrictionConditionBuilder = (SAMLObjectBuilder<AudienceRestrictionCondition>) getBuilderFactory()
141                 .getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
142         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
143         nameIdBuilder = (SAMLObjectBuilder<NameIdentifier>) getBuilderFactory().getBuilder(
144                 NameIdentifier.DEFAULT_ELEMENT_NAME);
145         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
146                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
147         confirmationMethodBuilder = (SAMLObjectBuilder<ConfirmationMethod>) getBuilderFactory().getBuilder(
148                 ConfirmationMethod.DEFAULT_ELEMENT_NAME);
149         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
150         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
151         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
152                 StatusCode.DEFAULT_ELEMENT_NAME);
153         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
154                 StatusMessage.DEFAULT_ELEMENT_NAME);
155         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
156     }
157
158     /**
159      * Convenience method for getting the SAML 1 Response builder.
160      * 
161      * @return SAML 1 Response builder
162      */
163     public SAMLObjectBuilder<Response> getResponseBuilder() {
164         return responseBuilder;
165     }
166
167     /**
168      * Convenience method for getting the SAML 1 Assertion builder.
169      * 
170      * @return SAML 1 Assertion builder
171      */
172     public SAMLObjectBuilder<Assertion> getAssertionBuilder() {
173         return assertionBuilder;
174     }
175
176     /**
177      * Convenience method for getting the SAML 1 Conditions builder.
178      * 
179      * @return SAML 1 Conditions builder
180      */
181     public SAMLObjectBuilder<Conditions> getConditionsBuilder() {
182         return conditionsBuilder;
183     }
184
185     /**
186      * Convenience method for getting the SAML 1 AudienceRestrictionCondition builder.
187      * 
188      * @return SAML 1 AudienceRestrictionCondition builder
189      */
190     public SAMLObjectBuilder<AudienceRestrictionCondition> getAudienceRestrictionConditionBuilder() {
191         return audienceRestrictionConditionBuilder;
192     }
193
194     /**
195      * Convenience method for getting the SAML 1 Audience builder.
196      * 
197      * @return SAML 1 Audience builder
198      */
199     public SAMLObjectBuilder<Audience> getAudienceBuilder() {
200         return audienceBuilder;
201     }
202
203     /**
204      * Convenience method for getting the SAML 1 NameIdentifier builder.
205      * 
206      * @return SAML 1 NameIdentifier builder
207      */
208     public SAMLObjectBuilder<NameIdentifier> getNameIdentifierBuilder() {
209         return nameIdBuilder;
210     }
211
212     /**
213      * Convenience method for getting the SAML 1 SubjectConfirmation builder.
214      * 
215      * @return SAML 1 SubjectConfirmation builder
216      */
217     public SAMLObjectBuilder<SubjectConfirmation> getSubjectConfirmationBuilder() {
218         return subjectConfirmationBuilder;
219     }
220
221     /**
222      * Convenience method for getting the SAML 1 ConfirmationMethod builder.
223      * 
224      * @return SAML 1 ConfirmationMethod builder
225      */
226     public SAMLObjectBuilder<ConfirmationMethod> getConfirmationMethodBuilder() {
227         return confirmationMethodBuilder;
228     }
229
230     /**
231      * Convenience method for getting the SAML 1 Subject builder.
232      * 
233      * @return SAML 1 Subject builder
234      */
235     public SAMLObjectBuilder<Subject> getSubjectBuilder() {
236         return subjectBuilder;
237     }
238
239     /**
240      * Convenience method for getting the SAML 1 Status builder.
241      * 
242      * @return SAML 1 Status builder
243      */
244     public SAMLObjectBuilder<Status> getStatusBuilder() {
245         return statusBuilder;
246     }
247
248     /**
249      * Convenience method for getting the SAML 1 StatusCode builder.
250      * 
251      * @return SAML 2 StatusCode builder
252      */
253     public SAMLObjectBuilder<StatusCode> getStatusCodeBuilder() {
254         return statusCodeBuilder;
255     }
256
257     /**
258      * Convenience method for getting the SAML 1 StatusMessage builder.
259      * 
260      * @return SAML StatusMessage builder
261      */
262     public SAMLObjectBuilder<StatusMessage> getStatusMessageBuilder() {
263         return statusMessageBuilder;
264     }
265
266     /**
267      * Checks that the SAML major version for a request is 1.
268      * 
269      * @param requestContext current request context containing the SAML message
270      * 
271      * @throws ProfileException thrown if the major version of the SAML request is not 1
272      */
273     protected void checkSamlVersion(SAML1ProfileRequestContext requestContext) throws ProfileException {
274         SAMLObject samlObject = requestContext.getSamlRequest();
275
276         if (samlObject instanceof RequestAbstractType) {
277             RequestAbstractType request = (RequestAbstractType) samlObject;
278             if (request.getMajorVersion() < 1) {
279                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_LOW,
280                         null));
281                 throw new ProfileException("SAML request major version too low");
282             } else if (request.getMajorVersion() > 1) {
283                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_HIGH,
284                         null));
285                 throw new ProfileException("SAML request major version too low");
286             }
287         }
288     }
289
290     /**
291      * Builds a response to the attribute query within the request context.
292      * 
293      * @param requestContext current request context
294      * @param statements the statements to include in the response
295      * 
296      * @return the built response
297      * 
298      * @throws ProfileException thrown if there is a problem creating the SAML response
299      */
300     protected Response buildResponse(SAML1ProfileRequestContext requestContext, List<Statement> statements)
301             throws ProfileException {
302
303         DateTime issueInstant = new DateTime();
304
305         // create the assertion and add the attribute statement
306         Assertion assertion = buildAssertion(requestContext, issueInstant);
307         if (statements != null) {
308             assertion.getStatements().addAll(statements);
309         }
310
311         // create the SAML response and add the assertion
312         Response samlResponse = getResponseBuilder().buildObject();
313         samlResponse.setIssueInstant(issueInstant);
314         populateStatusResponse(requestContext, samlResponse);
315
316         samlResponse.getAssertions().add(assertion);
317
318         // sign the assertion if it should be signed
319         signAssertion(requestContext, assertion);
320
321         Status status = buildStatus(StatusCode.SUCCESS, null, null);
322         samlResponse.setStatus(status);
323
324         return samlResponse;
325     }
326
327     /**
328      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
329      * 
330      * @param requestContext current request context
331      * @param issueInstant time to use as assertion issue instant
332      * 
333      * @return the built assertion
334      */
335     protected Assertion buildAssertion(SAML1ProfileRequestContext requestContext, DateTime issueInstant) {
336         Assertion assertion = getAssertionBuilder().buildObject();
337         assertion.setID(getIdGenerator().generateIdentifier());
338         assertion.setIssueInstant(issueInstant);
339         assertion.setVersion(SAMLVersion.VERSION_11);
340         assertion.setIssuer(requestContext.getAssertingPartyId());
341
342         Conditions conditions = buildConditions(requestContext, issueInstant);
343         assertion.setConditions(conditions);
344
345         return assertion;
346     }
347
348     /**
349      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
350      * restrictions, and proxy restrictions.
351      * 
352      * @param requestContext current request context
353      * @param issueInstant timestamp the assertion was created
354      * 
355      * @return constructed conditions
356      */
357     protected Conditions buildConditions(SAML1ProfileRequestContext requestContext, DateTime issueInstant) {
358         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
359
360         Conditions conditions = getConditionsBuilder().buildObject();
361         conditions.setNotBefore(issueInstant);
362         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
363
364         Collection<String> audiences;
365
366         // add audience restrictions
367         audiences = profileConfig.getAssertionAudiences();
368         if (audiences != null && audiences.size() > 0) {
369             AudienceRestrictionCondition audienceRestriction = getAudienceRestrictionConditionBuilder().buildObject();
370             for (String audienceUri : audiences) {
371                 Audience audience = getAudienceBuilder().buildObject();
372                 audience.setUri(audienceUri);
373                 audienceRestriction.getAudiences().add(audience);
374             }
375             conditions.getAudienceRestrictionConditions().add(audienceRestriction);
376         }
377
378         return conditions;
379     }
380
381     /**
382      * Builds the SAML subject for the user for the service provider.
383      * 
384      * @param requestContext current request context
385      * @param confirmationMethod subject confirmation method used for the subject
386      * 
387      * @return SAML subject for the user for the service provider
388      * 
389      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
390      *             name ID attribute or because there are no supported name formats
391      */
392     protected Subject buildSubject(SAML1ProfileRequestContext requestContext, String confirmationMethod)
393             throws ProfileException {
394         NameIdentifier nameID = buildNameId(requestContext);
395         requestContext.setSubjectNameID(nameID);
396
397         ConfirmationMethod method = getConfirmationMethodBuilder().buildObject();
398         method.setConfirmationMethod(confirmationMethod);
399
400         SubjectConfirmation subjectConfirmation = getSubjectConfirmationBuilder().buildObject();
401         subjectConfirmation.getConfirmationMethods().add(method);
402
403         Subject subject = getSubjectBuilder().buildObject();
404         subject.setNameIdentifier(nameID);
405         subject.setSubjectConfirmation(subjectConfirmation);
406
407         return subject;
408     }
409
410     /**
411      * Builds a NameIdentifier appropriate for this request. NameIdentifier are built by inspecting the SAML request and
412      * metadata, picking a name format that was requested by the relying party or is mutually supported by both the
413      * relying party and asserting party as described in their metadata entries. Once a set of supported name formats is
414      * determined the principals attributes are inspected for an attribute supported an attribute encoder whose category
415      * is one of the supported name formats.
416      * 
417      * @param requestContext current request context
418      * 
419      * @return the NameIdentifier appropriate for this request
420      * 
421      * @throws ProfileException thrown if a NameIdentifier can not be created either because there was a problem
422      *             encoding the name ID attribute or because there are no supported name formats
423      */
424     protected NameIdentifier buildNameId(SAML1ProfileRequestContext requestContext) throws ProfileException {
425         if (log.isDebugEnabled()) {
426             log.debug("Building assertion NameIdentifier to relying party " + requestContext.getRelyingPartyId()
427                     + " for principal " + requestContext.getPrincipalName());
428         }
429         Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
430         List<String> supportedNameFormats = getNameFormats(requestContext);
431
432         if (log.isDebugEnabled()) {
433             log.debug("Supported name formats: " + supportedNameFormats);
434         }
435
436         if (principalAttributes == null || supportedNameFormats == null) {
437             log.error("No attributes for principal " + requestContext.getPrincipalName()
438                     + " support constructions of NameIdentifier");
439             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
440                     "Unable to construct NameIdentifier"));
441             throw new ProfileException("No principal attributes support NameIdentifier construction");
442         }
443
444         try {
445             SAML1NameIdentifierEncoder nameIdEncoder;
446
447             for (BaseAttribute<?> attribute : principalAttributes.values()) {
448                 for (AttributeEncoder encoder : attribute.getEncoders()) {
449                     if (encoder instanceof SAML1NameIdentifierEncoder) {
450                         nameIdEncoder = (SAML1NameIdentifierEncoder) encoder;
451                         if (supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
452                             if (log.isDebugEnabled()) {
453                                 log.debug("Using attribute " + attribute.getId() + " suppoting name format "
454                                         + nameIdEncoder.getNameFormat()
455                                         + " to create the NameIdentifier for principal "
456                                         + requestContext.getPrincipalName());
457                             }
458                             return nameIdEncoder.encode(attribute);
459                         }
460                     }
461                 }
462             }
463             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
464             "Unable to construct NameIdentifier"));
465             throw new ProfileException("No principal attribute supported encoding into the a supported name ID format.");
466         } catch (AttributeEncodingException e) {
467             log.error("Unable to construct NameIdentifier", e);
468             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
469                     "Unable to construct NameIdentifier"));
470             throw new ProfileException("Unable to encode NameIdentifier attribute", e);
471         }
472
473     }
474
475     /**
476      * Gets the NameIdentifier format to use when creating NameIdentifiers for the relying party.
477      * 
478      * @param requestContext current request context
479      * 
480      * @return list of formats that may be used with the relying party
481      * 
482      * @throws ProfileException thrown if there is a problem determing the NameIdentifier format to use
483      */
484     protected List<String> getNameFormats(SAML1ProfileRequestContext requestContext) throws ProfileException {
485         ArrayList<String> nameFormats = new ArrayList<String>();
486
487         RoleDescriptor assertingPartyRole = requestContext.getAssertingPartyRoleMetadata();
488         List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
489
490         if (nameFormats.isEmpty()) {
491             RoleDescriptor relyingPartyRole = requestContext.getRelyingPartyRoleMetadata();
492             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
493
494             assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
495             nameFormats.addAll(assertingPartySupportedFormats);
496         }
497         if (nameFormats.isEmpty()) {
498             nameFormats.add("urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified");
499         }
500
501         return nameFormats;
502     }
503
504     /**
505      * Gets the list of NameIdentifier formats supported for a given role.
506      * 
507      * @param role the role to get the list of supported NameIdentifier formats
508      * 
509      * @return list of supported NameIdentifier formats
510      */
511     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
512         List<NameIDFormat> nameIDFormats = null;
513
514         if (role instanceof SSODescriptor) {
515             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
516         } else if (role instanceof AuthnAuthorityDescriptor) {
517             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
518         } else if (role instanceof PDPDescriptor) {
519             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
520         } else if (role instanceof AttributeAuthorityDescriptor) {
521             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
522         }
523
524         ArrayList<String> supportedFormats = new ArrayList<String>();
525         if (nameIDFormats != null) {
526             for (NameIDFormat format : nameIDFormats) {
527                 supportedFormats.add(format.getFormat());
528             }
529         }
530
531         return supportedFormats;
532     }
533
534     /**
535      * Constructs an SAML response message carrying a request error.
536      * 
537      * @param requestContext current request context containing the failure status
538      * 
539      * @return the constructed error response
540      */
541     protected Response buildErrorResponse(SAML1ProfileRequestContext requestContext) {
542         Response samlResponse = getResponseBuilder().buildObject();
543         samlResponse.setIssueInstant(new DateTime());
544         populateStatusResponse(requestContext, samlResponse);
545
546         samlResponse.setStatus(requestContext.getFailureStatus());
547
548         return samlResponse;
549     }
550
551     /**
552      * Populates the response's id, in response to, issue instant, version, and issuer properties.
553      * 
554      * @param requestContext current request context
555      * @param response the response to populate
556      */
557     protected void populateStatusResponse(SAML1ProfileRequestContext requestContext, ResponseAbstractType response) {
558         response.setID(getIdGenerator().generateIdentifier());
559
560         SAMLObject samlMessage = requestContext.getSamlRequest();
561         if (samlMessage != null && samlMessage instanceof RequestAbstractType) {
562             response.setInResponseTo(((RequestAbstractType) samlMessage).getID());
563         }
564         response.setVersion(SAMLVersion.VERSION_11);
565     }
566
567     /**
568      * Build a status message, with an optional second-level failure message.
569      * 
570      * @param topLevelCode top-level status code
571      * @param secondLevelCode second-level status code
572      * @param failureMessage An optional second-level failure message
573      * 
574      * @return a Status object.
575      */
576     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
577         Status status = getStatusBuilder().buildObject();
578
579         StatusCode statusCode = getStatusCodeBuilder().buildObject();
580         statusCode.setValue(topLevelCode);
581         status.setStatusCode(statusCode);
582
583         if (secondLevelCode != null) {
584             StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
585             secondLevelStatusCode.setValue(secondLevelCode);
586             statusCode.setStatusCode(secondLevelStatusCode);
587         }
588
589         if (failureMessage != null) {
590             StatusMessage msg = getStatusMessageBuilder().buildObject();
591             msg.setMessage(failureMessage);
592             status.setStatusMessage(msg);
593         }
594
595         return status;
596     }
597
598     /**
599      * Executes a query for attributes and builds a SAML attribute statement from the results.
600      * 
601      * @param requestContext current request context
602      * @param subjectConfMethod subject confirmation method
603      * 
604      * @return attribute statement resulting from the query
605      * 
606      * @throws ProfileException thrown if there is a problem making the query
607      */
608     protected AttributeStatement buildAttributeStatement(SAML1ProfileRequestContext requestContext,
609             String subjectConfMethod) throws ProfileException {
610
611         if (log.isDebugEnabled()) {
612             log.debug("Creating attribute statement in response to SAML request from relying party "
613                     + requestContext.getRelyingPartyId());
614         }
615
616         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
617         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
618
619         try {
620             if (log.isDebugEnabled()) {
621                 log.debug("Resolving attributes for principal " + requestContext.getPrincipalName()
622                         + " of SAML request from relying party " + requestContext.getRelyingPartyId());
623             }
624             Map<String, BaseAttribute> principalAttributes = attributeAuthority
625                     .getAttributes(buildAttributeRequestContext(requestContext));
626
627             requestContext.setPrincipalAttributes(principalAttributes);
628
629             AttributeStatement statment;
630             if (requestContext.getSamlRequest() instanceof AttributeQuery) {
631                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext.getSamlRequest(),
632                         principalAttributes.values());
633             } else {
634                 statment = attributeAuthority.buildAttributeStatement(null, principalAttributes.values());
635             }
636
637             Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
638             statment.setSubject(statementSubject);
639
640             return statment;
641         } catch (AttributeRequestException e) {
642             log.error("Error resolving attributes for SAML request from relying party "
643                     + requestContext.getRelyingPartyId(), e);
644             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
645             throw new ProfileException("Error resolving attributes for SAML request from relying party "
646                     + requestContext.getRelyingPartyId(), e);
647         }
648     }
649
650     /**
651      * Resolves the principal name of the subject of the request.
652      * 
653      * @param requestContext current request context
654      * 
655      * @throws ProfileException thrown if the principal name can not be resolved
656      */
657     protected void resolvePrincipal(SAML1ProfileRequestContext requestContext) throws ProfileException {
658         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
659         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
660
661         if (log.isDebugEnabled()) {
662             log.debug("Resolving principal name for subject of SAML request from relying party "
663                     + requestContext.getRelyingPartyId());
664         }
665
666         try {
667             String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
668             requestContext.setPrincipalName(principal);
669         } catch (AttributeRequestException e) {
670             log.error("Error resolving attributes for SAML request from relying party "
671                     + requestContext.getRelyingPartyId(), e);
672             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
673                     "Error resolving principal"));
674             throw new ProfileException("Error resolving attributes for SAML request from relying party "
675                     + requestContext.getRelyingPartyId(), e);
676         }
677     }
678
679     /**
680      * Creates an attribute query context from the current profile request context.
681      * 
682      * @param requestContext current profile request
683      * 
684      * @return created query context
685      */
686     protected ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> buildAttributeRequestContext(
687             SAML1ProfileRequestContext requestContext) {
688
689         ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> queryContext;
690
691         if (requestContext.getSamlRequest() instanceof Request) {
692             Request samlRequest = (Request) requestContext.getSamlRequest();
693             queryContext = new ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery>(
694                     getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), samlRequest
695                             .getAttributeQuery());
696         } else {
697             queryContext = new ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery>(
698                     getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), null);
699         }
700
701         queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
702         queryContext.setPrincipalName(requestContext.getPrincipalName());
703         queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
704         queryContext.setRequest(requestContext.getProfileRequest());
705
706         Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
707         if (userSession != null) {
708             queryContext.setUserSession(userSession);
709             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
710                     requestContext.getRelyingPartyId());
711             if (serviceInfo != null) {
712                 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
713
714                 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
715                 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
716             }
717         }
718
719         return queryContext;
720     }
721
722     /**
723      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
724      * signing credentials.
725      * 
726      * @param requestContext current request context
727      * @param assertion assertion to sign
728      * 
729      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
730      *             required, if a signing credential is not configured
731      */
732     protected void signAssertion(SAML1ProfileRequestContext requestContext, Assertion assertion)
733             throws ProfileException {
734         if (log.isDebugEnabled()) {
735             log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
736                     + " should be signed");
737         }
738
739         boolean signAssertion = false;
740
741         RoleDescriptor relyingPartyRole = requestContext.getRelyingPartyRoleMetadata();
742         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
743
744         if (relyingPartyRole instanceof SPSSODescriptor) {
745             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
746             if (ssoDescriptor.getWantAssertionsSigned() != null) {
747                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
748                 if (log.isDebugEnabled()) {
749                     log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
750                             + " indicates to sign assertions: " + signAssertion);
751                 }
752             }
753         } else if (profileConfig.getSignAssertions()) {
754             signAssertion = true;
755             log.debug("IdP relying party configuration "
756                     + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
757                     + " indicates to sign assertions: " + signAssertion);
758         }
759
760         if (!signAssertion) {
761             return;
762         }
763
764         if (log.isDebugEnabled()) {
765             log.debug("Determining signing credntial for assertion to relying party "
766                     + requestContext.getRelyingPartyId());
767         }
768         Credential signatureCredential = profileConfig.getSigningCredential();
769         if (signatureCredential == null) {
770             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
771         }
772
773         if (signatureCredential == null) {
774             throw new ProfileException("No signing credential is specified for relying party configuration "
775                     + requestContext.getRelyingPartyConfiguration().getProviderId()
776                     + " or it's SAML2 attribute query profile configuration");
777         }
778
779         if (log.isDebugEnabled()) {
780             log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
781         }
782         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
783         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
784         signature.getContentReferences().add(contentRef);
785         assertion.setSignature(signature);
786
787         Signer.signObject(signature);
788     }
789
790     /**
791      * Writes an aduit log entry indicating the successful response to the attribute request.
792      * 
793      * @param context current request context
794      */
795     protected void writeAuditLogEntry(SAML1ProfileRequestContext context) {
796         AuditLogEntry auditLogEntry = new AuditLogEntry();
797         auditLogEntry.setMessageProfile(getProfileId());
798         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
799         auditLogEntry.setPrincipalName(context.getPrincipalName());
800         auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
801         auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
802         if(context.getMessageDecoder().getBindingURI() != null){
803             auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
804         }
805         auditLogEntry.setRequestId(null);
806         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
807         auditLogEntry.setResponseId(context.getSamlResponse().getID());
808         getAduitLog().log(Level.CRITICAL, auditLogEntry);
809     }
810
811     /**
812      * Contextual object used to accumlate information as profile requests are being processed.
813      * 
814      * @param <RequestType> type of SAML 1 request
815      * @param <ResponseType> type of SAML 1 response
816      * @param <ProfileConfigurationType> configuration type for this profile
817      */
818     protected class SAML1ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends ResponseAbstractType, ProfileConfigurationType extends AbstractSAML1ProfileConfiguration>
819             extends SAMLProfileRequestContext {
820
821         /** SAML request message. */
822         private RequestType samlRequest;
823
824         /** SAML response message. */
825         private ResponseType samlResponse;
826
827         /** Request profile configuration. */
828         private ProfileConfigurationType profileConfiguration;
829
830         /** The NameIdentifier of the subject of this request. */
831         private NameIdentifier subjectNameIdentifier;
832
833         /** The request failure status. */
834         private Status failureStatus;
835
836         /**
837          * Constructor.
838          * 
839          * @param request current profile request
840          * @param response current profile response
841          */
842         public SAML1ProfileRequestContext(ProfileRequest<ServletRequest> request,
843                 ProfileResponse<ServletResponse> response) {
844             super(request, response);
845         }
846
847         /**
848          * Gets the NameIdentifier of the subject of this request.
849          * 
850          * @return NameIdentifier of the subject of this request
851          */
852         public NameIdentifier getSubjectNameID() {
853             return subjectNameIdentifier;
854         }
855
856         /**
857          * Sets the NameIdentifier of the subject of this request.
858          * 
859          * @param id NameIdentifier of the subject of this request
860          */
861         public void setSubjectNameID(NameIdentifier id) {
862             subjectNameIdentifier = id;
863         }
864
865         /**
866          * Gets the profile configuration for this request.
867          * 
868          * @return profile configuration for this request
869          */
870         public ProfileConfigurationType getProfileConfiguration() {
871             return profileConfiguration;
872         }
873
874         /**
875          * Sets the profile configuration for this request.
876          * 
877          * @param configuration profile configuration for this request
878          */
879         public void setProfileConfiguration(ProfileConfigurationType configuration) {
880             profileConfiguration = configuration;
881         }
882
883         /**
884          * Gets the SAML request message.
885          * 
886          * @return SAML request message
887          */
888         public RequestType getSamlRequest() {
889             return samlRequest;
890         }
891
892         /**
893          * Sets the SAML request message.
894          * 
895          * @param request SAML request message
896          */
897         public void setSamlRequest(RequestType request) {
898             samlRequest = request;
899         }
900
901         /**
902          * Gets the SAML response message.
903          * 
904          * @return SAML response message
905          */
906         public ResponseType getSamlResponse() {
907             return samlResponse;
908         }
909
910         /**
911          * Sets the SAML response message.
912          * 
913          * @param response SAML response message
914          */
915         public void setSamlResponse(ResponseType response) {
916             samlResponse = response;
917         }
918
919         /**
920          * Gets the status reflecting a request failure.
921          * 
922          * @return status reflecting a request failure
923          */
924         public Status getFailureStatus() {
925             return failureStatus;
926         }
927
928         /**
929          * Sets the status reflecting a request failure.
930          * 
931          * @param status status reflecting a request failure
932          */
933         public void setFailureStatus(Status status) {
934             failureStatus = status;
935         }
936     }
937 }