View Javadoc

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