Add a few logging messages
[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.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                 log.error("Unable to construct NameIdentifier", e);
453                 requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
454                         "Unable to construct NameIdentifier"));
455                 throw new ProfileException("Unable to encode NameIdentifier attribute", e);
456             }
457         }
458
459         log.error("No attributes for principal " + requestContext.getPrincipalName() 
460                 + " support constructions of NameIdentifier");
461         requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Unable to construct NameIdentifier"));
462         throw new ProfileException("No principal attributes support NameIdentifier construction");
463     }
464
465     /**
466      * Gets the NameIdentifier format to use when creating NameIdentifiers for the relying party.
467      * 
468      * @param requestContext current request context
469      * 
470      * @return list of formats that may be used with the relying party
471      * 
472      * @throws ProfileException thrown if there is a problem determing the NameIdentifier format to use
473      */
474     protected List<String> getNameFormats(SAML1ProfileRequestContext requestContext) throws ProfileException {
475         ArrayList<String> nameFormats = new ArrayList<String>();
476
477         RoleDescriptor assertingPartyRole = requestContext.getAssertingPartyRoleMetadata();
478         List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
479
480         if (nameFormats.isEmpty()) {
481             RoleDescriptor relyingPartyRole = requestContext.getRelyingPartyRoleMetadata();
482             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
483
484             assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
485             nameFormats.addAll(assertingPartySupportedFormats);
486         }
487         if (nameFormats.isEmpty()) {
488             nameFormats.add("urn:oasis:names:tc:SAML:1.0:nameid-format:unspecified");
489         }
490
491         return nameFormats;
492     }
493
494     /**
495      * Gets the list of NameIdentifier formats supported for a given role.
496      * 
497      * @param role the role to get the list of supported NameIdentifier formats
498      * 
499      * @return list of supported NameIdentifier formats
500      */
501     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
502         List<NameIDFormat> nameIDFormats = null;
503
504         if (role instanceof SSODescriptor) {
505             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
506         } else if (role instanceof AuthnAuthorityDescriptor) {
507             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
508         } else if (role instanceof PDPDescriptor) {
509             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
510         } else if (role instanceof AttributeAuthorityDescriptor) {
511             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
512         }
513
514         ArrayList<String> supportedFormats = new ArrayList<String>();
515         if (nameIDFormats != null) {
516             for (NameIDFormat format : nameIDFormats) {
517                 supportedFormats.add(format.getFormat());
518             }
519         }
520
521         return supportedFormats;
522     }
523
524     /**
525      * Constructs an SAML response message carrying a request error.
526      * 
527      * @param requestContext current request context containing the failure status
528      * 
529      * @return the constructed error response
530      */
531     protected Response buildErrorResponse(SAML1ProfileRequestContext requestContext) {
532         Response samlResponse = getResponseBuilder().buildObject();
533         samlResponse.setIssueInstant(new DateTime());
534         populateStatusResponse(requestContext, samlResponse);
535
536         samlResponse.setStatus(requestContext.getFailureStatus());
537
538         return samlResponse;
539     }
540
541     /**
542      * Populates the response's id, in response to, issue instant, version, and issuer properties.
543      * 
544      * @param requestContext current request context
545      * @param response the response to populate
546      */
547     protected void populateStatusResponse(SAML1ProfileRequestContext requestContext, ResponseAbstractType response) {
548         response.setID(getIdGenerator().generateIdentifier());
549
550         SAMLObject samlMessage = requestContext.getSamlRequest();
551         if (samlMessage != null && samlMessage instanceof RequestAbstractType) {
552             response.setInResponseTo(((RequestAbstractType) samlMessage).getID());
553         }
554         response.setVersion(SAMLVersion.VERSION_11);
555     }
556
557     /**
558      * Build a status message, with an optional second-level failure message.
559      * 
560      * @param topLevelCode top-level status code
561      * @param secondLevelCode second-level status code
562      * @param failureMessage An optional second-level failure message
563      * 
564      * @return a Status object.
565      */
566     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
567         Status status = getStatusBuilder().buildObject();
568
569         StatusCode statusCode = getStatusCodeBuilder().buildObject();
570         statusCode.setValue(topLevelCode);
571         status.setStatusCode(statusCode);
572
573         if (secondLevelCode != null) {
574             StatusCode secondLevelStatusCode = getStatusCodeBuilder().buildObject();
575             secondLevelStatusCode.setValue(secondLevelCode);
576             statusCode.setStatusCode(secondLevelStatusCode);
577         }
578
579         if (failureMessage != null) {
580             StatusMessage msg = getStatusMessageBuilder().buildObject();
581             msg.setMessage(failureMessage);
582             status.setStatusMessage(msg);
583         }
584
585         return status;
586     }
587
588     /**
589      * Executes a query for attributes and builds a SAML attribute statement from the results.
590      * 
591      * @param requestContext current request context
592      * @param subjectConfMethod subject confirmation method
593      * 
594      * @return attribute statement resulting from the query
595      * 
596      * @throws ProfileException thrown if there is a problem making the query
597      */
598     protected AttributeStatement buildAttributeStatement(SAML1ProfileRequestContext requestContext,
599             String subjectConfMethod) throws ProfileException {
600
601         if (log.isDebugEnabled()) {
602             log.debug("Creating attribute statement in response to SAML request from relying party "
603                     + requestContext.getRelyingPartyId());
604         }
605
606         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
607         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
608
609         try {
610             if (log.isDebugEnabled()) {
611                 log.debug("Resolving attributes for principal " + requestContext.getPrincipalName()
612                         + " of SAML request from relying party " + requestContext.getRelyingPartyId());
613             }
614             Map<String, BaseAttribute> principalAttributes = attributeAuthority
615                     .getAttributes(buildAttributeRequestContext(requestContext));
616
617             requestContext.setPrincipalAttributes(principalAttributes);
618
619             AttributeStatement statment;
620             if (requestContext.getSamlRequest() instanceof AttributeQuery) {
621                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext.getSamlRequest(),
622                         principalAttributes.values());
623             } else {
624                 statment = attributeAuthority.buildAttributeStatement(null, principalAttributes.values());
625             }
626
627             Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
628             statment.setSubject(statementSubject);
629
630             return statment;
631         } catch (AttributeRequestException e) {
632             log.error("Error resolving attributes for SAML request from relying party "
633                     + requestContext.getRelyingPartyId(), e);
634             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
635             throw new ProfileException("Error resolving attributes for SAML request from relying party "
636                     + requestContext.getRelyingPartyId(), e);
637         }
638     }
639
640     /**
641      * Resolves the principal name of the subject of the request.
642      * 
643      * @param requestContext current request context
644      * 
645      * @throws ProfileException thrown if the principal name can not be resolved
646      */
647     protected void resolvePrincipal(SAML1ProfileRequestContext requestContext) throws ProfileException {
648         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
649         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
650
651         if (log.isDebugEnabled()) {
652             log.debug("Resolving principal name for subject of SAML request from relying party "
653                     + requestContext.getRelyingPartyId());
654         }
655
656         try {
657             String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
658             requestContext.setPrincipalName(principal);
659         } catch (AttributeRequestException e) {
660             log.error("Error resolving attributes for SAML request from relying party "
661                     + requestContext.getRelyingPartyId(), e);
662             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
663                     "Error resolving principal"));
664             throw new ProfileException("Error resolving attributes for SAML request from relying party "
665                     + requestContext.getRelyingPartyId(), e);
666         }
667     }
668
669     /**
670      * Creates an attribute query context from the current profile request context.
671      * 
672      * @param requestContext current profile request
673      * 
674      * @return created query context
675      */
676     protected ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> buildAttributeRequestContext(
677             SAML1ProfileRequestContext requestContext) {
678
679         ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery> queryContext;
680
681         if (requestContext.getSamlRequest() instanceof Request) {
682             Request samlRequest = (Request) requestContext.getSamlRequest();
683             queryContext = new ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery>(
684                     getMetadataProvider(), requestContext.getRelyingPartyConfiguration(),
685                     samlRequest.getAttributeQuery());
686         } else {
687             queryContext = new ShibbolethSAMLAttributeRequestContext<NameIdentifier, AttributeQuery>(
688                     getMetadataProvider(), requestContext.getRelyingPartyConfiguration(), null);
689         }
690
691         queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
692         queryContext.setPrincipalName(requestContext.getPrincipalName());
693         queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
694         queryContext.setRequest(requestContext.getProfileRequest());
695
696         Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
697         if (userSession != null) {
698             queryContext.setUserSession(userSession);
699             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
700                     requestContext.getRelyingPartyId());
701             if (serviceInfo != null) {
702                 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
703
704                 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
705                 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
706             }
707         }
708
709         return queryContext;
710     }
711
712     /**
713      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
714      * signing credentials.
715      * 
716      * @param requestContext current request context
717      * @param assertion assertion to sign
718      * 
719      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
720      *             required, if a signing credential is not configured
721      */
722     protected void signAssertion(SAML1ProfileRequestContext requestContext, Assertion assertion)
723             throws ProfileException {
724         if (log.isDebugEnabled()) {
725             log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
726                     + " should be signed");
727         }
728
729         boolean signAssertion = false;
730
731         RoleDescriptor relyingPartyRole = requestContext.getRelyingPartyRoleMetadata();
732         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
733
734         if (relyingPartyRole instanceof SPSSODescriptor) {
735             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
736             if (ssoDescriptor.getWantAssertionsSigned() != null) {
737                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
738                 if (log.isDebugEnabled()) {
739                     log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
740                             + " indicates to sign assertions: " + signAssertion);
741                 }
742             }
743         } else if (profileConfig.getSignAssertions()) {
744             signAssertion = true;
745             log.debug("IdP relying party configuration "
746                     + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
747                     + " indicates to sign assertions: " + signAssertion);
748         }
749
750         if (!signAssertion) {
751             return;
752         }
753
754         if (log.isDebugEnabled()) {
755             log.debug("Determining signing credntial for assertion to relying party "
756                     + requestContext.getRelyingPartyId());
757         }
758         Credential signatureCredential = profileConfig.getSigningCredential();
759         if (signatureCredential == null) {
760             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
761         }
762
763         if (signatureCredential == null) {
764             throw new ProfileException("No signing credential is specified for relying party configuration "
765                     + requestContext.getRelyingPartyConfiguration().getProviderId()
766                     + " or it's SAML2 attribute query profile configuration");
767         }
768
769         if (log.isDebugEnabled()) {
770             log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
771         }
772         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
773         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
774         signature.getContentReferences().add(contentRef);
775         assertion.setSignature(signature);
776
777         Signer.signObject(signature);
778     }
779
780     /**
781      * Writes an aduit log entry indicating the successful response to the attribute request.
782      * 
783      * @param context current request context
784      */
785     protected void writeAuditLogEntry(SAML1ProfileRequestContext context) {
786         AuditLogEntry auditLogEntry = new AuditLogEntry();
787         auditLogEntry.setMessageProfile(getProfileId());
788         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
789         auditLogEntry.setPrincipalName(context.getPrincipalName());
790         auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
791         auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
792         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
793         auditLogEntry.setRequestId(null);
794         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
795         auditLogEntry.setResponseId(context.getSamlResponse().getID());
796         getAduitLog().log(Level.CRITICAL, auditLogEntry);
797     }
798
799     /**
800      * Contextual object used to accumlate information as profile requests are being processed.
801      * 
802      * @param <RequestType> type of SAML 1 request
803      * @param <ResponseType> type of SAML 1 response
804      * @param <ProfileConfigurationType> configuration type for this profile
805      */
806     protected class SAML1ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends ResponseAbstractType, ProfileConfigurationType extends AbstractSAML1ProfileConfiguration>
807             extends SAMLProfileRequestContext {
808
809         /** SAML request message. */
810         private RequestType samlRequest;
811
812         /** SAML response message. */
813         private ResponseType samlResponse;
814
815         /** Request profile configuration. */
816         private ProfileConfigurationType profileConfiguration;
817
818         /** The NameIdentifier of the subject of this request. */
819         private NameIdentifier subjectNameIdentifier;
820
821         /** The request failure status. */
822         private Status failureStatus;
823
824         /**
825          * Constructor.
826          * 
827          * @param request current profile request
828          * @param response current profile response
829          */
830         public SAML1ProfileRequestContext(ProfileRequest<ServletRequest> request,
831                 ProfileResponse<ServletResponse> response) {
832             super(request, response);
833         }
834
835         /**
836          * Gets the NameIdentifier of the subject of this request.
837          * 
838          * @return NameIdentifier of the subject of this request
839          */
840         public NameIdentifier getSubjectNameID() {
841             return subjectNameIdentifier;
842         }
843
844         /**
845          * Sets the NameIdentifier of the subject of this request.
846          * 
847          * @param id NameIdentifier of the subject of this request
848          */
849         public void setSubjectNameID(NameIdentifier id) {
850             subjectNameIdentifier = id;
851         }
852
853         /**
854          * Gets the profile configuration for this request.
855          * 
856          * @return profile configuration for this request
857          */
858         public ProfileConfigurationType getProfileConfiguration() {
859             return profileConfiguration;
860         }
861
862         /**
863          * Sets the profile configuration for this request.
864          * 
865          * @param configuration profile configuration for this request
866          */
867         public void setProfileConfiguration(ProfileConfigurationType configuration) {
868             profileConfiguration = configuration;
869         }
870
871         /**
872          * Gets the SAML request message.
873          * 
874          * @return SAML request message
875          */
876         public RequestType getSamlRequest() {
877             return samlRequest;
878         }
879
880         /**
881          * Sets the SAML request message.
882          * 
883          * @param request SAML request message
884          */
885         public void setSamlRequest(RequestType request) {
886             samlRequest = request;
887         }
888
889         /**
890          * Gets the SAML response message.
891          * 
892          * @return SAML response message
893          */
894         public ResponseType getSamlResponse() {
895             return samlResponse;
896         }
897
898         /**
899          * Sets the SAML response message.
900          * 
901          * @param response SAML response message
902          */
903         public void setSamlResponse(ResponseType response) {
904             samlResponse = response;
905         }
906
907         /**
908          * Gets the status reflecting a request failure.
909          * 
910          * @return status reflecting a request failure
911          */
912         public Status getFailureStatus() {
913             return failureStatus;
914         }
915
916         /**
917          * Sets the status reflecting a request failure.
918          * 
919          * @param status status reflecting a request failure
920          */
921         public void setFailureStatus(Status status) {
922             failureStatus = status;
923         }
924     }
925 }