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.Pair;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.slf4j.helpers.MessageFormatter;
49 import edu.internet2.middleware.shibboleth.common.attribute.BaseAttribute;
50 import edu.internet2.middleware.shibboleth.common.attribute.encoding.AttributeEncoder;
51 import edu.internet2.middleware.shibboleth.common.attribute.encoding.SAMLNameIdentifierEncoder;
52 import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
53 import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
54 import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
55 import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
56 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
57 import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
58 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
59 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
60 import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
61 import edu.internet2.middleware.shibboleth.idp.session.Session;
64 * Base class for SAML profile handlers.
66 public abstract class AbstractSAMLProfileHandler extends
67 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
69 /** SAML message audit log. */
70 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
73 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
75 /** Generator of IDs which may be used for SAML assertions, requests, etc. */
76 private IdentifierGenerator idGenerator;
78 /** All the SAML message decoders configured for the IdP. */
79 private Map<String, SAMLMessageDecoder> messageDecoders;
81 /** All the SAML message encoders configured for the IdP. */
82 private Map<String, SAMLMessageEncoder> messageEncoders;
84 /** SAML message binding used by inbound messages. */
85 private String inboundBinding;
87 /** SAML message bindings that may be used by outbound messages. */
88 private List<String> supportedOutboundBindings;
90 /** Resolver used to determine active security policy for an incoming request. */
91 private SecurityPolicyResolver securityPolicyResolver;
94 protected AbstractSAMLProfileHandler() {
99 * Gets the resolver used to determine active security policy for an incoming request.
101 * @return resolver used to determine active security policy for an incoming request
103 public SecurityPolicyResolver getSecurityPolicyResolver() {
104 if (securityPolicyResolver == null) {
105 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
108 return securityPolicyResolver;
112 * Sets the resolver used to determine active security policy for an incoming request.
114 * @param resolver resolver used to determine active security policy for an incoming request
116 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
117 securityPolicyResolver = resolver;
121 * Gets the audit log for this handler.
123 * @return audit log for this handler
125 protected Logger getAduitLog() {
130 * Gets an ID generator which may be used for SAML assertions, requests, etc.
132 * @return ID generator
134 public IdentifierGenerator getIdGenerator() {
139 * Gets the SAML message binding used by inbound messages.
141 * @return SAML message binding used by inbound messages
143 public String getInboundBinding() {
144 return inboundBinding;
148 * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
150 * @return SAML message decoders configured for the IdP indexed by SAML binding URI
152 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
153 return messageDecoders;
157 * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
159 * @return SAML message encoders configured for the IdP indexed by SAML binding URI
161 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
162 return messageEncoders;
166 * A convenience method for retrieving the SAML metadata provider from the relying party manager.
168 * @return the metadata provider or null
170 public MetadataProvider getMetadataProvider() {
171 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
172 if (rpcManager != null) {
173 return rpcManager.getMetadataProvider();
180 * Gets the SAML message bindings that may be used by outbound messages.
182 * @return SAML message bindings that may be used by outbound messages
184 public List<String> getSupportedOutboundBindings() {
185 return supportedOutboundBindings;
189 * Gets the user's session, if there is one.
191 * @param inTransport current inbound transport
193 * @return user's session
195 protected Session getUserSession(InTransport inTransport) {
196 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
197 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
201 * Gets the user's session based on their principal name.
203 * @param principalName user's principal name
205 * @return the user's session
207 protected Session getUserSession(String principalName) {
208 return getSessionManager().getSession(principalName);
212 * Gets an ID generator which may be used for SAML assertions, requests, etc.
214 * @param generator an ID generator which may be used for SAML assertions, requests, etc
216 public void setIdGenerator(IdentifierGenerator generator) {
217 idGenerator = generator;
221 * Sets the SAML message binding used by inbound messages.
223 * @param binding SAML message binding used by inbound messages
225 public void setInboundBinding(String binding) {
226 inboundBinding = binding;
230 * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
232 * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
234 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
235 messageDecoders = decoders;
239 * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
241 * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
243 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
244 messageEncoders = encoders;
248 * Sets the SAML message bindings that may be used by outbound messages.
250 * @param bindings SAML message bindings that may be used by outbound messages
252 public void setSupportedOutboundBindings(List<String> bindings) {
253 supportedOutboundBindings = bindings;
257 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
259 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
260 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
261 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
263 } catch (MetadataProviderException e) {
264 log.error("Unable to look up relying party metadata", e);
268 return super.getRelyingPartyConfiguration(relyingPartyId);
272 * Populates the request context with information.
274 * This method requires the the following request context properties to be populated: inbound message transport,
275 * peer entity ID, metadata provider
277 * This methods populates the following request context properties: user's session, user's principal name, service
278 * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
279 * issuer, local entity metadata
281 * @param requestContext current request context
282 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
284 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
285 populateRelyingPartyInformation(requestContext);
286 populateAssertingPartyInformation(requestContext);
287 populateSAMLMessageInformation(requestContext);
288 populateProfileInformation(requestContext);
289 populateUserInformation(requestContext);
293 * Populates the request context with information about the relying party.
295 * This method requires the the following request context properties to be populated: peer entity ID
297 * This methods populates the following request context properties: peer entity metadata, relying party
300 * @param requestContext current request context
301 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
303 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
304 throws ProfileException {
305 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
306 String relyingPartyId = requestContext.getInboundMessageIssuer();
308 EntityDescriptor relyingPartyMetadata;
310 relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
311 requestContext.setPeerEntityMetadata(relyingPartyMetadata);
312 } catch (MetadataProviderException e) {
313 log.error("Error looking up metadata for relying party " + relyingPartyId, e);
314 throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
317 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
318 if (rpConfig == null) {
319 log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
320 throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
323 requestContext.setRelyingPartyConfiguration(rpConfig);
327 * Populates the request context with information about the asserting party. Unless overridden,
328 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
329 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
330 * properties it provides are available in the request context.
332 * This method requires the the following request context properties to be populated: metadata provider, relying
333 * party configuration
335 * This methods populates the following request context properties: local entity ID, outbound message issuer, local
338 * @param requestContext current request context
339 * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
341 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
342 throws ProfileException {
343 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
344 requestContext.setLocalEntityId(assertingPartyId);
345 requestContext.setOutboundMessageIssuer(assertingPartyId);
348 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
350 if (localEntityDescriptor != null) {
351 requestContext.setLocalEntityMetadata(localEntityDescriptor);
353 } catch (MetadataProviderException e) {
354 log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
355 throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
360 * Populates the request context with information from the inbound SAML message. Unless overridden,
361 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
362 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
363 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
364 * properties they provide are available in the request context.
367 * @param requestContext current request context
369 * @throws ProfileException thrown if there is a problem populating the request context with information
371 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
372 throws ProfileException;
375 * Populates the request context with the information about the profile. Unless overridden,
376 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
377 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
378 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
379 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
380 * properties they provide are available in the request context.
382 * This method requires the the following request context properties to be populated: relying party configuration
384 * This methods populates the following request context properties: communication profile ID, profile configuration,
385 * outbound message artifact type, peer entity endpoint
387 * @param requestContext current request context
389 * @throws ProfileException thrown if there is a problem populating the profile information
391 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
392 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
393 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
394 if (profileConfig != null) {
395 requestContext.setProfileConfiguration(profileConfig);
396 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
399 Endpoint endpoint = selectEndpoint(requestContext);
400 if (endpoint == null) {
401 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
402 throw new ProfileException("No peer endpoint available to which to send SAML response");
404 requestContext.setPeerEntityEndpoint(endpoint);
408 * Attempts to select the most fitting name identifier attribute, and associated encoder, for a request. If no
409 * attributes for the request subject are available no name identifier is constructed. If a specific name format is
410 * required, as returned by {@link #getRequiredNameIDFormat(BaseSAMLProfileRequestContext)}, then either an
411 * attribute with an encoder supporting that format is selected or an exception is thrown. If no specific format is
412 * required then an attribute supporting a format listed as supported by the relying party is used. If the relying
413 * party does not list any supported formats then any attribute supporting the correct name identifier type is used.
415 * @param <T> type of name identifier encoder the attribute must support
416 * @param nameIdEncoderType type of name identifier encoder the attribute must support
417 * @param requestContext the current request context
419 * @return the select attribute, and its encoder, to be used to build the name identifier
421 * @throws ProfileException thrown if a specific name identifier format was required but not supported
423 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
424 Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
425 String requiredNameFormat = getRequiredNameIDFormat(requestContext);
426 String requiredNameFormatErr = MessageFormatter.format(
427 "No attribute of principal '{}' can be encoded in to a NameIdentifier of "
428 + "required format '{}' for relying party '{}'", new Object[] {
429 requestContext.getPrincipalName(), requiredNameFormat,
430 requestContext.getInboundMessageIssuer(), });
432 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
433 if (principalAttributes == null || principalAttributes.isEmpty()) {
434 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
435 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
436 if (requiredNameFormat == null) {
439 log.warn(requiredNameFormatErr);
440 throw new ProfileException(requiredNameFormatErr);
444 List<String> supportedNameFormats = getNameFormats(requestContext);
445 if (!supportedNameFormats.isEmpty()) {
446 log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
447 supportedNameFormats);
449 log.debug("Relying party '{}' indicated no preferred name formats", requestContext
450 .getInboundMessageIssuer());
453 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
454 if (requiredNameFormat == null) {
455 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
456 supportedNameFormats);
457 if (nameIdAttributeAndEncoder == null) {
458 log.debug("No attributes for principal '{}' supports encoding into a supported NameIdentifier format for relying party '{}'",
459 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
463 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
465 if (nameIdAttributeAndEncoder == null) {
466 log.warn(requiredNameFormatErr);
467 throw new ProfileException(requiredNameFormatErr);
471 return nameIdAttributeAndEncoder;
475 * Gets the name identifier format required to be sent back to the relying party.
477 * This implementation of this method returns null. Profile handler implementations should override this method if
478 * an incoming request is capable of requiring a specific format.
480 * @param requestContext current request context
482 * @return the required name ID format or null if no specific format is required
484 protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
489 * Gets the name identifier formats to use when creating identifiers for the relying party.
491 * @param requestContext current request context
493 * @return list of formats that may be used with the relying party, or an empty list for no preference
495 * @throws ProfileException thrown if there is a problem determining the name identifier format to use
497 protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
498 ArrayList<String> nameFormats = new ArrayList<String>();
500 RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
501 if (relyingPartyRole != null) {
502 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
503 if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
504 nameFormats.addAll(relyingPartySupportedFormats);
508 // If metadata contains the unspecified name format this means that any are supported
509 if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
513 if (!nameFormats.isEmpty()) {
514 log.debug("Relying party '{}' supports the name formats: {}", requestContext.getInboundMessageIssuer(),
517 log.debug("Relying party '{}' indicated no preferred name formats", requestContext
518 .getInboundMessageIssuer());
525 * Gets the list of name identifier formats supported for a given role.
527 * @param role the role to get the list of supported name identifier formats
529 * @return list of supported name identifier formats
531 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
532 List<NameIDFormat> nameIDFormats = null;
534 if (role instanceof SSODescriptor) {
535 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
536 } else if (role instanceof AuthnAuthorityDescriptor) {
537 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
538 } else if (role instanceof PDPDescriptor) {
539 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
540 } else if (role instanceof AttributeAuthorityDescriptor) {
541 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
544 ArrayList<String> supportedFormats = new ArrayList<String>();
545 if (nameIDFormats != null) {
546 for (NameIDFormat format : nameIDFormats) {
547 supportedFormats.add(format.getFormat());
551 return supportedFormats;
555 * Selects an attribute, resolved previously and of the required format, to encode as a NameID.
557 * @param <T> type of name identifier encoder the attribute must support
558 * @param nameIdEncoderType type of name identifier encoder the attribute must support
559 * @param principalAttributes resolved attributes
560 * @param requiredNameFormat required NameID format
562 * @return the attribute and its associated NameID encoder
564 * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
566 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
567 Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes, String requiredNameFormat)
568 throws ProfileException {
570 T nameIdEncoder = null;
572 if (principalAttributes != null) {
573 for (BaseAttribute<?> attribute : principalAttributes.values()) {
574 if (attribute == null) {
578 for (AttributeEncoder encoder : attribute.getEncoders()) {
579 if (encoder == null) {
583 if (nameIdEncoderType.isInstance(encoder)) {
584 nameIdEncoder = nameIdEncoderType.cast(encoder);
585 if (nameIdEncoder.getNameFormat().equals(requiredNameFormat)) {
586 return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
597 * Selects an attribute, resolved previously, to encode as a NameID.
599 * @param <T> type of name identifier encoder the attribute must support
600 * @param nameIdEncoderType type of name identifier encoder the attribute must support
601 * @param principalAttributes resolved attributes
602 * @param supportedNameFormats NameID formats supported by the relying party or an empty list if all formats are
605 * @return the attribute and its associated NameID encoder
607 * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
609 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
610 Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
611 List<String> supportedNameFormats) throws ProfileException {
613 T nameIdEncoder = null;
615 if (principalAttributes != null) {
616 for (BaseAttribute<?> attribute : principalAttributes.values()) {
617 if (attribute == null) {
621 for (AttributeEncoder encoder : attribute.getEncoders()) {
622 if (encoder == null) {
626 if (nameIdEncoderType.isInstance(encoder)) {
627 nameIdEncoder = nameIdEncoderType.cast(encoder);
628 if (supportedNameFormats.isEmpty()
629 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
630 return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
641 * Populates the request context with the information about the user if they have an existing session. Unless
642 * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
643 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
644 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
645 * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
646 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
647 * properties they provide are available in the request context.
649 * This method should populate: user's session, user's principal name, and service authentication method
651 * @param requestContext current request context
653 * @throws ProfileException thrown if there is a problem populating the user's information
655 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
656 throws ProfileException;
659 * Selects the appropriate endpoint for the relying party and stores it in the request context.
661 * @param requestContext current request context
663 * @return Endpoint selected from the information provided in the request context
665 * @throws ProfileException thrown if there is a problem selecting a response endpoint
667 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
670 * Encodes the request's SAML response and writes it to the servlet response.
672 * @param requestContext current request context
674 * @throws ProfileException thrown if no message encoder is registered for this profiles binding
676 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
678 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
680 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
681 .getProfileConfiguration();
682 if (profileConfig != null) {
683 if (isSignResponse(requestContext)) {
684 Credential signingCredential = profileConfig.getSigningCredential();
685 if (signingCredential == null) {
686 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
689 if (signingCredential == null) {
690 throw new ProfileException(
691 "Signing of responses is required but no signing credential is available");
694 if (signingCredential.getPrivateKey() == null) {
695 throw new ProfileException(
696 "Signing of response is required but signing credential does not have a private key");
699 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
703 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
704 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
706 requestContext.setMessageEncoder(encoder);
707 encoder.encode(requestContext);
708 } catch (MessageEncodingException e) {
709 throw new ProfileException("Unable to encode response to relying party: "
710 + requestContext.getInboundMessageIssuer(), e);
715 * Determine whether responses should be signed.
717 * @param requestContext the current request context
718 * @return true if responses should be signed, false otherwise
719 * @throws ProfileException if there is a problem determining whether responses should be signed
721 protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
723 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
725 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
726 .getProfileConfiguration();
728 if (profileConfig != null) {
730 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
731 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
732 .providesMessageIntegrity(requestContext));
733 } catch (MessageEncodingException e) {
734 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
735 encoder.getBindingURI());
736 throw new ProfileException("Unable to determine if outbound response should be signed");
745 * Get the outbound message encoder to use.
748 * The default implementation uses the binding URI from the
749 * {@link org.opensaml.common.binding.SAMLMessageContext#getPeerEntityEndpoint()} to lookup the encoder from the
750 * supported message encoders defined in {@link #getMessageEncoders()}.
754 * Subclasses may override to implement a different mechanism to determine the encoder to use, such as for example
755 * cases where an active intermediary actor sits between this provider and the peer entity endpoint (e.g. the SAML 2
759 * @param requestContext current request context
760 * @return the message encoder to use
761 * @throws ProfileException if the encoder to use can not be resolved based on the request context
763 protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
764 throws ProfileException {
765 SAMLMessageEncoder encoder = null;
767 Endpoint endpoint = requestContext.getPeerEntityEndpoint();
768 if (endpoint == null) {
769 log.warn("No peer endpoint available for peer. Unable to send response.");
770 throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
773 if (endpoint != null) {
774 encoder = getMessageEncoders().get(endpoint.getBinding());
775 if (encoder == null) {
776 log.error("No outbound message encoder configured for binding: {}", requestContext
777 .getPeerEntityEndpoint().getBinding());
778 throw new ProfileException("No outbound message encoder configured for binding: "
779 + requestContext.getPeerEntityEndpoint().getBinding());
786 * Get the inbound message decoder to use.
789 * The default implementation uses the binding URI from {@link #getInboundBinding()} to lookup the decoder from the
790 * supported message decoders defined in {@link #getMessageDecoders()}.
794 * Subclasses may override to implement a different mechanism to determine the decoder to use.
797 * @param requestContext current request context
798 * @return the message decoder to use
799 * @throws ProfileException if the decoder to use can not be resolved based on the request context
801 protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
802 throws ProfileException {
803 SAMLMessageDecoder decoder = null;
805 decoder = getMessageDecoders().get(getInboundBinding());
806 if (decoder == null) {
807 log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
808 throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
814 * Writes an audit log entry indicating the successful response to the attribute request.
816 * @param context current request context
818 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
819 AuditLogEntry auditLogEntry = new AuditLogEntry();
820 auditLogEntry.setMessageProfile(getProfileId());
821 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
822 auditLogEntry.setPrincipalName(context.getPrincipalName());
823 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
824 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
825 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
826 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
827 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
828 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
829 if (context.getReleasedAttributes() != null) {
830 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
833 getAduitLog().info(auditLogEntry.toString());