package org.atricore.idbus.capabilities.sso.main.sp.producers;
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.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.support.binding.SSOBinding;
import org.atricore.idbus.capabilities.sso.support.metadata.SSOService;
import org.atricore.idbus.common.sso._1_0.protocol.IDPSessionHeartBeatRequestType;
import org.atricore.idbus.common.sso._1_0.protocol.IDPSessionHeartBeatResponseType;
import org.atricore.idbus.common.sso._1_0.protocol.SPSessionHeartBeatRequestType;
import org.atricore.idbus.common.sso._1_0.protocol.SPSessionHeartBeatResponseType;
import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrust;
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.mediation.IdentityMediationException;
import org.atricore.idbus.kernel.main.mediation.MediationMessageImpl;
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.channel.SPChannel;
import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedLocalProvider;
import org.atricore.idbus.kernel.main.mediation.provider.FederatedProvider;
import org.atricore.idbus.kernel.main.mediation.provider.Provider;
import org.atricore.idbus.kernel.main.session.SSOSessionManager;
import org.atricore.idbus.kernel.main.session.exceptions.NoSuchSessionException;
import org.atricore.idbus.kernel.main.session.exceptions.SSOSessionException;
import org.atricore.idbus.kernel.main.util.UUIDGenerator;
import org.atricore.idbus.kernel.planning.IdentityPlanningException;
/**
* @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a>
* @version $Id$
*/
public class SessionHeartBeatProducer extends SSOProducer {
private UUIDGenerator uuidGenerator = new UUIDGenerator();
private static final Log logger = LogFactory.getLog(SessionHeartBeatProducer.class);
public SessionHeartBeatProducer(AbstractCamelEndpoint<CamelMediationExchange> endpoint) throws Exception {
super(endpoint);
}
@Override
protected void doProcess(CamelMediationExchange exchange) throws Exception {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
logger.debug("Processing Session Heart-Beat Message : " + in.getMessage().getContent());
if (in.getMessage().getContent() instanceof SPSessionHeartBeatRequestType) {
doProcessSPSessionHeartBeat(exchange, (SPSessionHeartBeatRequestType) in.getMessage().getContent());
} else {
throw new SSOException("Unsupported message type " + in.getMessage().getContent());
}
}
protected void doProcessSPSessionHeartBeat(CamelMediationExchange exchange, SPSessionHeartBeatRequestType request) throws SSOException,
IdentityPlanningException,
SSOSessionException {
CamelMediationMessage in = (CamelMediationMessage) exchange.getIn();
CamelMediationMessage out = (CamelMediationMessage) exchange.getOut();
// Recover local session information
SPSecurityContext secCtx =
(SPSecurityContext) in.getMessage().getState().getLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX");
SPSessionHeartBeatResponseType response = new SPSessionHeartBeatResponseType();
response.setID(uuidGenerator.generateId());
response.setInReplayTo(request.getID());
response.setSsoSessionId(request.getSsoSessionId());
response.setIssuer(getProvider().getName());
if (secCtx == null || secCtx.getSessionIndex() == null) {
if (logger.isDebugEnabled())
logger.debug("No Security Context found for " + getProvider().getName().toUpperCase() + "_SECURITY_CTX: " + secCtx);
// No SSO Session available, send response.
response.setValid(false);
} else {
if (logger.isDebugEnabled())
logger.debug("Security Context found " + secCtx);
try {
// Update local context and validate local session
updateSPSecurityContext(secCtx, exchange);
// Check if heartbeat is needed now.
SSOSPMediator mediator = (SSOSPMediator) channel.getIdentityMediator();
if (logger.isTraceEnabled())
logger.trace("Checking IDP Session heart beat for:" + secCtx + ". Configured interval: " + mediator.getIdpSessionHeartBeatInterval());
long now = System.currentTimeMillis();
if (secCtx.getLastIdPSessionHeartBeat() == null ||
secCtx.getLastIdPSessionHeartBeat() + mediator.getIdpSessionHeartBeatInterval() * 1000L < now) {
// Send HB request to IDP. If we get a null response, HB was not sent.
IDPSessionHeartBeatResponseType idpResp = performIdPSessionHeartBeat(exchange, secCtx);
secCtx.setLastIdPSessionHeartBeat(now);
if (idpResp != null) {
logger.debug("IDP HeartBeat sent, response: " + idpResp.getID() + " isValid:" + idpResp.isValid());
response.setValid(idpResp.isValid());
} else {
logger.debug("IDP HeartBeat not send");
response.setValid(true);
}
} else {
if (logger.isTraceEnabled())
logger.debug("IDP Session heart beat not necessary ["+secCtx.getIdpSsoSession()+"]");
response.setValid(true);
}
} catch (NoSuchSessionException e) {
if (logger.isDebugEnabled())
logger.debug("SP Session not found or invalid : " + secCtx.getSessionIndex());
// Do not send heart beat for invalid sessions.
response.setValid(false);
}
}
// Send response back
EndpointDescriptor destination = new EndpointDescriptorImpl("SPSessionHeartBeatService",
"SPSessionHeartBeatService",
endpoint.getBinding(),
null, null);
out.setMessage(new MediationMessageImpl(uuidGenerator.generateId(),
response, "SPSessionHeartBeatResponse", null, destination, in.getMessage().getState()));
}
protected IDPSessionHeartBeatResponseType performIdPSessionHeartBeat(CamelMediationExchange exchange,
SPSecurityContext secCtx) throws SSOException {
try {
if (logger.isDebugEnabled())
logger.debug("Triggering IDP Session heart beat");
CircleOfTrustMemberDescriptor idp = resolveIdp(secCtx.getIdpAlias());
logger.debug("Using IdP " + idp.getAlias());
// If no SP Channel is found is because the IDP is probably a remote provider and no SPChannel definition
// can be found.
// TODO : Add Heart Beat service to metadata to be able to send heartbeat requests to remote IDPS (only for Atricore IDPS)
SPChannel spChannel = resolveSpChannel(idp);
EndpointDescriptor ed = spChannel != null ? resolveIdpHeartBeatEndpoint(spChannel) : null;
if (ed == null) {
logger.debug("No HeartBeat endpoint found for " + idp.getAlias() + ". Heart Beat ignored.");
return null;
}
IDPSessionHeartBeatRequestType req = buildIDPSessionHeartBeatRequest(secCtx);
if (logger.isDebugEnabled())
logger.debug("Sending IDPSessionHeartBeatRequest " + req.getID() +
" to IDP " + idp.getAlias() +
" using endpoint " + ed.getLocation());
// We might need the
IDPSessionHeartBeatResponseType res =
(IDPSessionHeartBeatResponseType) channel.getIdentityMediator().sendMessage(req, ed, spChannel);
if (res.getInReplayTo() == null || !res.getInReplayTo().equals(req.getID()))
throw new SSOException("Received response is not expected, invalid 'inReplayTo' attribute " +
res.getInReplayTo() + ", expected " + req.getID());
return res;
} catch (IdentityMediationException e) {
throw new SSOException(e);
} catch (IdentityPlanningException e) {
throw new SSOException(e);
}
}
protected CircleOfTrustMemberDescriptor resolveIdp(String idpAlias) {
return getCotManager().lookupMemberByAlias(idpAlias);
}
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 SPChannel resolveSpChannel(CircleOfTrustMemberDescriptor idp) throws SSOException {
// The channel might be a binding or federation channel, get the main channel from the provider.
CircleOfTrust cot = ((FederatedLocalProvider)getProvider()).getChannel().getCircleOfTrust();
// The channel we need to send messages to
SPChannel spChannel = null;
for (Provider p : cot.getProviders()) {
// Because we send heartbeat events using SSO Protocol, we look for local providers ...
// TODO : Support MD for SSO Protocol to use remote providers
if (p instanceof FederatedLocalProvider) {
FederatedLocalProvider lp = (FederatedLocalProvider) p;
// Provider is probably a binding provider.
if (lp.getChannel() == null || lp.getChannel().getMember() == null)
continue;
if (lp.getChannel().getMember().getAlias().equals(idp.getAlias())) {
spChannel = (SPChannel) lp.getChannel();
break;
}
for (FederationChannel c : lp.getChannels()) {
if (c.getMember().getAlias().equals(idp.getAlias())) {
spChannel = (SPChannel) c;
break;
}
}
if (spChannel != null)
break;
}
}
if (spChannel == null) {
logger.debug("No SP Channel defined in local providers for " + idp.getAlias());
}
return spChannel;
}
protected EndpointDescriptor resolveIdpHeartBeatEndpoint(SPChannel spChannel) throws SSOException {
IdentityMediationEndpoint endpoint = null;
for (IdentityMediationEndpoint ep : spChannel.getEndpoints()) {
if (ep.getType().equals(SSOService.IDPSessionHeartBeatService.toString())) {
// Local endpoints are preferred
if (ep.getBinding().equals(SSOBinding.SSO_LOCAL.getValue())) {
endpoint = ep;
break;
} else if (ep.getBinding().equals(SSOBinding.SSO_SOAP.getValue())) {
endpoint = ep; // keep looking, maybe there is a local endpoint
}
}
}
if (endpoint == null) {
logger.warn("No IDP Endpoint supporting service/binding " + SSOService.IDPSessionHeartBeatService + " in channel " + spChannel.getName());
return null;
}
try {
return channel.getIdentityMediator().resolveEndpoint(spChannel, endpoint);
} catch (IdentityMediationException e) {
logger.error("Cannot resolve endpoint " + endpoint.getName() + ". " + e.getMessage(), e);
return null;
}
}
protected IDPSessionHeartBeatRequestType buildIDPSessionHeartBeatRequest(SPSecurityContext secCtx) throws SSOException, IdentityPlanningException {
IDPSessionHeartBeatRequestType request = new IDPSessionHeartBeatRequestType();
request.setID(uuidGenerator.generateId());
request.setSsoSessionId(secCtx.getIdpSsoSession());
// TODO : request.setIssuer();
return request;
}
protected SPSecurityContext updateSPSecurityContext(SPSecurityContext secCtx,
CamelMediationExchange exchange)
throws SSOException, SSOSessionException {
if (logger.isDebugEnabled())
logger.debug("Updating SP Security Context for " + secCtx.getSessionIndex());
// Use the main SSO Session manager, this should the same for all channels !
IdPChannel idPChannel = (IdPChannel) ((FederatedLocalProvider)getProvider()).getChannel();
SSOSessionManager ssoSessionManager = idPChannel.getSessionManager();
ssoSessionManager.accessSession(secCtx.getSessionIndex());
if (logger.isDebugEnabled())
logger.debug("Updated SP security context " + secCtx);
return secCtx;
}
}