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