package org.apereo.cas.web.flow; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CasProtocolConstants; import org.apereo.cas.authentication.principal.Service; import org.apereo.cas.authentication.principal.ServiceFactory; import org.apereo.cas.authentication.principal.WebApplicationService; import org.apereo.cas.configuration.model.core.logout.LogoutProperties; import org.apereo.cas.logout.LogoutRequest; import org.apereo.cas.logout.LogoutRequestStatus; import org.apereo.cas.services.RegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.web.support.WebUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.webflow.execution.Event; import org.springframework.webflow.execution.RequestContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.List; /** * Action to delete the TGT and the appropriate cookies. * It also performs the back-channel SLO on the services accessed by the user during its browsing. * After this back-channel SLO, a front-channel SLO can be started if some services require it. * The final logout page or a redirection url is also computed in this action. * * @author Scott Battaglia * @author Jerome Leleu * @since 3.0.0 */ public class LogoutAction extends AbstractLogoutAction { private static final Logger LOGGER = LoggerFactory.getLogger(LogoutAction.class); private final ServiceFactory<WebApplicationService> webApplicationServiceFactory; private final ServicesManager servicesManager; private final LogoutProperties logoutProperties; public LogoutAction(final ServiceFactory<WebApplicationService> webApplicationServiceFactory, final ServicesManager servicesManager, final LogoutProperties logoutProperties) { this.webApplicationServiceFactory = webApplicationServiceFactory; this.servicesManager = servicesManager; this.logoutProperties = logoutProperties; } @Override protected Event doInternalExecute(final HttpServletRequest request, final HttpServletResponse response, final RequestContext context) throws Exception { boolean needFrontSlo = false; final List<LogoutRequest> logoutRequests = WebUtils.getLogoutRequests(context); if (logoutRequests != null) { needFrontSlo = logoutRequests .stream() .anyMatch(logoutRequest -> logoutRequest.getStatus() == LogoutRequestStatus.NOT_ATTEMPTED); } final String paramName = StringUtils.defaultIfEmpty(logoutProperties.getRedirectParameter(), CasProtocolConstants.PARAMETER_SERVICE); LOGGER.debug("Using parameter name [{}] to detect destination service, if any", paramName); final String service = request.getParameter(paramName); LOGGER.debug("Located target service [{}] for redirection after logout", paramName); if (logoutProperties.isFollowServiceRedirects() && StringUtils.isNotBlank(service)) { final Service webAppService = webApplicationServiceFactory.createService(service); final RegisteredService rService = this.servicesManager.findServiceBy(webAppService); if (rService != null && rService.getAccessStrategy().isServiceAccessAllowed()) { LOGGER.debug("Redirecting to service [{}]", service); WebUtils.putLogoutRedirectUrl(context, service); } else { LOGGER.warn("Cannot redirect to [{}] given the service is unauthorized to use CAS. " + "Ensure the service is registered with CAS and is enabled to allowed access", service); } } else { LOGGER.debug("No target service is located for redirection after logout, or CAS is not allowed to follow redirects after logout"); } // there are some front services to logout, perform front SLO if (needFrontSlo) { LOGGER.debug("Proceeding forward with front-channel single logout"); return new Event(this, FRONT_EVENT); } LOGGER.debug("Moving forward to finish the logout process"); return new Event(this, FINISH_EVENT); } }