2 * Copyright [2007] [University Corporation for Advanced Internet Development, Inc.]
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package edu.internet2.middleware.shibboleth.idp.profile;
19 import java.util.ArrayList;
20 import java.util.List;
23 import javax.servlet.http.HttpServletRequest;
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.ws.message.encoder.MessageEncodingException;
40 import org.opensaml.ws.security.SecurityPolicyResolver;
41 import org.opensaml.ws.transport.InTransport;
42 import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
43 import org.opensaml.xml.security.credential.Credential;
44 import org.opensaml.xml.util.DatatypeHelper;
45 import org.opensaml.xml.util.Pair;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48 import org.slf4j.helpers.MessageFormatter;
50 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
51 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
52 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAMLNameIdentifierEncoder;
53 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
54 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
55 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
56 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
57 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
58 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
59 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
60 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
61 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
62 import edu.internet2.middleware.shibboleth.idp.session.Session;
65 * Base class for SAML profile handlers.
67 public abstract class AbstractSAMLProfileHandler extends
68 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
70 /** SAML message audit log. */
71 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
74 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
76 /** Generator of IDs which may be used for SAML assertions, requests, etc. */
77 private IdentifierGenerator idGenerator;
79 /** All the SAML message decoders configured for the IdP. */
80 private Map<String, SAMLMessageDecoder> messageDecoders;
82 /** All the SAML message encoders configured for the IdP. */
83 private Map<String, SAMLMessageEncoder> messageEncoders;
85 /** SAML message binding used by inbound messages. */
86 private String inboundBinding;
88 /** SAML message bindings that may be used by outbound messages. */
89 private List<String> supportedOutboundBindings;
91 /** Resolver used to determine active security policy for an incoming request. */
92 private SecurityPolicyResolver securityPolicyResolver;
95 protected AbstractSAMLProfileHandler() {
100 * Gets the resolver used to determine active security policy for an incoming request.
102 * @return resolver used to determine active security policy for an incoming request
104 public SecurityPolicyResolver getSecurityPolicyResolver() {
105 if (securityPolicyResolver == null) {
106 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
109 return securityPolicyResolver;
113 * Sets the resolver used to determine active security policy for an incoming request.
115 * @param resolver resolver used to determine active security policy for an incoming request
117 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
118 securityPolicyResolver = resolver;
122 * Gets the audit log for this handler.
124 * @return audit log for this handler
126 protected Logger getAduitLog() {
131 * Gets an ID generator which may be used for SAML assertions, requests, etc.
133 * @return ID generator
135 public IdentifierGenerator getIdGenerator() {
140 * Gets the SAML message binding used by inbound messages.
142 * @return SAML message binding used by inbound messages
144 public String getInboundBinding() {
145 return inboundBinding;
149 * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
151 * @return SAML message decoders configured for the IdP indexed by SAML binding URI
153 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
154 return messageDecoders;
158 * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
160 * @return SAML message encoders configured for the IdP indexed by SAML binding URI
162 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
163 return messageEncoders;
167 * A convenience method for retrieving the SAML metadata provider from the relying party manager.
169 * @return the metadata provider or null
171 public MetadataProvider getMetadataProvider() {
172 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
173 if (rpcManager != null) {
174 return rpcManager.getMetadataProvider();
181 * Gets the SAML message bindings that may be used by outbound messages.
183 * @return SAML message bindings that may be used by outbound messages
185 public List<String> getSupportedOutboundBindings() {
186 return supportedOutboundBindings;
190 * Gets the user's session, if there is one.
192 * @param inTransport current inbound transport
194 * @return user's session
196 protected Session getUserSession(InTransport inTransport) {
197 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
198 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
202 * Gets the user's session based on their principal name.
204 * @param principalName user's principal name
206 * @return the user's session
208 protected Session getUserSession(String principalName) {
209 return getSessionManager().getSession(principalName);
213 * Gets an ID generator which may be used for SAML assertions, requests, etc.
215 * @param generator an ID generator which may be used for SAML assertions, requests, etc
217 public void setIdGenerator(IdentifierGenerator generator) {
218 idGenerator = generator;
222 * Sets the SAML message binding used by inbound messages.
224 * @param binding SAML message binding used by inbound messages
226 public void setInboundBinding(String binding) {
227 inboundBinding = binding;
231 * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
233 * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
235 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
236 messageDecoders = decoders;
240 * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
242 * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
244 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
245 messageEncoders = encoders;
249 * Sets the SAML message bindings that may be used by outbound messages.
251 * @param bindings SAML message bindings that may be used by outbound messages
253 public void setSupportedOutboundBindings(List<String> bindings) {
254 supportedOutboundBindings = bindings;
258 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
260 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
261 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
262 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
264 } catch (MetadataProviderException e) {
265 log.error("Unable to look up relying party metadata", e);
269 return super.getRelyingPartyConfiguration(relyingPartyId);
273 * Populates the request context with information.
275 * This method requires the the following request context properties to be populated: inbound message transport,
276 * peer entity ID, metadata provider
278 * This methods populates the following request context properties: user's session, user's principal name, service
279 * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
280 * issuer, local entity metadata
282 * @param requestContext current request context
283 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
285 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
286 populateRelyingPartyInformation(requestContext);
287 populateAssertingPartyInformation(requestContext);
288 populateSAMLMessageInformation(requestContext);
289 populateProfileInformation(requestContext);
290 populateUserInformation(requestContext);
294 * Populates the request context with information about the relying party.
296 * This method requires the the following request context properties to be populated: peer entity ID
298 * This methods populates the following request context properties: peer entity metadata, relying party
301 * @param requestContext current request context
302 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
304 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
305 throws ProfileException {
306 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
307 String relyingPartyId = requestContext.getInboundMessageIssuer();
309 EntityDescriptor relyingPartyMetadata;
311 relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
312 requestContext.setPeerEntityMetadata(relyingPartyMetadata);
313 } catch (MetadataProviderException e) {
314 log.error("Error looking up metadata for relying party " + relyingPartyId, e);
315 throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
318 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
319 if (rpConfig == null) {
320 log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
321 throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
324 requestContext.setRelyingPartyConfiguration(rpConfig);
328 * Populates the request context with information about the asserting party. Unless overridden,
329 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
330 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
331 * properties it provides are available in the request context.
333 * This method requires the the following request context properties to be populated: metadata provider, relying
334 * party configuration
336 * This methods populates the following request context properties: local entity ID, outbound message issuer, local
339 * @param requestContext current request context
340 * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
342 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
343 throws ProfileException {
344 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
345 requestContext.setLocalEntityId(assertingPartyId);
346 requestContext.setOutboundMessageIssuer(assertingPartyId);
349 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
351 if (localEntityDescriptor != null) {
352 requestContext.setLocalEntityMetadata(localEntityDescriptor);
354 } catch (MetadataProviderException e) {
355 log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
356 throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
361 * Populates the request context with information from the inbound SAML message. Unless overridden,
362 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
363 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
364 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
365 * properties they provide are available in the request context.
368 * @param requestContext current request context
370 * @throws ProfileException thrown if there is a problem populating the request context with information
372 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
373 throws ProfileException;
376 * Populates the request context with the information about the profile. Unless overridden,
377 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
378 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
379 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
380 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
381 * properties they provide are available in the request context.
383 * This method requires the the following request context properties to be populated: relying party configuration
385 * This methods populates the following request context properties: communication profile ID, profile configuration,
386 * outbound message artifact type, peer entity endpoint
388 * @param requestContext current request context
390 * @throws ProfileException thrown if there is a problem populating the profile information
392 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
393 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
394 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
395 if (profileConfig != null) {
396 requestContext.setProfileConfiguration(profileConfig);
397 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
400 Endpoint endpoint = selectEndpoint(requestContext);
401 if (endpoint == null) {
402 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
403 throw new ProfileException("No peer endpoint available to which to send SAML response");
405 requestContext.setPeerEntityEndpoint(endpoint);
409 * Attempts to select the most fitting name identifier attribute, and associated encoder, for a request. If no
410 * attributes for the request subject are available no name identifier is constructed. If a specific name format is
411 * required, as returned by {@link #getRequiredNameIDFormat(BaseSAMLProfileRequestContext)}, then either an
412 * attribute with an encoder supporting that format is selected or an exception is thrown. If no specific format is
413 * required then an attribute supporting a format listed as supported by the relying party is used. If the relying
414 * party does not list any supported formats then any attribute supporting the correct name identifier type is used.
416 * @param <T> type of name identifier encoder the attribute must support
417 * @param nameIdEncoderType type of name identifier encoder the attribute must support
418 * @param requestContext the current request context
420 * @return the select attribute, and its encoder, to be used to build the name identifier
422 * @throws ProfileException thrown if a specific name identifier format was required but not supported
424 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
425 Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
427 String requiredNameFormat = DatatypeHelper.safeTrimOrNullString(getRequiredNameIDFormat(requestContext));
428 if (requiredNameFormat != null) {
429 log.debug("Attempting to build name identifier for relying party'{}' that requires format '{}'",
430 requestContext.getInboundMessageIssuer(), requiredNameFormat);
431 return selectNameIDAttributeAndEncoderByRequiredFormat(requiredNameFormat, nameIdEncoderType,
435 List<String> supportedNameFormats = getNameFormats(requestContext);
436 if (supportedNameFormats.isEmpty()) {
437 log.debug("Attempting to build name identifier for relying party '{}' that supports any format",
438 requestContext.getInboundMessageIssuer());
440 log.debug("Attempting to build name identifier for relying party '{}' that supports the formats: {}",
441 requestContext.getInboundMessageIssuer(), supportedNameFormats);
443 return selectNameIDAttributeAndEncoderBySupportedFormats(supportedNameFormats, nameIdEncoderType,
448 * Gets the name identifier format required to be sent back to the relying party.
450 * This implementation of this method returns null. Profile handler implementations should override this method if
451 * an incoming request is capable of requiring a specific format.
453 * @param requestContext current request context
455 * @return the required name ID format or null if no specific format is required
457 protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
462 * Selects the principal attribute that can be encoded in to the required name identifier format.
464 * @param <T> type of name identifier encoder the attribute must support
465 * @param requiredNameFormat required name identifier format type
466 * @param nameIdEncoderType type of name identifier encoder the attribute must support
467 * @param requestContext the current request context
469 * @return the select attribute, and its encoder, to be used to build the name identifier
471 * @throws ProfileException thrown if a specific name identifier format was required but not supported
473 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderByRequiredFormat(
474 String requiredNameFormat, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
475 throws ProfileException {
476 String requiredNameFormatErr = MessageFormatter.format(
477 "No attribute of principal '{}' can be encoded in to a NameIdentifier of "
478 + "required format '{}' for relying party '{}'", new Object[] {
479 requestContext.getPrincipalName(), requiredNameFormat,
480 requestContext.getInboundMessageIssuer(), });
482 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
483 if (principalAttributes == null || principalAttributes.isEmpty()) {
484 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
485 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
486 log.warn(requiredNameFormatErr);
487 throw new ProfileException(requiredNameFormatErr);
490 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType,
491 principalAttributes, java.util.Collections.singletonList(requiredNameFormat));
492 if (nameIdAttributeAndEncoder == null) {
493 log.warn(requiredNameFormatErr);
494 throw new ProfileException(requiredNameFormatErr);
497 return nameIdAttributeAndEncoder;
501 * Gets the name identifier formats to use when creating identifiers for the relying party.
503 * @param requestContext current request context
505 * @return list of formats that may be used with the relying party, or an empty list for no preference
507 * @throws ProfileException thrown if there is a problem determining the name identifier format to use
509 protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
510 ArrayList<String> nameFormats = new ArrayList<String>();
512 RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
513 if (relyingPartyRole != null) {
514 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
515 if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
516 nameFormats.addAll(relyingPartySupportedFormats);
520 // If metadata contains the unspecified name format this means that any are supported
521 if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
529 * Gets the list of name identifier formats supported for a given role.
531 * @param role the role to get the list of supported name identifier formats
533 * @return list of supported name identifier formats
535 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
536 List<NameIDFormat> nameIDFormats = null;
538 if (role instanceof SSODescriptor) {
539 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
540 } else if (role instanceof AuthnAuthorityDescriptor) {
541 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
542 } else if (role instanceof PDPDescriptor) {
543 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
544 } else if (role instanceof AttributeAuthorityDescriptor) {
545 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
548 ArrayList<String> supportedFormats = new ArrayList<String>();
549 if (nameIDFormats != null) {
550 for (NameIDFormat format : nameIDFormats) {
551 supportedFormats.add(format.getFormat());
555 return supportedFormats;
559 * Selects the principal attribute that can be encoded in to one of the supported name identifier formats.
561 * @param <T> type of name identifier encoder the attribute must support
562 * @param supportedNameFormats name identifier formats supported by the relaying part, or an empty list if all
563 * formats are supported
564 * @param nameIdEncoderType type of name identifier encoder the attribute must support
565 * @param requestContext the current request context
567 * @return the select attribute, and its encoder, to be used to build the name identifier
569 * @throws ProfileException thrown if there is a problem selecting the attribute
571 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderBySupportedFormats(
572 List<String> supportedNameFormats, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
573 throws ProfileException {
574 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
575 if (principalAttributes == null || principalAttributes.isEmpty()) {
576 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
577 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
581 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
582 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
583 supportedNameFormats);
584 if (nameIdAttributeAndEncoder == null) {
585 log.debug( "No attributes for principal '{}' support encoding into a supported name identifier format for relying party '{}'",
586 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
589 return nameIdAttributeAndEncoder;
593 * Selects an attribute, resolved previously, to encode as a NameID.
595 * @param <T> type of name identifier encoder the attribute must support
596 * @param nameIdEncoderType type of name identifier encoder the attribute must support
597 * @param principalAttributes resolved attributes
598 * @param supportedNameFormats NameID formats supported by the relying party or an empty list if all formats are
601 * @return the attribute and its associated NameID encoder
603 * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
605 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
606 Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
607 List<String> supportedNameFormats) throws ProfileException {
609 T nameIdEncoder = null;
611 if (principalAttributes != null) {
612 for (BaseAttribute<?> attribute : principalAttributes.values()) {
613 if (attribute == null) {
617 for (AttributeEncoder encoder : attribute.getEncoders()) {
618 if (encoder == null) {
622 if (nameIdEncoderType.isInstance(encoder)) {
623 nameIdEncoder = nameIdEncoderType.cast(encoder);
624 if (supportedNameFormats.isEmpty()
625 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
626 return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
637 * Populates the request context with the information about the user if they have an existing session. Unless
638 * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
639 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
640 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
641 * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
642 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
643 * properties they provide are available in the request context.
645 * This method should populate: user's session, user's principal name, and service authentication method
647 * @param requestContext current request context
649 * @throws ProfileException thrown if there is a problem populating the user's information
651 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
652 throws ProfileException;
655 * Selects the appropriate endpoint for the relying party and stores it in the request context.
657 * @param requestContext current request context
659 * @return Endpoint selected from the information provided in the request context
661 * @throws ProfileException thrown if there is a problem selecting a response endpoint
663 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
666 * Encodes the request's SAML response and writes it to the servlet response.
668 * @param requestContext current request context
670 * @throws ProfileException thrown if no message encoder is registered for this profiles binding
672 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
674 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
676 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
677 .getProfileConfiguration();
678 if (profileConfig != null) {
679 if (isSignResponse(requestContext)) {
680 Credential signingCredential = profileConfig.getSigningCredential();
681 if (signingCredential == null) {
682 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
685 if (signingCredential == null) {
686 throw new ProfileException(
687 "Signing of responses is required but no signing credential is available");
690 if (signingCredential.getPrivateKey() == null) {
691 throw new ProfileException(
692 "Signing of response is required but signing credential does not have a private key");
695 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
699 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
700 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
702 requestContext.setMessageEncoder(encoder);
703 encoder.encode(requestContext);
704 } catch (MessageEncodingException e) {
705 throw new ProfileException("Unable to encode response to relying party: "
706 + requestContext.getInboundMessageIssuer(), e);
711 * Determine whether responses should be signed.
713 * @param requestContext the current request context
714 * @return true if responses should be signed, false otherwise
715 * @throws ProfileException if there is a problem determining whether responses should be signed
717 protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
719 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
721 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
722 .getProfileConfiguration();
724 if (profileConfig != null) {
726 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
727 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
728 .providesMessageIntegrity(requestContext));
729 } catch (MessageEncodingException e) {
730 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
731 encoder.getBindingURI());
732 throw new ProfileException("Unable to determine if outbound response should be signed");
741 * Get the outbound message encoder to use.
744 * The default implementation uses the binding URI from the
745 * {@link org.opensaml.common.binding.SAMLMessageContext#getPeerEntityEndpoint()} to lookup the encoder from the
746 * supported message encoders defined in {@link #getMessageEncoders()}.
750 * Subclasses may override to implement a different mechanism to determine the encoder to use, such as for example
751 * cases where an active intermediary actor sits between this provider and the peer entity endpoint (e.g. the SAML 2
755 * @param requestContext current request context
756 * @return the message encoder to use
757 * @throws ProfileException if the encoder to use can not be resolved based on the request context
759 protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
760 throws ProfileException {
761 SAMLMessageEncoder encoder = null;
763 Endpoint endpoint = requestContext.getPeerEntityEndpoint();
764 if (endpoint == null) {
765 log.warn("No peer endpoint available for peer. Unable to send response.");
766 throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
769 if (endpoint != null) {
770 encoder = getMessageEncoders().get(endpoint.getBinding());
771 if (encoder == null) {
772 log.error("No outbound message encoder configured for binding: {}", requestContext
773 .getPeerEntityEndpoint().getBinding());
774 throw new ProfileException("No outbound message encoder configured for binding: "
775 + requestContext.getPeerEntityEndpoint().getBinding());
782 * Get the inbound message decoder to use.
785 * The default implementation uses the binding URI from {@link #getInboundBinding()} to lookup the decoder from the
786 * supported message decoders defined in {@link #getMessageDecoders()}.
790 * Subclasses may override to implement a different mechanism to determine the decoder to use.
793 * @param requestContext current request context
794 * @return the message decoder to use
795 * @throws ProfileException if the decoder to use can not be resolved based on the request context
797 protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
798 throws ProfileException {
799 SAMLMessageDecoder decoder = null;
801 decoder = getMessageDecoders().get(getInboundBinding());
802 if (decoder == null) {
803 log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
804 throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
810 * Writes an audit log entry indicating the successful response to the attribute request.
812 * @param context current request context
814 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
815 AuditLogEntry auditLogEntry = new AuditLogEntry();
816 auditLogEntry.setMessageProfile(getProfileId());
817 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
818 auditLogEntry.setPrincipalName(context.getPrincipalName());
819 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
820 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
821 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
822 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
823 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
824 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
825 if (context.getReleasedAttributes() != null) {
826 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
829 getAduitLog().info(auditLogEntry.toString());