decrease verbosity and logging messge of error message logged when a name ID can...
[java-idp.git] / src / main / java / 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.Collection;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.joda.time.DateTime;
24 import org.opensaml.Configuration;
25 import org.opensaml.common.SAMLObjectBuilder;
26 import org.opensaml.common.SAMLVersion;
27 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28 import org.opensaml.common.xml.SAMLConstants;
29 import org.opensaml.saml2.core.Assertion;
30 import org.opensaml.saml2.core.AttributeQuery;
31 import org.opensaml.saml2.core.AttributeStatement;
32 import org.opensaml.saml2.core.Audience;
33 import org.opensaml.saml2.core.AudienceRestriction;
34 import org.opensaml.saml2.core.AuthnRequest;
35 import org.opensaml.saml2.core.Conditions;
36 import org.opensaml.saml2.core.Issuer;
37 import org.opensaml.saml2.core.NameID;
38 import org.opensaml.saml2.core.NameIDPolicy;
39 import org.opensaml.saml2.core.ProxyRestriction;
40 import org.opensaml.saml2.core.Response;
41 import org.opensaml.saml2.core.Statement;
42 import org.opensaml.saml2.core.Status;
43 import org.opensaml.saml2.core.StatusCode;
44 import org.opensaml.saml2.core.StatusMessage;
45 import org.opensaml.saml2.core.StatusResponseType;
46 import org.opensaml.saml2.core.Subject;
47 import org.opensaml.saml2.core.SubjectConfirmation;
48 import org.opensaml.saml2.core.SubjectConfirmationData;
49 import org.opensaml.saml2.encryption.Encrypter;
50 import org.opensaml.saml2.encryption.Encrypter.KeyPlacement;
51 import org.opensaml.saml2.metadata.Endpoint;
52 import org.opensaml.saml2.metadata.SPSSODescriptor;
53 import org.opensaml.security.MetadataCredentialResolver;
54 import org.opensaml.security.MetadataCriteria;
55 import org.opensaml.ws.message.encoder.MessageEncodingException;
56 import org.opensaml.ws.transport.http.HTTPInTransport;
57 import org.opensaml.xml.XMLObjectBuilder;
58 import org.opensaml.xml.encryption.EncryptionException;
59 import org.opensaml.xml.encryption.EncryptionParameters;
60 import org.opensaml.xml.encryption.KeyEncryptionParameters;
61 import org.opensaml.xml.io.Marshaller;
62 import org.opensaml.xml.io.MarshallingException;
63 import org.opensaml.xml.security.CriteriaSet;
64 import org.opensaml.xml.security.SecurityConfiguration;
65 import org.opensaml.xml.security.SecurityException;
66 import org.opensaml.xml.security.SecurityHelper;
67 import org.opensaml.xml.security.credential.Credential;
68 import org.opensaml.xml.security.credential.UsageType;
69 import org.opensaml.xml.security.criteria.EntityIDCriteria;
70 import org.opensaml.xml.security.criteria.UsageCriteria;
71 import org.opensaml.xml.signature.Signature;
72 import org.opensaml.xml.signature.SignatureException;
73 import org.opensaml.xml.signature.Signer;
74 import org.opensaml.xml.util.DatatypeHelper;
75 import org.opensaml.xml.util.Pair;
76 import org.slf4j.Logger;
77 import org.slf4j.LoggerFactory;
78 import org.slf4j.helpers.MessageFormatter;
79
80 import edu.internet2.middleware.shibboleth.common.attribute.AttributeRequestException;
81 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
82 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncodingException;
83 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAML2NameIDEncoder;
84 import edu.internet2.middleware.shibboleth.common.attribute.provider.SAML2AttributeAuthority;
85 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
86 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
87 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
88 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
89 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.saml2.AbstractSAML2ProfileConfiguration;
90 import edu.internet2.middleware.shibboleth.idp.profile.AbstractSAMLProfileHandler;
91 import edu.internet2.middleware.shibboleth.idp.session.ServiceInformation;
92 import edu.internet2.middleware.shibboleth.idp.session.Session;
93
94 /** Common implementation details for profile handlers. */
95 public abstract class AbstractSAML2ProfileHandler extends AbstractSAMLProfileHandler {
96
97     /** SAML Version for this profile handler. */
98     public static final SAMLVersion SAML_VERSION = SAMLVersion.VERSION_20;
99
100     /** Class logger. */
101     private Logger log = LoggerFactory.getLogger(AbstractSAML2ProfileHandler.class);
102
103     /** For building response. */
104     private SAMLObjectBuilder<Response> responseBuilder;
105
106     /** For building status. */
107     private SAMLObjectBuilder<Status> statusBuilder;
108
109     /** For building statuscode. */
110     private SAMLObjectBuilder<StatusCode> statusCodeBuilder;
111
112     /** For building StatusMessages. */
113     private SAMLObjectBuilder<StatusMessage> statusMessageBuilder;
114
115     /** For building assertion. */
116     private SAMLObjectBuilder<Assertion> assertionBuilder;
117
118     /** For building issuer. */
119     private SAMLObjectBuilder<Issuer> issuerBuilder;
120
121     /** For building subject. */
122     private SAMLObjectBuilder<Subject> subjectBuilder;
123
124     /** For building subject confirmation. */
125     private SAMLObjectBuilder<SubjectConfirmation> subjectConfirmationBuilder;
126
127     /** For building subject confirmation data. */
128     private SAMLObjectBuilder<SubjectConfirmationData> subjectConfirmationDataBuilder;
129
130     /** For building conditions. */
131     private SAMLObjectBuilder<Conditions> conditionsBuilder;
132
133     /** For building audience restriction. */
134     private SAMLObjectBuilder<AudienceRestriction> audienceRestrictionBuilder;
135
136     /** For building proxy restrictions. */
137     private SAMLObjectBuilder<ProxyRestriction> proxyRestrictionBuilder;
138
139     /** For building audience. */
140     private SAMLObjectBuilder<Audience> audienceBuilder;
141
142     /** For building signature. */
143     private XMLObjectBuilder<Signature> signatureBuilder;
144
145     /** Constructor. */
146     @SuppressWarnings("unchecked")
147     protected AbstractSAML2ProfileHandler() {
148         super();
149
150         responseBuilder = (SAMLObjectBuilder<Response>) getBuilderFactory().getBuilder(Response.DEFAULT_ELEMENT_NAME);
151         statusBuilder = (SAMLObjectBuilder<Status>) getBuilderFactory().getBuilder(Status.DEFAULT_ELEMENT_NAME);
152         statusCodeBuilder = (SAMLObjectBuilder<StatusCode>) getBuilderFactory().getBuilder(
153                 StatusCode.DEFAULT_ELEMENT_NAME);
154         statusMessageBuilder = (SAMLObjectBuilder<StatusMessage>) getBuilderFactory().getBuilder(
155                 StatusMessage.DEFAULT_ELEMENT_NAME);
156         issuerBuilder = (SAMLObjectBuilder<Issuer>) getBuilderFactory().getBuilder(Issuer.DEFAULT_ELEMENT_NAME);
157         assertionBuilder = (SAMLObjectBuilder<Assertion>) getBuilderFactory()
158                 .getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
159         subjectBuilder = (SAMLObjectBuilder<Subject>) getBuilderFactory().getBuilder(Subject.DEFAULT_ELEMENT_NAME);
160         subjectConfirmationBuilder = (SAMLObjectBuilder<SubjectConfirmation>) getBuilderFactory().getBuilder(
161                 SubjectConfirmation.DEFAULT_ELEMENT_NAME);
162         subjectConfirmationDataBuilder = (SAMLObjectBuilder<SubjectConfirmationData>) getBuilderFactory().getBuilder(
163                 SubjectConfirmationData.DEFAULT_ELEMENT_NAME);
164         conditionsBuilder = (SAMLObjectBuilder<Conditions>) getBuilderFactory().getBuilder(
165                 Conditions.DEFAULT_ELEMENT_NAME);
166         audienceRestrictionBuilder = (SAMLObjectBuilder<AudienceRestriction>) getBuilderFactory().getBuilder(
167                 AudienceRestriction.DEFAULT_ELEMENT_NAME);
168         proxyRestrictionBuilder = (SAMLObjectBuilder<ProxyRestriction>) getBuilderFactory().getBuilder(
169                 ProxyRestriction.DEFAULT_ELEMENT_NAME);
170         audienceBuilder = (SAMLObjectBuilder<Audience>) getBuilderFactory().getBuilder(Audience.DEFAULT_ELEMENT_NAME);
171         signatureBuilder = (XMLObjectBuilder<Signature>) getBuilderFactory().getBuilder(Signature.DEFAULT_ELEMENT_NAME);
172     }
173
174     /** {@inheritDoc} */
175     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
176         BaseSAML2ProfileRequestContext saml2Request = (BaseSAML2ProfileRequestContext) requestContext;
177         try {
178             super.populateRequestContext(requestContext);
179         } catch (ProfileException e) {
180             if (saml2Request.getFailureStatus() == null) {
181                 saml2Request.setFailureStatus(buildStatus(StatusCode.REQUESTER_URI, null, e.getMessage()));
182             }
183             throw e;
184         }
185     }
186
187     /**
188      * Populates the request context with the information about the user.
189      * 
190      * This method requires the the following request context properties to be populated: inbound message transport,
191      * relying party ID
192      * 
193      * This methods populates the following request context properties: user's session, user's principal name, and
194      * service authentication method
195      * 
196      * @param requestContext current request context
197      */
198     protected void populateUserInformation(BaseSAMLProfileRequestContext requestContext) {
199         Session userSession = getUserSession(requestContext.getInboundMessageTransport());
200         if (userSession == null) {
201             NameID subject = (NameID) requestContext.getSubjectNameIdentifier();
202             if (subject != null && subject.getValue() != null) {
203                 userSession = getUserSession(subject.getValue());
204             }
205         }
206
207         if (userSession != null) {
208             requestContext.setUserSession(userSession);
209             requestContext.setPrincipalName(userSession.getPrincipalName());
210             ServiceInformation serviceInfo = userSession.getServicesInformation().get(
211                     requestContext.getInboundMessageIssuer());
212             if (serviceInfo != null) {
213                 requestContext.setPrincipalAuthenticationMethod(serviceInfo.getAuthenticationMethod()
214                         .getAuthenticationMethod());
215             }
216         }
217     }
218
219     /**
220      * Checks that the SAML major version for a request is 2.
221      * 
222      * @param requestContext current request context containing the SAML message
223      * 
224      * @throws ProfileException thrown if the major version of the SAML request is not 2
225      */
226     protected void checkSamlVersion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
227         SAMLVersion version = requestContext.getInboundSAMLMessage().getVersion();
228         if (version.getMajorVersion() < 2) {
229             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
230                     StatusCode.REQUEST_VERSION_TOO_LOW_URI, null));
231             throw new ProfileException("SAML request version too low");
232         } else if (version.getMajorVersion() > 2 || version.getMinorVersion() > 0) {
233             requestContext.setFailureStatus(buildStatus(StatusCode.VERSION_MISMATCH_URI,
234                     StatusCode.REQUEST_VERSION_TOO_HIGH_URI, null));
235             throw new ProfileException("SAML request version too high");
236         }
237     }
238
239     /**
240      * Builds a response to the attribute query within the request context.
241      * 
242      * @param requestContext current request context
243      * @param subjectConfirmationMethod confirmation method used for the subject
244      * @param statements the statements to include in the response
245      * 
246      * @return the built response
247      * 
248      * @throws ProfileException thrown if there is a problem creating the SAML response
249      */
250     protected Response buildResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
251             String subjectConfirmationMethod, List<Statement> statements) throws ProfileException {
252
253         DateTime issueInstant = new DateTime();
254
255         Response samlResponse = responseBuilder.buildObject();
256         samlResponse.setIssueInstant(issueInstant);
257         populateStatusResponse(requestContext, samlResponse);
258
259         Assertion assertion = null;
260         if (statements != null && !statements.isEmpty()) {
261             assertion = buildAssertion(requestContext, issueInstant);
262             assertion.getStatements().addAll(statements);
263             assertion.setSubject(buildSubject(requestContext, subjectConfirmationMethod, issueInstant));
264
265             postProcessAssertion(requestContext, assertion);
266
267             signAssertion(requestContext, assertion);
268
269             if (isEncryptAssertion(requestContext)) {
270                 log.debug("Attempting to encrypt assertion to relying party '{}'", requestContext
271                         .getInboundMessageIssuer());
272                 try {
273                     Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
274                     samlResponse.getEncryptedAssertions().add(encrypter.encrypt(assertion));
275                 } catch (SecurityException e) {
276                     log.error("Unable to construct encrypter", e);
277                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
278                             "Unable to encrypt assertion"));
279                     throw new ProfileException("Unable to construct encrypter", e);
280                 } catch (EncryptionException e) {
281                     log.error("Unable to encrypt assertion", e);
282                     requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null,
283                             "Unable to encrypt assertion"));
284                     throw new ProfileException("Unable to encrypt assertion", e);
285                 }
286             } else {
287                 samlResponse.getAssertions().add(assertion);
288             }
289         }
290
291         Status status = buildStatus(StatusCode.SUCCESS_URI, null, null);
292         samlResponse.setStatus(status);
293
294         postProcessResponse(requestContext, samlResponse);
295
296         return samlResponse;
297     }
298
299     /**
300      * Determine whether issued assertions should be encrypted.
301      * 
302      * @param requestContext the current request context
303      * @return true if assertions should be encrypted, false otherwise
304      * @throws ProfileException if there is a problem determining whether assertions should be encrypted
305      */
306     protected boolean isEncryptAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
307             throws ProfileException {
308
309         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
310         try {
311             return requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.always
312                     || (requestContext.getProfileConfiguration().getEncryptAssertion() == CryptoOperationRequirementLevel.conditional && !encoder
313                             .providesMessageConfidentiality(requestContext));
314         } catch (MessageEncodingException e) {
315             log.error("Unable to determine if outbound encoding '{}' can provide confidentiality", encoder
316                     .getBindingURI());
317             throw new ProfileException("Unable to determine if assertions should be encrypted");
318         }
319     }
320
321     /**
322      * Extension point for for subclasses to post-process the Response before it is signed and encoded.
323      * 
324      * @param requestContext the current request context
325      * @param samlResponse the SAML Response being built
326      * 
327      * @throws ProfileException if there was an error processing the response
328      */
329     protected void postProcessResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Response samlResponse)
330             throws ProfileException {
331     }
332
333     /**
334      * Extension point for for subclasses to post-process the Assertion before it is signed and encrypted.
335      * 
336      * @param requestContext the current request context
337      * @param assertion the SAML Assertion being built
338      * 
339      * @throws ProfileException if there is an error processing the assertion
340      */
341     protected void postProcessAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
342             throws ProfileException {
343     }
344
345     /**
346      * Builds a basic assertion with its id, issue instant, SAML version, issuer, subject, and conditions populated.
347      * 
348      * @param requestContext current request context
349      * @param issueInstant time to use as assertion issue instant
350      * 
351      * @return the built assertion
352      */
353     protected Assertion buildAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
354         Assertion assertion = assertionBuilder.buildObject();
355         assertion.setID(getIdGenerator().generateIdentifier());
356         assertion.setIssueInstant(issueInstant);
357         assertion.setVersion(SAMLVersion.VERSION_20);
358         assertion.setIssuer(buildEntityIssuer(requestContext));
359
360         Conditions conditions = buildConditions(requestContext, issueInstant);
361         assertion.setConditions(conditions);
362
363         return assertion;
364     }
365
366     /**
367      * Creates an {@link Issuer} populated with information about the relying party.
368      * 
369      * @param requestContext current request context
370      * 
371      * @return the built issuer
372      */
373     protected Issuer buildEntityIssuer(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
374         Issuer issuer = issuerBuilder.buildObject();
375         issuer.setFormat(Issuer.ENTITY);
376         issuer.setValue(requestContext.getLocalEntityId());
377
378         return issuer;
379     }
380
381     /**
382      * Builds a SAML assertion condition set. The following fields are set; not before, not on or after, audience
383      * restrictions, and proxy restrictions.
384      * 
385      * @param requestContext current request context
386      * @param issueInstant timestamp the assertion was created
387      * 
388      * @return constructed conditions
389      */
390     protected Conditions buildConditions(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, DateTime issueInstant) {
391         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
392
393         Conditions conditions = conditionsBuilder.buildObject();
394         conditions.setNotBefore(issueInstant);
395         conditions.setNotOnOrAfter(issueInstant.plus(profileConfig.getAssertionLifetime()));
396
397         Collection<String> audiences;
398
399         // add audience restrictions
400         AudienceRestriction audienceRestriction = audienceRestrictionBuilder.buildObject();
401         // TODO we should only do this for certain outgoing bindings, not globally
402         Audience audience = audienceBuilder.buildObject();
403         audience.setAudienceURI(requestContext.getInboundMessageIssuer());
404         audienceRestriction.getAudiences().add(audience);
405         audiences = profileConfig.getAssertionAudiences();
406         if (audiences != null && audiences.size() > 0) {
407             for (String audienceUri : audiences) {
408                 audience = audienceBuilder.buildObject();
409                 audience.setAudienceURI(audienceUri);
410                 audienceRestriction.getAudiences().add(audience);
411             }
412         }
413         conditions.getAudienceRestrictions().add(audienceRestriction);
414
415         // add proxy restrictions
416         audiences = profileConfig.getProxyAudiences();
417         if (audiences != null && audiences.size() > 0) {
418             ProxyRestriction proxyRestriction = proxyRestrictionBuilder.buildObject();
419             for (String audienceUri : audiences) {
420                 audience = audienceBuilder.buildObject();
421                 audience.setAudienceURI(audienceUri);
422                 proxyRestriction.getAudiences().add(audience);
423             }
424
425             proxyRestriction.setProxyCount(profileConfig.getProxyCount());
426             conditions.getConditions().add(proxyRestriction);
427         }
428
429         return conditions;
430     }
431
432     /**
433      * Populates the response's id, in response to, issue instant, version, and issuer properties.
434      * 
435      * @param requestContext current request context
436      * @param response the response to populate
437      */
438     protected void populateStatusResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
439             StatusResponseType response) {
440         response.setID(getIdGenerator().generateIdentifier());
441
442         response.setInResponseTo(requestContext.getInboundSAMLMessageId());
443         response.setIssuer(buildEntityIssuer(requestContext));
444
445         response.setVersion(SAMLVersion.VERSION_20);
446     }
447
448     /**
449      * Resolves the attributes for the principal.
450      * 
451      * @param requestContext current request context
452      * 
453      * @throws ProfileException thrown if there is a problem resolved attributes
454      */
455     protected void resolveAttributes(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
456         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
457         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
458         try {
459             log.debug("Resolving attributes for principal '{}' for SAML request from relying party '{}'",
460                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
461             Map<String, BaseAttribute> principalAttributes = attributeAuthority.getAttributes(requestContext);
462
463             requestContext.setAttributes(principalAttributes);
464         } catch (AttributeRequestException e) {
465             log
466                     .warn(
467                             "Error resolving attributes for principal '{}'.  No name identifier or attribute statement will be included in response",
468                             requestContext.getPrincipalName());
469         }
470     }
471
472     /**
473      * Executes a query for attributes and builds a SAML attribute statement from the results.
474      * 
475      * @param requestContext current request context
476      * 
477      * @return attribute statement resulting from the query
478      * 
479      * @throws ProfileException thrown if there is a problem making the query
480      */
481     protected AttributeStatement buildAttributeStatement(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext)
482             throws ProfileException {
483         if (requestContext.getAttributes() == null) {
484             return null;
485         }
486
487         log.debug("Creating attribute statement in response to SAML request '{}' from relying party '{}'",
488                 requestContext.getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
489
490         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
491         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
492         try {
493             if (requestContext.getInboundSAMLMessage() instanceof AttributeQuery) {
494                 return attributeAuthority.buildAttributeStatement((AttributeQuery) requestContext
495                         .getInboundSAMLMessage(), requestContext.getAttributes().values());
496             } else {
497                 return attributeAuthority.buildAttributeStatement(null, requestContext.getAttributes().values());
498             }
499         } catch (AttributeRequestException e) {
500             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Error resolving attributes"));
501             String msg = MessageFormatter.format("Error encoding attributes for principal '{}'", requestContext
502                     .getPrincipalName());
503             log.error(msg, e);
504             throw new ProfileException(msg, e);
505         }
506     }
507
508     /**
509      * Resolves the principal name of the subject of the request.
510      * 
511      * @param requestContext current request context
512      * 
513      * @throws ProfileException thrown if the principal name can not be resolved
514      */
515     protected void resolvePrincipal(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
516         AbstractSAML2ProfileConfiguration profileConfiguration = requestContext.getProfileConfiguration();
517         if (profileConfiguration == null) {
518             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.REQUEST_DENIED_URI,
519                     "Error resolving principal"));
520             String msg = MessageFormatter.format(
521                     "Unable to resolve principal, no SAML 2 profile configuration for relying party '{}'",
522                     requestContext.getInboundMessageIssuer());
523             log.warn(msg);
524             throw new ProfileException(msg);
525         }
526         SAML2AttributeAuthority attributeAuthority = profileConfiguration.getAttributeAuthority();
527         log.debug("Resolving principal name for subject of SAML request '{}' from relying party '{}'", requestContext
528                 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
529
530         try {
531             String principal = attributeAuthority.getPrincipal(requestContext);
532             requestContext.setPrincipalName(principal);
533         } catch (AttributeRequestException e) {
534             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.UNKNOWN_PRINCIPAL_URI,
535                     "Error resolving principal"));
536             String msg = MessageFormatter.arrayFormat(
537                     "Error resolving principal name for SAML request '{}' from relying party '{}'. Cause: {}", new Object[]{requestContext
538                             .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer(),e.getMessage()});
539             log.warn(msg);
540             throw new ProfileException(msg, e);
541         }
542     }
543
544     /**
545      * Signs the given assertion if either the current profile configuration or the relying party configuration contains
546      * signing credentials.
547      * 
548      * @param requestContext current request context
549      * @param assertion assertion to sign
550      * 
551      * @throws ProfileException thrown if the metadata can not be located for the relying party or, if signing is
552      *             required, if a signing credential is not configured
553      */
554     protected void signAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, Assertion assertion)
555             throws ProfileException {
556         log.debug("Determining if SAML assertion to relying party '{}' should be signed", requestContext
557                 .getInboundMessageIssuer());
558
559         boolean signAssertion = isSignAssertion(requestContext);
560
561         if (!signAssertion) {
562             return;
563         }
564
565         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
566
567         log.debug("Determining signing credntial for assertion to relying party '{}'", requestContext
568                 .getInboundMessageIssuer());
569         Credential signatureCredential = profileConfig.getSigningCredential();
570         if (signatureCredential == null) {
571             signatureCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
572         }
573
574         if (signatureCredential == null) {
575             String msg = MessageFormatter.format(
576                     "No signing credential is specified for relying party configuration '{}'", requestContext
577                             .getRelyingPartyConfiguration().getProviderId());
578             log.warn(msg);
579             throw new ProfileException(msg);
580         }
581
582         log.debug("Signing assertion to relying party {}", requestContext.getInboundMessageIssuer());
583         Signature signature = signatureBuilder.buildObject(Signature.DEFAULT_ELEMENT_NAME);
584
585         signature.setSigningCredential(signatureCredential);
586         try {
587             // TODO pull SecurityConfiguration from SAMLMessageContext? needs to be added
588             // TODO how to pull what keyInfoGenName to use?
589             SecurityHelper.prepareSignatureParams(signature, signatureCredential, null, null);
590         } catch (SecurityException e) {
591             String msg = "Error preparing signature for signing";
592             log.error(msg);
593             throw new ProfileException(msg, e);
594         }
595
596         assertion.setSignature(signature);
597
598         Marshaller assertionMarshaller = Configuration.getMarshallerFactory().getMarshaller(assertion);
599         try {
600             assertionMarshaller.marshall(assertion);
601             Signer.signObject(signature);
602         } catch (MarshallingException e) {
603             String errMsg = "Unable to marshall assertion for signing";
604             log.error(errMsg, e);
605             throw new ProfileException(errMsg, e);
606         } catch (SignatureException e) {
607             String msg = "Unable to sign assertion";
608             log.error(msg, e);
609             throw new ProfileException(msg, e);
610         }
611     }
612
613     /**
614      * Determine whether issued assertions should be signed.
615      * 
616      * @param requestContext the current request context
617      * @return true if assertions should be signed, false otherwise
618      * @throws ProfileException if there is a problem determining whether assertions should be signed
619      */
620     protected boolean isSignAssertion(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
621
622         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
623         AbstractSAML2ProfileConfiguration profileConfig = requestContext.getProfileConfiguration();
624
625         try {
626             boolean signAssertion = profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.always
627                     || (profileConfig.getSignAssertions() == CryptoOperationRequirementLevel.conditional && !encoder
628                             .providesMessageIntegrity(requestContext));
629
630             log.debug("IdP relying party configuration '{}' indicates to sign assertions: {}", requestContext
631                     .getRelyingPartyConfiguration().getRelyingPartyId(), signAssertion);
632
633             if (!signAssertion && requestContext.getPeerEntityRoleMetadata() instanceof SPSSODescriptor) {
634                 SPSSODescriptor ssoDescriptor = (SPSSODescriptor) requestContext.getPeerEntityRoleMetadata();
635                 if (ssoDescriptor.getWantAssertionsSigned() != null) {
636                     signAssertion = ssoDescriptor.getWantAssertionsSigned().booleanValue();
637                     log.debug("Entity metadata for relying party '{} 'indicates to sign assertions: {}", requestContext
638                             .getInboundMessageIssuer(), signAssertion);
639                 }
640             }
641
642             return signAssertion;
643         } catch (MessageEncodingException e) {
644             log.error("Unable to determine if outbound encoding '{}' provides message integrity protection", encoder
645                     .getBindingURI());
646             throw new ProfileException("Unable to determine if outbound assertion should be signed");
647         }
648     }
649
650     /**
651      * Build a status message, with an optional second-level failure message.
652      * 
653      * @param topLevelCode The top-level status code. Should be from saml-core-2.0-os, sec. 3.2.2.2
654      * @param secondLevelCode An optional second-level failure code. Should be from saml-core-2.0-is, sec 3.2.2.2. If
655      *            null, no second-level Status element will be set.
656      * @param failureMessage An optional second-level failure message
657      * 
658      * @return a Status object.
659      */
660     protected Status buildStatus(String topLevelCode, String secondLevelCode, String failureMessage) {
661         Status status = statusBuilder.buildObject();
662
663         StatusCode statusCode = statusCodeBuilder.buildObject();
664         statusCode.setValue(DatatypeHelper.safeTrimOrNullString(topLevelCode));
665         status.setStatusCode(statusCode);
666
667         if (secondLevelCode != null) {
668             StatusCode secondLevelStatusCode = statusCodeBuilder.buildObject();
669             secondLevelStatusCode.setValue(DatatypeHelper.safeTrimOrNullString(secondLevelCode));
670             statusCode.setStatusCode(secondLevelStatusCode);
671         }
672
673         if (failureMessage != null) {
674             StatusMessage msg = statusMessageBuilder.buildObject();
675             msg.setMessage(failureMessage);
676             status.setStatusMessage(msg);
677         }
678
679         return status;
680     }
681
682     /**
683      * Builds the SAML subject for the user for the service provider.
684      * 
685      * @param requestContext current request context
686      * @param confirmationMethod subject confirmation method used for the subject
687      * @param issueInstant instant the subject confirmation data should reflect for issuance
688      * 
689      * @return SAML subject for the user for the service provider
690      * 
691      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
692      *             name ID attribute or because there are no supported name formats
693      */
694     protected Subject buildSubject(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext, String confirmationMethod,
695             DateTime issueInstant) throws ProfileException {
696         Subject subject = subjectBuilder.buildObject();
697         subject.getSubjectConfirmations().add(
698                 buildSubjectConfirmation(requestContext, confirmationMethod, issueInstant));
699
700         NameID nameID = buildNameId(requestContext);
701         if (nameID == null) {
702             return subject;
703         }
704
705         requestContext.setSubjectNameIdentifier(nameID);
706
707         if (isEncryptNameID(requestContext)) {
708             log.debug("Attempting to encrypt NameID to relying party '{}'", requestContext.getInboundMessageIssuer());
709             try {
710                 Encrypter encrypter = getEncrypter(requestContext.getInboundMessageIssuer());
711                 subject.setEncryptedID(encrypter.encrypt(nameID));
712             } catch (SecurityException e) {
713                 log.error("Unable to construct encrypter", e);
714                 requestContext
715                         .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt NameID"));
716                 throw new ProfileException("Unable to construct encrypter", e);
717             } catch (EncryptionException e) {
718                 log.error("Unable to encrypt NameID", e);
719                 requestContext
720                         .setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to encrypt NameID"));
721                 throw new ProfileException("Unable to encrypt NameID", e);
722             }
723         } else {
724             subject.setNameID(nameID);
725         }
726
727         return subject;
728     }
729
730     /**
731      * Determine whether NameID's should be encrypted.
732      * 
733      * @param requestContext the current request context
734      * @return true if NameID's should be encrypted, false otherwise
735      * @throws ProfileException if there is a problem determining whether NameID's should be encrypted
736      */
737     protected boolean isEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
738
739         boolean nameIdEncRequiredByAuthnRequest = isRequestRequiresEncryptNameID(requestContext);
740
741         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
742         boolean nameIdEncRequiredByConfig = false;
743         try {
744             nameIdEncRequiredByConfig = requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.always
745                     || (requestContext.getProfileConfiguration().getEncryptNameID() == CryptoOperationRequirementLevel.conditional && !encoder
746                             .providesMessageConfidentiality(requestContext));
747         } catch (MessageEncodingException e) {
748             String msg = MessageFormatter.format(
749                     "Unable to determine if outbound encoding '{}' provides message confidentiality protection",
750                     encoder.getBindingURI());
751             log.error(msg);
752             throw new ProfileException(msg);
753         }
754
755         return nameIdEncRequiredByAuthnRequest || nameIdEncRequiredByConfig;
756     }
757
758     /**
759      * Determine whether information in the SAML request requires the issued NameID to be encrypted.
760      * 
761      * @param requestContext the current request context
762      * @return true if the request indicates NameID encryption is required, false otherwise
763      */
764     protected boolean isRequestRequiresEncryptNameID(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
765         boolean nameIdEncRequiredByAuthnRequest = false;
766         if (requestContext.getInboundSAMLMessage() instanceof AuthnRequest) {
767             AuthnRequest authnRequest = (AuthnRequest) requestContext.getInboundSAMLMessage();
768             NameIDPolicy policy = authnRequest.getNameIDPolicy();
769             if (policy != null && DatatypeHelper.safeEquals(policy.getFormat(), NameID.ENCRYPTED)) {
770                 nameIdEncRequiredByAuthnRequest = true;
771             }
772         }
773         return nameIdEncRequiredByAuthnRequest;
774     }
775
776     /**
777      * Builds the SubjectConfirmation appropriate for this request.
778      * 
779      * @param requestContext current request context
780      * @param confirmationMethod confirmation method to use for the request
781      * @param issueInstant issue instant of the response
782      * 
783      * @return the constructed subject confirmation
784      */
785     protected SubjectConfirmation buildSubjectConfirmation(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext,
786             String confirmationMethod, DateTime issueInstant) {
787         SubjectConfirmationData confirmationData = subjectConfirmationDataBuilder.buildObject();
788         HTTPInTransport inTransport = (HTTPInTransport) requestContext.getInboundMessageTransport();
789         confirmationData.setAddress(inTransport.getPeerAddress());
790         confirmationData.setInResponseTo(requestContext.getInboundSAMLMessageId());
791         confirmationData.setNotOnOrAfter(issueInstant.plus(requestContext.getProfileConfiguration()
792                 .getAssertionLifetime()));
793
794         Endpoint relyingPartyEndpoint = requestContext.getPeerEntityEndpoint();
795         if (relyingPartyEndpoint != null) {
796             if (relyingPartyEndpoint.getResponseLocation() != null) {
797                 confirmationData.setRecipient(relyingPartyEndpoint.getResponseLocation());
798             } else {
799                 confirmationData.setRecipient(relyingPartyEndpoint.getLocation());
800             }
801         }
802
803         SubjectConfirmation subjectConfirmation = subjectConfirmationBuilder.buildObject();
804         subjectConfirmation.setMethod(confirmationMethod);
805         subjectConfirmation.setSubjectConfirmationData(confirmationData);
806
807         return subjectConfirmation;
808     }
809
810     /**
811      * Builds a NameID appropriate for this request. NameIDs are built by inspecting the SAML request and metadata,
812      * picking a name format that was requested by the relying party or is mutually supported by both the relying party
813      * and asserting party as described in their metadata entries. Once a set of supported name formats is determined
814      * the principals attributes are inspected for an attribute supported an attribute encoder whose category is one of
815      * the supported name formats.
816      * 
817      * @param requestContext current request context
818      * 
819      * @return the NameID appropriate for this request
820      * 
821      * @throws ProfileException thrown if a NameID can not be created either because there was a problem encoding the
822      *             name ID attribute or because there are no supported name formats
823      */
824     protected NameID buildNameId(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) throws ProfileException {
825         log.debug("Attemping to build NameID for principal '{}' in response to request from relying party '{}",
826                 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
827         
828         Pair<BaseAttribute, SAML2NameIDEncoder> nameIdAttributeAndEncoder = null;
829         try {
830             nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(SAML2NameIDEncoder.class, requestContext);
831         } catch (ProfileException e) {
832             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, StatusCode.INVALID_NAMEID_POLICY_URI,
833                     "Required NameID format not supported"));
834             throw e;
835         }
836
837         BaseAttribute<?> nameIdAttribute = nameIdAttributeAndEncoder.getFirst();
838         SAML2NameIDEncoder nameIdEncoder = nameIdAttributeAndEncoder.getSecond();
839
840         log.debug("Using attribute '{}' supporting NameID format '{}' to create the NameID for relying party '{}'",
841                 new Object[] { nameIdAttribute.getId(), nameIdEncoder.getNameFormat(),
842                         requestContext.getInboundMessageIssuer(), });
843         try {
844             // build the actual NameID
845             NameID nameId = nameIdEncoder.encode(nameIdAttribute);
846             nameId.setNameQualifier(requestContext.getRelyingPartyConfiguration().getProviderId());
847             return nameId;
848         } catch (AttributeEncodingException e) {
849             log.error("Unable to encode NameID attribute", e);
850             requestContext.setFailureStatus(buildStatus(StatusCode.RESPONDER_URI, null, "Unable to construct NameID"));
851             throw new ProfileException("Unable to encode NameID attribute", e);
852         }
853     }
854
855     /**
856      * Constructs an SAML response message carrying a request error.
857      * 
858      * @param requestContext current request context
859      * 
860      * @return the constructed error response
861      */
862     protected Response buildErrorResponse(BaseSAML2ProfileRequestContext<?, ?, ?> requestContext) {
863         Response samlResponse = responseBuilder.buildObject();
864         samlResponse.setIssueInstant(new DateTime());
865         populateStatusResponse(requestContext, samlResponse);
866
867         samlResponse.setStatus(requestContext.getFailureStatus());
868
869         return samlResponse;
870     }
871
872     /**
873      * Gets an encrypter that may be used encrypt content to a given peer.
874      * 
875      * @param peerEntityId entity ID of the peer
876      * 
877      * @return encrypter that may be used encrypt content to a given peer
878      * 
879      * @throws SecurityException thrown if there is a problem constructing the encrypter. This normally occurs if the
880      *             key encryption credential for the peer can not be resolved or a required encryption algorithm is not
881      *             supported by the VM's JCE.
882      */
883     protected Encrypter getEncrypter(String peerEntityId) throws SecurityException {
884         SecurityConfiguration securityConfiguration = Configuration.getGlobalSecurityConfiguration();
885
886         EncryptionParameters dataEncParams = SecurityHelper
887                 .buildDataEncryptionParams(null, securityConfiguration, null);
888
889         Credential keyEncryptionCredential = getKeyEncryptionCredential(peerEntityId);
890         if (keyEncryptionCredential == null) {
891             log.error("Could not resolve a key encryption credential for peer entity: {}", peerEntityId);
892             throw new SecurityException("Could not resolve key encryption credential");
893         }
894         String wrappedJCAKeyAlgorithm = SecurityHelper.getKeyAlgorithmFromURI(dataEncParams.getAlgorithm());
895         KeyEncryptionParameters keyEncParams = SecurityHelper.buildKeyEncryptionParams(keyEncryptionCredential,
896                 wrappedJCAKeyAlgorithm, securityConfiguration, null, null);
897
898         Encrypter encrypter = new Encrypter(dataEncParams, keyEncParams);
899         encrypter.setKeyPlacement(KeyPlacement.INLINE);
900         return encrypter;
901     }
902
903     /**
904      * Gets the credential that can be used to encrypt encryption keys for a peer.
905      * 
906      * @param peerEntityId entity ID of the peer
907      * 
908      * @return credential that can be used to encrypt encryption keys for a peer
909      * 
910      * @throws SecurityException thrown if there is a problem resolving the credential from the peer's metadata
911      */
912     protected Credential getKeyEncryptionCredential(String peerEntityId) throws SecurityException {
913         MetadataCredentialResolver kekCredentialResolver = new MetadataCredentialResolver(getMetadataProvider());
914
915         CriteriaSet criteriaSet = new CriteriaSet();
916         criteriaSet.add(new EntityIDCriteria(peerEntityId));
917         criteriaSet.add(new MetadataCriteria(SPSSODescriptor.DEFAULT_ELEMENT_NAME, SAMLConstants.SAML20P_NS));
918         criteriaSet.add(new UsageCriteria(UsageType.ENCRYPTION));
919
920         return kekCredentialResolver.resolveSingle(criteriaSet);
921     }
922
923     /**
924      * Writes an audit log entry indicating the successful response to the attribute request.
925      * 
926      * @param context current request context
927      */
928     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
929         SAML2AuditLogEntry auditLogEntry = new SAML2AuditLogEntry();
930         auditLogEntry.setSAMLResponse((StatusResponseType) context.getOutboundSAMLMessage());
931         auditLogEntry.setMessageProfile(getProfileId());
932         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
933         auditLogEntry.setPrincipalName(context.getPrincipalName());
934         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
935         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
936         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
937         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
938         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
939         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
940         if (context.getReleasedAttributes() != null) {
941             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
942         }
943
944         getAduitLog().info(auditLogEntry.toString());
945     }
946
947     /** SAML 1 specific audit log entry. */
948     protected class SAML2AuditLogEntry extends AuditLogEntry {
949
950         /** The response to the SAML request. */
951         private StatusResponseType samlResponse;
952
953         /** The unencrypted NameID for the SAML response. */
954         private NameID unencryptedNameId;
955
956         /**
957          * Gets the response to the SAML request.
958          * 
959          * @return the response to the SAML request
960          */
961         public StatusResponseType getSAMLResponse() {
962             return samlResponse;
963         }
964
965         /**
966          * Sets the response to the SAML request.
967          * 
968          * @param response the response to the SAML request
969          */
970         public void setSAMLResponse(StatusResponseType response) {
971             samlResponse = response;
972         }
973
974         /**
975          * Gets the unencrypted NameID for the SAML response.
976          * 
977          * @return unencrypted NameID for the SAML response
978          */
979         public NameID getUnencryptedNameId() {
980             return unencryptedNameId;
981         }
982
983         /**
984          * Sets the unencrypted NameID for the SAML response.
985          * 
986          * @param id unencrypted NameID for the SAML response
987          */
988         public void setUnencryptedNameId(NameID id) {
989             unencryptedNameId = id;
990         }
991
992         /** {@inheritDoc} */
993         public String toString() {
994             StringBuilder entryString = new StringBuilder(super.toString());
995
996             StringBuilder assertionIds = new StringBuilder();
997
998             if (samlResponse instanceof Response) {
999                 List<Assertion> assertions = ((Response) samlResponse).getAssertions();
1000                 if (assertions != null && !assertions.isEmpty()) {
1001                     for (Assertion assertion : assertions) {
1002                         assertionIds.append(assertion.getID());
1003                         assertionIds.append(",");
1004                     }
1005                 }
1006             }
1007
1008             if (unencryptedNameId != null) {
1009                 entryString.append(unencryptedNameId.getValue());
1010             }
1011             entryString.append("|");
1012
1013             entryString.append(assertionIds.toString());
1014             entryString.append("|");
1015
1016             return entryString.toString();
1017         }
1018     }
1019 }