/* * 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.idp.producers; import oasis.names.tc.saml._2_0.metadata.EntityDescriptorType; import oasis.names.tc.saml._2_0.metadata.RoleDescriptorType; import oasis.names.tc.saml._2_0.metadata.SPSSODescriptorType; import oasis.names.tc.saml._2_0.protocol.LogoutRequestType; import oasis.names.tc.saml._2_0.protocol.StatusResponseType; 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.idp.IdPSecurityContext; import org.atricore.idbus.capabilities.sso.main.idp.IdentityProviderConstants; import org.atricore.idbus.capabilities.sso.main.idp.ProviderSecurityContext; import org.atricore.idbus.capabilities.sso.main.idp.plans.SamlR2SloRequestToSamlR2RespPlan; import org.atricore.idbus.capabilities.sso.main.idp.plans.SamlR2SloRequestToSpSamlR2SloRequestPlan; import org.atricore.idbus.capabilities.sso.support.SAMLR2Constants; import org.atricore.idbus.capabilities.sso.support.binding.SSOBinding; import org.atricore.idbus.capabilities.sso.support.core.SSORequestException; import org.atricore.idbus.capabilities.sso.support.core.SSOResponseException; import org.atricore.idbus.capabilities.sso.support.core.StatusCode; import org.atricore.idbus.capabilities.sso.support.core.StatusDetails; import org.atricore.idbus.capabilities.sso.support.core.encryption.SamlR2Encrypter; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureException; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2SignatureValidationException; import org.atricore.idbus.capabilities.sso.support.core.signature.SamlR2Signer; import org.atricore.idbus.capabilities.sso.support.metadata.SSOService; import org.atricore.idbus.capabilities.sts.main.SecurityTokenEmissionException; import org.atricore.idbus.common.sso._1_0.protocol.*; import org.atricore.idbus.kernel.auditing.core.ActionOutcome; import org.atricore.idbus.kernel.auditing.core.AuditingServer; import org.atricore.idbus.kernel.main.authn.SimplePrincipal; import org.atricore.idbus.kernel.main.federation.metadata.CircleOfTrustManagerException; 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.MetadataEntry; import org.atricore.idbus.kernel.main.mediation.*; 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.SPChannel; import org.atricore.idbus.kernel.main.mediation.endpoint.IdentityMediationEndpoint; 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.monitoring.core.MonitoringServer; import org.atricore.idbus.kernel.planning.*; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.List; import java.security.Principal; /** * @author <a href="mailto:sgonzalez@atricore.org">Sebastian Gonzalez Oyuela</a> * @version $Id: IDPSingleSignOnServiceProducer.java 1246 2009-06-05 20:30:58Z sgonzalez $ */ public class SingleLogoutProducer extends SSOProducer { private static final Log logger = LogFactory.getLog( SingleLogoutProducer.class ); private static final UUIDGenerator uuidGenerator = new UUIDGenerator(); private static SSOBinding[] sloSpBindings = new SSOBinding[] { SSOBinding.SAMLR2_ARTIFACT, SSOBinding.SAMLR2_POST, SSOBinding.SAMLR2_REDIRECT}; public SingleLogoutProducer( AbstractCamelEndpoint<CamelMediationExchange> endpoint ) throws Exception { super( endpoint ); } @Override protected void doProcess( CamelMediationExchange exchange ) throws Exception { CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); Object content = in.getMessage().getContent(); // May be used later by HTTP-Redirect binding! AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); in.getMessage().getState().setAttribute("SAMLR2Signer", mediator.getSigner()); long s = System.currentTimeMillis(); String metric = mediator.getMetricsPrefix() + "/Sso/Transactions/"; boolean proxy = (channel instanceof SPChannel) && ((SPChannel)channel).getProxy() != null; try { if (content instanceof LogoutRequestType) { // Process and exit if (!proxy) { metric += "doProcessSLORequest"; doProcessSLORequest(exchange, (LogoutRequestType) content, in.getMessage().getRelayState()); } else { metric += "doProcessSLORequestAsProxy"; doProcessSLORequestAsProxy(exchange, (LogoutRequestType) content); } return; } else if (content instanceof IDPInitiatedLogoutRequestType) { // Process and exit if (!proxy) { metric += "doProcessIdPInitiatedSLORequest"; doProcessIdPInitiatedSLORequest(exchange, (IDPInitiatedLogoutRequestType) content); } else { // TODO logger.warn("IDP Initiated SLO not supported on proxy mode yet!"); metric += "doProcessIdPInitiatedSLORequest"; doProcessIdPInitiatedSLORequest(exchange, (IDPInitiatedLogoutRequestType) content); } return; } else if (content instanceof SSOResponseType) { // Do process proxy response, we only get an SSO response when acting as proxy. metric += "doProcessSLOResponseAsProxy"; doProcessSLOResponseAsProxy(exchange, (SSOResponseType) content); return; } else { metric += "Unknonw"; } } catch (SSORequestException e) { throw new IdentityMediationFault( e.getTopLevelStatusCode() != null ? e.getTopLevelStatusCode().getValue() : StatusCode.TOP_RESPONDER.getValue(), e.getSecondLevelStatusCode() != null ? e.getSecondLevelStatusCode().getValue() : null, e.getStatusDtails() != null ? e.getStatusDtails().getValue() : StatusDetails.UNKNOWN_REQUEST.getValue(), e.getErrorDetails() != null ? e.getErrorDetails() : content.getClass().getName(), e); } catch (SSOException e) { throw new IdentityMediationFault(StatusCode.TOP_RESPONDER.getValue(), null, StatusDetails.UNKNOWN_REQUEST.getValue(), content.getClass().getName(), e); } finally { MonitoringServer mServer = mediator.getMonitoringServer(); long e = System.currentTimeMillis(); mServer.recordResponseTimeMetric(metric, e - s); } // We couldn't handle the message ... throw new IdentityMediationFault(StatusCode.TOP_RESPONDER.getValue(), null, StatusDetails.UNKNOWN_REQUEST.getValue(), content.getClass().getName(), null); } protected void doProcessIdPInitiatedSLORequest(CamelMediationExchange exchange, IDPInitiatedLogoutRequestType sloRequest) throws Exception { CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); // TODO : Validate request! MediationState mediationState = in.getMessage().getState(); String varName = getProvider().getName().toUpperCase() + "_SECURITY_CTX"; IdPSecurityContext secCtx = (IdPSecurityContext) mediationState.getLocalVariable(varName); Principal ssoUser = secCtx != null ? secCtx.getSubject().getPrincipals(SimplePrincipal.class).iterator().next() : null; boolean partialLogout = performSlo(exchange, secCtx, null); AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); AuditingServer aServer = mediator.getAuditingServer(); recordInfoAuditTrail("SLO-TOUT", ActionOutcome.SUCCESS, ssoUser != null ? ssoUser.getName() : null, exchange); // Send status response! if (logger.isDebugEnabled()) logger.debug("Building SLO Response for SSO Session " + (secCtx != null ? secCtx.getSessionIndex() : "<NONE>")); SSOResponseType response = buildSsoResponse(sloRequest); CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); // This is probably a back-channel message (local, soap, etc), we don't need a destination out.setMessage(new MediationMessageImpl(response.getID(), response, "SSOResponse", in.getMessage().getRelayState(), null, in.getMessage().getState())); exchange.setOut(out); } protected void doProcessSLORequest(CamelMediationExchange exchange, LogoutRequestType sloRequest, String relayState) throws Exception { CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); MediationState mediationState = in.getMessage().getState(); String varName = getProvider().getName().toUpperCase() + "_SECURITY_CTX"; IdPSecurityContext secCtx = (IdPSecurityContext) mediationState.getLocalVariable(varName); AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); String ssoSessionId = secCtx != null ? secCtx.getSessionIndex() : "<NONE>"; Principal ssoUser = secCtx != null ? secCtx.getSubject().getPrincipals(SimplePrincipal.class).iterator().next() : null; validateRequest(sloRequest, in.getMessage().getRawContent(), in.getMessage().getState()); // Keep track of used IDs if (mediator.isVerifyUniqueIDs()) mediator.getIdRegistry().register(sloRequest.getID()); // This will destroy the security context boolean partialLogout = performSlo(exchange, secCtx, sloRequest); recordInfoAuditTrail("SLO", ActionOutcome.SUCCESS, ssoUser != null ? ssoUser.getName() : null, exchange); // We can send the response using any front-channel binding . // SSOBinding binding = SSOBinding.asEnum(endpoint.getBinding()); // Send status response! if (logger.isDebugEnabled()) logger.debug("Building SLO Response for SSO Session " + ssoSessionId); CircleOfTrustMemberDescriptor sp = resolveProviderDescriptor(sloRequest.getIssuer()); EndpointDescriptor ed = resolveSpSloEndpoint(sloRequest.getIssuer(), sloSpBindings , true); // TODO : Send partialLogout status code if required StatusResponseType response = buildSamlSloResponse(exchange, sloRequest, sp, ed); CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); out.setMessage(new MediationMessageImpl(response.getID(), response, "LogoutResponse", relayState, ed, in.getMessage().getState())); exchange.setOut(out); } protected void doProcessSLORequestAsProxy(CamelMediationExchange exchange, LogoutRequestType sloRequest) throws Exception { // We need to perform an SLO as proxy, the simplest way is to ask our binding channel to start an SP Initiated SLO ?! CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); MediationState state =in.getMessage().getState(); validateRequest(sloRequest, in.getMessage().getRawContent(), in.getMessage().getState()); // Store original SLO request and relay state. state.setLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORequest", sloRequest); state.setLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORelayState", in.getMessage().getRelayState()); SPChannel spChannel = (SPChannel) channel; BindingChannel bChannel = (BindingChannel) spChannel.getProxy(); EndpointDescriptor ed = resolveProxySPInitiatedSLOEndpointDescriptor(exchange, bChannel); SPInitiatedLogoutRequestType request = buildProxySLORequest(exchange, bChannel); CamelMediationMessage out = (CamelMediationMessage) exchange.getOut(); out.setMessage(new MediationMessageImpl(request.getID(), request, "SSOLogoutRequest", null, ed, in.getMessage().getState())); exchange.setOut(out); } protected void doProcessSLOResponseAsProxy(CamelMediationExchange exchange, SSOResponseType sloResposne) throws Exception { // TODO : CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); MediationState state = in.getMessage().getState(); LogoutRequestType sloRequest = (LogoutRequestType) state.getLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORequest"); String relayState = (String) state.getLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORelayState"); state.removeLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORequest"); state.removeLocalVariable("urn:org:atricore:idbus:sso:idp:proxySLORelayState"); // Just trigger the SLO Request doProcessSLORequest(exchange, sloRequest, relayState); } protected EndpointDescriptor resolveProxySPInitiatedSLOEndpointDescriptor(CamelMediationExchange exchange, BindingChannel bChannel) throws SSOException { try { if (logger.isDebugEnabled()) logger.debug("Looking for " + SSOService.SPInitiatedSingleLogoutServiceProxy.toString()); for (IdentityMediationEndpoint endpoint : bChannel.getEndpoints()) { if (logger.isDebugEnabled()) logger.debug("Processing endpoint : " + endpoint.getType() + "["+endpoint.getBinding()+"]"); if (endpoint.getType().equals(SSOService.SPInitiatedSingleLogoutServiceProxy.toString())) { if (endpoint.getBinding().equals(SSOBinding.SSO_ARTIFACT.getValue())) { // This is the endpoint we're looking for return bChannel.getIdentityMediator().resolveEndpoint(bChannel, endpoint); } } } } catch (IdentityMediationException e) { throw new SSOException(e); } throw new SSOException("No SP endpoint found for SP Initiated SLO using SSO Artifact binding"); } protected SPInitiatedLogoutRequestType buildProxySLORequest(CamelMediationExchange exchange, BindingChannel bChannel) throws IdentityMediationException { SPInitiatedLogoutRequestType req = new SPInitiatedLogoutRequestType(); req.setID(uuidGenerator.generateId()); IdentityMediator mediator = bChannel.getIdentityMediator(); for (IdentityMediationEndpoint endpoint : bChannel.getEndpoints()) { if (endpoint.getType().equals(SSOService.ProxySingleLogoutService.toString())) { if (endpoint.getBinding().equals(SSOBinding.SSO_ARTIFACT.getValue())) { EndpointDescriptor ed = mediator.resolveEndpoint(channel, endpoint); req.setReplyTo(ed.getResponseLocation() != null ? ed.getResponseLocation() : ed.getLocation()); if (logger.isDebugEnabled()) logger.debug("SLORequest.Reply-To:" + req.getReplyTo()); } } } CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); // Send all transient vars to SP for (String tvarName : in.getMessage().getState().getTransientVarNames()) { RequestAttributeType a = new RequestAttributeType (); a.setName(tvarName); a.setValue(in.getMessage().getState().getTransientVariable(tvarName)); req.getRequestAttribute().add(a); } return req; } protected StatusResponseType buildSamlSloResponse(CamelMediationExchange exchange, LogoutRequestType sloRequest, CircleOfTrustMemberDescriptor sp, EndpointDescriptor spEndpoint) throws Exception { // Build sloresponse IdentityPlan identityPlan = findIdentityPlanOfType(SamlR2SloRequestToSamlR2RespPlan.class); IdentityPlanExecutionExchange idPlanExchange = createIdentityPlanExecutionExchange(); // Publish SP springmetadata idPlanExchange.setProperty(VAR_DESTINATION_COT_MEMBER, sp); idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, spEndpoint); idPlanExchange.setProperty(VAR_REQUEST, sloRequest); // Create in/out artifacts IdentityArtifact<LogoutRequestType> in = new IdentityArtifactImpl<LogoutRequestType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "LogoutRequest"), sloRequest); idPlanExchange.setIn(in); IdentityArtifact<StatusResponseType> out = new IdentityArtifactImpl<StatusResponseType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "LogoutResponse"), new StatusResponseType()); 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 (StatusResponseType) idPlanExchange.getOut().getContent(); } // TODO : Reuse basic SAML response validations .... protected void validateResponse(LogoutRequestType spSloRequest, StatusResponseType spSloResponse, String originalSloResponse) throws SSOResponseException { AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); SamlR2Signer signer = mediator.getSigner(); SamlR2Encrypter encrypter = mediator.getEncrypter(); // Metadata from the IDP String spAlias = null; SPSSODescriptorType spMd = null; try { spAlias = spSloResponse.getIssuer().getValue(); MetadataEntry md = getCotManager().findEntityMetadata(spAlias); EntityDescriptorType saml2Md = (EntityDescriptorType) md.getEntry(); boolean found = false; for (RoleDescriptorType roleMd : saml2Md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) { if (roleMd instanceof SPSSODescriptorType) { spMd = (SPSSODescriptorType) roleMd; } } } catch (CircleOfTrustManagerException e) { throw new SSOResponseException(spSloResponse, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, null, spSloRequest.getIssuer().getValue(), e); } // XML Signature, saml2 core, section 5 // If no signature is present, throw an exception. We always require signed responses ... if (spSloResponse.getSignature() == null) throw new SSOResponseException(spSloResponse, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE); try { if (originalSloResponse != null) signer.validateDom(spMd, originalSloResponse); else signer.validate(spMd, spSloResponse, "LogoutResponse"); } catch (SamlR2SignatureValidationException e) { throw new SSOResponseException(spSloResponse, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } catch (SamlR2SignatureException e) { //other exceptions like JAXB, xml parser... throw new SSOResponseException(spSloResponse, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } if (mediator.isVerifyUniqueIDs() && mediator.getIdRegistry().isUsed(spSloResponse.getID())) { if (logger.isDebugEnabled()) logger.debug("Duplicated SAML ID " + spSloResponse.getID()); throw new SSOResponseException(spSloResponse, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.DUPLICATED_ID ); } } // TODO : Reuse basic SAML request validations .... protected void validateRequest(LogoutRequestType request, String originalRequest, MediationState state) throws SSORequestException, SSOException { AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); SamlR2Signer signer = mediator.getSigner(); SamlR2Encrypter encrypter = mediator.getEncrypter(); // Metadata from the IDP String spAlais = null; SPSSODescriptorType spMd = null; try { spAlais = request.getIssuer().getValue(); MetadataEntry md = getCotManager().findEntityMetadata(spAlais); EntityDescriptorType saml2Md = (EntityDescriptorType) md.getEntry(); boolean found = false; for (RoleDescriptorType roleMd : saml2Md.getRoleDescriptorOrIDPSSODescriptorOrSPSSODescriptor()) { if (roleMd instanceof SPSSODescriptorType) { spMd = (SPSSODescriptorType) roleMd; } } } catch (CircleOfTrustManagerException e) { throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, null, request.getIssuer().getValue(), e); } // XML Signature, saml2 core, section 5 if (mediator.isValidateRequestsSignature()) { if (!endpoint.getBinding().equals(SSOBinding.SAMLR2_REDIRECT.getValue())) { // If no signature is present, throw an exception! if (request.getSignature() == null) throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE); try { if (originalRequest != null) signer.validateDom(spMd, originalRequest); else signer.validate(spMd, request); } catch (SamlR2SignatureValidationException e) { throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } catch (SamlR2SignatureException e) { //other exceptions like JAXB, xml parser... throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } } else { // HTTP-Redirect binding signature validation ! try { signer.validateQueryString(spMd, state.getTransientVariable("SAMLRequest"), state.getTransientVariable("RelayState"), state.getTransientVariable("SigAlg"), state.getTransientVariable("Signature"), true); } catch (SamlR2SignatureValidationException e) { throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } catch (SamlR2SignatureException e) { //other exceptions like JAXB, xml parser... throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.INVALID_RESPONSE_SIGNATURE, e); } } } if (mediator.isVerifyUniqueIDs() && mediator.getIdRegistry().isUsed(request.getID())) { if (logger.isDebugEnabled()) logger.debug("Duplicated SAML ID " + request.getID()); throw new SSORequestException(request, StatusCode.TOP_REQUESTER, StatusCode.REQUEST_DENIED, StatusDetails.DUPLICATED_ID ); } } protected boolean performSlo(CamelMediationExchange exchange, IdPSecurityContext secCtx, LogoutRequestType sloRequest ) throws Exception { boolean partialLogout = false; // ----------------------------------------------------------------------------- // Invalidate SSO Session // ----------------------------------------------------------------------------- if (secCtx == null || secCtx.getSessionIndex() == null) { // No session information ... logger.debug("No session information foud, sending SUCCESS status"); return partialLogout; } try { if (logger.isDebugEnabled()) logger.debug("Terminating SSO Session " + secCtx.getSessionIndex()); SSOSessionManager sessionMgr = ((SPChannel)channel).getSessionManager(); AbstractSSOMediator mediator = (AbstractSSOMediator) channel.getIdentityMediator(); // Notify other SPs using either back or front channels for (ProviderSecurityContext pSecCtx : secCtx.lookupProviders()) { // Skip from the list the SP that requested SLO, if any if (sloRequest != null && pSecCtx.getProviderId().getValue().equals(sloRequest.getIssuer().getValue())) { if (logger.isDebugEnabled()) logger.debug("SP requested SLO, avoid sending back-channel request" + sloRequest.getIssuer().getValue()); continue; } CircleOfTrustMemberDescriptor sp = resolveProviderDescriptor(pSecCtx.getProviderId()); boolean sloPerformed = false; // Build a list with all supported back-channel endpoints for the SP List<EndpointDescriptor> eds = new ArrayList<EndpointDescriptor>(); EndpointDescriptor localEd = resolveSpSloEndpoint(pSecCtx.getProviderId(), new SSOBinding[] { SSOBinding.SAMLR2_LOCAL}, true); if (localEd != null) { if (logger.isDebugEnabled()) logger.debug("Adding SLO endpoint " + localEd.getName() + " for " + pSecCtx.getProviderId()); eds.add(localEd); } EndpointDescriptor soapEd = resolveSpSloEndpoint(pSecCtx.getProviderId(), new SSOBinding[] { SSOBinding.SAMLR2_SOAP}, true); if (soapEd != null) { if (logger.isDebugEnabled()) logger.debug("Adding SLO endpoint " + soapEd.getName() + " for " + pSecCtx.getProviderId()); eds.add(soapEd); } if (eds.size() == 0) { if (logger.isTraceEnabled()) logger.trace("Ignoring SP : No SLO endpoint found : " + pSecCtx.getProviderId()); continue; } // Try each endpoint on the list for (EndpointDescriptor ed : eds) { // Build SLO Request LogoutRequestType spSloRequest = buildSamlSloRequest(exchange, secCtx, sloRequest, sp, ed); try { if (logger.isDebugEnabled()) logger.debug("Sending SLO Request " + spSloRequest.getID() + " to SP " + sp.getAlias() + " using endpoint " + ed.getLocation()); // Send request and process response StatusResponseType spSloResponse = (StatusResponseType) channel.getIdentityMediator().sendMessage(spSloRequest, ed, channel); validateResponse(spSloRequest, spSloResponse, null); // Keep track of used IDs if (mediator.isVerifyUniqueIDs()) mediator.getIdRegistry().register(spSloResponse.getID()); // Successfully performed SLO for this SP sloPerformed = true; break; } catch (ClassCastException e) { // This normally happens when the SOAP transport fails ... logger.error("Error performing SLO for SP : " + sp.getAlias(), e); } catch (Exception e) { logger.error("Error performing SLO for SP : " + sp.getAlias(), e); } } if (!sloPerformed) { if (logger.isDebugEnabled()) logger.debug("No back-channel SLO performed for " + pSecCtx.getProviderId()); partialLogout = true; continue; } } CamelMediationMessage in = (CamelMediationMessage) exchange.getIn(); // Remove the SSO Session var in.getMessage().getState().removeLocalVariable(getProvider().getName().toUpperCase() + "_SECURITY_CTX"); in.getMessage().getState().getLocalState().removeAlternativeId(IdentityProviderConstants.SEC_CTX_SSOSESSION_KEY); // Remove pre-authn token in.getMessage().getState().removeRemoteVariable(getProvider().getStateManager().getNamespace().toUpperCase() + "_" + getProvider().getName().toUpperCase() + "_RM"); // Invalidate SSO Session sessionMgr.invalidate(secCtx.getSessionIndex()); secCtx.clear(); } catch (NoSuchSessionException e) { if (logger.isDebugEnabled()) logger.debug("JOSSO Session is not valid : " + secCtx.getSessionIndex()); } return partialLogout; } protected LogoutRequestType buildSamlSloRequest(CamelMediationExchange exchange, IdPSecurityContext secCtx, LogoutRequestType sloRequest, CircleOfTrustMemberDescriptor sp, EndpointDescriptor spEndpoint) throws Exception { // Build sloresponse IdentityPlan identityPlan = findIdentityPlanOfType(SamlR2SloRequestToSpSamlR2SloRequestPlan.class); IdentityPlanExecutionExchange idPlanExchange = createIdentityPlanExecutionExchange(); // Publish SP springmetadata idPlanExchange.setProperty(VAR_DESTINATION_COT_MEMBER, sp); idPlanExchange.setProperty(VAR_DESTINATION_ENDPOINT_DESCRIPTOR, spEndpoint); idPlanExchange.setProperty(VAR_REQUEST, sloRequest); idPlanExchange.setProperty(VAR_SECURITY_CONTEXT, secCtx); // Create in/out artifacts IdentityArtifact<LogoutRequestType> in = new IdentityArtifactImpl<LogoutRequestType>(new QName(SAMLR2Constants.SAML_PROTOCOL_NS, "LogoutRequest"), sloRequest); 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 SSOResponseType buildSsoResponse(SSORequestAbstractType request) { // TODO : Use Planning planning to build SSO Response !!! SSOResponseType response = new SSOResponseType(); response.setID(uuidGenerator.generateId()); response.setInReplayTo(request.getID()); response.setIssuer(getProvider().getName()); return response; } }