Log released attributes
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / profile / saml2 / AbstractSAML2ProfileHandler.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.saml2;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.List;
22 import java.util.Map;
23
24 import javax.servlet.ServletRequest;
25 import javax.servlet.ServletResponse;
26
27 import org.apache.log4j.Logger;
28 import org.joda.time.DateTime;
29 import org.opensaml.common.SAMLObjectBuilder;
30 import org.opensaml.common.SAMLVersion;
31 import org.opensaml.common.impl.SAMLObjectContentReference;
32 import org.opensaml.log.Level;
33 import org.opensaml.saml2.core.Assertion;
34 import org.opensaml.saml2.core.AttributeQuery;
35 import org.opensaml.saml2.core.AttributeStatement;
36 import org.opensaml.saml2.core.Audience;
37 import org.opensaml.saml2.core.AudienceRestriction;
38 import org.opensaml.saml2.core.AuthnRequest;
39 import org.opensaml.saml2.core.Conditions;
40 import org.opensaml.saml2.core.Issuer;
41 import org.opensaml.saml2.core.NameID;
42 import org.opensaml.saml2.core.ProxyRestriction;
43 import org.opensaml.saml2.core.RequestAbstractType;
44 import org.opensaml.saml2.core.Response;
45 import org.opensaml.saml2.core.Statement;
46 import org.opensaml.saml2.core.Status;
47 import org.opensaml.saml2.core.StatusCode;
48 import org.opensaml.saml2.core.StatusMessage;
49 import org.opensaml.saml2.core.StatusResponseType;
50 import org.opensaml.saml2.core.Subject;
51 import org.opensaml.saml2.core.SubjectConfirmation;
52 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
53 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
54 import org.opensaml.saml2.metadata.NameIDFormat;
55 import org.opensaml.saml2.metadata.PDPDescriptor;
56 import org.opensaml.saml2.metadata.RoleDescriptor;
57 import org.opensaml.saml2.metadata.SPSSODescriptor;
58 import org.opensaml.saml2.metadata.SSODescriptor;
59 import org.opensaml.xml.XMLObjectBuilder;
60 import org.opensaml.xml.security.credential.Credential;
61 import org.opensaml.xml.signature.Signature;
62 import org.opensaml.xml.signature.Signer;
63 import org.opensaml.xml.util.DatatypeHelper;
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.SAML2NameIDAttributeEncoder;
70 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
71 import edu.internet2.middleware.shibboleth.common.attribute.provider.ShibbolethSAMLAttributeRequestContext;
72 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
73 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
74 import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
75 import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
76 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
77 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
78 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
79 import edu.internet2.middleware.shibboleth.idp.session.Session;
80
81 /** Common implementation details for profile handlers. */
82 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
83
84     /** SAML Version for this profile handler. */
85     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
86
87     /** Class logger. */
88     private Logger log = Logger.getLogger(AbstractSAML2ProfileHandler.class);
89
90     /** For building response. */
91     private SAMLObjectBuilder<Response> responseBuilder;
92
93     /** For building status. */
94     private SAMLObjectBuilder<Status> statusBuilder;
95
96     /** For building statuscode. */
97     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
98
99     /** For building StatusMessages. */
100     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
101
102     /** For building assertion. */
103     private SAMLObjectBuilder<Assertion> assertionBuilder;
104
105     /** For building issuer. */
106     private SAMLObjectBuilder<Issuer> issuerBuilder;
107
108     /** For building subject. */
109     private SAMLObjectBuilder<Subject> subjectBuilder;
110
111     /** For builder subject confirmation. */
112     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
113
114     /** For building conditions. */
115     private SAMLObjectBuilder<Conditions> conditionsBuilder;
116
117     /** For building audience restriction. */
118     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
119
120     /** For building proxy retrictions. */
121     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
122
123     /** For building audience. */
124     private SAMLObjectBuilder<Audience> audienceBuilder;
125
126     /** For building signature. */
127     private XMLObjectBuilder<Signature> signatureBuilder;
128
129     /** Constructor. */
130     @SuppressWarnings("unchecked")
131     protected AbstractSAML2ProfileHandler() {
132         super();
133
134         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
135         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
136         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
137                 StatusCode.DEFAULT_ELEMENT_NAME);
138         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
139                 StatusMessage.DEFAULT_ELEMENT_NAME);
140         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
141         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
142                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
143         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
144         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
145                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
146         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
147                 Conditions.DEFAULT_ELEMENT_NAME);
148         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
149                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
150         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
151                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
152         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
153         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
154     }
155
156     /**
157      * Checks that the SAML major version for a request is 2.
158      * 
159      * @param requestContext current request context containing the SAML message
160      * 
161      * @throws ProfileException thrown if the major version of the SAML request is not 2
162      */
163     protected void checkSamlVersion(SAML2ProfileRequestContext requestContext) throws ProfileException {
164         SAMLVersion version = requestContext.getSamlRequest().getVersion();
165         if (version.getMajorVersion() < 2) {
166             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
167                     StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
168             throw new ProfileException("SAML request version too low");
169         } else if (version.getMajorVersion() > 2) {
170             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
171                     StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
172             throw new ProfileException("SAML request version too high");
173         }
174     }
175
176     /**
177      * Builds a response to the attribute query within the request context.
178      * 
179      * @param requestContext current request context
180      * @param assertionSubject subject of the assertion within the response
181      * @param statements the statements to include in the response
182      * 
183      * @return the built response
184      * 
185      * @throws ProfileException thrown if there is a problem creating the SAML response
186      */
187     protected Response buildResponse(SAML2ProfileRequestContext requestContext, Subject assertionSubject,
188             List<Statement> statements) throws ProfileException {
189
190         DateTime issueInstant = new DateTime();
191
192         // create the assertion and add the attribute statement
193         Assertion assertion = buildAssertion(requestContext, issueInstant);
194         assertion.setSubject(assertionSubject);
195         if (statements != null) {
196             assertion.getStatements().addAll(statements);
197         }
198
199         // create the SAML response and add the assertion
200         Response samlResponse = responseBuilder.buildObject();
201         samlResponse.setIssueInstant(issueInstant);
202         populateStatusResponse(requestContext, samlResponse);
203
204         samlResponse.getAssertions().add(assertion);
205
206         // sign the assertion if it should be signed
207         signAssertion(requestContext, assertion);
208
209         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
210         samlResponse.setStatus(status);
211
212         return samlResponse;
213     }
214
215     /**
216      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
217      * 
218      * @param requestContext current request context
219      * @param issueInstant time to use as assertion issue instant
220      * 
221      * @return the built assertion
222      */
223     protected Assertion buildAssertion(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
224         Assertion assertion = assertionBuilder.buildObject();
225         assertion.setID(getIdGenerator().generateIdentifier());
226         assertion.setIssueInstant(issueInstant);
227         assertion.setVersion(SAMLVersion.VERSION_20);
228         assertion.setIssuer(buildEntityIssuer(requestContext));
229
230         Conditions conditions = buildConditions(requestContext, issueInstant);
231         assertion.setConditions(conditions);
232
233         return assertion;
234     }
235
236     /**
237      * Creates an {@link Issuer} populated with information about the relying party.
238      * 
239      * @param requestContext current request context
240      * 
241      * @return the built issuer
242      */
243     protected Issuer buildEntityIssuer(SAML2ProfileRequestContext requestContext) {
244         Issuer issuer = issuerBuilder.buildObject();
245         issuer.setFormat(Issuer.ENTITY);
246         issuer.setValue(requestContext.getAssertingPartyId());
247
248         return issuer;
249     }
250
251     /**
252      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
253      * restrictions, and proxy restrictions.
254      * 
255      * @param requestContext current request context
256      * @param issueInstant timestamp the assertion was created
257      * 
258      * @return constructed conditions
259      */
260     protected Conditions buildConditions(SAML2ProfileRequestContext requestContext, DateTime issueInstant) {
261         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
262
263         Conditions conditions = conditionsBuilder.buildObject();
264         conditions.setNotBefore(issueInstant);
265         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
266
267         Collection<String> audiences;
268
269         // add audience restrictions
270         audiences = profileConfig.getAssertionAudiences();
271         if (audiences != null && audiences.size() > 0) {
272             AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
273             for (String audienceUri : audiences) {
274                 Audience audience = audienceBuilder.buildObject();
275                 audience.setAudienceURI(audienceUri);
276                 audienceRestriction.getAudiences().add(audience);
277             }
278             conditions.getAudienceRestrictions().add(audienceRestriction);
279         }
280
281         // add proxy restrictions
282         audiences = profileConfig.getProxyAudiences();
283         if (audiences != null && audiences.size() > 0) {
284             ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
285             Audience audience;
286             for (String audienceUri : audiences) {
287                 audience = audienceBuilder.buildObject();
288                 audience.setAudienceURI(audienceUri);
289                 proxyRestriction.getAudiences().add(audience);
290             }
291
292             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
293             conditions.getConditions().add(proxyRestriction);
294         }
295
296         return conditions;
297     }
298
299     /**
300      * Populates the response's id, in response to, issue instant, version, and issuer properties.
301      * 
302      * @param requestContext current request context
303      * @param response the response to populate
304      */
305     protected void populateStatusResponse(SAML2ProfileRequestContext requestContext, StatusResponseType response) {
306         response.setID(getIdGenerator().generateIdentifier());
307         if (requestContext.getSamlRequest() != null) {
308             response.setInResponseTo(requestContext.getSamlRequest().getID());
309         }
310         response.setVersion(SAMLVersion.VERSION_20);
311         response.setIssuer(buildEntityIssuer(requestContext));
312     }
313
314     /**
315      * Executes a query for attributes and builds a SAML attribute statement from the results.
316      * 
317      * @param requestContext current request context
318      * 
319      * @return attribute statement resulting from the query
320      * 
321      * @throws ProfileException thrown if there is a problem making the query
322      */
323     protected AttributeStatement buildAttributeStatement(SAML2ProfileRequestContext requestContext)
324             throws ProfileException {
325
326         if (log.isDebugEnabled()) {
327             log.debug("Creating attribute statement in response to SAML request "
328                     + requestContext.getSamlRequest().getID() + " from relying party "
329                     + requestContext.getRelyingPartyId());
330         }
331
332         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
333         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
334
335         try {
336             if (log.isDebugEnabled()) {
337                 log.debug("Resolving attributes for principal " + requestContext.getPrincipalName()
338                         + " of SAML request " + requestContext.getSamlRequest().getID() + " from relying party "
339                         + requestContext.getRelyingPartyId());
340             }
341             Map<String, BaseAttribute> principalAttributes = attributeAuthority
342                     .getAttributes(buildAttributeRequestContext(requestContext));
343
344             requestContext.setPrincipalAttributes(principalAttributes);
345
346             if (requestContext.getSamlRequest() instanceof AttributeQuery) {
347                 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext.getSamlRequest(),
348                         principalAttributes.values());
349             } else {
350                 return attributeAuthority.buildAttributeStatement(null, principalAttributes.values());
351             }
352         } catch (AttributeRequestException e) {
353             log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
354                     + " from relying party " + requestContext.getRelyingPartyId(), e);
355             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
356             throw new ProfileException("Error resolving attributes for SAML request "
357                     + requestContext.getSamlRequest().getID() + " from relying party "
358                     + requestContext.getRelyingPartyId(), e);
359         }
360     }
361
362     /**
363      * Resolves the principal name of the subject of the request.
364      * 
365      * @param requestContext current request context
366      * 
367      * @throws ProfileException thrown if the principal name can not be resolved
368      */
369     protected void resolvePrincipal(SAML2ProfileRequestContext requestContext) throws ProfileException {
370         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
371         if (profileConfiguration == null) {
372             log.error("Unable to resolve principal, no SAML 2 profile configuration for relying party "
373                     + requestContext.getRelyingPartyId());
374             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
375                     "Error resolving principal"));
376             throw new ProfileException(
377                     "Unable to resolve principal, no SAML 2 profile configuration for relying party "
378                             + requestContext.getRelyingPartyId());
379         }
380         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
381
382         if (log.isDebugEnabled()) {
383             log.debug("Resolving principal name for subject of SAML request " + requestContext.getSamlRequest().getID()
384                     + " from relying party " + requestContext.getRelyingPartyId());
385         }
386
387         try {
388             String principal = attributeAuthority.getPrincipal(buildAttributeRequestContext(requestContext));
389             requestContext.setPrincipalName(principal);
390         } catch (AttributeRequestException e) {
391             log.error("Error resolving attributes for SAML request " + requestContext.getSamlRequest().getID()
392                     + " from relying party " + requestContext.getRelyingPartyId(), e);
393             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
394                     "Error resolving principal"));
395             throw new ProfileException("Error resolving attributes for SAML request "
396                     + requestContext.getSamlRequest().getID() + " from relying party "
397                     + requestContext.getRelyingPartyId(), e);
398         }
399     }
400
401     /**
402      * Creates an attribute query context from the current profile request context.
403      * 
404      * @param requestContext current profile request
405      * 
406      * @return created query context
407      */
408     protected ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> buildAttributeRequestContext(
409             SAML2ProfileRequestContext requestContext) {
410
411         ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery> queryContext;
412
413         queryContext = new ShibbolethSAMLAttributeRequestContext<NameID, AttributeQuery>(getMetadataProvider(),
414                 requestContext.getRelyingPartyConfiguration(), (AttributeQuery) requestContext.getSamlRequest());
415         queryContext.setAttributeRequester(requestContext.getAssertingPartyId());
416         queryContext.setPrincipalName(requestContext.getPrincipalName());
417         queryContext.setProfileConfiguration(requestContext.getProfileConfiguration());
418         queryContext.setRequest(requestContext.getProfileRequest());
419
420         Session userSession = getSessionManager().getSession(getUserSessionId(requestContext.getProfileRequest()));
421         if (userSession != null) {
422             queryContext.setUserSession(userSession);
423             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
424                     requestContext.getRelyingPartyId());
425             if (serviceInfo != null) {
426                 String principalAuthenticationMethod = serviceInfo.getAuthenticationMethod().getAuthenticationMethod();
427
428                 requestContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
429                 queryContext.setPrincipalAuthenticationMethod(principalAuthenticationMethod);
430             }
431         }
432
433         return queryContext;
434     }
435
436     /**
437      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
438      * signing credentials.
439      * 
440      * @param requestContext current request context
441      * @param assertion assertion to sign
442      * 
443      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
444      *             required, if a signing credential is not configured
445      */
446     protected void signAssertion(SAML2ProfileRequestContext requestContext, Assertion assertion)
447             throws ProfileException {
448         if (log.isDebugEnabled()) {
449             log.debug("Determining if SAML assertion to relying party " + requestContext.getRelyingPartyId()
450                     + " should be signed");
451         }
452
453         boolean signAssertion = false;
454
455         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
456
457         if (requestContext.getRelyingPartyRoleMetadata() instanceof SPSSODescriptor) {
458             SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getRelyingPartyRoleMetadata();
459             if (ssoDescriptor.getWantAssertionsSigned() != null) {
460                 signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
461                 if (log.isDebugEnabled()) {
462                     log.debug("Entity metadata for relying party " + requestContext.getRelyingPartyId()
463                             + " indicates to sign assertions: " + signAssertion);
464                 }
465             }
466         } else if (profileConfig.getSignAssertions()) {
467             signAssertion = true;
468             log.debug("IdP relying party configuration "
469                     + requestContext.getRelyingPartyConfiguration().getRelyingPartyId()
470                     + " indicates to sign assertions: " + signAssertion);
471         }
472
473         if (!signAssertion) {
474             return;
475         }
476
477         if (log.isDebugEnabled()) {
478             log.debug("Determining signing credntial for assertion to relying party "
479                     + requestContext.getRelyingPartyId());
480         }
481         Credential signatureCredential = profileConfig.getSigningCredential();
482         if (signatureCredential == null) {
483             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
484         }
485
486         if (signatureCredential == null) {
487             throw new ProfileException("No signing credential is specified for relying party configuration "
488                     + requestContext.getRelyingPartyConfiguration().getProviderId()
489                     + " or it's SAML2 attribute query profile configuration");
490         }
491
492         if (log.isDebugEnabled()) {
493             log.debug("Signing assertion to relying party " + requestContext.getRelyingPartyId());
494         }
495         SAMLObjectContentReference contentRef = new SAMLObjectContentReference(assertion);
496         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
497         signature.getContentReferences().add(contentRef);
498         assertion.setSignature(signature);
499
500         Signer.signObject(signature);
501     }
502
503     /**
504      * Build a status message, with an optional second-level failure message.
505      * 
506      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
507      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
508      *            null, no second-level Status element will be set.
509      * @param failureMessage An optional second-level failure message
510      * 
511      * @return a Status object.
512      */
513     protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
514         Status status = statusBuilder.buildObject();
515
516         StatusCode statusCode = statusCodeBuilder.buildObject();
517         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
518         status.setStatusCode(statusCode);
519
520         if (secondLevelCode != null) {
521             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
522             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
523             statusCode.setStatusCode(secondLevelStatusCode);
524         }
525
526         if (failureMessage != null) {
527             StatusMessage msg = statusMessageBuilder.buildObject();
528             msg.setMessage(failureMessage);
529             status.setStatusMessage(msg);
530         }
531
532         return status;
533     }
534
535     /**
536      * Builds the SAML subject for the user for the service provider.
537      * 
538      * @param requestContext current request context
539      * @param confirmationMethod subject confirmation method used for the subject
540      * 
541      * @return SAML subject for the user for the service provider
542      * 
543      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
544      *             name ID attribute or because there are no supported name formats
545      */
546     protected Subject buildSubject(SAML2ProfileRequestContext requestContext, String confirmationMethod)
547             throws ProfileException {
548         NameID nameID = buildNameId(requestContext);
549         requestContext.setSubjectNameID(nameID);
550         // TODO handle encryption
551
552         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
553         subjectConfirmation.setMethod(confirmationMethod);
554
555         Subject subject = subjectBuilder.buildObject();
556         subject.setNameID(nameID);
557         subject.getSubjectConfirmations().add(subjectConfirmation);
558
559         return subject;
560     }
561
562     /**
563      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
564      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
565      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
566      * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
567      * the supported name formats.
568      * 
569      * @param requestContext current request context
570      * 
571      * @return the NameID appropriate for this request
572      * 
573      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
574      *             name ID attribute or because there are no supported name formats
575      */
576     protected NameID buildNameId(SAML2ProfileRequestContext requestContext) throws ProfileException {
577         if (log.isDebugEnabled()) {
578             log.debug("Building assertion NameID for principal/relying party:" + requestContext.getPrincipalName()
579                     + "/" + requestContext.getRelyingPartyId());
580         }
581         Map<String, BaseAttribute> principalAttributes = requestContext.getPrincipalAttributes();
582         List<String> supportedNameFormats = getNameFormats(requestContext);
583
584         if (log.isDebugEnabled()) {
585             log.debug("Supported NameID formats: " + supportedNameFormats);
586         }
587
588         if (principalAttributes == null || supportedNameFormats == null) {
589             log.error("No attributes for principal " + requestContext.getPrincipalName() 
590                     + " support constructions of NameID");
591             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
592                     "Unable to construct NameID"));
593             throw new ProfileException("No principal attributes support NameID construction");
594         }
595
596         try {
597             SAML2NameIDAttributeEncoder nameIdEncoder;
598             for (BaseAttribute<?> attribute : principalAttributes.values()) {
599                 for (AttributeEncoder encoder : attribute.getEncoders()) {
600                     if (encoder instanceof SAML2NameIDAttributeEncoder) {
601                         nameIdEncoder = (SAML2NameIDAttributeEncoder) encoder;
602                         if (supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
603                             if (log.isDebugEnabled()) {
604                                 log.debug("Using attribute " + attribute.getId() + " suppoting NameID format "
605                                         + nameIdEncoder.getNameFormat() + " to create the NameID for principal "
606                                         + requestContext.getPrincipalName());
607                             }
608                             return nameIdEncoder.encode(attribute);
609                         }
610                     }
611                 }
612             }
613             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
614             throw new ProfileException("No principal attribute supported encoding into the a supported name ID format.");
615         } catch (AttributeEncodingException e) {
616             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
617             throw new ProfileException("Unable to encode NameID attribute", e);
618         }
619     }
620
621     /**
622      * Gets the NameID format to use when creating NameIDs for the relying party.
623      * 
624      * @param requestContext current request context
625      * 
626      * @return list of nameID formats that may be used with the relying party
627      * 
628      * @throws ProfileException thrown if there is a problem determing the NameID format to use
629      */
630     protected List<String> getNameFormats(SAML2ProfileRequestContext requestContext) throws ProfileException {
631         ArrayList<String> nameFormats = new ArrayList<String>();
632
633         List<String> assertingPartySupportedFormats = getEntitySupportedFormats(requestContext
634                 .getAssertingPartyRoleMetadata());
635
636         String nameFormat = null;
637         if (requestContext.getSamlRequest() instanceof AuthnRequest) {
638             AuthnRequest authnRequest = (AuthnRequest) requestContext.getSamlRequest();
639             if (authnRequest.getNameIDPolicy() != null) {
640                 nameFormat = authnRequest.getNameIDPolicy().getFormat();
641                 if (assertingPartySupportedFormats.contains(nameFormat)) {
642                     nameFormats.add(nameFormat);
643                 } else {
644                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI,
645                             StatusCode.INVALID_NAMEID_POLICY_URI, "Format not supported: " + nameFormat));
646                     throw new ProfileException("NameID format required by relying party is not supported");
647                 }
648             }
649         }
650
651         if (nameFormats.isEmpty()) {
652             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(requestContext
653                     .getRelyingPartyRoleMetadata());
654
655             assertingPartySupportedFormats.retainAll(relyingPartySupportedFormats);
656             nameFormats.addAll(assertingPartySupportedFormats);
657         }
658         if (nameFormats.isEmpty()) {
659             nameFormats.add("urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified");
660         }
661
662         return nameFormats;
663     }
664
665     /**
666      * Gets the list of NameID formats supported for a given role.
667      * 
668      * @param role the role to get the list of supported NameID formats
669      * 
670      * @return list of supported NameID formats
671      */
672     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
673         List<NameIDFormat> nameIDFormats = null;
674
675         if (role instanceof SSODescriptor) {
676             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
677         } else if (role instanceof AuthnAuthorityDescriptor) {
678             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
679         } else if (role instanceof PDPDescriptor) {
680             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
681         } else if (role instanceof AttributeAuthorityDescriptor) {
682             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
683         }
684
685         ArrayList<String> supportedFormats = new ArrayList<String>();
686         if (nameIDFormats != null) {
687             for (NameIDFormat format : nameIDFormats) {
688                 supportedFormats.add(format.getFormat());
689             }
690         }
691
692         return supportedFormats;
693     }
694
695     /**
696      * Constructs an SAML response message carrying a request error.
697      * 
698      * @param requestContext current request context
699      * 
700      * @return the constructed error response
701      */
702     protected Response buildErrorResponse(SAML2ProfileRequestContext requestContext) {
703         Response samlResponse = responseBuilder.buildObject();
704         samlResponse.setIssueInstant(new DateTime());
705         populateStatusResponse(requestContext, samlResponse);
706
707         samlResponse.setStatus(requestContext.getFailureStatus());
708
709         return samlResponse;
710     }
711
712     /**
713      * Writes an aduit log entry indicating the successful response to the attribute request.
714      * 
715      * @param context current request context
716      */
717     protected void writeAuditLogEntry(SAML2ProfileRequestContext context) {
718         AuditLogEntry auditLogEntry = new AuditLogEntry();
719         auditLogEntry.setMessageProfile(getProfileId());
720         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
721         auditLogEntry.setPrincipalName(context.getPrincipalName());
722         auditLogEntry.setAssertingPartyId(context.getAssertingPartyId());
723         auditLogEntry.setRelyingPartyId(context.getRelyingPartyId());
724         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
725         auditLogEntry.setRequestId(context.getSamlRequest().getID());
726         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
727         auditLogEntry.setResponseId(context.getSamlResponse().getID());
728         if(context.getPrincipalAttributes() != null){
729             auditLogEntry.getReleasedAttributes().addAll(context.getPrincipalAttributes().keySet());
730         }
731         getAduitLog().log(Level.CRITICAL, auditLogEntry);
732     }
733
734     /**
735      * Contextual object used to accumlate information as profile requests are being processed.
736      * 
737      * @param <RequestType> type of SAML 2 request
738      * @param <ResponseType> type of SAML 2 response
739      * @param <ProfileConfigurationType> configuration type for this profile
740      */
741     protected class SAML2ProfileRequestContext<RequestType extends RequestAbstractType, ResponseType extends StatusResponseType, ProfileConfigurationType extends AbstractSAML2ProfileConfiguration>
742             extends SAMLProfileRequestContext {
743
744         /** SAML request message. */
745         private RequestType samlRequest;
746
747         /** SAML response message. */
748         private ResponseType samlResponse;
749
750         /** Request profile configuration. */
751         private ProfileConfigurationType profileConfiguration;
752
753         /** The NameID of the subject of this request. */
754         private NameID subjectNameID;
755
756         /** The request failure status. */
757         private Status failureStatus;
758
759         /**
760          * Constructor.
761          * 
762          * @param request current profile request
763          * @param response current profile response
764          */
765         public SAML2ProfileRequestContext(ProfileRequest<ServletRequest> request,
766                 ProfileResponse<ServletResponse> response) {
767             super(request, response);
768         }
769
770         /**
771          * Gets the NameID of the subject of this request.
772          * 
773          * @return NameID of the subject of this request
774          */
775         public NameID getSubjectNameID() {
776             return subjectNameID;
777         }
778
779         /**
780          * Sets the NameID of the subject of this request.
781          * 
782          * @param nameID NameID of the subject of this request
783          */
784         public void setSubjectNameID(NameID nameID) {
785             subjectNameID = nameID;
786         }
787
788         /**
789          * Gets the profile configuration for this request.
790          * 
791          * @return profile configuration for this request
792          */
793         public ProfileConfigurationType getProfileConfiguration() {
794             return profileConfiguration;
795         }
796
797         /**
798          * Sets the profile configuration for this request.
799          * 
800          * @param configuration profile configuration for this request
801          */
802         public void setProfileConfiguration(ProfileConfigurationType configuration) {
803             profileConfiguration = configuration;
804         }
805
806         /**
807          * Gets the SAML request message.
808          * 
809          * @return SAML request message
810          */
811         public RequestType getSamlRequest() {
812             return samlRequest;
813         }
814
815         /**
816          * Sets the SAML request message.
817          * 
818          * @param request SAML request message
819          */
820         public void setSamlRequest(RequestType request) {
821             samlRequest = request;
822         }
823
824         /**
825          * Gets the SAML response message.
826          * 
827          * @return SAML response message
828          */
829         public ResponseType getSamlResponse() {
830             return samlResponse;
831         }
832
833         /**
834          * Sets the SAML response message.
835          * 
836          * @param response SAML response message
837          */
838         public void setSamlResponse(ResponseType response) {
839             samlResponse = response;
840         }
841
842         /**
843          * Gets the status reflecting a request failure.
844          * 
845          * @return status reflecting a request failure
846          */
847         public Status getFailureStatus() {
848             return failureStatus;
849         }
850
851         /**
852          * Sets the status reflecting a request failure.
853          * 
854          * @param status status reflecting a request failure
855          */
856         public void setFailureStatus(Status status) {
857             failureStatus = status;
858         }
859     }
860 }