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; } }