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