View Javadoc

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