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  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 }