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.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;
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;
66 * Base class for SAML profile handlers.
68 public abstract class AbstractSAMLProfileHandler extends
69 AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
71 /** SAML message audit log. */
72 private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
75 private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
77 /** Generator of IDs which may be used for SAML assertions, requests, etc. */
78 private IdentifierGenerator idGenerator;
80 /** All the SAML message decoders configured for the IdP. */
81 private Map<String, SAMLMessageDecoder> messageDecoders;
83 /** All the SAML message encoders configured for the IdP. */
84 private Map<String, SAMLMessageEncoder> messageEncoders;
86 /** SAML message binding used by inbound messages. */
87 private String inboundBinding;
89 /** SAML message bindings that may be used by outbound messages. */
90 private List<String> supportedOutboundBindings;
92 /** Resolver used to determine active security policy for an incoming request. */
93 private SecurityPolicyResolver securityPolicyResolver;
96 protected AbstractSAMLProfileHandler() {
101 * Gets the resolver used to determine active security policy for an incoming request.
103 * @return resolver used to determine active security policy for an incoming request
105 public SecurityPolicyResolver getSecurityPolicyResolver() {
106 if (securityPolicyResolver == null) {
107 setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
110 return securityPolicyResolver;
114 * Sets the resolver used to determine active security policy for an incoming request.
116 * @param resolver resolver used to determine active security policy for an incoming request
118 public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
119 securityPolicyResolver = resolver;
123 * Gets the audit log for this handler.
125 * @return audit log for this handler
127 protected Logger getAduitLog() {
132 * Gets an ID generator which may be used for SAML assertions, requests, etc.
134 * @return ID generator
136 public IdentifierGenerator getIdGenerator() {
141 * Gets the SAML message binding used by inbound messages.
143 * @return SAML message binding used by inbound messages
145 public String getInboundBinding() {
146 return inboundBinding;
150 * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
152 * @return SAML message decoders configured for the IdP indexed by SAML binding URI
154 public Map<String, SAMLMessageDecoder> getMessageDecoders() {
155 return messageDecoders;
159 * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
161 * @return SAML message encoders configured for the IdP indexed by SAML binding URI
163 public Map<String, SAMLMessageEncoder> getMessageEncoders() {
164 return messageEncoders;
168 * A convenience method for retrieving the SAML metadata provider from the relying party manager.
170 * @return the metadata provider or null
172 public MetadataProvider getMetadataProvider() {
173 SAMLMDRelyingPartyConfigurationManager rpcManager = getRelyingPartyConfigurationManager();
174 if (rpcManager != null) {
175 return rpcManager.getMetadataProvider();
182 * A convenience method for obtaining a metadata credential resolver for the current metadata provider.
184 * @return the metadata credential resolver or null
186 public MetadataCredentialResolver getMetadataCredentialResolver() {
187 MetadataCredentialResolverFactory mcrFactory = MetadataCredentialResolverFactory.getFactory();
188 MetadataProvider metadataProvider = getMetadataProvider();
189 return mcrFactory.getInstance(metadataProvider);
193 * Gets the SAML message bindings that may be used by outbound messages.
195 * @return SAML message bindings that may be used by outbound messages
197 public List<String> getSupportedOutboundBindings() {
198 return supportedOutboundBindings;
202 * Gets the user's session, if there is one.
204 * @param inTransport current inbound transport
206 * @return user's session
208 protected Session getUserSession(InTransport inTransport) {
209 HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
210 return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
214 * Gets the user's session based on their principal name.
216 * @param principalName user's principal name
218 * @return the user's session
220 protected Session getUserSession(String principalName) {
221 return getSessionManager().getSession(principalName);
225 * Gets an ID generator which may be used for SAML assertions, requests, etc.
227 * @param generator an ID generator which may be used for SAML assertions, requests, etc
229 public void setIdGenerator(IdentifierGenerator generator) {
230 idGenerator = generator;
234 * Sets the SAML message binding used by inbound messages.
236 * @param binding SAML message binding used by inbound messages
238 public void setInboundBinding(String binding) {
239 inboundBinding = binding;
243 * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
245 * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
247 public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
248 messageDecoders = decoders;
252 * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
254 * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
256 public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
257 messageEncoders = encoders;
261 * Sets the SAML message bindings that may be used by outbound messages.
263 * @param bindings SAML message bindings that may be used by outbound messages
265 public void setSupportedOutboundBindings(List<String> bindings) {
266 supportedOutboundBindings = bindings;
270 public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
272 if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
273 log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
274 return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
276 } catch (MetadataProviderException e) {
277 log.error("Unable to look up relying party metadata", e);
281 return super.getRelyingPartyConfiguration(relyingPartyId);
285 * Populates the request context with information.
287 * This method requires the the following request context properties to be populated: inbound message transport,
288 * peer entity ID, metadata provider
290 * This methods populates the following request context properties: user's session, user's principal name, service
291 * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
292 * issuer, local entity metadata
294 * @param requestContext current request context
295 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
297 protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
298 populateRelyingPartyInformation(requestContext);
299 populateAssertingPartyInformation(requestContext);
300 populateSAMLMessageInformation(requestContext);
301 populateProfileInformation(requestContext);
302 populateUserInformation(requestContext);
306 * Populates the request context with information about the relying party.
308 * This method requires the the following request context properties to be populated: peer entity ID
310 * This methods populates the following request context properties: peer entity metadata, relying party
313 * @param requestContext current request context
314 * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
316 protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
317 throws ProfileException {
318 MetadataProvider metadataProvider = requestContext.getMetadataProvider();
319 String relyingPartyId = requestContext.getInboundMessageIssuer();
321 EntityDescriptor relyingPartyMetadata;
323 relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
324 requestContext.setPeerEntityMetadata(relyingPartyMetadata);
325 } catch (MetadataProviderException e) {
326 log.error("Error looking up metadata for relying party " + relyingPartyId, e);
327 throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
330 RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
331 if (rpConfig == null) {
332 log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
333 throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
336 requestContext.setRelyingPartyConfiguration(rpConfig);
340 * Populates the request context with information about the asserting party. Unless overridden,
341 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
342 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
343 * properties it provides are available in the request context.
345 * This method requires the the following request context properties to be populated: metadata provider, relying
346 * party configuration
348 * This methods populates the following request context properties: local entity ID, outbound message issuer, local
351 * @param requestContext current request context
352 * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
354 protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
355 throws ProfileException {
356 String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
357 requestContext.setLocalEntityId(assertingPartyId);
358 requestContext.setOutboundMessageIssuer(assertingPartyId);
361 EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
363 if (localEntityDescriptor != null) {
364 requestContext.setLocalEntityMetadata(localEntityDescriptor);
366 } catch (MetadataProviderException e) {
367 log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
368 throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
373 * Populates the request context with information from the inbound SAML message. Unless overridden,
374 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
375 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
376 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
377 * properties they provide are available in the request context.
380 * @param requestContext current request context
382 * @throws ProfileException thrown if there is a problem populating the request context with information
384 protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
385 throws ProfileException;
388 * Populates the request context with the information about the profile. Unless overridden,
389 * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
390 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
391 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
392 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
393 * properties they provide are available in the request context.
395 * This method requires the the following request context properties to be populated: relying party configuration
397 * This methods populates the following request context properties: communication profile ID, profile configuration,
398 * outbound message artifact type, peer entity endpoint
400 * @param requestContext current request context
402 * @throws ProfileException thrown if there is a problem populating the profile information
404 protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
405 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
406 .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
407 if (profileConfig != null) {
408 requestContext.setProfileConfiguration(profileConfig);
409 requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
412 Endpoint endpoint = selectEndpoint(requestContext);
413 if (endpoint == null) {
414 log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
415 throw new ProfileException("No peer endpoint available to which to send SAML response");
417 requestContext.setPeerEntityEndpoint(endpoint);
421 * Attempts to select the most fitting name identifier attribute, and associated encoder, for a request. If no
422 * attributes for the request subject are available no name identifier is constructed. If a specific name format is
423 * required, as returned by {@link #getRequiredNameIDFormat(BaseSAMLProfileRequestContext)}, then either an
424 * attribute with an encoder supporting that format is selected or an exception is thrown. If no specific format is
425 * required then an attribute supporting a format listed as supported by the relying party is used. If the relying
426 * party does not list any supported formats then any attribute supporting the correct name identifier type is used.
428 * @param <T> type of name identifier encoder the attribute must support
429 * @param nameIdEncoderType type of name identifier encoder the attribute must support
430 * @param requestContext the current request context
432 * @return the select attribute, and its encoder, to be used to build the name identifier
434 * @throws ProfileException thrown if a specific name identifier format was required but not supported
436 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
437 Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext) throws ProfileException {
439 String requiredNameFormat = DatatypeHelper.safeTrimOrNullString(getRequiredNameIDFormat(requestContext));
440 if (requiredNameFormat != null) {
441 log.debug("Attempting to build name identifier for relying party'{}' that requires format '{}'",
442 requestContext.getInboundMessageIssuer(), requiredNameFormat);
443 return selectNameIDAttributeAndEncoderByRequiredFormat(requiredNameFormat, nameIdEncoderType,
447 List<String> supportedNameFormats = getNameFormats(requestContext);
448 if (supportedNameFormats.isEmpty()) {
449 log.debug("Attempting to build name identifier for relying party '{}' that supports any format",
450 requestContext.getInboundMessageIssuer());
452 log.debug("Attempting to build name identifier for relying party '{}' that supports the formats: {}",
453 requestContext.getInboundMessageIssuer(), supportedNameFormats);
455 return selectNameIDAttributeAndEncoderBySupportedFormats(supportedNameFormats, nameIdEncoderType,
460 * Gets the name identifier format required to be sent back to the relying party.
462 * This implementation of this method returns null. Profile handler implementations should override this method if
463 * an incoming request is capable of requiring a specific format.
465 * @param requestContext current request context
467 * @return the required name ID format or null if no specific format is required
469 protected String getRequiredNameIDFormat(BaseSAMLProfileRequestContext requestContext) {
474 * Selects the principal attribute that can be encoded in to the required name identifier format.
476 * @param <T> type of name identifier encoder the attribute must support
477 * @param requiredNameFormat required name identifier format type
478 * @param nameIdEncoderType type of name identifier encoder the attribute must support
479 * @param requestContext the current request context
481 * @return the select attribute, and its encoder, to be used to build the name identifier
483 * @throws ProfileException thrown if a specific name identifier format was required but not supported
485 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderByRequiredFormat(
486 String requiredNameFormat, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
487 throws ProfileException {
488 String requiredNameFormatErr = "No attribute of principal '" + requestContext.getPrincipalName()
489 + "' can be encoded in to a NameIdentifier of " + "required format '" + requiredNameFormat
490 + "' for relying party '" + requestContext.getInboundMessageIssuer() + "'";
492 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
493 if (principalAttributes == null || principalAttributes.isEmpty()) {
494 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
495 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
496 log.warn(requiredNameFormatErr);
497 throw new ProfileException(requiredNameFormatErr);
500 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType,
501 principalAttributes, java.util.Collections.singletonList(requiredNameFormat));
502 if (nameIdAttributeAndEncoder == null) {
503 log.warn(requiredNameFormatErr);
504 throw new ProfileException(requiredNameFormatErr);
507 return nameIdAttributeAndEncoder;
511 * Gets the name identifier formats to use when creating identifiers for the relying party.
513 * @param requestContext current request context
515 * @return list of formats that may be used with the relying party, or an empty list for no preference
517 * @throws ProfileException thrown if there is a problem determining the name identifier format to use
519 protected List<String> getNameFormats(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
520 ArrayList<String> nameFormats = new ArrayList<String>();
522 RoleDescriptor relyingPartyRole = requestContext.getPeerEntityRoleMetadata();
523 if (relyingPartyRole != null) {
524 List<String> relyingPartySupportedFormats = getEntitySupportedFormats(relyingPartyRole);
525 if (relyingPartySupportedFormats != null && !relyingPartySupportedFormats.isEmpty()) {
526 nameFormats.addAll(relyingPartySupportedFormats);
530 // If metadata contains the unspecified name format this means that any are supported
531 if (nameFormats.contains(NameIdentifier.UNSPECIFIED)) {
539 * Gets the list of name identifier formats supported for a given role.
541 * @param role the role to get the list of supported name identifier formats
543 * @return list of supported name identifier formats
545 protected List<String> getEntitySupportedFormats(RoleDescriptor role) {
546 List<NameIDFormat> nameIDFormats = null;
548 if (role instanceof SSODescriptor) {
549 nameIDFormats = ((SSODescriptor) role).getNameIDFormats();
550 } else if (role instanceof AuthnAuthorityDescriptor) {
551 nameIDFormats = ((AuthnAuthorityDescriptor) role).getNameIDFormats();
552 } else if (role instanceof PDPDescriptor) {
553 nameIDFormats = ((PDPDescriptor) role).getNameIDFormats();
554 } else if (role instanceof AttributeAuthorityDescriptor) {
555 nameIDFormats = ((AttributeAuthorityDescriptor) role).getNameIDFormats();
558 ArrayList<String> supportedFormats = new ArrayList<String>();
559 if (nameIDFormats != null) {
560 for (NameIDFormat format : nameIDFormats) {
561 supportedFormats.add(format.getFormat());
565 return supportedFormats;
569 * Selects the principal attribute that can be encoded in to one of the supported name identifier formats.
571 * @param <T> type of name identifier encoder the attribute must support
572 * @param supportedNameFormats name identifier formats supported by the relaying part, or an empty list if all
573 * formats are supported
574 * @param nameIdEncoderType type of name identifier encoder the attribute must support
575 * @param requestContext the current request context
577 * @return the select attribute, and its encoder, to be used to build the name identifier
579 * @throws ProfileException thrown if there is a problem selecting the attribute
581 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoderBySupportedFormats(
582 List<String> supportedNameFormats, Class<T> nameIdEncoderType, BaseSAMLProfileRequestContext requestContext)
583 throws ProfileException {
584 Map<String, BaseAttribute> principalAttributes = requestContext.getAttributes();
585 if (principalAttributes == null || principalAttributes.isEmpty()) {
586 log.debug("No attributes for principal '{}', no name identifier will be created for relying party '{}'",
587 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
591 Pair<BaseAttribute, T> nameIdAttributeAndEncoder = null;
592 nameIdAttributeAndEncoder = selectNameIDAttributeAndEncoder(nameIdEncoderType, principalAttributes,
593 supportedNameFormats);
594 if (nameIdAttributeAndEncoder == null) {
597 "No attributes for principal '{}' support encoding into a supported name identifier format for relying party '{}'",
598 requestContext.getPrincipalName(), requestContext.getInboundMessageIssuer());
601 return nameIdAttributeAndEncoder;
605 * Selects an attribute, resolved previously, to encode as a NameID.
607 * @param <T> type of name identifier encoder the attribute must support
608 * @param nameIdEncoderType type of name identifier encoder the attribute must support
609 * @param principalAttributes resolved attributes
610 * @param supportedNameFormats NameID formats supported by the relying party or an empty list if all formats are
613 * @return the attribute and its associated NameID encoder
615 * @throws ProfileException thrown if no attribute can be encoded in to a NameID of the required type
617 protected <T extends SAMLNameIdentifierEncoder> Pair<BaseAttribute, T> selectNameIDAttributeAndEncoder(
618 Class<T> nameIdEncoderType, Map<String, BaseAttribute> principalAttributes,
619 List<String> supportedNameFormats) throws ProfileException {
621 T nameIdEncoder = null;
623 if (principalAttributes != null) {
624 for (BaseAttribute<?> attribute : principalAttributes.values()) {
625 if (attribute == null) {
629 for (AttributeEncoder encoder : attribute.getEncoders()) {
630 if (encoder == null) {
634 if (nameIdEncoderType.isInstance(encoder)) {
635 nameIdEncoder = nameIdEncoderType.cast(encoder);
636 if (supportedNameFormats.isEmpty()
637 || supportedNameFormats.contains(nameIdEncoder.getNameFormat())) {
638 return new Pair<BaseAttribute, T>(attribute, nameIdEncoder);
649 * Populates the request context with the information about the user if they have an existing session. Unless
650 * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
651 * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
652 * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
653 * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
654 * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
655 * properties they provide are available in the request context.
657 * This method should populate: user's session, user's principal name, and service authentication method
659 * @param requestContext current request context
661 * @throws ProfileException thrown if there is a problem populating the user's information
663 protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
664 throws ProfileException;
667 * Selects the appropriate endpoint for the relying party and stores it in the request context.
669 * @param requestContext current request context
671 * @return Endpoint selected from the information provided in the request context
673 * @throws ProfileException thrown if there is a problem selecting a response endpoint
675 protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
678 * Encodes the request's SAML response and writes it to the servlet response.
680 * @param requestContext current request context
682 * @throws ProfileException thrown if no message encoder is registered for this profiles binding
684 protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
686 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
688 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
689 .getProfileConfiguration();
690 if (profileConfig != null) {
691 if (isSignResponse(requestContext)) {
692 Credential signingCredential = profileConfig.getSigningCredential();
693 if (signingCredential == null) {
694 signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
697 if (signingCredential == null) {
698 throw new ProfileException(
699 "Signing of responses is required but no signing credential is available");
702 if (signingCredential.getPrivateKey() == null) {
703 throw new ProfileException(
704 "Signing of response is required but signing credential does not have a private key");
707 requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
711 log.debug("Encoding response to SAML request {} from relying party {}", requestContext
712 .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
714 requestContext.setMessageEncoder(encoder);
715 encoder.encode(requestContext);
716 } catch (MessageEncodingException e) {
717 throw new ProfileException("Unable to encode response to relying party: "
718 + requestContext.getInboundMessageIssuer(), e);
723 * Determine whether responses should be signed.
725 * @param requestContext the current request context
726 * @return true if responses should be signed, false otherwise
727 * @throws ProfileException if there is a problem determining whether responses should be signed
729 protected boolean isSignResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
731 SAMLMessageEncoder encoder = getOutboundMessageEncoder(requestContext);
733 AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
734 .getProfileConfiguration();
736 if (profileConfig != null) {
738 return profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
739 || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
740 .providesMessageIntegrity(requestContext));
741 } catch (MessageEncodingException e) {
742 log.error("Unable to determine if outbound encoding '{}' provides message integrity protection",
743 encoder.getBindingURI());
744 throw new ProfileException("Unable to determine if outbound response should be signed");
753 * Get the outbound message encoder to use.
756 * The default implementation uses the binding URI from the
757 * {@link org.opensaml.common.binding.SAMLMessageContext#getPeerEntityEndpoint()} to lookup the encoder from the
758 * supported message encoders defined in {@link #getMessageEncoders()}.
762 * Subclasses may override to implement a different mechanism to determine the encoder to use, such as for example
763 * cases where an active intermediary actor sits between this provider and the peer entity endpoint (e.g. the SAML 2
767 * @param requestContext current request context
768 * @return the message encoder to use
769 * @throws ProfileException if the encoder to use can not be resolved based on the request context
771 protected SAMLMessageEncoder getOutboundMessageEncoder(BaseSAMLProfileRequestContext requestContext)
772 throws ProfileException {
773 SAMLMessageEncoder encoder = null;
775 Endpoint endpoint = requestContext.getPeerEntityEndpoint();
776 if (endpoint == null) {
777 log.warn("No peer endpoint available for peer. Unable to send response.");
778 throw new ProfileException("No peer endpoint available for peer. Unable to send response.");
781 if (endpoint != null) {
782 encoder = getMessageEncoders().get(endpoint.getBinding());
783 if (encoder == null) {
784 log.error("No outbound message encoder configured for binding: {}", requestContext
785 .getPeerEntityEndpoint().getBinding());
786 throw new ProfileException("No outbound message encoder configured for binding: "
787 + requestContext.getPeerEntityEndpoint().getBinding());
794 * Get the inbound message decoder to use.
797 * The default implementation uses the binding URI from {@link #getInboundBinding()} to lookup the decoder from the
798 * supported message decoders defined in {@link #getMessageDecoders()}.
802 * Subclasses may override to implement a different mechanism to determine the decoder to use.
805 * @param requestContext current request context
806 * @return the message decoder to use
807 * @throws ProfileException if the decoder to use can not be resolved based on the request context
809 protected SAMLMessageDecoder getInboundMessageDecoder(BaseSAMLProfileRequestContext requestContext)
810 throws ProfileException {
811 SAMLMessageDecoder decoder = null;
813 decoder = getMessageDecoders().get(getInboundBinding());
814 if (decoder == null) {
815 log.error("No inbound message decoder configured for binding: {}", getInboundBinding());
816 throw new ProfileException("No inbound message decoder configured for binding: " + getInboundBinding());
822 * Writes an audit log entry indicating the successful response to the attribute request.
824 * @param context current request context
826 protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
827 AuditLogEntry auditLogEntry = new AuditLogEntry();
828 auditLogEntry.setMessageProfile(getProfileId());
829 auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
830 auditLogEntry.setPrincipalName(context.getPrincipalName());
831 auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
832 auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
833 auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
834 auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
835 auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
836 auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
837 if (context.getReleasedAttributes() != null) {
838 auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
841 getAduitLog().info(auditLogEntry.toString());