[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