View Javadoc

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