package org.apereo.cas.logout;
import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan;
import org.apereo.cas.authentication.principal.WebApplicationService;
import org.apereo.cas.services.LogoutType;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.http.HttpClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URL;
/**
* This is {@link DefaultSingleLogoutServiceMessageHandler} which handles the processing of logout messages
* to logout endpoints processed by the logout manager.
*
* @author Misagh Moayyed
* @since 5.0.0
*/
public class DefaultSingleLogoutServiceMessageHandler implements SingleLogoutServiceMessageHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSingleLogoutServiceMessageHandler.class);
private final ServicesManager servicesManager;
private final HttpClient httpClient;
private boolean asynchronous = true;
private final LogoutMessageCreator logoutMessageBuilder;
private final SingleLogoutServiceLogoutUrlBuilder singleLogoutServiceLogoutUrlBuilder;
private final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies;
/**
* Instantiates a new Single logout service message handler.
*
* @param httpClient to send the requests
* @param logoutMessageCreator creates the message
* @param servicesManager finds services to logout from
* @param singleLogoutServiceLogoutUrlBuilder creates the URL
* @param asyncCallbacks if messages are sent in an asynchronous fashion.
* @param authenticationRequestServiceSelectionStrategies the authentication request service selection strategies
*/
public DefaultSingleLogoutServiceMessageHandler(final HttpClient httpClient, final LogoutMessageCreator logoutMessageCreator,
final ServicesManager servicesManager,
final SingleLogoutServiceLogoutUrlBuilder singleLogoutServiceLogoutUrlBuilder,
final boolean asyncCallbacks,
final AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies) {
this.httpClient = httpClient;
this.logoutMessageBuilder = logoutMessageCreator;
this.servicesManager = servicesManager;
this.singleLogoutServiceLogoutUrlBuilder = singleLogoutServiceLogoutUrlBuilder;
this.asynchronous = asyncCallbacks;
this.authenticationRequestServiceSelectionStrategies = authenticationRequestServiceSelectionStrategies;
}
/**
* Handle logout for slo service.
*
* @param singleLogoutService the service
* @param ticketId the ticket id
* @return the logout request
*/
@Override
public LogoutRequest handle(final WebApplicationService singleLogoutService, final String ticketId) {
if (singleLogoutService.isLoggedOutAlready()) {
LOGGER.debug("Service [{}] is already logged out.", singleLogoutService);
return null;
}
final WebApplicationService selectedService = WebApplicationService.class.cast(
this.authenticationRequestServiceSelectionStrategies.resolveService(singleLogoutService));
LOGGER.debug("Processing logout request for service [{}]...", selectedService);
final RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService);
if (!serviceSupportsSingleLogout(registeredService)) {
LOGGER.debug("Service [{}] does not support single logout.", selectedService);
return null;
}
LOGGER.debug("Service [{}] supports single logout and is found in the registry as [{}]. Proceeding...", selectedService, registeredService);
final URL logoutUrl = this.singleLogoutServiceLogoutUrlBuilder.determineLogoutUrl(registeredService, selectedService);
LOGGER.debug("Prepared logout url [{}] for service [{}]", logoutUrl, selectedService);
if (logoutUrl == null) {
LOGGER.debug("Service [{}] does not support logout operations given no logout url could be determined.", selectedService);
return null;
}
LOGGER.debug("Creating logout request for [{}] and ticket id [{}]", selectedService, ticketId);
final DefaultLogoutRequest logoutRequest = new DefaultLogoutRequest(ticketId, selectedService, logoutUrl);
LOGGER.debug("Logout request [{}] created for [{}] and ticket id [{}]", logoutRequest, selectedService, ticketId);
final LogoutType type = registeredService.getLogoutType() == null ? LogoutType.BACK_CHANNEL : registeredService.getLogoutType();
LOGGER.debug("Logout type registered for [{}] is [{}]", selectedService, type);
switch (type) {
case BACK_CHANNEL:
if (performBackChannelLogout(logoutRequest)) {
logoutRequest.setStatus(LogoutRequestStatus.SUCCESS);
} else {
logoutRequest.setStatus(LogoutRequestStatus.FAILURE);
LOGGER.warn("Logout message is not sent to [{}]; Continuing processing...", singleLogoutService.getId());
}
break;
default:
LOGGER.debug("Logout operation is not yet attempted for [{}] given logout type is set to [{}]", selectedService, type);
logoutRequest.setStatus(LogoutRequestStatus.NOT_ATTEMPTED);
break;
}
return logoutRequest;
}
/**
* Log out of a service through back channel.
*
* @param request the logout request.
* @return if the logout has been performed.
*/
public boolean performBackChannelLogout(final LogoutRequest request) {
try {
LOGGER.debug("Creating back-channel logout request based on [{}]", request);
final String logoutRequest = this.logoutMessageBuilder.create(request);
final WebApplicationService logoutService = request.getService();
logoutService.setLoggedOutAlready(true);
LOGGER.debug("Preparing logout request for [{}] to [{}]", logoutService.getId(), request.getLogoutUrl());
final LogoutHttpMessage msg = new LogoutHttpMessage(request.getLogoutUrl(), logoutRequest, this.asynchronous);
LOGGER.debug("Prepared logout message to send is [{}]. Sending...", msg);
return this.httpClient.sendMessageToEndPoint(msg);
} catch (final Exception e) {
LOGGER.error(e.getMessage(), e);
}
return false;
}
/**
* Service supports back channel single logout?
* Service must be found in the registry. enabled and logout type must not be {@link LogoutType#NONE}.
*
* @param registeredService the registered service
* @return true, if support is available.
*/
private static boolean serviceSupportsSingleLogout(final RegisteredService registeredService) {
return registeredService != null
&& registeredService.getAccessStrategy().isServiceAccessAllowed()
&& registeredService.getLogoutType() != LogoutType.NONE;
}
public ServicesManager getServicesManager() {
return this.servicesManager;
}
}