Correct unspec name ID format URNs
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml1 / AbstractSAML1ProfileHandler.java
1 /*
2  * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.profile.saml1;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.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.AttributeAuthorityDescriptor;
50 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
51 import org.opensaml.saml2.metadata.NameIDFormat;
52 import org.opensaml.saml2.metadata.PDPDescriptor;
53 import org.opensaml.saml2.metadata.RoleDescriptor;
54 import org.opensaml.saml2.metadata.SPSSODescriptor;
55 import org.opensaml.saml2.metadata.SSODescriptor;
56 import org.opensaml.ws.message.encoder.MessageEncodingException;
57 import org.opensaml.xml.XMLObjectBuilder;
58 import org.opensaml.xml.io.Marshaller;
59 import org.opensaml.xml.io.MarshallingException;
60 import org.opensaml.xml.security.SecurityException;
61 import org.opensaml.xml.security.SecurityHelper;
62 import org.opensaml.xml.security.credential.Credential;
63 import org.opensaml.xml.signature.Signature;
64 import org.opensaml.xml.signature.Signer;
65 import org.slf4j.Logger;
66 import org.slf4j.LoggerFactory;
67
68 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
69 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
70 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
71 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
72 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML1NameIdentifierEncoder;
73 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML1AttributeAuthority;
74 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
75 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml1.AbstractSAML1ProfileConfiguration;
77 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
78
79 /** Common implementation details for profile handlers. */
80 public abstract class AbstractSAML1ProfileHandler extends AbstractSAMLProfileHandler {
81
82     /** SAML Version for this profile handler. */
83     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_11;
84
85     /** Class logger. */
86     private static Logger log = LoggerFactory.getLogger(AbstractSAML1ProfileHandler.class);
87
88     /** Builder of Response objects. */
89     private SAMLObjectBuilder<Response> responseBuilder;
90
91     /** Builder of Assertion objects. */
92     private SAMLObjectBuilder<Assertion> assertionBuilder;
93
94     /** Builder of Conditions objects. */
95     private SAMLObjectBuilder<Conditions> conditionsBuilder;
96
97     /** Builder of AudienceRestrictionCondition objects. */
98     private SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionConditionBuilder;
99
100     /** Builder of AudienceRestrictionCondition objects. */
101     private SAMLObjectBuilder<Audience> audienceBuilder;
102
103     /** Builder of SubjectConfirmation objects. */
104     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
105
106     /** Builder of ConfirmationMethod objects. */
107     private SAMLObjectBuilder<ConfirmationMethod> confirmationMethodBuilder;
108
109     /** Builder of Subject objects. */
110     private SAMLObjectBuilder<Subject> subjectBuilder;
111
112     /** Builder for Status objects. */
113     private SAMLObjectBuilder<Status> statusBuilder;
114
115     /** Builder for StatusCode objects. */
116     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
117
118     /** Builder for StatusMessage objects. */
119     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
120
121     /** For building signature. */
122     private XMLObjectBuilder<Signature> signatureBuilder;
123
124     /**
125      * Default constructor.
126      */
127     @SuppressWarnings("unchecked")
128     public AbstractSAML1ProfileHandler() {
129         super();
130         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
131         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
132                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
133         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
134                 Conditions.DEFAULT_ELEMENT_NAME);
135         audienceRestrictionConditionBuilder = (SAMLObjectBuilder<AudienceRestrictionCondition>) getBuilderFactory()
136                 .getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
137         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
138         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
139                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
140         confirmationMethodBuilder = (SAMLObjectBuilder<ConfirmationMethod>) getBuilderFactory().getBuilder(
141                 ConfirmationMethod.DEFAULT_ELEMENT_NAME);
142         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
143         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
144         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
145                 StatusCode.DEFAULT_ELEMENT_NAME);
146         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
147                 StatusMessage.DEFAULT_ELEMENT_NAME);
148         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
149     }
150
151     /**
152      * Checks that the SAML major version for a request is 1.
153      * 
154      * @param requestContext current request context containing the SAML message
155      * 
156      * @throws ProfileException thrown if the major version of the SAML request is not 1
157      */
158     protected void checkSamlVersion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
159         SAMLObject samlObject = requestContext.getInboundSAMLMessage();
160
161         if (samlObject instanceof RequestAbstractType) {
162             RequestAbstractType request = (RequestAbstractType) samlObject;
163             if (request.getMajorVersion() < 1) {
164                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_LOW,
165                         null));
166                 throw new ProfileException("SAML request major version too low");
167             } else if (request.getMajorVersion() > 1) {
168                 requestContext.setFailureStatus(buildStatus(StatusCode.REQUESTER, StatusCode.REQUEST_VERSION_TOO_HIGH,
169                         null));
170                 throw new ProfileException("SAML request major version too low");
171             }
172         }
173     }
174
175     /**
176      * Builds a response to the attribute query within the request context.
177      * 
178      * @param requestContext current request context
179      * @param statements the statements to include in the response
180      * 
181      * @return the built response
182      * 
183      * @throws ProfileException thrown if there is a problem creating the SAML response
184      */
185     protected Response buildResponse(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, List<Statement> statements)
186             throws ProfileException {
187
188         DateTime issueInstant = new DateTime();
189
190         // create the assertion and add the attribute statement
191         Assertion assertion = buildAssertion(requestContext, issueInstant);
192         if (statements != null && !statements.isEmpty()) {
193             assertion.getStatements().addAll(statements);
194         }
195
196         // create the SAML response and add the assertion
197         Response samlResponse = responseBuilder.buildObject();
198         samlResponse.setIssueInstant(issueInstant);
199         populateStatusResponse(requestContext, samlResponse);
200
201         samlResponse.getAssertions().add(assertion);
202
203         // sign the assertion if it should be signed
204         signAssertion(requestContext, assertion);
205
206         Status status = buildStatus(StatusCode.SUCCESS, null, null);
207         samlResponse.setStatus(status);
208
209         return samlResponse;
210     }
211
212     /**
213      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
214      * 
215      * @param requestContext current request context
216      * @param issueInstant time to use as assertion issue instant
217      * 
218      * @return the built assertion
219      */
220     protected Assertion buildAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
221         Assertion assertion = assertionBuilder.buildObject();
222         assertion.setID(getIdGenerator().generateIdentifier());
223         assertion.setIssueInstant(issueInstant);
224         assertion.setVersion(SAMLVersion.VERSION_11);
225         assertion.setIssuer(requestContext.getLocalEntityId());
226
227         Conditions conditions = buildConditions(requestContext, issueInstant);
228         assertion.setConditions(conditions);
229
230         return assertion;
231     }
232
233     /**
234      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
235      * restrictions, and proxy restrictions.
236      * 
237      * @param requestContext current request context
238      * @param issueInstant timestamp the assertion was created
239      * 
240      * @return constructed conditions
241      */
242     protected Conditions buildConditions(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
243         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
244
245         Conditions conditions = conditionsBuilder.buildObject();
246         conditions.setNotBefore(issueInstant);
247         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
248
249         Collection<String> audiences;
250
251         // add audience restrictions
252         audiences = profileConfig.getAssertionAudiences();
253         if (audiences != null && audiences.size() > 0) {
254             AudienceRestrictionCondition audienceRestriction = audienceRestrictionConditionBuilder.buildObject();
255             for (String audienceUri : audiences) {
256                 Audience audience = audienceBuilder.buildObject();
257                 audience.setUri(audienceUri);
258                 audienceRestriction.getAudiences().add(audience);
259             }
260             conditions.getAudienceRestrictionConditions().add(audienceRestriction);
261         }
262
263         return conditions;
264     }
265
266     /**
267      * Builds the SAML subject for the user for the service provider.
268      * 
269      * @param requestContext current request context
270      * @param confirmationMethod subject confirmation method used for the subject
271      * 
272      * @return SAML subject for the user for the service provider
273      * 
274      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
275      *             name ID attribute or because there are no supported name formats
276      */
277     protected Subject buildSubject(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod)
278             throws ProfileException {
279         NameIdentifier nameID = buildNameId(requestContext);
280         requestContext.setSubjectNameIdentifier(nameID);
281
282         ConfirmationMethod method = confirmationMethodBuilder.buildObject();
283         method.setConfirmationMethod(confirmationMethod);
284
285         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
286         subjectConfirmation.getConfirmationMethods().add(method);
287
288         Subject subject = subjectBuilder.buildObject();
289         subject.setNameIdentifier(nameID);
290         subject.setSubjectConfirmation(subjectConfirmation);
291
292         return subject;
293     }
294
295     /**
296      * Builds a NameIdentifier appropriate for this request. NameIdentifier are built by inspecting the SAML request and
297      * metadata, picking a name format that was requested by the relying party or is mutually supported by both the
298      * relying party and asserting party as described in their metadata entries. Once a set of supported name formats is
299      * determined the principals attributes are inspected for an attribute supported an attribute encoder whose category
300      * is one of the supported name formats.
301      * 
302      * @param requestContext current request context
303      * 
304      * @return the NameIdentifier appropriate for this request
305      * 
306      * @throws ProfileException thrown if a NameIdentifier can not be created either because there was a problem
307      *             encoding the name ID attribute or because there are no supported name formats
308      */
309     protected NameIdentifier buildNameId(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
310             throws ProfileException {
311         log.debug("Building assertion NameIdentifier to relying party {} for principal {}", requestContext
312                 .getInboundMessageIssuer(), requestContext.getPrincipalName());
313         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
314         if (principalAttributes == null || principalAttributes.isEmpty()) {
315             log.error("No attributes for principal {}, unable to construct of NameID", requestContext
316                     .getPrincipalName());
317             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
318                     "Unable to construct NameIdentifier"));
319             throw new ProfileException("No principal attributes support NameIdentifier construction");
320         }
321
322         List<String> supportedNameFormats = getNameFormats(requestContext);
323         if (supportedNameFormats == null || supportedNameFormats.isEmpty()) {
324             log.error("No common NameID formats supported by SP {} and IdP", requestContext.getInboundMessageIssuer());
325             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
326                     "Unable to construct NameIdentifier"));
327             throw new ProfileException("No principal attributes support NameIdentifier construction");
328         }
329
330         log.debug("Supported name formats: {}", supportedNameFormats);
331         try {
332             SAML1NameIdentifierEncoder nameIdEncoder;
333
334             for (BaseAttribute<?> attribute : principalAttributes.values()) {
335                 for (AttributeEncoder encoder : attribute.getEncoders()) {
336                     if (encoder instanceof SAML1NameIdentifierEncoder) {
337                         nameIdEncoder = (SAML1NameIdentifierEncoder) encoder;
338                         if (supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
339                             log
340                                     .debug(
341                                             "Using attribute {} suppoting name format {} to create the NameIdentifier for principal",
342                                             attribute.getId(), nameIdEncoder.getNameFormat());
343                             return nameIdEncoder.encode(attribute);
344                         }
345                     }
346                 }
347             }
348             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
349                     "Unable to construct NameIdentifier"));
350             log.error("No principal attribute supports an encoding into a supported name ID format.");
351             throw new ProfileException("No principal attribute supports an encoding into a supported name ID format.");
352         } catch (AttributeEncodingException e) {
353             log.error("Unable to construct NameIdentifier", e);
354             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null,
355                     "Unable to construct NameIdentifier"));
356             throw new ProfileException("Unable to encode NameIdentifier attribute", e);
357         }
358
359     }
360
361     /**
362      * Gets the NameIdentifier format to use when creating NameIdentifiers for the relying party.
363      * 
364      * @param requestContext current request context
365      * 
366      * @return list of formats that may be used with the relying party
367      * 
368      * @throws ProfileException thrown if there is a problem determining the NameIdentifier format to use
369      */
370     protected List<String> getNameFormats(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext)
371             throws ProfileException {
372         ArrayList<String> nameFormats = new ArrayList<String>();
373
374         RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
375         if (relyingPartyRole != null) {
376             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
377             if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
378                 nameFormats.addAll(relyingPartySupportedFormats);
379
380                 RoleDescriptor assertingPartyRole = requestContext.getLocalEntityRoleMetadata();
381                 if (assertingPartyRole != null) {
382                     List<String> assertingPartySupportedFormats = getEntitySupportedFormats(assertingPartyRole);
383                     if (assertingPartySupportedFormats != null && !assertingPartySupportedFormats.isEmpty()) {
384                         nameFormats.retainAll(assertingPartySupportedFormats);
385                     }
386                 }
387             }
388         }
389
390         if (nameFormats.isEmpty()) {
391             nameFormats.add("urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified");
392         }
393
394         return nameFormats;
395     }
396
397     /**
398      * Gets the list of NameIdentifier formats supported for a given role.
399      * 
400      * @param role the role to get the list of supported NameIdentifier formats
401      * 
402      * @return list of supported NameIdentifier formats
403      */
404     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
405         List<NameIDFormat> nameIDFormats = null;
406
407         if (role instanceof SSODescriptor) {
408             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
409         } else if (role instanceof AuthnAuthorityDescriptor) {
410             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
411         } else if (role instanceof PDPDescriptor) {
412             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
413         } else if (role instanceof AttributeAuthorityDescriptor) {
414             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
415         }
416
417         ArrayList<String> supportedFormats = new ArrayList<String>();
418         if (nameIDFormats != null) {
419             for (NameIDFormat format : nameIDFormats) {
420                 supportedFormats.add(format.getFormat());
421             }
422         }
423
424         return supportedFormats;
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         response.setVersion(SAMLVersion.VERSION_11);
459     }
460
461     /**
462      * Build a status message, with an optional second-level failure message.
463      * 
464      * @param topLevelCode top-level status code
465      * @param secondLevelCode second-level status code
466      * @param failureMessage An optional second-level failure message
467      * 
468      * @return a Status object.
469      */
470     protected Status buildStatus(QName topLevelCode, QName secondLevelCode, String failureMessage) {
471         Status status = statusBuilder.buildObject();
472
473         StatusCode statusCode = statusCodeBuilder.buildObject();
474         statusCode.setValue(topLevelCode);
475         status.setStatusCode(statusCode);
476
477         if (secondLevelCode != null) {
478             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
479             secondLevelStatusCode.setValue(secondLevelCode);
480             statusCode.setStatusCode(secondLevelStatusCode);
481         }
482
483         if (failureMessage != null) {
484             StatusMessage msg = statusMessageBuilder.buildObject();
485             msg.setMessage(failureMessage);
486             status.setStatusMessage(msg);
487         }
488
489         return status;
490     }
491
492     /**
493      * Resolved the attributes for the principal.
494      * 
495      * @param requestContext current request context
496      * 
497      * @throws ProfileException thrown if attributes can not be resolved
498      */
499     protected void resolveAttributes(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
500         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
501         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
502
503         try {
504             log.debug("Resolving attributes for principal {} of SAML request from relying party {}", requestContext
505                     .getPrincipalName(), requestContext.getInboundMessageIssuer());
506             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
507
508             requestContext.setAttributes(principalAttributes);
509         } catch (AttributeRequestException e) {
510             log.error("Error resolving attributes for SAML request from relying party "
511                     + requestContext.getInboundMessageIssuer(), e);
512             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
513             throw new ProfileException("Error resolving attributes for SAML request from relying party "
514                     + requestContext.getInboundMessageIssuer(), e);
515         }
516     }
517
518     /**
519      * Executes a query for attributes and builds a SAML attribute statement from the results.
520      * 
521      * @param requestContext current request context
522      * @param subjectConfMethod subject confirmation method
523      * 
524      * @return attribute statement resulting from the query
525      * 
526      * @throws ProfileException thrown if there is a problem making the query
527      */
528     protected AttributeStatement buildAttributeStatement(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext,
529             String subjectConfMethod) throws ProfileException {
530
531         log.debug("Creating attribute statement in response to SAML request from relying party {}", requestContext
532                 .getInboundMessageIssuer());
533         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
534         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
535
536         try {
537             AttributeStatement statment;
538             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
539                 statment = attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
540                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
541             } else {
542                 statment = attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
543             }
544
545             if (statment != null) {
546                 Subject statementSubject = buildSubject(requestContext, subjectConfMethod);
547                 statment.setSubject(statementSubject);
548             }
549
550             return statment;
551         } catch (AttributeRequestException e) {
552             log.error("Error encoding attributes for principal " + requestContext.getPrincipalName(), e);
553             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, null, "Error resolving attributes"));
554             throw new ProfileException("Error encoding attributes for principal " + requestContext.getPrincipalName(),
555                     e);
556         }
557     }
558
559     /**
560      * Resolves the principal name of the subject of the request.
561      * 
562      * @param requestContext current request context
563      * 
564      * @throws ProfileException thrown if the principal name can not be resolved
565      */
566     protected void resolvePrincipal(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
567         AbstractSAML1ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
568         SAML1AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
569
570         log.debug("Resolving principal name for subject of SAML request from relying party {}", requestContext
571                 .getInboundMessageIssuer());
572
573         try {
574             String principal = attributeAuthority.getPrincipal(requestContext);
575             requestContext.setPrincipalName(principal);
576         } catch (AttributeRequestException e) {
577             log.error("Error resolving attributes for SAML request from relying party "
578                     + requestContext.getInboundMessageIssuer(), e);
579             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER, StatusCode.REQUEST_DENIED,
580                     "Error resolving principal"));
581             throw new ProfileException("Error resolving attributes for SAML request from relying party "
582                     + requestContext.getInboundMessageIssuer(), e);
583         }
584     }
585
586     /**
587      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
588      * signing credentials.
589      * 
590      * @param requestContext current request context
591      * @param assertion assertion to sign
592      * 
593      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
594      *             required, if a signing credential is not configured
595      */
596     protected void signAssertion(BaseSAML1ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
597             throws ProfileException {
598         log.debug("Determining if SAML assertion to relying party {} should be signed", requestContext
599                 .getInboundMessageIssuer());
600
601         boolean signAssertion = false;
602
603         RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
604         SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
605         AbstractSAML1ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
606
607         try {
608             if (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
609                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
610                             .providesMessageIntegrity(requestContext))) {
611                 signAssertion = true;
612                 log.debug("IdP relying party configuration {} indicates to sign assertions: {}", requestContext
613                         .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
614             }
615         } catch (MessageEncodingException e) {
616             log.error("Unable to determine if outbound encoding {} can provide integrity", encoder.getBindingURI());
617             throw new ProfileException("Unable to determine if outbound message should be signed");
618         }
619
620         if (!signAssertion && relyingPartyRole instanceof SPSSODescriptor) {
621             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) relyingPartyRole;
622             if (ssoDescriptor.getWantAssertionsSigned() != null) {
623                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
624                 log.debug("Entity metadata for relying party {} indicates to sign assertions: {}", requestContext
625                         .getInboundMessageIssuer(), signAssertion);
626             }
627         }
628
629         if (!signAssertion) {
630             return;
631         }
632
633         log.debug("Determining signing credntial for assertion to relying party {}", requestContext
634                 .getInboundMessageIssuer());
635         Credential signatureCredential = profileConfig.getSigningCredential();
636         if (signatureCredential == null) {
637             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
638         }
639
640         if (signatureCredential == null) {
641             throw new ProfileException("No signing credential is specified for relying party configuration "
642                     + requestContext.getRelyingPartyConfiguration().getProviderId()
643                     + " or it's SAML2 attribute query profile configuration");
644         }
645
646         log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
647         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
648
649         signature.setSigningCredential(signatureCredential);
650         try {
651             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
652             // TODO how to pull what keyInfoGenName to use?
653             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
654         } catch (SecurityException e) {
655             throw new ProfileException("Error preparing signature for signing", e);
656         }
657
658         assertion.setSignature(signature);
659
660         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
661         try {
662             assertionMarshaller.marshall(assertion);
663             Signer.signObject(signature);
664         } catch (MarshallingException e) {
665             log.error("Unable to marshall assertion for signing", e);
666             throw new ProfileException("Unable to marshall assertion for signing", e);
667         }
668     }
669 }