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