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