package edu.internet2.middleware.shibboleth.idp.profile;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
+import java.util.List;
+import java.util.Map;
+
import javax.servlet.http.HttpServletRequest;
-import org.apache.log4j.Logger;
import org.opensaml.common.IdentifierGenerator;
-import org.opensaml.common.SAMLObject;
-import org.opensaml.common.binding.decoding.MessageDecoderFactory;
-import org.opensaml.common.binding.encoding.MessageEncoderFactory;
+import org.opensaml.common.binding.decoding.SAMLMessageDecoder;
+import org.opensaml.common.binding.encoding.SAMLMessageEncoder;
+import org.opensaml.saml2.metadata.Endpoint;
import org.opensaml.saml2.metadata.EntityDescriptor;
-import org.opensaml.saml2.metadata.RoleDescriptor;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
+import org.opensaml.saml2.metadata.provider.MetadataProviderException;
+import org.opensaml.ws.message.encoder.MessageEncodingException;
+import org.opensaml.ws.security.SecurityPolicyResolver;
+import org.opensaml.ws.transport.InTransport;
+import org.opensaml.ws.transport.http.HttpServletRequestAdapter;
+import org.opensaml.xml.security.credential.Credential;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import edu.internet2.middleware.shibboleth.common.log.AuditLogEntry;
-import edu.internet2.middleware.shibboleth.common.profile.ProfileRequest;
-import edu.internet2.middleware.shibboleth.common.profile.ProfileResponse;
+import edu.internet2.middleware.shibboleth.common.profile.ProfileException;
import edu.internet2.middleware.shibboleth.common.profile.provider.AbstractShibbolethProfileHandler;
+import edu.internet2.middleware.shibboleth.common.profile.provider.BaseSAMLProfileRequestContext;
+import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartyConfiguration;
+import edu.internet2.middleware.shibboleth.common.relyingparty.RelyingPartySecurityPolicyResolver;
+import edu.internet2.middleware.shibboleth.common.relyingparty.provider.AbstractSAMLProfileConfiguration;
+import edu.internet2.middleware.shibboleth.common.relyingparty.provider.CryptoOperationRequirementLevel;
import edu.internet2.middleware.shibboleth.common.relyingparty.provider.SAMLMDRelyingPartyConfigurationManager;
import edu.internet2.middleware.shibboleth.idp.session.Session;
AbstractShibbolethProfileHandler<SAMLMDRelyingPartyConfigurationManager, Session> {
/** SAML message audit log. */
- private final Logger auditLog = Logger.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
+ private final Logger auditLog = LoggerFactory.getLogger(AuditLogEntry.AUDIT_LOGGER_NAME);
+
+ /** Class logger. */
+ private final Logger log = LoggerFactory.getLogger(AbstractSAMLProfileHandler.class);
/** Generator of IDs which may be used for SAML assertions, requests, etc. */
private IdentifierGenerator idGenerator;
- /** Factory of message decoders. */
- private MessageDecoderFactory decoderFactory;
+ /** All the SAML message decoders configured for the IdP. */
+ private Map<String, SAMLMessageDecoder> messageDecoders;
+
+ /** All the SAML message encoders configured for the IdP. */
+ private Map<String, SAMLMessageEncoder> messageEncoders;
+
+ /** SAML message binding used by inbound messages. */
+ private String inboundBinding;
- /** Factory of message encoders. */
- private MessageEncoderFactory encoderFactory;
+ /** SAML message bindings that may be used by outbound messages. */
+ private List<String> supportedOutboundBindings;
+
+ /** Resolver used to determine active security policy for an incoming request. */
+ private SecurityPolicyResolver securityPolicyResolver;
/** Constructor. */
protected AbstractSAMLProfileHandler() {
}
/**
- * Gets an ID generator which may be used for SAML assertions, requests, etc.
+ * Gets the resolver used to determine active security policy for an incoming request.
*
- * @return ID generator
+ * @return resolver used to determine active security policy for an incoming request
*/
- public IdentifierGenerator getIdGenerator() {
- return idGenerator;
+ public SecurityPolicyResolver getSecurityPolicyResolver() {
+ if (securityPolicyResolver == null) {
+ setSecurityPolicyResolver(new RelyingPartySecurityPolicyResolver(getRelyingPartyConfigurationManager()));
+ }
+
+ return securityPolicyResolver;
}
/**
- * Gets an ID generator which may be used for SAML assertions, requests, etc.
+ * Sets the resolver used to determine active security policy for an incoming request.
*
- * @param generator an ID generator which may be used for SAML assertions, requests, etc
+ * @param resolver resolver used to determine active security policy for an incoming request
*/
- public void setIdGenerator(IdentifierGenerator generator) {
- idGenerator = generator;
+ public void setSecurityPolicyResolver(SecurityPolicyResolver resolver) {
+ securityPolicyResolver = resolver;
}
/**
- * Gets the factory used to build new message decoders.
+ * Gets the audit log for this handler.
+ *
+ * @return audit log for this handler
+ */
+ protected Logger getAduitLog() {
+ return auditLog;
+ }
+
+ /**
+ * Gets an ID generator which may be used for SAML assertions, requests, etc.
*
- * @return factory used to build new message decoders
+ * @return ID generator
*/
- public MessageDecoderFactory getMessageDecoderFactory() {
- return decoderFactory;
+ public IdentifierGenerator getIdGenerator() {
+ return idGenerator;
}
/**
- * Sets the factory used to build new message decoders.
+ * Gets the SAML message binding used by inbound messages.
*
- * @param factory factory used to build new message decoders
+ * @return SAML message binding used by inbound messages
*/
- public void setMessageDecoderFactory(MessageDecoderFactory factory) {
- decoderFactory = factory;
+ public String getInboundBinding() {
+ return inboundBinding;
}
/**
- * Gets the factory used to build message encoders.
+ * Gets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
*
- * @return factory used to build message encoders
+ * @return SAML message decoders configured for the IdP indexed by SAML binding URI
*/
- public MessageEncoderFactory getMessageEncoderFactory() {
- return encoderFactory;
+ public Map<String, SAMLMessageDecoder> getMessageDecoders() {
+ return messageDecoders;
}
/**
- * Sets the factory used to build message encoders.
+ * Gets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
*
- * @param factory factory used to build message encoders
+ * @return SAML message encoders configured for the IdP indexed by SAML binding URI
*/
- public void setMessageEncoderFactory(MessageEncoderFactory factory) {
- encoderFactory = factory;
+ public Map<String, SAMLMessageEncoder> getMessageEncoders() {
+ return messageEncoders;
}
/**
}
/**
- * Gets the audit log for this handler.
+ * Gets the SAML message bindings that may be used by outbound messages.
*
- * @return audit log for this handler
+ * @return SAML message bindings that may be used by outbound messages
*/
- protected Logger getAduitLog() {
- return auditLog;
+ public List<String> getSupportedOutboundBindings() {
+ return supportedOutboundBindings;
}
/**
- * Gets the user's session ID from the current request.
+ * Gets the user's session, if there is one.
*
- * @param request current request
+ * @param inTransport current inbound transport
*
- * @return user's session ID
+ * @return user's session
*/
- protected String getUserSessionId(ProfileRequest<ServletRequest> request) {
- HttpServletRequest rawRequest = (HttpServletRequest) request.getRawRequest();
- if (rawRequest != null) {
- return (String) rawRequest.getSession().getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
- }
+ protected Session getUserSession(InTransport inTransport) {
+ HttpServletRequest rawRequest = ((HttpServletRequestAdapter) inTransport).getWrappedRequest();
+ return (Session) rawRequest.getAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE);
+ }
- return null;
+ /**
+ * Gets the user's session based on their principal name.
+ *
+ * @param principalName user's principal name
+ *
+ * @return the user's session
+ */
+ protected Session getUserSession(String principalName) {
+ return getSessionManager().getSession(principalName);
}
/**
- * Contextual object used to accumlate information as profile requests are being processed.
+ * Gets an ID generator which may be used for SAML assertions, requests, etc.
*
- * @param <StatusType> type of Status object
+ * @param generator an ID generator which may be used for SAML assertions, requests, etc
*/
- protected class SAMLProfileRequestContext<StatusType extends SAMLObject> extends ShibbolethProfileRequestContext {
+ public void setIdGenerator(IdentifierGenerator generator) {
+ idGenerator = generator;
+ }
- /** Entity descriptor for the asserting party. */
- private EntityDescriptor assertingPartyMetadata;
+ /**
+ * Sets the SAML message binding used by inbound messages.
+ *
+ * @param binding SAML message binding used by inbound messages
+ */
+ public void setInboundBinding(String binding) {
+ inboundBinding = binding;
+ }
- /** Role descriptor meatadata for the asserting party. */
- private RoleDescriptor assertingPartyRoleMetadata;
+ /**
+ * Sets all the SAML message decoders configured for the IdP indexed by SAML binding URI.
+ *
+ * @param decoders SAML message decoders configured for the IdP indexed by SAML binding URI
+ */
+ public void setMessageDecoders(Map<String, SAMLMessageDecoder> decoders) {
+ messageDecoders = decoders;
+ }
- /** Entity descriptor for the relying party. */
- private EntityDescriptor relyingPartyMetadata;
+ /**
+ * Sets all the SAML message encoders configured for the IdP indexed by SAML binding URI.
+ *
+ * @param encoders SAML message encoders configured for the IdP indexed by SAML binding URI
+ */
+ public void setMessageEncoders(Map<String, SAMLMessageEncoder> encoders) {
+ messageEncoders = encoders;
+ }
- /** Role descriptor meatadata for the relying party. */
- private RoleDescriptor relyingPartyRoleMetadata;
+ /**
+ * Sets the SAML message bindings that may be used by outbound messages.
+ *
+ * @param bindings SAML message bindings that may be used by outbound messages
+ */
+ public void setSupportedOutboundBindings(List<String> bindings) {
+ supportedOutboundBindings = bindings;
+ }
- /**
- * Constructor.
- *
- * @param request current profile request
- * @param response current profile response
- */
- public SAMLProfileRequestContext(ProfileRequest<ServletRequest> request,
- ProfileResponse<ServletResponse> response) {
- super(request, response);
+ /** {@inheritDoc} */
+ public RelyingPartyConfiguration getRelyingPartyConfiguration(String relyingPartyId) {
+ try {
+ if (getMetadataProvider().getEntityDescriptor(relyingPartyId) == null) {
+ log.warn("No metadata for relying party {}, treating party as anonymous", relyingPartyId);
+ return getRelyingPartyConfigurationManager().getAnonymousRelyingConfiguration();
+ }
+ } catch (MetadataProviderException e) {
+ log.error("Unable to look up relying party metadata", e);
+ return null;
}
- /**
- * Gets the metadata for the asserting party.
- *
- * @return metadata for the asserting party
- */
- public EntityDescriptor getAssertingPartyMetadata() {
- return assertingPartyMetadata;
- }
+ return super.getRelyingPartyConfiguration(relyingPartyId);
+ }
- /**
- * Sets the metadata for the asserting party.
- *
- * @param metadata metadata for the asserting party
- */
- public void setAssertingPartyMetadata(EntityDescriptor metadata) {
- assertingPartyMetadata = metadata;
+ /**
+ * Populates the request context with information.
+ *
+ * This method requires the the following request context properties to be populated: inbound message transport,
+ * peer entity ID, metadata provider
+ *
+ * This methods populates the following request context properties: user's session, user's principal name, service
+ * authentication method, peer entity metadata, relying party configuration, local entity ID, outbound message
+ * issuer, local entity metadata
+ *
+ * @param requestContext current request context
+ * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
+ */
+ protected void populateRequestContext(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
+ populateRelyingPartyInformation(requestContext);
+ populateAssertingPartyInformation(requestContext);
+ populateSAMLMessageInformation(requestContext);
+ populateProfileInformation(requestContext);
+ populateUserInformation(requestContext);
+ }
+
+ /**
+ * Populates the request context with information about the relying party.
+ *
+ * This method requires the the following request context properties to be populated: peer entity ID
+ *
+ * This methods populates the following request context properties: peer entity metadata, relying party
+ * configuration
+ *
+ * @param requestContext current request context
+ * @throws ProfileException thrown if there is a problem looking up the relying party's metadata
+ */
+ protected void populateRelyingPartyInformation(BaseSAMLProfileRequestContext requestContext)
+ throws ProfileException {
+ MetadataProvider metadataProvider = requestContext.getMetadataProvider();
+ String relyingPartyId = requestContext.getInboundMessageIssuer();
+
+ EntityDescriptor relyingPartyMetadata;
+ try {
+ relyingPartyMetadata = metadataProvider.getEntityDescriptor(relyingPartyId);
+ requestContext.setPeerEntityMetadata(relyingPartyMetadata);
+ } catch (MetadataProviderException e) {
+ log.error("Error looking up metadata for relying party " + relyingPartyId, e);
+ throw new ProfileException("Error looking up metadata for relying party " + relyingPartyId);
}
- /**
- * Gets the role descriptor for the asserting party.
- *
- * @return role descriptor for the asserting party
- */
- public RoleDescriptor getAssertingPartyRoleMetadata() {
- return assertingPartyRoleMetadata;
+ RelyingPartyConfiguration rpConfig = getRelyingPartyConfiguration(relyingPartyId);
+ if (rpConfig == null) {
+ log.error("Unable to retrieve relying party configuration data for entity with ID {}", relyingPartyId);
+ throw new ProfileException("Unable to retrieve relying party configuration data for entity with ID "
+ + relyingPartyId);
}
+ requestContext.setRelyingPartyConfiguration(rpConfig);
+ }
- /**
- * Sets the role descriptor for the asserting party.
- *
- * @param descriptor role descriptor for the asserting party
- */
- public void setAssertingPartyRoleMetadata(RoleDescriptor descriptor) {
- assertingPartyRoleMetadata = descriptor;
+ /**
+ * Populates the request context with information about the asserting party. Unless overridden,
+ * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
+ * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)} has already been invoked and the
+ * properties it provides are available in the request context.
+ *
+ * This method requires the the following request context properties to be populated: metadata provider, relying
+ * party configuration
+ *
+ * This methods populates the following request context properties: local entity ID, outbound message issuer, local
+ * entity metadata
+ *
+ * @param requestContext current request context
+ * @throws ProfileException thrown if there is a problem looking up the asserting party's metadata
+ */
+ protected void populateAssertingPartyInformation(BaseSAMLProfileRequestContext requestContext)
+ throws ProfileException {
+ String assertingPartyId = requestContext.getRelyingPartyConfiguration().getProviderId();
+ requestContext.setLocalEntityId(assertingPartyId);
+ requestContext.setOutboundMessageIssuer(assertingPartyId);
+
+ try {
+ EntityDescriptor localEntityDescriptor = requestContext.getMetadataProvider().getEntityDescriptor(
+ assertingPartyId);
+ if (localEntityDescriptor != null) {
+ requestContext.setLocalEntityMetadata(localEntityDescriptor);
+ }
+ } catch (MetadataProviderException e) {
+ log.error("Error looking up metadata for asserting party " + assertingPartyId, e);
+ throw new ProfileException("Error looking up metadata for asserting party " + assertingPartyId);
}
+ }
+
+ /**
+ * Populates the request context with information from the inbound SAML message. Unless overridden,
+ * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
+ * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},and
+ * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
+ * properties they provide are available in the request context.
+ *
+ *
+ * @param requestContext current request context
+ *
+ * @throws ProfileException thrown if there is a problem populating the request context with information
+ */
+ protected abstract void populateSAMLMessageInformation(BaseSAMLProfileRequestContext requestContext)
+ throws ProfileException;
- /**
- * Gets the metadata for the relying party.
- *
- * @return metadata for the relying party
- */
- public EntityDescriptor getRelyingPartyMetadata() {
- return relyingPartyMetadata;
+ /**
+ * Populates the request context with the information about the profile. Unless overridden,
+ * {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
+ * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
+ * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)}, and
+ * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
+ * properties they provide are available in the request context.
+ *
+ * This method requires the the following request context properties to be populated: relying party configuration
+ *
+ * This methods populates the following request context properties: communication profile ID, profile configuration,
+ * outbound message artifact type, peer entity endpoint
+ *
+ * @param requestContext current request context
+ *
+ * @throws ProfileException thrown if there is a problem populating the profile information
+ */
+ protected void populateProfileInformation(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
+ AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
+ .getRelyingPartyConfiguration().getProfileConfiguration(getProfileId());
+ if (profileConfig != null) {
+ requestContext.setProfileConfiguration(profileConfig);
+ requestContext.setOutboundMessageArtifactType(profileConfig.getOutboundArtifactType());
}
- /**
- * Sets the metadata for the relying party.
- *
- * @param metadata metadata for the relying party
- */
- public void setRelyingPartyMetadata(EntityDescriptor metadata) {
- relyingPartyMetadata = metadata;
+ Endpoint endpoint = selectEndpoint(requestContext);
+ if (endpoint == null) {
+ log.error("No return endpoint available for relying party {}", requestContext.getInboundMessageIssuer());
+ throw new ProfileException("No peer endpoint available to which to send SAML response");
}
+ requestContext.setPeerEntityEndpoint(endpoint);
+ }
+
+ /**
+ * Populates the request context with the information about the user if they have an existing session. Unless
+ * overridden, {@link #populateRequestContext(BaseSAMLProfileRequestContext)} has already invoked
+ * {@link #populateRelyingPartyInformation(BaseSAMLProfileRequestContext)},
+ * {@link #populateAssertingPartyInformation(BaseSAMLProfileRequestContext)},
+ * {@link #populateProfileInformation(BaseSAMLProfileRequestContext)}, and
+ * {@link #populateSAMLMessageInformation(BaseSAMLProfileRequestContext)} have already been invoked and the
+ * properties they provide are available in the request context.
+ *
+ * This method should populate: user's session, user's principal name, and service authentication method
+ *
+ * @param requestContext current request context
+ *
+ * @throws ProfileException thrown if there is a problem populating the user's information
+ */
+ protected abstract void populateUserInformation(BaseSAMLProfileRequestContext requestContext)
+ throws ProfileException;
+
+ /**
+ * Selects the appropriate endpoint for the relying party and stores it in the request context.
+ *
+ * @param requestContext current request context
+ *
+ * @return Endpoint selected from the information provided in the request context
+ *
+ * @throws ProfileException thrown if there is a problem selecting a response endpoint
+ */
+ protected abstract Endpoint selectEndpoint(BaseSAMLProfileRequestContext requestContext) throws ProfileException;
- /**
- * Gets the role descriptor for the relying party.
- *
- * @return role descriptor for the relying party
- */
- public RoleDescriptor getRelyingPartyRoleMetadata() {
- return relyingPartyRoleMetadata;
+ /**
+ * Encodes the request's SAML response and writes it to the servlet response.
+ *
+ * @param requestContext current request context
+ *
+ * @throws ProfileException thrown if no message encoder is registered for this profiles binding
+ */
+ protected void encodeResponse(BaseSAMLProfileRequestContext requestContext) throws ProfileException {
+ try {
+ SAMLMessageEncoder encoder = getMessageEncoders().get(requestContext.getPeerEntityEndpoint().getBinding());
+ if (encoder == null) {
+ log.error("No outbound message encoder configured for binding {}", requestContext
+ .getPeerEntityEndpoint().getBinding());
+ throw new ProfileException("No outbound message encoder configured for binding "
+ + requestContext.getPeerEntityEndpoint().getBinding());
+ }
+
+ AbstractSAMLProfileConfiguration profileConfig = (AbstractSAMLProfileConfiguration) requestContext
+ .getProfileConfiguration();
+ if (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.always
+ || (profileConfig.getSignResponses() == CryptoOperationRequirementLevel.conditional && !encoder
+ .providesMessageIntegrity(requestContext))) {
+ Credential signingCredential = null;
+ if (profileConfig.getSigningCredential() != null) {
+ signingCredential = profileConfig.getSigningCredential();
+ } else if (requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential() != null) {
+ signingCredential = requestContext.getRelyingPartyConfiguration().getDefaultSigningCredential();
+ }
+
+ if (signingCredential == null) {
+ throw new ProfileException(
+ "Signing of responses is required but no signing credential is available");
+ }
+
+ requestContext.setOutboundSAMLMessageSigningCredential(signingCredential);
+ }
+
+ log.debug("Encoding response to SAML request {} from relying party {}", requestContext
+ .getInboundSAMLMessageId(), requestContext.getInboundMessageIssuer());
+
+ requestContext.setMessageEncoder(encoder);
+ encoder.encode(requestContext);
+ } catch (MessageEncodingException e) {
+ throw new ProfileException("Unable to encode response to relying party: "
+ + requestContext.getInboundMessageIssuer(), e);
}
+ }
- /**
- * Sets the role descriptor for the relying party.
- *
- * @param descriptor role descriptor for the relying party
- */
- public void setRelyingPartyRoleMetadata(RoleDescriptor descriptor) {
- relyingPartyRoleMetadata = descriptor;
+ /**
+ * Writes an aduit log entry indicating the successful response to the attribute request.
+ *
+ * @param context current request context
+ */
+ protected void writeAuditLogEntry(BaseSAMLProfileRequestContext context) {
+ AuditLogEntry auditLogEntry = new AuditLogEntry();
+ auditLogEntry.setMessageProfile(getProfileId());
+ auditLogEntry.setPrincipalAuthenticationMethod(context.getPrincipalAuthenticationMethod());
+ auditLogEntry.setPrincipalName(context.getPrincipalName());
+ auditLogEntry.setAssertingPartyId(context.getLocalEntityId());
+ auditLogEntry.setRelyingPartyId(context.getInboundMessageIssuer());
+ auditLogEntry.setRequestBinding(context.getMessageDecoder().getBindingURI());
+ auditLogEntry.setRequestId(context.getInboundSAMLMessageId());
+ auditLogEntry.setResponseBinding(context.getMessageEncoder().getBindingURI());
+ auditLogEntry.setResponseId(context.getOutboundSAMLMessageId());
+ if (context.getReleasedAttributes() != null) {
+ auditLogEntry.getReleasedAttributes().addAll(context.getReleasedAttributes());
}
+
+ getAduitLog().info(auditLogEntry.toString());
}
}
\ No newline at end of file