[java-idp-oidc] 02/03: JOIDC-300 - RP-initiated logout is hardcoded to use GET method

Codeberg noreply at shibboleth.net
Thu Nov 5 17:34:38 UTC 2026


This is an automated email from the git hooks/post-receive script.

codeberg pushed a commit to branch main
in repository java-idp-oidc.

View the commit online:
https://codeberg.org/Shibboleth/java-idp-oidc/commit/b83f956dee9faab8ab0326c8171553fec1f604d3

commit b83f956dee9faab8ab0326c8171553fec1f604d3
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Nov 5 19:31:32 2026 +0200

    JOIDC-300 - RP-initiated logout is hardcoded to use GET method
    
    https://shibboleth.atlassian.net/browse/JOIDC-300
    
    Refactored the condition to not depend on the method but use solely the parameter values
---
 .../flows/oidc/end-session/end-session-flow.xml    |   6 +-
 .../oidc/op/profile/flow/EndSessionFlowTest.java   | 120 +++++++++++++++++++++
 2 files changed, 123 insertions(+), 3 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
index 784b86ce..2c8ae130 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-flow.xml
@@ -10,11 +10,11 @@
         <evaluate expression="FlowStartPopulateAuditContext" />
         <evaluate expression="'proceed'" />
         
-        <transition on="proceed" to="IsEmptyQuery" />
+        <transition on="proceed" to="IsMissingMandatoryParameters" />
     </action-state>
     
-    <decision-state id="IsEmptyQuery">
-        <if test="flowRequestContext.getExternalContext().getNativeRequest().getQueryString() == null or (flowRequestContext.getExternalContext().getNativeRequest().getParameter('id_token_hint') == null and flowRequestContext.getExternalContext().getNativeRequest().getParameter('client_id') == null)"
+    <decision-state id="IsMissingMandatoryParameters">
+        <if test="flowRequestContext.getExternalContext().getNativeRequest().getParameter('id_token_hint') == null and flowRequestContext.getExternalContext().getNativeRequest().getParameter('client_id') == null"
             then="RedirectToGenericLogout" else="DecodeMessage" />
     </decision-state>
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/EndSessionFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/EndSessionFlowTest.java
index 9a02f30a..e575cea7 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/EndSessionFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/EndSessionFlowTest.java
@@ -25,6 +25,7 @@ import java.time.Instant;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 
 import org.opensaml.storage.StorageService;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -103,6 +104,17 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals("RedirectToGenericLogout", result.getOutcome().getId());
     }
 
+    @Test
+    public void testPostWithNoParameters() {
+        setHttpFormRequest(request, "POST", Collections.emptyMap());
+        storeMetadata(storageService, clientId, clientSecret);
+
+        initializeThreadLocals();
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertEquals("RedirectToGenericLogout", result.getOutcome().getId());
+    }
+
     @Test
     public void testWithClientIdNoSession() {
         setRequestParameters(List.of(new Pair<>("client_id", clientId)));
@@ -115,6 +127,17 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertEquals("ErrorView", result.getOutcome().getId());
     }
 
+    @Test
+    public void testPostWithClientIdNoSession() {
+        setHttpFormRequest(request, "POST", Map.of("client_id", clientId));
+        storeMetadata(storageService, clientId, clientSecret, postLogoutRedirectUri);
+
+        initializeThreadLocals();
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertEquals("ErrorView", result.getOutcome().getId());
+    }
+
     @Test
     public void testWithClientIdAndSession_noRedirection() {
         setRequestParameters(List.of(
@@ -139,6 +162,27 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(result2.isEnded());
     }
 
+    @Test
+    public void testPostWithClientIdAndSession_noRedirection() {
+        setHttpFormRequest(request, "POST", Map.of("client_id", clientId, "logout_hint", sessionId));
+        storeMetadata(storageService, clientId, clientSecret, postLogoutRedirectUri);
+
+        initializeThreadLocals();
+        
+        final IdPSession session = buildIdPSessionWithDefaultSP();
+        request.setCookies(response.getCookies());
+        Assert.assertTrue(isSessionValid(session));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertFalse(result.isEnded());
+        Assert.assertFalse(isSessionValid(session));
+        
+        ((MockParameterMap) externalContext.getRequestParameterMap()).put("_eventId", "proceed");
+        final FlowExecutionResult result2 = flowExecutor.resumeExecution(result.getPausedKey(), externalContext);
+        Assert.assertFalse(isSessionValid(session));
+        Assert.assertEquals(response.getStatus(), 200);
+        Assert.assertTrue(result2.isEnded());
+    }
+
     @Test
     public void testWithClientIdAndSession_withPostLogoutRedirectionNotStored() {
         setRequestParameters(List.of(
@@ -183,6 +227,29 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(result2.isEnded());
     }
 
+    @Test
+    public void testPostWithClientIdAndSession_withPostLogoutRedirection() {
+        setHttpFormRequest(request, "POST", Map.of("client_id", clientId, "logout_hint", sessionId,
+                "post_logout_redirect_uri", postLogoutRedirectUri));
+        storeMetadata(storageService, clientId, clientSecret, postLogoutRedirectUri);
+
+        initializeThreadLocals();
+        
+        final IdPSession session = buildIdPSessionWithDefaultSP();
+        request.setCookies(response.getCookies());
+        Assert.assertTrue(isSessionValid(session));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertFalse(result.isEnded());
+        Assert.assertFalse(isSessionValid(session));
+        
+        ((MockParameterMap) externalContext.getRequestParameterMap()).put("_eventId", "proceed");
+        final FlowExecutionResult result2 = flowExecutor.resumeExecution(result.getPausedKey(), externalContext);
+        Assert.assertFalse(isSessionValid(session));
+        Assert.assertEquals(response.getStatus(), 302);
+        Assert.assertEquals(response.getHeaderValue("Location"), postLogoutRedirectUri);
+        Assert.assertTrue(result2.isEnded());
+    }
+
     @Test
     public void testWithClientIdAndSessionAndState_withPostLogoutRedirection() {
         final String state = idGenerator.generateIdentifier();
@@ -211,6 +278,30 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(result2.isEnded());
     }
 
+    @Test
+    public void testPostWithClientIdAndSessionAndState_withPostLogoutRedirection() {
+        final String state = idGenerator.generateIdentifier();
+        setHttpFormRequest(request, "POST", Map.of("client_id", clientId, "logout_hint", sessionId,
+                "post_logout_redirect_uri", postLogoutRedirectUri, "state", state));
+        storeMetadata(storageService, clientId, clientSecret, postLogoutRedirectUri);
+
+        initializeThreadLocals();
+        
+        final IdPSession session = buildIdPSessionWithDefaultSP();
+        request.setCookies(response.getCookies());
+        Assert.assertTrue(isSessionValid(session));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertFalse(result.isEnded());
+        Assert.assertFalse(isSessionValid(session));
+        
+        ((MockParameterMap) externalContext.getRequestParameterMap()).put("_eventId", "proceed");
+        final FlowExecutionResult result2 = flowExecutor.resumeExecution(result.getPausedKey(), externalContext);
+        Assert.assertFalse(isSessionValid(session));
+        Assert.assertEquals(response.getStatus(), 302);
+        Assert.assertEquals(response.getHeaderValue("Location"), postLogoutRedirectUri + "?state=" + state);
+        Assert.assertTrue(result2.isEnded());
+    }
+
     @Test
     public void testWithInvalidIdTokenHint() {
         setRequestParameters(List.of(new Pair<>("id_token_hint", "not a JWT")));
@@ -301,6 +392,35 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
         Assert.assertTrue(result2.isEnded());
     }
 
+    @Test
+    public void testPostWithValidIdTokenHint_noRedirection() {
+        final SignedJWT idTokenHint;
+        try {
+            idTokenHint = createPrivateKeyJWT(JWTClaimsSet.parse(getDefaultIdTokenHintPayload()),
+                    (RSAPrivateKey) loadRSSigningCredential().getPrivateKey(), JWSAlgorithm.RS256);
+        } catch (JOSEException | ParseException e) {
+            Assert.fail();
+            return;
+        }
+        setHttpFormRequest(request, "POST", Map.of("id_token_hint", idTokenHint.serialize()));
+        storeMetadata(storageService, clientId, clientSecret, postLogoutRedirectUri);
+
+        initializeThreadLocals();
+
+        final IdPSession session = buildIdPSessionWithDefaultSP();
+        request.setCookies(response.getCookies());
+        Assert.assertTrue(isSessionValid(session));
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertFalse(result.isEnded());
+
+        ((MockParameterMap) externalContext.getRequestParameterMap()).put("_eventId", "proceed");
+        final FlowExecutionResult result2 = flowExecutor.resumeExecution(result.getPausedKey(), externalContext);
+        Assert.assertFalse(isSessionValid(session));
+        Assert.assertEquals(response.getStatus(), 200);
+        Assert.assertTrue(result2.isEnded());
+    }
+
     @Test
     public void testWithValidIdTokenHint_withPostLogoutRedirectUriNotStored() {
         final SignedJWT idTokenHint;

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list