package org.pac4j.vertx.cas.logout; import org.pac4j.cas.logout.CasLogoutHandler; import org.pac4j.context.session.ExtendedSessionStore; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.profile.ExtendedProfileManager; import org.pac4j.core.profile.ProfileManager; import org.pac4j.core.store.Store; import org.pac4j.core.util.CommonHelper; import org.pac4j.vertx.VertxWebContext; import org.pac4j.vertx.context.session.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.function.Function; /** * */ public class VertxCasLogoutHandler implements CasLogoutHandler<VertxWebContext> { public static final String PAC4J_CAS_TICKET = "pac4jCasTicket"; protected static final Logger logger = LoggerFactory.getLogger(VertxCasLogoutHandler.class); private final Store<String, Object> store; private final boolean destroySession; private final Function<VertxWebContext, ProfileManager> profileManagerFactory; public VertxCasLogoutHandler(final Store<String, Object> store, final boolean destroySession) { this(store, destroySession, webContext -> new ProfileManager(webContext)); } public VertxCasLogoutHandler(final Store<String, Object> store, final boolean destroySession, Function<VertxWebContext, ProfileManager> profileManagerFactory) { this.store = store; this.destroySession = destroySession; this.profileManagerFactory = profileManagerFactory; } @Override public void recordSession(final VertxWebContext context, final String ticket) { // Record session connection as per the existing cas behaviour final SessionStore sessionStore = context.getSessionStore(); if (sessionStore == null) { logger.error("No session store available for this web context"); } else { final String sessionId = sessionStore.getOrCreateSessionId(context); if (sessionId != null) { logger.debug("ticket: {} -> sessionId: {}", ticket, sessionId); store.set(ticket, sessionId); context.setSessionAttribute(PAC4J_CAS_TICKET, ticket); // Gives us a two-way link } else { logger.debug("Can not identify id for current session"); } } } @Override public void destroySessionFront(VertxWebContext context, String ticket) { store.remove(ticket); final SessionStore sessionStore = context.getSessionStore(); if (sessionStore == null) { logger.error("No session store available for this web context"); } else { final String currentSessionId = sessionStore.getOrCreateSessionId(context); logger.debug("currentSessionId: {}", currentSessionId); final String sessionToTicket = (String) sessionStore.get(context, PAC4J_CAS_TICKET); logger.debug("-> ticket: {}", ticket); sessionStore.set(context, PAC4J_CAS_TICKET, null); if (CommonHelper.areEquals(ticket, sessionToTicket)) { // remove profiles final ProfileManager manager = profileManagerFactory.apply(context); manager.logout(); logger.debug("destroy the user profiles"); // and optionally the web session if (destroySession) { logger.debug("destroy the whole session"); final boolean invalidated = sessionStore.destroySession(context); if (!invalidated) { logger.error("The session has not been invalidated for front channel logout"); } } } else { logger.error("The user profiles (and session) can not be destroyed for CAS front channel logout because the provided ticket is not the same as the one linked to the current session"); } } } @Override public void destroySessionBack(final VertxWebContext context, final String ticket) { // Use the ticket to determine a session id final String sessionId = (String) store.get(ticket); // Take the session id and use it to retrieve a session // Log the user out of the session - need a profile manager extension to handle this // Destroy the session if required logger.debug("ticket: {} -> trackableSession: {}", ticket, sessionId); if (sessionId == null) { logger.error("No session found for back channel logout. Possibly it has expired from the store and the store settings must be updated (expired data)"); } else { store.remove(ticket); // renew context with the original session store final SessionStore sessionStore = context.getSessionStore(); if (sessionStore == null || !(sessionStore instanceof ExtendedSessionStore)) { logger.error("No extended session store available for this web context"); } else { final ExtendedSessionStore extendedSessionStore = (ExtendedSessionStore) sessionStore; final Session session = extendedSessionStore.getSession(sessionId); if (session != null) { logger.debug("session: {}", session); final ProfileManager manager = profileManagerFactory.apply(context); if (!(manager instanceof ExtendedProfileManager)) { logger.error("Profile manager not capable of back-channel logout"); } else { final ExtendedProfileManager extendedProfileManager = (ExtendedProfileManager)manager; extendedProfileManager.removeFromSession(session); } logger.debug("remove sessionId from session: {}", sessionId); session.remove(PAC4J_CAS_TICKET); if (destroySession) { logger.debug("destroy the whole session"); session.destroy(); } } else { logger.error("Session not found for session id {}", sessionId); } } } } @Override public void renewSession(String oldSessionId, VertxWebContext context) { final String ticket = (String) context.getSessionAttribute(PAC4J_CAS_TICKET); logger.debug("oldSessionId: {} -> ticket: {}", oldSessionId, ticket); final SessionStore sessionStore = context.getSessionStore(); if (!(sessionStore instanceof ExtendedSessionStore)) { logger.error("Session store does not support session renewal"); } else { if (ticket != null) { store.remove(ticket); final ExtendedSessionStore extendedSessionStore = (ExtendedSessionStore) sessionStore; final Session session = extendedSessionStore.getSession(oldSessionId); if (session != null) { // Switch to use optional session.set(PAC4J_CAS_TICKET, null); } recordSession(context, ticket); } } } }