Merge remote branch 'tags/2.3.4'
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / profile / saml1 / AbstractSAML1ProfileHandler.java
1 /*
2  * Licensed to the University Corporation for Advanced Internet Development, 
3  * Inc. (UCAID) under one or more contributor license agreements.  See the 
4  * NOTICE file distributed with this work for additional information regarding
5  * copyright ownership. The UCAID licenses this file to You under the Apache 
6  * License, Version 2.0 (the "License"); you may not use this file except in 
7  * compliance with the License.  You may obtain a copy of the License at
8  *
9  *    http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
19
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.xml.namespace.QName;
25
26 import org.joda.time.DateTime;
27 import org.opensaml.Configuration;
28 import org.opensaml.common.SAMLObject;
29 import org.opensaml.common.SAMLObjectBuilder;
30 import org.opensaml.common.SAMLVersion;
31 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
32 import org.opensaml.saml1.core.Assertion;
33 import org.opensaml.saml1.core.AttributeQuery;
34 import org.opensaml.saml1.core.AttributeStatement;
35 import org.opensaml.saml1.core.Audience;
36 import org.opensaml.saml1.core.AudienceRestrictionCondition;
37 import org.opensaml.saml1.core.Conditions;
38 import org.opensaml.saml1.core.ConfirmationMethod;
39 import org.opensaml.saml1.core.NameIdentifier;
40 import org.opensaml.saml1.core.RequestAbstractType;
41 import org.opensaml.saml1.core.Response;
42 import org.opensaml.saml1.core.ResponseAbstractType;
43 import org.opensaml.saml1.core.Statement;
44 import org.opensaml.saml1.core.Status;
45 import org.opensaml.saml1.core.StatusCode;
46 import org.opensaml.saml1.core.StatusMessage;
47 import org.opensaml.saml1.core.Subject;
48 import org.opensaml.saml1.core.SubjectConfirmation;
49 import org.opensaml.saml2.metadata.SPSSODescriptor;
50 import org.opensaml.ws.message.encoder.MessageEncodingException;
51 import org.opensaml.xml.XMLObjectBuilder;
52 import org.opensaml.xml.io.Marshaller;
53 import org.opensaml.xml.io.MarshallingException;
54 import org.opensaml.xml.security.SecurityException;
55 import org.opensaml.xml.security.SecurityHelper;
56 import org.opensaml.xml.security.credential.Credential;
57 import org.opensaml.xml.signature.Signature;
58 import org.opensaml.xml.signature.SignatureException;
59 import org.opensaml.xml.signature.Signer;
60 import org.opensaml.xml.util.Pair;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
63
64 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
65 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
66 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
67 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML1NameIdentifierEncoder;
68 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
69 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
70 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
71 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
72 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
73 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AbstractSAML1ProfileConfiguration;
74 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
75 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
76 import edu.internet2.middleware.shibboleth.idp.session.Session;
77
78 /** Common implementation details for profile handlers. */
79 public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHandler {
80
81     /** SAML Version for this profile handler. */
82     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_11;
83
84     /** Class logger. */
85     private static Logger log = LoggerFactory.getLogger(AbstractSAML1ProfileHandler.class);
86
87     /** Builder of Response objects. */
88     private SAMLObjectBuilder<Response> responseBuilder;
89
90     /** Builder of Assertion objects. */
91     private SAMLObjectBuilder<Assertion> assertionBuilder;
92
93     /** Builder of Conditions objects. */
94     private SAMLObjectBuilder<Conditions> conditionsBuilder;
95
96     /** Builder of AudienceRestrictionCondition objects. */
97     private SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionConditionBuilder;
98
99     /** Builder of AudienceRestrictionCondition objects. */
100     private SAMLObjectBuilder<Audience> audienceBuilder;
101
102     /** Builder of SubjectConfirmation objects. */
103     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
104
105     /** Builder of ConfirmationMethod objects. */
106     private SAMLObjectBuilder<ConfirmationMethod> confirmationMethodBuilder;
107
108     /** Builder of Subject objects. */
109     private SAMLObjectBuilder<Subject> subjectBuilder;
110
111     /** Builder for Status objects. */
112     private SAMLObjectBuilder<Status> statusBuilder;
113
114     /** Builder for StatusCode objects. */
115     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
116
117     /** Builder for StatusMessage objects. */
118     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
119
120     /** For building signature. */
121     private XMLObjectBuilder<Signature> signatureBuilder;
122
123     /**
124      * Default constructor.
125      */
126     @SuppressWarnings("unchecked")
127     public AbstractSAML1ProfileHandler() {
128         super();
129         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
130         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
131                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
132         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
133                 Conditions.DEFAULT_ELEMENT_NAME);
134         audienceRestrictionConditionBuilder = (SAMLObjectBuilder<AudienceRestrictionCondition>) getBuilderFactory()
135                 .getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
136         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
137         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
138                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
139         confirmationMethodBuilder = (SAMLObjectBuilder<ConfirmationMethod>) getBuilderFactory().getBuilder(
140                 ConfirmationMethod.DEFAULT_ELEMENT_NAME);
141         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
142         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
143         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
144                 StatusCode.DEFAULT_ELEMENT_NAME);
145         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
146                 StatusMessage.DEFAULT_ELEMENT_NAME);
147         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
148     }
149
150     /** {@inheritDoc} */
151     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
152         BaseSAML1ProfileRequestContext saml1Request = (BaseSAML1ProfileRequestContext) requestContext;
153         try {
154             super.populateRequestContext(requestContext);
155         } catch (ProfileException e) {
156             if (saml1Request.getFailureStatus() == null) {
157                 saml1Request.setFailureStatus(buildStatus(StatusCode.REQUESTER, null, e.getMessage()));
158             }
159             throw e;
160         }
161     }
162
163     /**
164      * Populates the request context with the information about the user.
165      * 
166      * This method requires the the following request context properties to be populated: inbound message transport,
167      * relying party ID
168      * 
169      * This methods populates the following request context properties: user's session, user's principal name, and
170      * service authentication method
171      * 
172      * @param requestContext current request context
173      */
174     protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
175         Session userSession = getUserSession(requestContext.getInboundMessageTransport());
176         if (userSession == null) {
177             NameIdentifier subject = (NameIdentifier) requestContext.getSubjectNameIdentifier();
178             if (subject != null && subject.getNameIdentifier() != null) {
179                 userSession = getUserSession(subject.getNameIdentifier());
180             }
181         }
182
183         if (userSession != null) {
184             requestContext.setUserSession(userSession);
185             requestContext.setPrincipalName(userSession.getPrincipalName());
186             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
187                     requestContext.getInboundMessageIssuer());
188             if (serviceInfo != null) {
189                 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
190                         .getAuthenticationMethod());
191             }
192         }
193     }
194
195     /**
196      * Checks that the SAML major version for a request is 1.
197      * 
198      * @param requestContext current request context containing the SAML message
199      * 
200      * @throws ProfileException thrown if the major version of the SAML request is not 1
201      */
202     protected void checkSamlVersion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
203         SAMLObject samlObject = requestContext.getInboundSAMLMessage();
204
205         if (samlObject instanceof RequestAbstractType) {
206             RequestAbstractType request = (RequestAbstractType) samlObject;
207             if (request.getMajorVersion() < 1) {
208                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_LOW,
209                         null));
210                 throw new ProfileException("SAML request major version too low");
211             } else if (request.getMajorVersion() > 1) {
212                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_HIGH,
213                         null));
214                 throw new ProfileException("SAML request major version too low");
215             }
216         }
217     }
218
219     /**
220      * Builds a response to the attribute query within the request context.
221      * 
222      * @param requestContext current request context
223      * @param statements the statements to include in the response
224      * 
225      * @return the built response
226      * 
227      * @throws ProfileException thrown if there is a problem creating the SAML response
228      */
229     protected Response buildResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, List<Statement> statements)
230             throws ProfileException {
231
232         DateTime issueInstant = new DateTime();
233
234         // create the SAML response and add the assertion
235         Response samlResponse = responseBuilder.buildObject();
236         samlResponse.setIssueInstant(issueInstant);
237         populateStatusResponse(requestContext, samlResponse);
238
239         // create the assertion and add the attribute statement
240         Assertion assertion = null;
241         if (statements != null && !statements.isEmpty()) {
242             assertion = buildAssertion(requestContext, issueInstant);
243             assertion.getStatements().addAll(statements);
244             samlResponse.getAssertions().add(assertion);
245             signAssertion(requestContext, assertion);
246         }
247
248         Status status = buildStatus(StatusCode.SUCCESS, null, null);
249         samlResponse.setStatus(status);
250
251         return samlResponse;
252     }
253
254     /**
255      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
256      * 
257      * @param requestContext current request context
258      * @param issueInstant time to use as assertion issue instant
259      * 
260      * @return the built assertion
261      */
262     protected Assertion buildAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
263         Assertion assertion = assertionBuilder.buildObject();
264         assertion.setID(getIdGenerator().generateIdentifier());
265         assertion.setIssueInstant(issueInstant);
266         assertion.setVersion(SAMLVersion.VERSION_11);
267         assertion.setIssuer(requestContext.getLocalEntityId());
268
269         Conditions conditions = buildConditions(requestContext, issueInstant);
270         assertion.setConditions(conditions);
271
272         return assertion;
273     }
274
275     /**
276      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
277      * restrictions, and proxy restrictions.
278      * 
279      * @param requestContext current request context
280      * @param issueInstant timestamp the assertion was created
281      * 
282      * @return constructed conditions
283      */
284     protected Conditions buildConditions(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
285         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
286
287         Conditions conditions = conditionsBuilder.buildObject();
288         conditions.setNotBefore(issueInstant);
289         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
290
291         Collection<String> audiences;
292
293         AudienceRestrictionCondition audienceRestriction = audienceRestrictionConditionBuilder.buildObject();
294         conditions.getAudienceRestrictionConditions().add(audienceRestriction);
295
296         Audience audience = audienceBuilder.buildObject();
297         audience.setUri(requestContext.getInboundMessageIssuer());
298         audienceRestriction.getAudiences().add(audience);
299
300         // add other audience restrictions
301         audiences = profileConfig.getAssertionAudiences();
302         if (audiences != null && audiences.size() > 0) {
303             for (String audienceUri : audiences) {
304                 audience = audienceBuilder.buildObject();
305                 audience.setUri(audienceUri);
306                 audienceRestriction.getAudiences().add(audience);
307             }
308         }
309
310         return conditions;
311     }
312
313     /**
314      * Builds the SAML subject for the user for the service provider.
315      * 
316      * @param requestContext current request context
317      * @param confirmationMethod subject confirmation method used for the subject
318      * 
319      * @return SAML subject for the user for the service provider
320      * 
321      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
322      *             name ID attribute or because there are no supported name formats
323      */
324     protected Subject buildSubject(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod)
325             throws ProfileException {
326
327         ConfirmationMethod method = confirmationMethodBuilder.buildObject();
328         method.setConfirmationMethod(confirmationMethod);
329
330         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
331         subjectConfirmation.getConfirmationMethods().add(method);
332
333         Subject subject = subjectBuilder.buildObject();
334         subject.setSubjectConfirmation(subjectConfirmation);
335
336         NameIdentifier nameID = buildNameId(requestContext);
337         if (nameID != null) {
338             subject.setNameIdentifier(nameID);
339             requestContext.setSubjectNameIdentifier(nameID);
340         }
341
342         return subject;
343     }
344
345     /**
346      * Builds a NameIdentifier appropriate for this request. NameIdentifier are built by inspecting the SAML request and
347      * metadata, picking a name format that was requested by the relying party or is mutually supported by both the
348      * relying party and asserting party as described in their metadata entries. Once a set of supported name formats is
349      * determined the principals attributes are inspected for an attribute supported an attribute encoder whose category
350      * is one of the supported name formats.
351      * 
352      * @param requestContext current request context
353      * 
354      * @return the NameIdentifier appropriate for this request
355      * 
356      * @throws ProfileException thrown if a NameIdentifier can not be created either because there was a problem
357      *             encoding the name ID attribute or because there are no supported name formats
358      */
359     protected NameIdentifier buildNameId(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
360             throws ProfileException {
361         Pair<BaseAttribute, SAML1NameIdentifierEncoder> nameIdAttributeAndEncoder = null;
362         try {
363             nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML1NameIdentifierEncoder.class,
364                     requestContext);
365         } catch (ProfileException e) {
366             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
367                     "Required NameIdentifier format not supported"));
368             throw e;
369         }
370
371         if (nameIdAttributeAndEncoder == null) {
372             return null;
373         }
374
375         BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
376         requestContext.setNameIdentifierAttribute(nameIdAttribute);
377         SAML1NameIdentifierEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
378
379         try {
380             log
381                     .debug(
382                             "Using attribute '{}' supporting name format '{}' to create the NameIdentifier for relying party '{}'",
383                             new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
384                                     requestContext.getInboundMessageIssuer(), });
385             NameIdentifier nameId = nameIdEncoder.encode(nameIdAttribute);
386             if (nameId.getNameQualifier() == null) {
387                 nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
388             }
389             return nameId;
390         } catch (AttributeEncodingException e) {
391             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Unable to encode NameIdentifier"));
392             String msg = "Unable to encode NameIdentifier for relying party "
393                     + requestContext.getInboundMessageIssuer();
394             log.error(msg, e);
395             throw new ProfileException(msg, e);
396         }
397     }
398
399     /**
400      * Constructs an SAML response message carrying a request error.
401      * 
402      * @param requestContext current request context containing the failure status
403      * 
404      * @return the constructed error response
405      */
406     protected Response buildErrorResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) {
407         Response samlResponse = responseBuilder.buildObject();
408         samlResponse.setIssueInstant(new DateTime());
409         populateStatusResponse(requestContext, samlResponse);
410
411         samlResponse.setStatus(requestContext.getFailureStatus());
412
413         return samlResponse;
414     }
415
416     /**
417      * Populates the response's id, in response to, issue instant, version, and issuer properties.
418      * 
419      * @param requestContext current request context
420      * @param response the response to populate
421      */
422     protected void populateStatusResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
423             ResponseAbstractType response) {
424         response.setID(getIdGenerator().generateIdentifier());
425
426         SAMLObject samlMessage = requestContext.getInboundSAMLMessage();
427         if (samlMessage != null && samlMessage instanceof RequestAbstractType) {
428             response.setInResponseTo(((RequestAbstractType) samlMessage).getID());
429         }
430
431         response.setVersion(SAMLVersion.VERSION_11);
432     }
433
434     /**
435      * Build a status message, with an optional second-level failure message.
436      * 
437      * @param topLevelCode top-level status code
438      * @param secondLevelCode second-level status code
439      * @param failureMessage An optional second-level failure message
440      * 
441      * @return a Status object.
442      */
443     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
444         Status status = statusBuilder.buildObject();
445
446         StatusCode statusCode = statusCodeBuilder.buildObject();
447         statusCode.setValue(topLevelCode);
448         status.setStatusCode(statusCode);
449
450         if (secondLevelCode != null) {
451             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
452             secondLevelStatusCode.setValue(secondLevelCode);
453             statusCode.setStatusCode(secondLevelStatusCode);
454         }
455
456         if (failureMessage != null) {
457             StatusMessage msg = statusMessageBuilder.buildObject();
458             msg.setMessage(failureMessage);
459             status.setStatusMessage(msg);
460         }
461
462         return status;
463     }
464
465     /**
466      * Resolved the attributes for the principal.
467      * 
468      * @param requestContext current request context
469      * 
470      * @throws ProfileException thrown if there is a problem resolving the attributes for the subject.
471      */
472     protected void resolveAttributes(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
473         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
474         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
475
476         try {
477             log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
478                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
479             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
480
481             requestContext.setAttributes(principalAttributes);
482         } catch (AttributeRequestException e) {
483             log
484                     .warn(
485                             "Error resolving attributes for principal '{}'.  No name identifier or attribute statement will be included in response",
486                             requestContext.getPrincipalName());
487         }
488     }
489
490     /**
491      * Executes a query for attributes and builds a SAML attribute statement from the results.
492      * 
493      * @param requestContext current request context
494      * @param subjectConfMethod subject confirmation method
495      * 
496      * @return attribute statement resulting from the query
497      * 
498      * @throws ProfileException thrown if there is a problem making the query
499      */
500     protected AttributeStatement buildAttributeStatement(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
501             String subjectConfMethod) throws ProfileException {
502
503         if (requestContext.getAttributes() == null) {
504             return null;
505         }
506
507         log.debug(
508                 "Creating attribute statement about principal '{}'in response to SAML request from relying party '{}'",
509                 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
510         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
511         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
512
513         try {
514             AttributeStatement statment;
515             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
516                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
517                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
518             } else {
519                 statment = attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
520             }
521
522             if (statment != null) {
523                 Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
524                 statment.setSubject(statementSubject);
525             }
526
527             return statment;
528         } catch (AttributeRequestException e) {
529             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
530             String msg = "Error encoding attributes for principal " + requestContext.getPrincipalName();
531             log.error(msg, e);
532             throw new ProfileException(msg, e);
533         }
534     }
535
536     /**
537      * Resolves the principal name of the subject of the request.
538      * 
539      * @param requestContext current request context
540      * 
541      * @throws ProfileException thrown if the principal name can not be resolved
542      */
543     protected void resolvePrincipal(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
544         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
545         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
546
547         log.debug("Resolving principal name for subject of SAML request from relying party '{}'", requestContext
548                 .getInboundMessageIssuer());
549
550         try {
551             String principal = attributeAuthority.getPrincipal(requestContext);
552             requestContext.setPrincipalName(principal);
553         } catch (AttributeRequestException e) {
554             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
555                     "Error resolving principal"));
556             String msg = "Error resolving principal name for SAML request from relying party '"
557                     + requestContext.getInboundMessageIssuer() + "'. Cause: " + e.getMessage();
558             log.warn(msg);
559             throw new ProfileException(msg, e);
560         }
561     }
562
563     /**
564      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
565      * signing credentials.
566      * 
567      * @param requestContext current request context
568      * @param assertion assertion to sign
569      * 
570      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
571      *             required, if a signing credential is not configured
572      */
573     protected void signAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
574             throws ProfileException {
575         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
576                 .getInboundMessageIssuer());
577
578         boolean signAssertion = isSignAssertion(requestContext);
579
580         if (!signAssertion) {
581             return;
582         }
583
584         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
585
586         log.debug("Determining credential to use to sign assertion to relying party '{}'", requestContext
587                 .getInboundMessageIssuer());
588         Credential signatureCredential = profileConfig.getSigningCredential();
589         if (signatureCredential == null) {
590             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
591         }
592
593         if (signatureCredential == null) {
594             String msg = "No signing credential is specified for relying party configuration "
595                     + requestContext.getRelyingPartyConfiguration().getProviderId();
596             log.warn(msg);
597             throw new ProfileException(msg);
598         }
599
600         log.debug("Signing assertion to relying party '{}'", requestContext.getInboundMessageIssuer());
601         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
602
603         signature.setSigningCredential(signatureCredential);
604         try {
605             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
606             // TODO how to pull what keyInfoGenName to use?
607             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
608         } catch (SecurityException e) {
609             String msg = "Error preparing signature for signing";
610             log.error(msg);
611             throw new ProfileException(msg, e);
612         }
613
614         assertion.setSignature(signature);
615
616         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
617         try {
618             assertionMarshaller.marshall(assertion);
619             Signer.signObject(signature);
620         } catch (MarshallingException e) {
621             String errMsg = "Unable to marshall assertion for signing";
622             log.error(errMsg, e);
623             throw new ProfileException(errMsg, e);
624         } catch (SignatureException e) {
625             String msg = "Unable to sign assertion";
626             log.error(msg, e);
627             throw new ProfileException(msg, e);
628         }
629     }
630
631     /**
632      * Determine whether issued assertions should be signed.
633      * 
634      * @param requestContext the current request context
635      * @return true if assertions should be signed, false otherwise
636      * @throws ProfileException if there is a problem determining whether assertions should be signed
637      */
638     protected boolean isSignAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
639
640         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
641         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
642
643         try {
644             boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
645                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
646                             .providesMessageIntegrity(requestContext));
647
648             log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
649                     .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
650
651             if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
652                 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
653                 if (ssoDescriptor.getWantAssertionsSigned() != null) {
654                     signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
655                     log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
656                             .getInboundMessageIssuer(), signAssertion);
657                 }
658             }
659
660             return signAssertion;
661         } catch (MessageEncodingException e) {
662             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
663                     .getBindingURI());
664             throw new ProfileException("Unable to determine if outbound assertion should be signed");
665         }
666     }
667
668     /**
669      * Writes an audit log entry indicating the successful response to the attribute request.
670      * 
671      * @param context current request context
672      */
673     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
674         SAML1AuditLogEntry auditLogEntry = new SAML1AuditLogEntry();
675         auditLogEntry.setSAMLResponse((Response) context.getOutboundSAMLMessage());
676         auditLogEntry.setMessageProfile(getProfileId());
677         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
678         auditLogEntry.setPrincipalName(context.getPrincipalName());
679         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
680         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
681         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
682         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
683         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
684         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
685         if (context.getReleasedAttributes() != null) {
686             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
687         }
688         
689         if (context.getNameIdentifierAttribute() != null) {
690             Object idValue = context.getNameIdentifierAttribute().getValues().iterator().next();
691             if(idValue != null){
692                 auditLogEntry.setNameIdValue(idValue.toString());
693             }
694         }
695
696         getAduitLog().info(auditLogEntry.toString());
697     }
698
699     /** SAML 1 specific audit log entry. */
700     protected class SAML1AuditLogEntry extends AuditLogEntry {
701
702         /** The response to the SAML 1 request. */
703         private Response samlResponse;
704
705         /**
706          * Gets the response to the SAML 1 request.
707          * 
708          * @return the response to the SAML 1 request
709          */
710         public Response getSAMLResponse() {
711             return samlResponse;
712         }
713
714         /**
715          * Sets the response to the SAML 1 request.
716          * 
717          * @param response the response to the SAML 1 request
718          */
719         public void setSAMLResponse(Response response) {
720             samlResponse = response;
721         }
722
723         /** {@inheritDoc} */
724         public String toString() {
725             StringBuilder entryString = new StringBuilder(super.toString());
726
727             StringBuilder assertionIds = new StringBuilder();
728             List<Assertion> assertions = samlResponse.getAssertions();
729             if (assertions != null && !assertions.isEmpty()) {
730                 for (Assertion assertion : assertions) {
731                     assertionIds.append(assertion.getID());
732                     assertionIds.append(",");
733                 }
734             }
735
736             if (getNameIdValue() != null) {
737                 entryString.append(getNameIdValue());
738             }
739             entryString.append("|");
740
741             entryString.append(assertionIds.toString());
742             entryString.append("|");
743
744             return entryString.toString();
745         }
746     }
747 }