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