/*
* Atricore IDBus
*
* Copyright (c) 2009, Atricore Inc.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.atricore.idbus.capabilities.sso.main.sp.producers;
import oasis.names.tc.saml._2_0.metadata.EndpointType;
import oasis.names.tc.saml._2_0.metadata.EntityDescriptorType;
import oasis.names.tc.saml._2_0.metadata.IDPSSODescriptorType;
import oasis.names.tc.saml._2_0.metadata.RoleDescriptorType;
import oasis.names.tc.saml._2_0.protocol.LogoutRequestType;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.atricore.idbus.capabilities.sso.main.SSOException;
import org.atricore.idbus.capabilities.sso.main.common.AbstractSSOMediator;
import org.atricore.idbus.capabilities.sso.main.common.producers.SSOProducer;
import org.atricore.idbus.capabilities.sso.main.sp.SPSecurityContext;
import org.atricore.idbus.capabilities.sso.main.sp.SSOSPMediator;
import org.atricore.idbus.capabilities.sso.main.sp.plans.SPInitiatedLogoutReqToSamlR2LogoutReqPlan;
import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants;
import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding;
import org.atricore.idbus.capabilities.sts.main.SecurityTokenEmissionException;
import org.atricore.idbus.common.sso._1_0.protocol.SPInitiatedLogoutRequestType;
import org.atricore.idbus.common.sso._1_0.protocol.SSOResponseType;
import org.atricore.idbus.kernel.auditing.core.ActionOutcome;
import org.atricore.idbus.kernel.main.federation.SubjectNameID;
import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrustMemberDescriptor;
import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptor;
import org.atricore.idbus.kernel.main.federation.metadata.EndpointDescriptorImpl;
import org.atricore.idbus.kernel.main.federation.metadata.MetadataEntry;
import org.atricore.idbus.kernel.main.mediation.IdentityMediationException;
import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl;
import org.atricore.idbus.kernel.main.mediation.MediationState;
import org.atricore.idbus.kernel.main.mediation.binding.BindingChannel;
import org.atricore.idbus.kernel.main.mediation.camel.AbstractCamelEndpoint;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationExchange;
import org.atricore.idbus.kernel.main.mediation.camel.component.binding.CamelMediationMessage;
import org.atricore.idbus.kernel.main.mediation.channel.FederationChannel;
import org.atricore.idbus.kernel.main.mediation.channel.IdPChannel;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedLocalProvider;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedProvider;
import org.atricore.idbus.kernel.main.session.SSOSessionManager;
import org.atricore.idbus.kernel.main.session.exceptions.NoSuchSessionException;
import org.atricore.idbus.kernel.main.util.UUIDGenerator;
import org.atricore.idbus.kernel.planning.*;
import javax.xml.namespace.QName;
import java.util.Properties;
import java.util.Set;
/**
* @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a>
* @version $Id$
*/
public class SPInitiatedSingleLogoutProducer extends SSOProducer {
private static final Log logger = LogFactory.getLog( SPInitiatedSingleLogoutProducer .class );
protected UUIDGenerator uuidGenerator = new UUIDGenerator();
public SPInitiatedSingleLogoutProducer ( AbstractCamelEndpoint<CamelMediationExchange> endpoint ) throws Exception {
super( endpoint );
}
@Override
protected void doProcess(CamelMediationExchange exchange) throws SSOException {
logger.debug("Processing SP Initiated Single Logout on HTTP Redirect");
try {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
MediationState state = in.getMessage().getState();
// May be used later by HTTP-Redirect binding!
AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator();
in.getMessage().getState().setAttribute("SAMLR2Signer", mediator.getSigner());
SPSecurityContext secCtx =
(SPSecurityContext) in.getMessage().getState().getLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX");
if (secCtx == null || secCtx.getSessionIndex() == null) {
// No SSO Session found SLO Request
// Go back to partner application
SSOResponseType ssoResponse = new SSOResponseType();
ssoResponse.setID(uuidGenerator.generateId());
ssoResponse.setIssuer(getProvider().getName());
String destinationLocation = ((SSOSPMediator) channel.getIdentityMediator()).getSpBindingSLO();
EndpointDescriptor destination =
new EndpointDescriptorImpl("EmbeddedSPAcs",
"SingleLogoutService",
SSOBinding.SSO_ARTIFACT.getValue(),
destinationLocation, null);
logger.debug("Sending JOSSO SLO Response to " + destination);
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(ssoResponse.getID(),
ssoResponse, "SPLogoutResponse", null, destination, in.getMessage().getState()));
exchange.setOut(out);
return;
}
if (logger.isDebugEnabled())
logger.debug("Starting SP Initiated SLO for SP session " +
(secCtx != null ? secCtx.getSessionIndex() : "<NULL>"));
if (logger.isTraceEnabled())
logger.trace("Starting SP Initiated SLO with SP Security Context " + secCtx);
CircleOfTrustMemberDescriptor idp = resolveIdp(exchange, secCtx.getIdpAlias());
if (idp == null) {
throw new SSOException("No IdP descriptor found for " + secCtx.getIdpAlias());
}
logger.debug("Using IDP " + idp.getAlias());
IdPChannel idpChannel = (IdPChannel) resolveIdpChannel(idp);
logger.debug("Using IDP Channel " + idpChannel.getName());
// Look for SPInitiatedLogoutRequest and store it for future use
SPInitiatedLogoutRequestType ssoLogoutRequest = (SPInitiatedLogoutRequestType) in.getMessage().getContent();
in.getMessage().getState().setLocalVariable("urn:org:atricore:idbus:sso:protocol:SPInitiatedLogoutRequest", ssoLogoutRequest);
// ------------------------------------------------------
// Send SLO Request to IdP
// ------------------------------------------------------
SSOBinding binding = SSOBinding.asEnum(SSOBinding.SAMLR2_REDIRECT.getValue());
// Select endpoint, must be a SingleSingOnService endpoint
EndpointType idpSsoEndpoint = resolveIdpSloEndpoint(idp, true);
if (idpSsoEndpoint == null) {
if (logger.isDebugEnabled())
logger.debug("IdP does not support SLO : " + idp.getAlias() + ", performing SP logout");
destroySPSecurityContext(exchange, secCtx);
SSOResponseType ssoResponse = new SSOResponseType();
ssoResponse.setID(uuidGenerator.generateId());
ssoResponse.setIssuer(getProvider().getName());
String destinationLocation = ((SSOSPMediator) channel.getIdentityMediator()).getSpBindingSLO();
EndpointDescriptor destination =
new EndpointDescriptorImpl("EmbeddedSPAcs",
"SingleLogoutService",
SSOBinding.SSO_ARTIFACT.getValue(),
destinationLocation, null);
logger.debug("Sending JOSSO SLO Response to " + destination);
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(ssoResponse.getID(),
ssoResponse, "SPLogoutResponse", null, destination, in.getMessage().getState()));
exchange.setOut(out);
return;
}
EndpointDescriptor ed = new EndpointDescriptorImpl(
"IDPSLOEndpoint",
"SingleLogoutService",
idpSsoEndpoint.getBinding(),
idpSsoEndpoint.getLocation(),
idpSsoEndpoint.getResponseLocation());
LogoutRequestType sloRequest = buildSLORequest(exchange, idp, idpChannel, ed, secCtx);
Properties auditProps = new Properties();
auditProps.put("idpAlias", secCtx.getIdpAlias());
auditProps.put("idpSession", secCtx.getIdpSsoSession());
Set<SubjectNameID> principals = secCtx.getSubject().getPrincipals(SubjectNameID.class);
SubjectNameID principal = null;
if (principals.size() == 1) {
principal = principals.iterator().next();
}
recordInfoAuditTrail("SP-SLO", ActionOutcome.SUCCESS, principal != null ? principal.getName() : null, exchange, auditProps);
if (binding.isFrontChannel()) {
// Send the IDP SLO Request to the browser for delivery.
in.getMessage().getState().setLocalVariable(SAMLR2Constants.SAML_PROTOCOL_NS + ":LogoutRequest", sloRequest);
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(sloRequest.getID(),
sloRequest, "LogoutRequest", state.getLocalState().getId(), ed, in.getMessage().getState()));
exchange.setOut(out);
} else {
destroySPSecurityContext(exchange, secCtx);
EndpointDescriptor destination =
new EndpointDescriptorImpl("EmbeddedSPAcs",
"SingleLogoutService",
endpoint.getBinding(),
null, null);
try {
channel.getIdentityMediator().sendMessage(sloRequest, destination, channel);
// TODO : Verify IDP Response!
} catch (IdentityMediationException e) {
throw new SSOException("Can't logout from IDP:" + e);
}
SSOResponseType ssoResponse = new SSOResponseType();
ssoResponse.setID(uuidGenerator.generateId());
ssoResponse.setInReplayTo(sloRequest.getID());
ssoResponse.setIssuer(getProvider().getName());
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
out.setMessage(new MediationMessageImpl(sloRequest.getID(),
ssoResponse, "SSOResponseType", null, destination, in.getMessage().getState()));
exchange.setOut(out);
}
} catch (IdentityPlanningException e) {
throw new SSOException(e);
}
}
protected LogoutRequestType buildSLORequest(CamelMediationExchange exchange,
CircleOfTrustMemberDescriptor idp,
IdPChannel idpChannel,
EndpointDescriptor ed, SPSecurityContext secCtx) throws IdentityPlanningException, SSOException {
CamelMediationMessage samlIn = (CamelMediationMessage) exchange.getIn();
IdentityPlan identityPlan = findIdentityPlanOfType(SPInitiatedLogoutReqToSamlR2LogoutReqPlan.class);
IdentityPlanExecutionExchange idPlanExchange = createIdentityPlanExecutionExchange();
// Publish IDP springmetadata
idPlanExchange.setProperty(VAR_DESTINATION_COT_MEMBER, idp);
idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, ed);
idPlanExchange.setProperty(VAR_SECURITY_CONTEXT, secCtx);
idPlanExchange.setProperty(VAR_RESPONSE_CHANNEL, idpChannel);
// Get SPInitiated authn request, if any!
SPInitiatedLogoutRequestType ssoLogoutRequest = (SPInitiatedLogoutRequestType) samlIn.getMessage().getContent();
// Create in/out artifacts
IdentityArtifact in =
new IdentityArtifactImpl(new QName("urn:org:atricore:idbus:sso:protocol", "SPInitiatedAuthnRequest"), ssoLogoutRequest);
idPlanExchange.setIn(in);
IdentityArtifact<LogoutRequestType> out =
new IdentityArtifactImpl<LogoutRequestType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "LogoutRequest"),
new LogoutRequestType());
idPlanExchange.setOut(out);
// Prepare execution
identityPlan.prepare(idPlanExchange);
// Perform execution
identityPlan.perform(idPlanExchange);
if (!idPlanExchange.getStatus().equals(IdentityPlanExecutionStatus.SUCCESS)) {
throw new SecurityTokenEmissionException("Identity plan returned : " + idPlanExchange.getStatus());
}
if (idPlanExchange.getOut() == null)
throw new SecurityTokenEmissionException("Plan Exchange OUT must not be null!");
return (LogoutRequestType) idPlanExchange.getOut().getContent();
}
protected CircleOfTrustMemberDescriptor resolveIdp(CamelMediationExchange exchange, String idpAlias) throws SSOException {
return getCotManager().lookupMemberByAlias(idpAlias);
}
/**
* @return
*/
protected FederationChannel resolveIdpChannel(CircleOfTrustMemberDescriptor idpDescriptor) {
// Resolve IdP channel, then look for the ACS endpoint
BindingChannel bChannel = (BindingChannel) channel;
FederatedLocalProvider sp = bChannel.getFederatedProvider();
FederationChannel idpChannel = sp.getChannel();
for (FederationChannel fChannel : sp.getChannels()) {
FederatedProvider idp = fChannel.getTargetProvider();
for (CircleOfTrustMemberDescriptor member : idp.getMembers()) {
if (member.getAlias().equals(idpDescriptor.getAlias())) {
if (logger.isDebugEnabled())
logger.debug("Selected IdP channel " + fChannel.getName() + " for provider " + idp.getName());
idpChannel = fChannel;
break;
}
}
}
return idpChannel;
}
protected EndpointType resolveIdpSloEndpoint(CircleOfTrustMemberDescriptor idp, boolean frontChannel) throws SSOException {
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
SSOBinding preferredBinding = mediator.getPreferredIdpSSOBindingValue();
MetadataEntry idpMd = idp.getMetadata();
if (idpMd == null || idpMd.getEntry() == null)
throw new SSOException("No metadata descriptor found for IDP " + idp);
if (idpMd.getEntry() instanceof EntityDescriptorType) {
EntityDescriptorType md = (EntityDescriptorType) idpMd.getEntry();
for (RoleDescriptorType role : md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) {
if (role instanceof IDPSSODescriptorType) {
IDPSSODescriptorType idpSsoRole = (IDPSSODescriptorType) role;
EndpointType endpoint = null;
EndpointType postEndpoint = null;
EndpointType redirEndpoint = null;
if (logger.isTraceEnabled())
logger.trace("SLO Endpoints count for IdP " + (idpSsoRole.getSingleLogoutService() == null ? "<EMPTY>" : idpSsoRole.getSingleLogoutService().size() + ""));
for (EndpointType idpSloEndpoint : idpSsoRole.getSingleLogoutService()) {
if (logger.isTraceEnabled())
logger.trace("Looking at SLO Endpoint " + idpSloEndpoint.getBinding());
SSOBinding b = SSOBinding.asEnum(idpSloEndpoint.getBinding());
if (b.isFrontChannel() != frontChannel)
continue;
if (b.equals(preferredBinding)) {
if (logger.isDebugEnabled())
logger.debug("Selected IdP SLO Endpoint " + idpSloEndpoint.getBinding());
return idpSloEndpoint;
}
// If POST is available, use it
if (b.equals(SSOBinding.SAMLR2_POST))
postEndpoint = idpSloEndpoint;
if (b.equals(SSOBinding.SAMLR2_REDIRECT))
redirEndpoint = idpSloEndpoint;
// Take the first front channel endpoint
if (endpoint == null)
endpoint = idpSloEndpoint;
}
// First use redirect
if (redirEndpoint != null)
endpoint = redirEndpoint;
// If no redirect, use post
if (postEndpoint != null)
endpoint = postEndpoint;
// Use any front-channel endpoint
if (logger.isDebugEnabled())
logger.debug("Selected IdP SLO Endpoint " + (endpoint != null ? endpoint.getBinding() : "<NONE>"));
return endpoint;
}
}
} else {
throw new SSOException("Unknown metadata descriptor type " + idpMd.getEntry().getClass().getName());
}
logger.debug("No IDP Endpoint supporting binding : " + preferredBinding);
throw new SSOException("IDP does not support preferred binding " + preferredBinding);
}
protected void destroySPSecurityContext(CamelMediationExchange exchange,
SPSecurityContext secCtx) throws SSOException {
CircleOfTrustMemberDescriptor idp = getCotManager().lookupMemberByAlias(secCtx.getIdpAlias());
IdPChannel idpChannel = (IdPChannel) resolveIdpChannel(idp);
SSOSessionManager ssoSessionManager = idpChannel.getSessionManager();
try {
ssoSessionManager.invalidate(secCtx.getSessionIndex());
secCtx.clear();
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
in.getMessage().getState().removeRemoteVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX");
} catch (NoSuchSessionException e) {
logger.debug("SSO Session already invalidated " + secCtx.getSessionIndex());
} catch (Exception e) {
throw new SSOException(e);
}
}
}