2828
2929import fr .gouv .vitamui .cas .util .Utils ;
3030import 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 ;
3136import lombok .extern .slf4j .Slf4j ;
3237import org .apache .commons .lang3 .StringUtils ;
3338import 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 ;
3444import 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 ;
3548import org .apereo .cas .configuration .model .core .logout .LogoutProperties ;
3649import 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 ;
3855import org .apereo .cas .ticket .InvalidTicketException ;
3956import org .apereo .cas .ticket .TicketGrantingTicket ;
4057import org .apereo .cas .ticket .registry .TicketRegistry ;
4158import org .apereo .cas .web .cookie .CasCookieBuilder ;
4259import org .apereo .cas .web .flow .logout .TerminateSessionAction ;
4360import org .apereo .cas .web .support .WebUtils ;
4461import org .springframework .context .ConfigurableApplicationContext ;
62+ import org .springframework .webflow .execution .Action ;
4563import org .springframework .webflow .execution .Event ;
4664import org .springframework .webflow .execution .RequestContext ;
4765
66+ import java .util .ArrayList ;
67+ import java .util .Collection ;
4868import java .util .List ;
4969import java .util .Map ;
5070
5777public 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