package org.pac4j.saml.sso.impl;
import org.apache.velocity.app.VelocityEngine;
import org.opensaml.messaging.encoder.MessageEncoder;
import org.opensaml.messaging.encoder.MessageEncodingException;
import org.opensaml.saml.common.binding.impl.SAMLOutboundDestinationHandler;
import org.opensaml.saml.common.binding.security.impl.EndpointURLSchemeSecurityHandler;
import org.opensaml.saml.common.binding.security.impl.SAMLOutboundProtocolMessageSigningHandler;
import org.opensaml.saml.common.xml.SAMLConstants;
import org.opensaml.saml.saml2.core.LogoutRequest;
import org.opensaml.saml.saml2.metadata.AssertionConsumerService;
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
import org.opensaml.saml.saml2.metadata.SPSSODescriptor;
import org.opensaml.saml.saml2.metadata.SingleLogoutService;
import org.pac4j.saml.context.SAML2MessageContext;
import org.pac4j.saml.crypto.SignatureSigningParametersProvider;
import org.pac4j.saml.exceptions.SAMLException;
import org.pac4j.saml.sso.SAML2MessageSender;
import org.pac4j.saml.storage.SAMLMessageStorage;
import org.pac4j.saml.transport.Pac4jHTTPPostEncoder;
import org.pac4j.saml.transport.Pac4jHTTPRedirectDeflateEncoder;
import org.pac4j.saml.transport.Pac4jSAMLResponse;
import org.pac4j.saml.util.VelocityEngineFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
/**
* Sender for SAML logout messages
*
* @author Matthieu Taggiasco
* @since 2.0.0
*/
public class SAML2LogoutMessageSender implements SAML2MessageSender<LogoutRequest> {
private final static Logger logger = LoggerFactory.getLogger(SAML2LogoutMessageSender.class);
private final SignatureSigningParametersProvider signatureSigningParametersProvider;
private final String destinationBindingType;
private final boolean signErrorResponses;
private final boolean forceSignRedirectBindingLogoutRequest;
public SAML2LogoutMessageSender(final SignatureSigningParametersProvider signatureSigningParametersProvider,
final String destinationBindingType,
final boolean signErrorResponses,
final boolean forceSignRedirectBindingLogoutRequest) {
this.signatureSigningParametersProvider = signatureSigningParametersProvider;
this.destinationBindingType = destinationBindingType;
this.signErrorResponses = signErrorResponses;
this.forceSignRedirectBindingLogoutRequest = forceSignRedirectBindingLogoutRequest;
}
@Override
public void sendMessage(final SAML2MessageContext context,
final LogoutRequest logoutRequest,
final Object relayState) {
final SPSSODescriptor spDescriptor = context.getSPSSODescriptor();
final IDPSSODescriptor idpssoDescriptor = context.getIDPSSODescriptor();
final SingleLogoutService ssoLogoutService = context.getIDPSingleLogoutService(destinationBindingType);
final AssertionConsumerService acsService = context.getSPAssertionConsumerService();
final MessageEncoder encoder = getMessageEncoder(context);
final SAML2MessageContext outboundContext = new SAML2MessageContext(context);
outboundContext.getProfileRequestContext().setProfileId(context.getProfileRequestContext().getProfileId());
outboundContext.getProfileRequestContext().setInboundMessageContext(
context.getProfileRequestContext().getInboundMessageContext());
outboundContext.getProfileRequestContext().setOutboundMessageContext(
context.getProfileRequestContext().getOutboundMessageContext());
outboundContext.setMessage(logoutRequest);
outboundContext.getSAMLEndpointContext().setEndpoint(acsService);
outboundContext.getSAMLPeerEndpointContext().setEndpoint(ssoLogoutService);
outboundContext.getSAMLPeerEntityContext().setRole(context.getSAMLPeerEntityContext().getRole());
outboundContext.getSAMLPeerEntityContext().setEntityId(context.getSAMLPeerEntityContext().getEntityId());
outboundContext.getSAMLProtocolContext().setProtocol(context.getSAMLProtocolContext().getProtocol());
outboundContext.getSecurityParametersContext()
.setSignatureSigningParameters(this.signatureSigningParametersProvider.build(spDescriptor));
if (relayState != null) {
outboundContext.getSAMLBindingContext().setRelayState(relayState.toString());
}
invokeOutboundMessageHandlers(spDescriptor, idpssoDescriptor, outboundContext);
try {
encoder.setMessageContext(outboundContext);
encoder.initialize();
encoder.prepareContext();
encoder.encode();
final SAMLMessageStorage messageStorage = context.getSAMLMessageStorage();
if (messageStorage != null) {
messageStorage.storeMessage(logoutRequest.getID(), logoutRequest);
}
} catch (final MessageEncodingException e) {
throw new SAMLException("Error encoding saml message", e);
} catch (final ComponentInitializationException e) {
throw new SAMLException("Error initializing saml encoder", e);
}
}
protected final void invokeOutboundMessageHandlers(final SPSSODescriptor spDescriptor,
final IDPSSODescriptor idpssoDescriptor,
final SAML2MessageContext outboundContext) {
try {
final EndpointURLSchemeSecurityHandler handlerEnd =
new EndpointURLSchemeSecurityHandler();
handlerEnd.initialize();
handlerEnd.invoke(outboundContext);
final SAMLOutboundDestinationHandler handlerDest =
new SAMLOutboundDestinationHandler();
handlerDest.initialize();
handlerDest.invoke(outboundContext);
if (spDescriptor.isAuthnRequestsSigned()) {
final SAMLOutboundProtocolMessageSigningHandler handler = new
SAMLOutboundProtocolMessageSigningHandler();
handler.setSignErrorResponses(this.signErrorResponses);
handler.invoke(outboundContext);
} else if (idpssoDescriptor.getWantAuthnRequestsSigned()) {
logger.warn("IdP wants authn requests signed, it will perhaps reject your authn requests unless you provide a keystore");
}
} catch (final Exception e) {
throw new SAMLException(e);
}
}
/**
* Build the WebSSO handler for sending and receiving SAML2 messages.
* @param ctx
* @return the encoder instance
*/
private MessageEncoder getMessageEncoder(final SAML2MessageContext ctx) {
final Pac4jSAMLResponse adapter = ctx.getProfileRequestContextOutboundMessageTransportResponse();
if (SAMLConstants.SAML2_POST_BINDING_URI.equals(destinationBindingType)) {
final VelocityEngine velocityEngine = VelocityEngineFactory.getEngine();
final Pac4jHTTPPostEncoder encoder = new Pac4jHTTPPostEncoder(adapter);
encoder.setVelocityEngine(velocityEngine);
return encoder;
}
if (SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(destinationBindingType)) {
final Pac4jHTTPRedirectDeflateEncoder encoder =
new Pac4jHTTPRedirectDeflateEncoder(adapter, forceSignRedirectBindingLogoutRequest);
return encoder;
}
throw new UnsupportedOperationException("Binding type - "
+ destinationBindingType + " is not supported");
}
}