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