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 }