This is an automated email from the ASF dual-hosted git repository.
ilgrosso pushed a commit to branch 4_0_X
in repository https://gitbox.apache.org/repos/asf/syncope.git
The following commit(s) were added to refs/heads/4_0_X by this push:
new c523775a8d [SYNCOPE-2004] - Fix OIDCC4UI back-channel logout token
validator initialization (#1594)
c523775a8d is described below
commit c523775a8d0d32120d0298ebbc6d5f7aaa2a9d64
Author: Matteo Tatoni <[email protected]>
AuthorDate: Mon Nov 2 10:54:00 2026 +0100
[SYNCOPE-2004] - Fix OIDCC4UI back-channel logout token validator
initialization (#1594)
---
.../syncope/core/logic/oidc/OIDCClientCache.java | 1 +
.../org/apache/syncope/fit/ui/OIDCC4UIITCase.java | 63 ++++++++++++++++++++--
2 files changed, 59 insertions(+), 5 deletions(-)
diff --git
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
index df970fcbd2..27a4442c1d 100644
---
a/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
+++
b/ext/oidcc4ui/logic/src/main/java/org/apache/syncope/core/logic/oidc/OIDCClientCache.java
@@ -149,6 +149,7 @@ public class OIDCClientCache {
client.setCallbackUrlResolver(new NoParameterCallbackUrlResolver());
client.setCallbackUrl(callbackUrl);
client.init();
+ cfg.getOpMetadataResolver().init();
cache.add(client);
return client;
diff --git
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
index cf33cd836d..cebe25385a 100644
---
a/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
+++
b/fit/wa-reference/src/test/java/org/apache/syncope/fit/ui/OIDCC4UIITCase.java
@@ -28,6 +28,7 @@ import static org.junit.jupiter.api.Assertions.fail;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -43,6 +44,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
@@ -51,6 +53,7 @@ import org.apache.http.util.EntityUtils;
import org.apache.syncope.client.ui.commons.panels.OIDCC4UIConstants;
import org.apache.syncope.common.lib.OIDCScopeConstants;
import org.apache.syncope.common.lib.SyncopeConstants;
+import org.apache.syncope.common.lib.to.AccessTokenTO;
import org.apache.syncope.common.lib.to.Item;
import org.apache.syncope.common.lib.to.OIDCC4UIProviderTO;
import org.apache.syncope.common.lib.to.OIDCRPClientAppTO;
@@ -60,9 +63,12 @@ import org.apache.syncope.common.lib.types.LogoutType;
import org.apache.syncope.common.lib.types.OIDCResponseType;
import org.apache.syncope.common.lib.types.OIDCSubjectType;
import org.apache.syncope.common.rest.api.RESTHeaders;
+import org.apache.syncope.common.rest.api.beans.AccessTokenQuery;
+import org.apache.syncope.common.rest.api.service.AccessTokenService;
import org.apache.syncope.common.rest.api.service.wa.WAConfigService;
import org.jsoup.Jsoup;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
public class OIDCC4UIITCase extends AbstractUIITCase {
@@ -211,8 +217,28 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
oidcSetup(getAppName(ENDUSER_ADDRESS), false, true);
}
+ @Test
+ public void backChannelLogout() throws IOException {
+ // Start with a fresh Syncope token so its key matches the new OIDC
session ID.
+ AccessTokenService accessTokenService =
ADMIN_CLIENT.getService(AccessTokenService.class);
+ accessTokenService.list(new
AccessTokenQuery.Builder().size(1000).build()).getResult().stream().
+ filter(token -> "bellini".equals(token.getOwner())).
+ map(AccessTokenTO::getKey).
+ forEach(accessTokenService::delete);
+
+ sso(CONSOLE_ADDRESS, "bellini", "password", true);
+ }
+
@Override
protected void sso(final String baseURL, final String username, final
String password) throws IOException {
+ sso(baseURL, username, password, false);
+ }
+
+ private void sso(
+ final String baseURL,
+ final String username,
+ final String password,
+ final boolean backChannelLogout) throws IOException {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpClientContext context = HttpClientContext.create();
context.setCookieStore(new BasicCookieStore());
@@ -282,11 +308,38 @@ public class OIDCC4UIITCase extends AbstractUIITCase {
assertEquals(HttpStatus.SC_OK,
response.getStatusLine().getStatusCode());
assertTrue(EntityUtils.toString(response.getEntity()).contains(username));
- // 4. logout
- get = new HttpGet(CONSOLE_ADDRESS.equals(baseURL)
- ? baseURL +
"wicket/bookmarkable/org.apache.syncope.client.console.pages.Logout"
- : baseURL +
"wicket/bookmarkable/org.apache.syncope.client.enduser.pages.Logout");
- httpclient.execute(get, context);
+ if (backChannelLogout) {
+ String accessToken = context.getRedirectLocations().stream().
+ flatMap(uri -> URLEncodedUtils.parse(uri,
StandardCharsets.UTF_8).stream()).
+ filter(param ->
OIDCC4UIConstants.OIDCC4UI_JWT.equals(param.getName())).
+ map(NameValuePair::getValue).
+ findFirst().orElseThrow(() -> new AssertionError("No
Syncope JWT in console login redirect"));
+
+ // Confirm that the console's token works before logging out
directly from WA.
+ try (Response self = WebClient.create(CORE_ADDRESS +
"/users/self").
+ header(HttpHeaders.AUTHORIZATION, "Bearer " +
accessToken).get()) {
+ assertEquals(HttpStatus.SC_OK, self.getStatus());
+ }
+ try (CloseableHttpResponse logout = httpclient.execute(new
HttpGet(WA_ADDRESS + "/logout"), context)) {
+ assertEquals(HttpStatus.SC_OK,
logout.getStatusLine().getStatusCode());
+ EntityUtils.consume(logout.getEntity());
+ }
+
+ // WA sends back-channel logout asynchronously; the same JWT must
then be rejected by Core.
+ await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
+ try (Response self = WebClient.create(CORE_ADDRESS +
"/users/self").
+ header(HttpHeaders.AUTHORIZATION, "Bearer " +
accessToken).get()) {
+ assertEquals(HttpStatus.SC_UNAUTHORIZED, self.getStatus());
+ }
+ });
+ } else {
+ // 4. logout
+ get = new HttpGet(CONSOLE_ADDRESS.equals(baseURL)
+ ? baseURL +
"wicket/bookmarkable/org.apache.syncope.client.console.pages.Logout"
+ : baseURL +
"wicket/bookmarkable/org.apache.syncope.client.enduser.pages.Logout");
+ httpclient.execute(get, context);
+ }
+ httpclient.close();
}
@Override