View Javadoc

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