Skip to content

Commit 973a2a6

Browse files
committed
wip: logout
1 parent a40b0cf commit 973a2a6

File tree

4 files changed

+232
-141
lines changed

4 files changed

+232
-141
lines changed

cas/cas-server/src/main/java/fr/gouv/vitamui/cas/config/WebflowConfig.java

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
7878
import org.apereo.cas.pm.PasswordManagementService;
7979
import org.apereo.cas.pm.PasswordResetUrlBuilder;
80+
import org.apereo.cas.services.ServicesManager;
8081
import org.apereo.cas.ticket.TransientSessionTicket;
8182
import org.apereo.cas.ticket.factory.DefaultTicketFactory;
8283
import org.apereo.cas.ticket.factory.DefaultTransientSessionTicketFactory;
@@ -295,37 +296,33 @@ public Action terminateSessionAction(
295296
final CasConfigurationProperties casProperties,
296297
final ConfigurableApplicationContext applicationContext,
297298
@Qualifier(LogoutManager.DEFAULT_BEAN_NAME) final LogoutManager logoutManager,
298-
@Qualifier(
299-
CasCookieBuilder.BEAN_NAME_TICKET_GRANTING_COOKIE_BUILDER
300-
) final CasCookieBuilder ticketGrantingTicketCookieGenerator,
299+
@Qualifier(CasCookieBuilder.BEAN_NAME_TICKET_GRANTING_COOKIE_BUILDER) final CasCookieBuilder ticketGrantingTicketCookieGenerator,
301300
@Qualifier("warnCookieGenerator") final CasCookieBuilder warnCookieGenerator,
302-
@Qualifier(
303-
CentralAuthenticationService.BEAN_NAME
304-
) final CentralAuthenticationService centralAuthenticationService,
305-
@Qualifier(
306-
SingleLogoutRequestExecutor.BEAN_NAME
307-
) final SingleLogoutRequestExecutor defaultSingleLogoutRequestExecutor,
301+
@Qualifier(CentralAuthenticationService.BEAN_NAME) final CentralAuthenticationService centralAuthenticationService,
308302
final Utils utils,
309303
final CasApi casApi,
310-
final TicketRegistry ticketRegistry
304+
final ServicesManager servicesManager,
305+
final TicketRegistry ticketRegistry,
306+
@Qualifier(CasWebflowConstants.ACTION_ID_FRONT_CHANNEL_LOGOUT) final Action frontChannelLogoutAction
311307
) {
312308
return WebflowActionBeanSupplier.builder()
313309
.withApplicationContext(applicationContext)
314310
.withProperties(casProperties)
315-
.withAction(
316-
() ->
317-
new TerminateApiSessionAction(
318-
centralAuthenticationService,
319-
ticketGrantingTicketCookieGenerator,
320-
warnCookieGenerator,
321-
casProperties.getLogout(),
322-
logoutManager,
323-
applicationContext,
324-
defaultSingleLogoutRequestExecutor,
325-
utils,
326-
casApi,
327-
ticketRegistry
328-
)
311+
.withAction(() ->
312+
new TerminateApiSessionAction(
313+
centralAuthenticationService,
314+
ticketGrantingTicketCookieGenerator,
315+
warnCookieGenerator,
316+
casProperties.getLogout(),
317+
logoutManager,
318+
applicationContext,
319+
utils,
320+
casApi,
321+
servicesManager,
322+
casProperties,
323+
frontChannelLogoutAction,
324+
ticketRegistry
325+
)
329326
)
330327
.withId(CasWebflowConstants.ACTION_ID_TERMINATE_SESSION)
331328
.build()

cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/CustomDelegatedAuthenticationClientLogoutAction.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import fr.gouv.vitamui.cas.delegation.ProvidersService;
3131
import fr.gouv.vitamui.iam.common.utils.IdentityProviderHelper;
32+
import lombok.extern.slf4j.Slf4j;
3233
import lombok.val;
3334
import org.apereo.cas.pac4j.client.DelegatedIdentityProviders;
3435
import org.apereo.cas.web.flow.actions.logout.DelegatedAuthenticationClientLogoutAction;
@@ -41,13 +42,9 @@
4142
/**
4243
* Propagate the logout from CAS to the authn delegated server.
4344
*/
44-
45+
@Slf4j
4546
public class CustomDelegatedAuthenticationClientLogoutAction extends DelegatedAuthenticationClientLogoutAction {
4647

47-
private static final org.slf4j.Logger LOGGER = org.slf4j.LoggerFactory.getLogger(
48-
CustomDelegatedAuthenticationClientLogoutAction.class
49-
);
50-
5148
private final ProvidersService providersService;
5249

5350
private final IdentityProviderHelper identityProviderHelper;

cas/cas-server/src/main/java/fr/gouv/vitamui/cas/logout/TerminateApiSessionAction.java

Lines changed: 116 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,43 @@
2828

2929
import fr.gouv.vitamui.cas.util.Utils;
3030
import fr.gouv.vitamui.iam.openapiclient.CasApi;
31+
import jakarta.servlet.http.HttpServletRequest;
32+
import jakarta.servlet.http.HttpServletResponse;
33+
import lombok.Getter;
34+
import lombok.RequiredArgsConstructor;
35+
import lombok.SneakyThrows;
3136
import lombok.extern.slf4j.Slf4j;
3237
import org.apache.commons.lang3.StringUtils;
3338
import org.apereo.cas.CentralAuthenticationService;
39+
import org.apereo.cas.authentication.Authentication;
40+
import org.apereo.cas.authentication.AuthenticationResult;
41+
import org.apereo.cas.authentication.DefaultAuthenticationBuilder;
42+
import org.apereo.cas.authentication.DefaultAuthenticationResultBuilder;
43+
import org.apereo.cas.authentication.principal.DefaultPrincipalElectionStrategy;
3444
import org.apereo.cas.authentication.principal.Principal;
45+
import org.apereo.cas.authentication.principal.WebApplicationService;
46+
import org.apereo.cas.authentication.principal.WebApplicationServiceFactory;
47+
import org.apereo.cas.configuration.CasConfigurationProperties;
3548
import org.apereo.cas.configuration.model.core.logout.LogoutProperties;
3649
import org.apereo.cas.logout.LogoutManager;
37-
import org.apereo.cas.logout.slo.SingleLogoutRequestExecutor;
50+
import org.apereo.cas.logout.slo.SingleLogoutExecutionRequest;
51+
import org.apereo.cas.logout.slo.SingleLogoutRequestContext;
52+
import org.apereo.cas.services.BaseRegisteredService;
53+
import org.apereo.cas.services.RegisteredService;
54+
import org.apereo.cas.services.ServicesManager;
3855
import org.apereo.cas.ticket.InvalidTicketException;
3956
import org.apereo.cas.ticket.TicketGrantingTicket;
4057
import org.apereo.cas.ticket.registry.TicketRegistry;
4158
import org.apereo.cas.web.cookie.CasCookieBuilder;
4259
import org.apereo.cas.web.flow.logout.TerminateSessionAction;
4360
import org.apereo.cas.web.support.WebUtils;
4461
import org.springframework.context.ConfigurableApplicationContext;
62+
import org.springframework.webflow.execution.Action;
4563
import org.springframework.webflow.execution.Event;
4664
import org.springframework.webflow.execution.RequestContext;
4765

66+
import java.util.ArrayList;
67+
import java.util.Collection;
4868
import java.util.List;
4969
import java.util.Map;
5070

@@ -57,9 +77,10 @@
5777
public class TerminateApiSessionAction extends TerminateSessionAction {
5878

5979
private final Utils utils;
60-
6180
private final CasApi casApi;
62-
81+
private final ServicesManager servicesManager;
82+
private final CasConfigurationProperties casProperties;
83+
private final Action frontChannelLogoutAction;
6384
private final TicketRegistry ticketRegistry;
6485

6586
public TerminateApiSessionAction(
@@ -69,128 +90,126 @@ public TerminateApiSessionAction(
6990
final LogoutProperties logoutProperties,
7091
final LogoutManager logoutManager,
7192
final ConfigurableApplicationContext applicationContext,
72-
final SingleLogoutRequestExecutor singleLogoutRequestExecutor,
7393
final Utils utils,
7494
final CasApi casApi,
95+
final ServicesManager servicesManager,
96+
final CasConfigurationProperties casProperties,
97+
final Action frontChannelLogoutAction,
7598
final TicketRegistry ticketRegistry
7699
) {
77-
super(
78-
centralAuthenticationService,
79-
ticketGrantingTicketCookieGenerator,
80-
warnCookieGenerator,
81-
logoutProperties,
82-
logoutManager,
83-
applicationContext,
84-
singleLogoutRequestExecutor
85-
);
100+
super(centralAuthenticationService, ticketGrantingTicketCookieGenerator, warnCookieGenerator, logoutProperties,
101+
logoutManager, applicationContext, null);
86102
this.utils = utils;
87103
this.casApi = casApi;
104+
this.servicesManager = servicesManager;
105+
this.casProperties = casProperties;
106+
this.frontChannelLogoutAction = frontChannelLogoutAction;
88107
this.ticketRegistry = ticketRegistry;
89108
}
90109

91110
@Override
92-
protected Event terminate(final RequestContext requestContext) throws Exception {
93-
final var request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
94-
var tgtId = WebUtils.getTicketGrantingTicketId(requestContext);
111+
@SneakyThrows
112+
public Event terminate(final RequestContext context) {
113+
final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
114+
final HttpServletResponse response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
115+
116+
String tgtId = WebUtils.getTicketGrantingTicketId(context);
95117
if (StringUtils.isBlank(tgtId)) {
96118
tgtId = ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
97119
}
98120

99-
// if we found a ticket, properly log out the user via the IAM web services
121+
TicketGrantingTicket ticket = null;
100122
if (StringUtils.isNotBlank(tgtId)) {
101123
try {
102-
final var ticket = ticketRegistry.getTicket(tgtId, TicketGrantingTicket.class);
124+
ticket = ticketRegistry.getTicket(tgtId, TicketGrantingTicket.class);
103125
if (ticket != null) {
104126
final Principal principal = ticket.getAuthentication().getPrincipal();
105127
final Map<String, List<Object>> attributes = principal.getAttributes();
106128
final String authToken = (String) utils.getAttributeValue(attributes, AUTHTOKEN_ATTRIBUTE);
107129
final String superUserEmail = (String) utils.getAttributeValue(attributes, SUPER_USER_ATTRIBUTE);
108-
final String superUserCustomerId = (String) utils.getAttributeValue(
109-
attributes,
110-
SUPER_USER_CUSTOMER_ID_ATTRIBUTE
111-
);
130+
final String superUserCustomerId = (String) utils.getAttributeValue(attributes, SUPER_USER_CUSTOMER_ID_ATTRIBUTE);
131+
132+
LOGGER.debug("Calling logout for authToken={} and superUser={}, superUserCustomerId={}",
133+
authToken, superUserEmail, superUserCustomerId);
112134

113-
LOGGER.debug("calling logout for authToken={} and superUser={}", authToken, superUserEmail);
114135
casApi.logout(authToken, superUserEmail, superUserCustomerId);
115136
}
116137
} catch (final InvalidTicketException e) {
117138
LOGGER.warn("No TGT found for the CAS cookie: {}", tgtId);
118139
}
119140
}
120141

121-
return super.terminate(requestContext);
142+
final Event event = super.terminate(context);
143+
144+
// Remove IdP cookie
145+
response.addCookie(utils.buildIdpCookie(null, casProperties.getTgc()));
146+
147+
// Fallback general logout
148+
if (tgtId == null || ticket == null || ticket.isExpired()) {
149+
List<SingleLogoutRequestContext> logoutRequests = performGeneralLogout(tgtId != null ? tgtId : "nocookie");
150+
WebUtils.putLogoutRequests(context, logoutRequests);
151+
}
152+
153+
// Front channel logout in login flow
154+
if ("login".equals(context.getFlowExecutionContext().getDefinition().getId())) {
155+
LOGGER.debug("Computing front channel logout URLs");
156+
frontChannelLogoutAction.execute(context);
157+
}
158+
159+
return event;
160+
}
161+
162+
protected List<SingleLogoutRequestContext> performGeneralLogout(final String tgtId) {
163+
try {
164+
// 1️⃣ Crée l'Authentication factice
165+
Authentication fakeAuthentication = new DefaultAuthenticationBuilder()
166+
.setPrincipal(new FakePrincipal(tgtId))
167+
.build();
168+
169+
// 2️⃣ Crée un AuthenticationResult factice à partir de l'authentication
170+
AuthenticationResult authenticationResult = new DefaultAuthenticationResultBuilder()
171+
.collect(fakeAuthentication)
172+
.build(new DefaultPrincipalElectionStrategy()); // PrincipalElectionStrategy trivial
173+
174+
// 3️⃣ Crée le TGT factice via l'API CAS 7
175+
TicketGrantingTicket fakeTgt = centralAuthenticationService.createTicketGrantingTicket(authenticationResult);
176+
177+
Collection<RegisteredService> registeredServices = servicesManager.getAllServices();
178+
// Préparer le factory de services CAS
179+
WebApplicationServiceFactory serviceFactory = new WebApplicationServiceFactory();
180+
181+
for (RegisteredService registeredService : registeredServices) {
182+
if (!(registeredService instanceof BaseRegisteredService baseService)) continue;
183+
184+
String logoutUrl = baseService.getLogoutUrl();
185+
if (logoutUrl != null) {
186+
WebApplicationService service = serviceFactory.createService(logoutUrl);
187+
188+
// Génère le ST factice lié au TGT factice
189+
centralAuthenticationService.grantServiceTicket(
190+
fakeTgt.getId(),
191+
service,
192+
authenticationResult
193+
);
194+
// Pas besoin de collecter les ST dans une liste
195+
}
196+
}
197+
198+
// Ensuite le logout général
199+
return logoutManager.performLogout(
200+
SingleLogoutExecutionRequest.builder()
201+
.ticketGrantingTicket(fakeTgt)
202+
.build()
203+
);
204+
} catch (Throwable e) {
205+
LOGGER.error("Unable to perform general logout", e);
206+
return new ArrayList<>();
207+
}
208+
}
209+
210+
@Getter
211+
@RequiredArgsConstructor
212+
private static class FakePrincipal implements Principal {
213+
private final String id;
122214
}
123215
}
124-
// TODO: Our old terminate impl.
125-
/**
126-
*
127-
* public Event terminate(final RequestContext context) {
128-
* final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
129-
* String tgtId = WebUtils.getTicketGrantingTicketId(context);
130-
* if (StringUtils.isBlank(tgtId)) {
131-
* tgtId = ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
132-
* }
133-
*
134-
* // if we found a ticket, properly log out the user in the IAM web services
135-
* TicketGrantingTicket ticket = null;
136-
* if (StringUtils.isNotBlank(tgtId)) {
137-
* try {
138-
* ticket = ticketRegistry.getTicket(tgtId, TicketGrantingTicket.class);
139-
* if (ticket != null) {
140-
* final Principal principal = ticket.getAuthentication().getPrincipal();
141-
* final Map<String, List<Object>> attributes = principal.getAttributes();
142-
* final String authToken = (String) utils.getAttributeValue(attributes, AUTHTOKEN_ATTRIBUTE);
143-
* final String principalEmail = (String) utils.getAttributeValue(attributes, EMAIL_ATTRIBUTE);
144-
* final String superUserEmail = (String) utils.getAttributeValue(attributes, SUPER_USER_ATTRIBUTE);
145-
* final String superUserCustomerId = (String) utils.getAttributeValue(
146-
* attributes,
147-
* SUPER_USER_CUSTOMER_ID_ATTRIBUTE
148-
* );
149-
*
150-
* final HttpContext httpContext;
151-
* if (StringUtils.isNotBlank(superUserCustomerId)) {
152-
* httpContext = utils.buildContext(superUserEmail);
153-
* } else {
154-
* httpContext = utils.buildContext(principalEmail);
155-
* }
156-
*
157-
* LOGGER.debug(
158-
* "calling logout for authToken={} and superUser={}, superUserCustomerId={}",
159-
* authToken,
160-
* superUserEmail,
161-
* superUserCustomerId
162-
* );
163-
* casRestClient.logout(httpContext, authToken, superUserEmail, superUserCustomerId);
164-
* }
165-
* } catch (final InvalidTicketException e) {
166-
* LOGGER.warn("No TGT found for the CAS cookie: {}", tgtId);
167-
* }
168-
* }
169-
*
170-
* final Event event = super.terminate(context);
171-
*
172-
* final HttpServletResponse response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);
173-
* // remove the idp cookie
174-
* response.addCookie(utils.buildIdpCookie(null, casProperties.getTgc()));
175-
*
176-
* // fallback cases:
177-
* // no CAS cookie -> general logout
178-
* if (tgtId == null) {
179-
* final List<SingleLogoutRequestContext> logoutRequests = performGeneralLogout("nocookie");
180-
* WebUtils.putLogoutRequests(context, logoutRequests);
181-
* // no ticket or expired -> general logout
182-
* } else if (ticket == null || ticket.isExpired()) {
183-
* final List<SingleLogoutRequestContext> logoutRequests = performGeneralLogout(tgtId);
184-
* WebUtils.putLogoutRequests(context, logoutRequests);
185-
* }
186-
*
187-
* // if we are in the login webflow, compute the logout URLs
188-
* if ("login".equals(context.getFlowExecutionContext().getDefinition().getId())) {
189-
* LOGGER.debug("Computing front channel logout URLs");
190-
* frontChannelLogoutAction.execute(context);
191-
* }
192-
*
193-
* return event;
194-
* }
195-
*
196-
*/

0 commit comments

Comments
 (0)