Fix another NPE dealing with encoding attributes to name identifiers
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / profile / AbstractSAMLProfileHandler.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;
18
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import javax.servlet.http.HttpServletRequest;
24
25 import org.opensaml.common.IdentifierGenerator;
26 import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
27 import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
28 import org.opensaml.saml1.core.NameIdentifier;
29 import org.opensaml.saml2.metadata.AttributeAuthorityDescriptor;
30 import org.opensaml.saml2.metadata.AuthnAuthorityDescriptor;
31 import org.opensaml.saml2.metadata.Endpoint;
32 import org.opensaml.saml2.metadata.EntityDescriptor;
33 import org.opensaml.saml2.metadata.NameIDFormat;
34 import org.opensaml.saml2.metadata.PDPDescriptor;
35 import org.opensaml.saml2.metadata.RoleDescriptor;
36 import org.opensaml.saml2.metadata.SSODescriptor;
37 import org.opensaml.saml2.metadata.provider.MetadataProvider;
38 import org.opensaml.saml2.metadata.provider.MetadataProviderException;
39 import org.opensaml.ws.message.encoder.MessageEncodingException;
40 import org.opensaml.ws.security.SecurityPolicyResolver;
41 import org.opensaml.ws.transport.InTransport;
42 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
43 import org.opensaml.xml.security.credential.Credential;
44 import org.opensaml.xml.util.DatatypeHelper;
45 import org.opensaml.xml.util.Pair;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.slf4j.helpers.MessageFormatter;
49
50 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
51 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
52 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAMLNameIdentifierEncoder;
53 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
54 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
55 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
56 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
57 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
58 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
59 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
60 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
61 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
62 import edu.internet2.middleware.shibboleth.idp.session.Session;
63
64 /**
65  * Base class for SAML profile handlers.
66  */
67 public abstract class AbstractSAMLProfileHandler extends
68         AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
69
70     /** SAML message audit log. */
71     private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
72
73     /** Class logger. */
74     private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
75
76     /** Generator of IDs which may be used for SAML assertions, requests, etc. */
77     private IdentifierGenerator idGenerator;
78
79     /** All the SAML message decoders configured for the IdP. */
80     private Map<String, SAMLMessageDecoder> messageDecoders;
81
82     /** All the SAML message encoders configured for the IdP. */
83     private Map<String, SAMLMessageEncoder> messageEncoders;
84
85     /** SAML message binding used by inbound messages. */
86     private String inboundBinding;
87
88     /** SAML message bindings that may be used by outbound messages. */
89     private List<String> supportedOutboundBindings;
90
91     /** Resolver used to determine active security policy for an incoming request. */
92     private SecurityPolicyResolver securityPolicyResolver;
93
94     /** Constructor. */
95     protected AbstractSAMLProfileHandler() {
96         super();
97     }
98
99     /**
100      * Gets the resolver used to determine active security policy for an incoming request.
101      * 
102      * @return resolver used to determine active security policy for an incoming request
103      */
104     public SecurityPolicyResolver getSecurityPolicyResolver() {
105         if (securityPolicyResolver == null) {
106             setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
107         }
108
109         return securityPolicyResolver;
110     }
111
112     /**
113      * Sets the resolver used to determine active security policy for an incoming request.
114      * 
115      * @param resolver resolver used to determine active security policy for an incoming request
116      */
117     public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
118         securityPolicyResolver = resolver;
119     }
120
121     /**
122      * Gets the audit log for this handler.
123      * 
124      * @return audit log for this handler
125      */
126     protected Logger getAduitLog() {
127         return auditLog;
128     }
129
130     /**
131      * Gets an ID generator which may be used for SAML assertions, requests, etc.
132      * 
133      * @return ID generator
134      */
135     public IdentifierGenerator getIdGenerator() {
136         return idGenerator;
137     }
138
139     /**
140      * Gets the SAML message binding used by inbound messages.
141      * 
142      * @return SAML message binding used by inbound messages
143      */
144     public String getInboundBinding() {
145         return inboundBinding;
146     }
147
148     /**
149      * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
150      * 
151      * @return SAML message decoders configured for the IdP indexed by SAML binding URI
152      */
153     public Map<String, SAMLMessageDecoder> getMessageDecoders() {
154         return messageDecoders;
155     }
156
157     /**
158      * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
159      * 
160      * @return SAML message encoders configured for the IdP indexed by SAML binding URI
161      */
162     public Map<String, SAMLMessageEncoder> getMessageEncoders() {
163         return messageEncoders;
164     }
165
166     /**
167      * A convenience method for retrieving the SAML metadata provider from the relying party manager.
168      * 
169      * @return the metadata provider or null
170      */
171     public MetadataProvider getMetadataProvider() {
172         SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
173         if (rpcManager != null) {
174             return rpcManager.getMetadataProvider();
175         }
176
177         return null;
178     }
179
180     /**
181      * Gets the SAML message bindings that may be used by outbound messages.
182      * 
183      * @return SAML message bindings that may be used by outbound messages
184      */
185     public List<String> getSupportedOutboundBindings() {
186         return supportedOutboundBindings;
187     }
188
189     /**
190      * Gets the user's session, if there is one.
191      * 
192      * @param inTransport current inbound transport
193      * 
194      * @return user's session
195      */
196     protected Session getUserSession(InTransport inTransport) {
197         HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
198         return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
199     }
200
201     /**
202      * Gets the user's session based on their principal name.
203      * 
204      * @param principalName user's principal name
205      * 
206      * @return the user's session
207      */
208     protected Session getUserSession(String principalName) {
209         return getSessionManager().getSession(principalName);
210     }
211
212     /**
213      * Gets an ID generator which may be used for SAML assertions, requests, etc.
214      * 
215      * @param generator an ID generator which may be used for SAML assertions, requests, etc
216      */
217     public void setIdGenerator(IdentifierGenerator generator) {
218         idGenerator = generator;
219     }
220
221     /**
222      * Sets the SAML message binding used by inbound messages.
223      * 
224      * @param binding SAML message binding used by inbound messages
225      */
226     public void setInboundBinding(String binding) {
227         inboundBinding = binding;
228     }
229
230     /**
231      * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
232      * 
233      * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
234      */
235     public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
236         messageDecoders = decoders;
237     }
238
239     /**
240      * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
241      * 
242      * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
243      */
244     public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
245         messageEncoders = encoders;
246     }
247
248     /**
249      * Sets the SAML message bindings that may be used by outbound messages.
250      * 
251      * @param bindings SAML message bindings that may be used by outbound messages
252      */
253     public void setSupportedOutboundBindings(List<String> bindings) {
254         supportedOutboundBindings = bindings;
255     }
256
257     /** {@inheritDoc} */
258     public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
259         try {
260             if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
261                 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
262                 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
263             }
264         } catch (MetadataProviderException e) {
265             log.error("Unable to look up relying party metadata", e);
266             return null;
267         }
268
269         return super.getRelyingPartyConfiguration(relyingPartyId);
270     }
271
272     /**
273      * Populates the request context with information.
274      * 
275      * This method requires the the following request context properties to be populated: inbound message transport,
276      * peer entity ID, metadata provider
277      * 
278      * This methods populates the following request context properties: user's session, user's principal name, service
279      * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
280      * issuer, local entity metadata
281      * 
282      * @param requestContext current request context
283      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
284      */
285     protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
286         populateRelyingPartyInformation(requestContext);
287         populateAssertingPartyInformation(requestContext);
288         populateSAMLMessageInformation(requestContext);
289         populateProfileInformation(requestContext);
290         populateUserInformation(requestContext);
291     }
292
293     /**
294      * Populates the request context with information about the relying party.
295      * 
296      * This method requires the the following request context properties to be populated: peer entity ID
297      * 
298      * This methods populates the following request context properties: peer entity metadata, relying party
299      * configuration
300      * 
301      * @param requestContext current request context
302      * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
303      */
304     protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
305             throws ProfileException {
306         MetadataProvider metadataProvider = requestContext.getMetadataProvider();
307         String relyingPartyId = requestContext.getInboundMessageIssuer();
308
309         EntityDescriptor relyingPartyMetadata;
310         try {
311             relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
312             requestContext.setPeerEntityMetadata(relyingPartyMetadata);
313         } catch (MetadataProviderException e) {
314             log.error("Error looking up metadata for relying party " + relyingPartyId, e);
315             throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
316         }
317
318         RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
319         if (rpConfig == null) {
320             log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
321             throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
322                     + relyingPartyId);
323         }
324         requestContext.setRelyingPartyConfiguration(rpConfig);
325     }
326
327     /**
328      * Populates the request context with information about the asserting party. Unless overridden,
329      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
330      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
331      * properties it provides are available in the request context.
332      * 
333      * This method requires the the following request context properties to be populated: metadata provider, relying
334      * party configuration
335      * 
336      * This methods populates the following request context properties: local entity ID, outbound message issuer, local
337      * entity metadata
338      * 
339      * @param requestContext current request context
340      * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
341      */
342     protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
343             throws ProfileException {
344         String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
345         requestContext.setLocalEntityId(assertingPartyId);
346         requestContext.setOutboundMessageIssuer(assertingPartyId);
347
348         try {
349             EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
350                     assertingPartyId);
351             if (localEntityDescriptor != null) {
352                 requestContext.setLocalEntityMetadata(localEntityDescriptor);
353             }
354         } catch (MetadataProviderException e) {
355             log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
356             throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
357         }
358     }
359
360     /**
361      * Populates the request context with information from the inbound SAML message. Unless overridden,
362      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
363      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
364      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
365      * properties they provide are available in the request context.
366      * 
367      * 
368      * @param requestContext current request context
369      * 
370      * @throws ProfileException thrown if there is a problem populating the request context with information
371      */
372     protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
373             throws ProfileException;
374
375     /**
376      * Populates the request context with the information about the profile. Unless overridden,
377      * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
378      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
379      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
380      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
381      * properties they provide are available in the request context.
382      * 
383      * This method requires the the following request context properties to be populated: relying party configuration
384      * 
385      * This methods populates the following request context properties: communication profile ID, profile configuration,
386      * outbound message artifact type, peer entity endpoint
387      * 
388      * @param requestContext current request context
389      * 
390      * @throws ProfileException thrown if there is a problem populating the profile information
391      */
392     protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
393         AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
394                 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
395         if (profileConfig != null) {
396             requestContext.setProfileConfiguration(profileConfig);
397             requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
398         }
399
400         Endpoint endpoint = selectEndpoint(requestContext);
401         if (endpoint == null) {
402             log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
403             throw new ProfileException("No peer endpoint available to which to send SAML response");
404         }
405         requestContext.setPeerEntityEndpoint(endpoint);
406     }
407
408     /**
409      * Attempts to select the most fitting name identifier attribute, and associated encoder, for a request. If no
410      * attributes for the request subject are available no name identifier is constructed. If a specific name format is
411      * required, as returned by {@link #getRequiredNameIDFormat(BaseSAMLProfileRequestContext)}, then either an
412      * attribute with an encoder supporting that format is selected or an exception is thrown. If no specific format is
413      * required then an attribute supporting a format listed as supported by the relying party is used. If the relying
414      * party does not list any supported formats then any attribute supporting the correct name identifier type is used.
415      * 
416      * @param <T> type of name identifier encoder the attribute must support
417      * @param nameIdEncoderType type of name identifier encoder the attribute must support
418      * @param requestContext the current request context
419      * 
420      * @return the select attribute, and its encoder, to be used to build the name identifier
421      * 
422      * @throws ProfileException thrown if a specific name identifier format was required but not supported
423      */
424     protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
425             Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
426
427         String requiredNameFormat = DatatypeHelper.safeTrimOrNullString(getRequiredNameIDFormat(requestContext));
428         if (requiredNameFormat != null) {
429             log.debug("Attempting to build name identifier for relying party'{}' that requires format '{}'",
430                     requestContext.getInboundMessageIssuer(), requiredNameFormat);
431             return selectNameIDAttributeAndEncoderByRequiredFormat(requiredNameFormat, nameIdEncoderType,
432                     requestContext);
433         }
434
435         List<String> supportedNameFormats = getNameFormats(requestContext);
436         if (supportedNameFormats.isEmpty()) {
437             log.debug("Attempting to build name identifier for relying party '{}' that supports any format",
438                     requestContext.getInboundMessageIssuer());
439         } else {
440             log.debug("Attempting to build name identifier for relying party '{}' that supports the formats: {}",
441                     requestContext.getInboundMessageIssuer(), supportedNameFormats);
442         }
443         return selectNameIDAttributeAndEncoderBySupportedFormats(supportedNameFormats, nameIdEncoderType,
444                 requestContext);
445     }
446
447     /**
448      * Gets the name identifier format required to be sent back to the relying party.
449      * 
450      * This implementation of this method returns null. Profile handler implementations should override this method if
451      * an incoming request is capable of requiring a specific format.
452      * 
453      * @param requestContext current request context
454      * 
455      * @return the required name ID format or null if no specific format is required
456      */
457     protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
458         return null;
459     }
460
461     /**
462      * Selects the principal attribute that can be encoded in to the required name identifier format.
463      * 
464      * @param <T> type of name identifier encoder the attribute must support
465      * @param requiredNameFormat required name identifier format type
466      * @param nameIdEncoderType type of name identifier encoder the attribute must support
467      * @param requestContext the current request context
468      * 
469      * @return the select attribute, and its encoder, to be used to build the name identifier
470      * 
471      * @throws ProfileException thrown if a specific name identifier format was required but not supported
472      */
473     protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderByRequiredFormat(
474             String requiredNameFormat, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
475             throws ProfileException {
476         String requiredNameFormatErr = MessageFormatter.format(
477                 "No attribute of principal '{}' can be encoded in to a NameIdentifier of "
478                         + "required format '{}' for relying party '{}'", new Object[] {
479                         requestContext.getPrincipalName(), requiredNameFormat,
480                         requestContext.getInboundMessageIssuer(), });
481
482         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
483         if (principalAttributes == null || principalAttributes.isEmpty()) {
484             log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
485                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
486             log.warn(requiredNameFormatErr);
487             throw new ProfileException(requiredNameFormatErr);
488         }
489
490         Pair<BaseAttribute, T> nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType,
491                 principalAttributes, java.util.Collections.singletonList(requiredNameFormat));
492         if (nameIdAttributeAndEncoder == null) {
493             log.warn(requiredNameFormatErr);
494             throw new ProfileException(requiredNameFormatErr);
495         }
496
497         return nameIdAttributeAndEncoder;
498     }
499
500     /**
501      * Gets the name identifier formats to use when creating identifiers for the relying party.
502      * 
503      * @param requestContext current request context
504      * 
505      * @return list of formats that may be used with the relying party, or an empty list for no preference
506      * 
507      * @throws ProfileException thrown if there is a problem determining the name identifier format to use
508      */
509     protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
510         ArrayList<String> nameFormats = new ArrayList<String>();
511
512         RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
513         if (relyingPartyRole != null) {
514             List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
515             if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
516                 nameFormats.addAll(relyingPartySupportedFormats);
517             }
518         }
519
520         // If metadata contains the unspecified name format this means that any are supported
521         if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
522             nameFormats.clear();
523         }
524
525         return nameFormats;
526     }
527
528     /**
529      * Gets the list of name identifier formats supported for a given role.
530      * 
531      * @param role the role to get the list of supported name identifier formats
532      * 
533      * @return list of supported name identifier formats
534      */
535     protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
536         List<NameIDFormat> nameIDFormats = null;
537
538         if (role instanceof SSODescriptor) {
539             nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
540         } else if (role instanceof AuthnAuthorityDescriptor) {
541             nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
542         } else if (role instanceof PDPDescriptor) {
543             nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
544         } else if (role instanceof AttributeAuthorityDescriptor) {
545             nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
546         }
547
548         ArrayList<String> supportedFormats = new ArrayList<String>();
549         if (nameIDFormats != null) {
550             for (NameIDFormat format : nameIDFormats) {
551                 supportedFormats.add(format.getFormat());
552             }
553         }
554
555         return supportedFormats;
556     }
557
558     /**
559      * Selects the principal attribute that can be encoded in to one of the supported name identifier formats.
560      * 
561      * @param <T> type of name identifier encoder the attribute must support
562      * @param supportedNameFormats name identifier formats supported by the relaying part, or an empty list if all
563      *            formats are supported
564      * @param nameIdEncoderType type of name identifier encoder the attribute must support
565      * @param requestContext the current request context
566      * 
567      * @return the select attribute, and its encoder, to be used to build the name identifier
568      * 
569      * @throws ProfileException thrown if there is a problem selecting the attribute
570      */
571     protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderBySupportedFormats(
572             List<String> supportedNameFormats, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
573             throws ProfileException {
574         Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
575         if (principalAttributes == null || principalAttributes.isEmpty()) {
576             log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
577                     requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
578             return null;
579         }
580
581         Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
582         nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
583                 supportedNameFormats);
584         if (nameIdAttributeAndEncoder == null) {
585             log.debug( "No attributes for principal '{}' support encoding into a supported name identifier format for relying party '{}'",
586                             requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
587         }
588
589         return nameIdAttributeAndEncoder;
590     }
591
592     /**
593      * Selects an attribute, resolved previously, to encode as a NameID.
594      * 
595      * @param <T> type of name identifier encoder the attribute must support
596      * @param nameIdEncoderType type of name identifier encoder the attribute must support
597      * @param principalAttributes resolved attributes
598      * @param supportedNameFormats NameID formats supported by the relying party or an empty list if all formats are
599      *            acceptable
600      * 
601      * @return the attribute and its associated NameID encoder
602      * 
603      * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
604      */
605     protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
606             Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
607             List<String> supportedNameFormats) throws ProfileException {
608
609         T nameIdEncoder = null;
610
611         if (principalAttributes != null) {
612             for (BaseAttribute<?> attribute : principalAttributes.values()) {
613                 if (attribute == null) {
614                     continue;
615                 }
616
617                 for (AttributeEncoder encoder : attribute.getEncoders()) {
618                     if (encoder == null) {
619                         continue;
620                     }
621
622                     if (nameIdEncoderType.isInstance(encoder)) {
623                         nameIdEncoder = nameIdEncoderType.cast(encoder);
624                         if (supportedNameFormats.isEmpty()
625                                 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
626                             return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
627                         }
628                     }
629                 }
630             }
631         }
632
633         return null;
634     }
635
636     /**
637      * Populates the request context with the information about the user if they have an existing session. Unless
638      * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
639      * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
640      * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
641      * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
642      * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
643      * properties they provide are available in the request context.
644      * 
645      * This method should populate: user's session, user's principal name, and service authentication method
646      * 
647      * @param requestContext current request context
648      * 
649      * @throws ProfileException thrown if there is a problem populating the user's information
650      */
651     protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
652             throws ProfileException;
653
654     /**
655      * Selects the appropriate endpoint for the relying party and stores it in the request context.
656      * 
657      * @param requestContext current request context
658      * 
659      * @return Endpoint selected from the information provided in the request context
660      * 
661      * @throws ProfileException thrown if there is a problem selecting a response endpoint
662      */
663     protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
664
665     /**
666      * Encodes the request's SAML response and writes it to the servlet response.
667      * 
668      * @param requestContext current request context
669      * 
670      * @throws ProfileException thrown if no message encoder is registered for this profiles binding
671      */
672     protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
673         try {
674             SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
675
676             AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
677                     .getProfileConfiguration();
678             if (profileConfig != null) {
679                 if (isSignResponse(requestContext)) {
680                     Credential signingCredential = profileConfig.getSigningCredential();
681                     if (signingCredential == null) {
682                         signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
683                     }
684
685                     if (signingCredential == null) {
686                         throw new ProfileException(
687                                 "Signing of responses is required but no signing credential is available");
688                     }
689
690                     if (signingCredential.getPrivateKey() == null) {
691                         throw new ProfileException(
692                                 "Signing of response is required but signing credential does not have a private key");
693                     }
694
695                     requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
696                 }
697             }
698
699             log.debug("Encoding response to SAML request {} from relying party {}", requestContext
700                     .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
701
702             requestContext.setMessageEncoder(encoder);
703             encoder.encode(requestContext);
704         } catch (MessageEncodingException e) {
705             throw new ProfileException("Unable to encode response to relying party: "
706                     + requestContext.getInboundMessageIssuer(), e);
707         }
708     }
709
710     /**
711      * Determine whether responses should be signed.
712      * 
713      * @param requestContext the current request context
714      * @return true if responses should be signed, false otherwise
715      * @throws ProfileException if there is a problem determining whether responses should be signed
716      */
717     protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
718
719         SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
720
721         AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
722                 .getProfileConfiguration();
723
724         if (profileConfig != null) {
725             try {
726                 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
727                         || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
728                                 .providesMessageIntegrity(requestContext));
729             } catch (MessageEncodingException e) {
730                 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
731                         encoder.getBindingURI());
732                 throw new ProfileException("Unable to determine if outbound response should be signed");
733             }
734         } else {
735             return false;
736         }
737
738     }
739
740     /**
741      * Get the outbound message encoder to use.
742      * 
743      * <p>
744      * The default implementation uses the binding URI from the
745      * {@link org.opensaml.common.binding.SAMLMessageContext#getPeerEntityEndpoint()} to lookup the encoder from the
746      * supported message encoders defined in {@link #getMessageEncoders()}.
747      * </p>
748      * 
749      * <p>
750      * Subclasses may override to implement a different mechanism to determine the encoder to use, such as for example
751      * cases where an active intermediary actor sits between this provider and the peer entity endpoint (e.g. the SAML 2
752      * ECP case).
753      * </p>
754      * 
755      * @param requestContext current request context
756      * @return the message encoder to use
757      * @throws ProfileException if the encoder to use can not be resolved based on the request context
758      */
759     protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
760             throws ProfileException {
761         SAMLMessageEncoder encoder = null;
762
763         Endpoint endpoint = requestContext.getPeerEntityEndpoint();
764         if (endpoint == null) {
765             log.warn("No peer endpoint available for peer. Unable to send response.");
766             throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
767         }
768
769         if (endpoint != null) {
770             encoder = getMessageEncoders().get(endpoint.getBinding());
771             if (encoder == null) {
772                 log.error("No outbound message encoder configured for binding: {}", requestContext
773                         .getPeerEntityEndpoint().getBinding());
774                 throw new ProfileException("No outbound message encoder configured for binding: "
775                         + requestContext.getPeerEntityEndpoint().getBinding());
776             }
777         }
778         return encoder;
779     }
780
781     /**
782      * Get the inbound message decoder to use.
783      * 
784      * <p>
785      * The default implementation uses the binding URI from {@link #getInboundBinding()} to lookup the decoder from the
786      * supported message decoders defined in {@link #getMessageDecoders()}.
787      * </p>
788      * 
789      * <p>
790      * Subclasses may override to implement a different mechanism to determine the decoder to use.
791      * </p>
792      * 
793      * @param requestContext current request context
794      * @return the message decoder to use
795      * @throws ProfileException if the decoder to use can not be resolved based on the request context
796      */
797     protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
798             throws ProfileException {
799         SAMLMessageDecoder decoder = null;
800
801         decoder = getMessageDecoders().get(getInboundBinding());
802         if (decoder == null) {
803             log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
804             throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
805         }
806         return decoder;
807     }
808
809     /**
810      * Writes an audit log entry indicating the successful response to the attribute request.
811      * 
812      * @param context current request context
813      */
814     protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
815         AuditLogEntry auditLogEntry = new AuditLogEntry();
816         auditLogEntry.setMessageProfile(getProfileId());
817         auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
818         auditLogEntry.setPrincipalName(context.getPrincipalName());
819         auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
820         auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
821         auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
822         auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
823         auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
824         auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
825         if (context.getReleasedAttributes() != null) {
826             auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
827         }
828
829         getAduitLog().info(auditLogEntry.toString());
830     }
831 }