[java-idp-oidc] branch main updated: JOIDC-259 - Option to ignore an invalid post_logout_redirect_uri instead of fail
Codeberg
noreply at shibboleth.net
Thu Jun 25 08:09:39 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/f80ebec709e4b36a325d44f2ab9c6a7c790e559b
The following commit(s) were added to refs/heads/main by this push:
new f80ebec7 JOIDC-259 - Option to ignore an invalid post_logout_redirect_uri instead of fail
f80ebec7 is described below
commit f80ebec709e4b36a325d44f2ab9c6a7c790e559b
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Jun 25 11:08:50 2026 +0300
JOIDC-259 - Option to ignore an invalid post_logout_redirect_uri instead of fail
https://shibboleth.atlassian.net/browse/JOIDC-259
Exploit the new OIDC.Logout option 'ignoreInvalidPostLogoutRedirectUri'
- Also wire 'idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri' to OIDC.Logout and OIDC.Logout.MDDriven, defaults to 'false'
---
.../impl/ValidatePostLogoutRedirectURI.java | 55 +++++++++++++++++++---
.../idp/service/relying-party/postconfig.xml | 11 ++++-
.../idp/plugin/oidc/op/conf/oidc.properties | 3 ++
.../impl/ValidatePostLogoutRedirectURITest.java | 45 ++++++++++++++++--
.../oidc/op/profile/flow/EndSessionFlowTest.java | 42 ++++++++++++++++-
.../shibboleth/idp/module/conf/relying-party.xml | 7 +++
6 files changed, 150 insertions(+), 13 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURI.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURI.java
index a522f5f5..4b25060f 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURI.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURI.java
@@ -17,6 +17,7 @@ package net.shibboleth.idp.plugin.oidc.op.logout.profile.impl;
import java.net.URI;
import java.util.function.BiPredicate;
import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -27,6 +28,7 @@ import org.slf4j.Logger;
import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCRpInitiatedLogoutContext;
import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPostLogoutRedirectURIValidationPredicate;
+import net.shibboleth.oidc.profile.config.logic.IgnoreInvalidPostLogoutRedirectUriPredicate;
import net.shibboleth.oidc.profile.config.navigate.CustomPostLogoutRedirectUriValidationStrategyLookupFunction;
import net.shibboleth.oidc.profile.core.OidcEventIds;
import net.shibboleth.shared.logic.Constraint;
@@ -47,6 +49,9 @@ public class ValidatePostLogoutRedirectURI extends AbstractOIDCRpInitiatedLogout
@Nonnull private Function<ProfileRequestContext,BiPredicate<URI, ProfileRequestContext>>
customPostLogoutRedirectUriValidationStrategyLookupStrategy;
+ /** Condition whether to ignore invalid post logout redirect URI. */
+ @Nonnull private Predicate<ProfileRequestContext> ignoreInvalidPostLogoutRedirectUriPredicate;
+
/** The redirect URI to be validated. */
@Nullable private URI requestedRedirectURI;
@@ -57,6 +62,7 @@ public class ValidatePostLogoutRedirectURI extends AbstractOIDCRpInitiatedLogout
redirectURIValidationStrategy = new DefaultPostLogoutRedirectURIValidationPredicate();
customPostLogoutRedirectUriValidationStrategyLookupStrategy =
new CustomPostLogoutRedirectUriValidationStrategyLookupFunction();
+ ignoreInvalidPostLogoutRedirectUriPredicate = new IgnoreInvalidPostLogoutRedirectUriPredicate();
}
/**
@@ -86,6 +92,19 @@ public class ValidatePostLogoutRedirectURI extends AbstractOIDCRpInitiatedLogout
"Custom post logout redirect URI validation lookup strategy cannot be null");
}
+ /**
+ * Set the condition whether to ignore invalid post logout redirect URI.
+ *
+ * @param condition predicate
+ */
+ public void setIgnoreInvalidPostLogoutRedirectUriPredicate(@Nonnull
+ final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+
+ ignoreInvalidPostLogoutRedirectUriPredicate = Constraint.isNotNull(condition,
+ "Ignore invalid post logout redirection URI predicate cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -108,13 +127,21 @@ public class ValidatePostLogoutRedirectURI extends AbstractOIDCRpInitiatedLogout
/** {@inheritDoc} */
@Override
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final boolean ignoreInvalidPostLogoutRedirectUri =
+ ignoreInvalidPostLogoutRedirectUriPredicate.test(profileRequestContext);
final BiPredicate<URI, ProfileRequestContext> customPostLogoutRedirectUriValidationStrategy =
customPostLogoutRedirectUriValidationStrategyLookupStrategy.apply(profileRequestContext);
if (customPostLogoutRedirectUriValidationStrategy != null) {
if (!customPostLogoutRedirectUriValidationStrategy.test(requestedRedirectURI, profileRequestContext)) {
- log.warn("{} Custom post logout redirect URI validation failed for {}", getLogPrefix(),
- requestedRedirectURI);
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REDIRECT_URI);
+ if (!ignoreInvalidPostLogoutRedirectUri) {
+ log.warn("{} Custom post logout redirect URI validation failed for {}", getLogPrefix(),
+ requestedRedirectURI);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REDIRECT_URI);
+ } else {
+ log.info("{} Ignoring failed custom redirect URI validation for {}", getLogPrefix(),
+ requestedRedirectURI);
+ removePostLogoutRedirectUri();
+ }
} else {
log.debug("{} Custom post logout redirect URI validation successful for {}", getLogPrefix(),
requestedRedirectURI);
@@ -123,12 +150,28 @@ public class ValidatePostLogoutRedirectURI extends AbstractOIDCRpInitiatedLogout
}
if (!redirectURIValidationStrategy.test(profileRequestContext, requestedRedirectURI)) {
- log.warn("{} Post logout redirection URI {} did not pass the validation", getLogPrefix(),
- requestedRedirectURI);
- ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REDIRECT_URI);
+ if (!ignoreInvalidPostLogoutRedirectUri) {
+ log.warn("{} Post logout redirection URI {} did not pass the validation", getLogPrefix(),
+ requestedRedirectURI);
+ ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REDIRECT_URI);
+ } else {
+ log.info("{} Ignoring failed post logout redirection URI validation for {}", getLogPrefix(),
+ requestedRedirectURI);
+ removePostLogoutRedirectUri();
+ }
return;
}
log.debug("{} Post logout redirection URI {} successfully validated", getLogPrefix(), requestedRedirectURI);
}
+
+ /**
+ * Sets the URI into null via {@link OIDCRpInitiatedLogoutContext#setPostLogoutRedirectUri(URI)}
+ */
+ protected void removePostLogoutRedirectUri() {
+ log.debug("{} Set post logout redirection URI into null", getLogPrefix());
+ final OIDCRpInitiatedLogoutContext rpInitiatedLogoutContext = getRpInitiatedLogoutContext();
+ assert rpInitiatedLogoutContext != null;
+ rpInitiatedLogoutContext.setPostLogoutRedirectUri(null);
+ }
}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 5dbc86ee..98dee9b5 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -94,7 +94,8 @@
p:frontChannelSuccess="%{idp.oidc.logout.frontChannelSuccess:false}"
p:revokeTokens="%{idp.oidc.logout.revokeTokens:true}"
p:requireIdTokenHint="%{idp.oidc.logout.requireIdTokenHint:true}"
- p:encryptionOptional="%{idp.oidc.logout.encryptionOptional:true}"/>
+ p:encryptionOptional="%{idp.oidc.logout.encryptionOptional:true}"
+ p:ignoreInvalidPostLogoutRedirectUri="%{idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri:false}"/>
<bean id="OAUTH2.PAR" parent="OIDC.SSO" lazy-init="true"
class="net.shibboleth.oidc.profile.oauth2.config.impl.DefaultOAuth2PushedAuthorizationRequestConfiguration"
@@ -431,6 +432,14 @@
<constructor-arg value="%{idp.oidc.logout.requireIdTokenHint:true}" />
</bean>
</property>
+ <property name="ignoreInvalidPostLogoutRedirectUriPredicate">
+ <bean class="net.shibboleth.shared.logic.PredicateSupport" factory-method="fromFunction">
+ <constructor-arg>
+ <bean parent="shibboleth.MDDrivenBoolProperty" p:propertyName="ignoreInvalidPostLogoutRedirectUri" />
+ </constructor-arg>
+ <constructor-arg value="%{idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri:false}" />
+ </bean>
+ </property>
<property name="logoutHintMatchingStrategyLookupStrategy">
<bean parent="shibboleth.MDDrivenBeanProperty" p:propertyName="logoutHintMatchingStrategy"
p:propertyType="#{T(java.util.function.BiPredicate)}"
diff --git a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
index 197b4f12..5899d690 100644
--- a/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
+++ b/idp-oidc-extension-impl/src/main/resources/net/shibboleth/idp/plugin/oidc/op/conf/oidc.properties
@@ -146,6 +146,9 @@ idp.oidc.subject.salt = this_too_should_be_ch4ng3d
# The offset to add to the 'exp' claim time for logout token claims set
#idp.oidc.logout.logoutTokenExpiryOffset = PT30S
+# Whether to ignore invalid value for post_logout_redirect_uri parameter in the RP-initiated logout request.
+#idp.oidc.logout.ignoreInvalidPostLogoutRedirectUri = false
+
# Settings for issue-registration-access-token flow
#idp.oidc.admin.registration.logging = IssueRegistrationAccessToken
#idp.oidc.admin.registration.nonBrowserSupported = true
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java
index bf1fd2e3..6ebf6888 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/logout/profile/impl/ValidatePostLogoutRedirectURITest.java
@@ -19,6 +19,7 @@ import java.net.URISyntaxException;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Function;
+import java.util.function.Predicate;
import org.opensaml.profile.context.ProfileRequestContext;
import org.testng.Assert;
@@ -40,15 +41,19 @@ public class ValidatePostLogoutRedirectURITest
@Override
protected ValidatePostLogoutRedirectURI constructAction() {
- return constructAction(null);
+ return constructAction(null, null);
}
protected ValidatePostLogoutRedirectURI constructAction(
- final Function<ProfileRequestContext,BiPredicate<URI, ProfileRequestContext>> customStrategy) {
+ final Function<ProfileRequestContext,BiPredicate<URI, ProfileRequestContext>> customStrategy,
+ final Predicate<ProfileRequestContext> ignoreInvalidPredicate) {
final ValidatePostLogoutRedirectURI action = new ValidatePostLogoutRedirectURI();
if (customStrategy != null) {
action.setCustomPostLogoutRedirectUriValidationStrategyLookupStrategy(customStrategy);
}
+ if (ignoreInvalidPredicate != null) {
+ action.setIgnoreInvalidPostLogoutRedirectUriPredicate(ignoreInvalidPredicate);
+ }
try {
action.initialize();
} catch (final ComponentInitializationException e) {
@@ -68,9 +73,17 @@ public class ValidatePostLogoutRedirectURITest
ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_REDIRECT_URI);
}
+ @Test
+ public void noRegisteredPostLogoutRedirectURis_ignoreInvalid() {
+ action = constructAction(null, prc -> true);
+ rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+ }
+
@Test
public void noRegisteredPostLogoutRedirectURis_overrideWithCustomPredicate() {
- action = constructAction(p -> ((prc, uri) -> true));
+ action = constructAction(p -> ((prc, uri) -> true), null);
rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
}
@@ -84,9 +97,20 @@ public class ValidatePostLogoutRedirectURITest
ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_REDIRECT_URI);
}
+ @Test
+ public void noMatchingRegisteredPostLogoutRedirectURis_ignoreInvalid() throws URISyntaxException {
+ action = constructAction(null, prc -> true);
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setPostLogoutRedirectionURIs(Set.of(new URI("https://rp.example.com/notMatching")));
+ oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+ rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+ }
+
@Test
public void noMatchingRegisteredPostLogoutRedirectURis_overrideWithCustomPredicate() throws URISyntaxException {
- action = constructAction(p -> ((prc, uri) -> true));
+ action = constructAction(p -> ((prc, uri) -> true), null);
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setPostLogoutRedirectionURIs(Set.of(new URI("https://rp.example.com/notMatching")));
oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
@@ -105,7 +129,7 @@ public class ValidatePostLogoutRedirectURITest
@Test
public void matchingRegisteredPostLogoutRedirectURis_overrideWithCustomPredicate() {
- action = constructAction(p -> ((prc, uri) -> false));
+ action = constructAction(p -> ((prc, uri) -> false), null);
final OIDCClientMetadata metadata = new OIDCClientMetadata();
metadata.setPostLogoutRedirectionURIs(Set.of(mockPostLogoutRedirectUri()));
oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
@@ -113,6 +137,17 @@ public class ValidatePostLogoutRedirectURITest
ActionTestingSupport.assertEvent(action.execute(requestContext), OidcEventIds.INVALID_REDIRECT_URI);
}
+ @Test
+ public void matchingRegisteredPostLogoutRedirectURis_overrideWithCustomPredicate_ignoreInvalid() {
+ action = constructAction(p -> ((prc, uri) -> false), prc -> true);
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setPostLogoutRedirectionURIs(Set.of(mockPostLogoutRedirectUri()));
+ oidcMetadataContext.setClientInformation(new OIDCClientInformation(new ClientID("mockClientId"), metadata));
+ rpInitiatedLogoutContext.setPostLogoutRedirectUri(mockPostLogoutRedirectUri());
+ ActionTestingSupport.assertProceedEvent(action.execute(requestContext));
+ Assert.assertNull(rpInitiatedLogoutContext.getPostLogoutRedirectUri());
+ }
+
protected URI mockPostLogoutRedirectUri() {
try {
return new URI("https://rp.example.com/postLogout");
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 54ec8951..15fbaeea 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
@@ -71,6 +71,7 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
String sessionId = "mockSessionId";
String subject = "mockSubject";
String clientId = "mockClientId";
+ String clientIdIgnoreInvalidRedirectUri = "mockClientIdIgnoreInvalidRedirectUri";
String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
@Autowired
@@ -326,6 +327,40 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
Assert.assertEquals("ErrorView", result.getOutcome().getId());
}
+ @Test
+ public void testWithValidIdTokenHint_withPostLogoutRedirectUriNotStored_invalidUriIgnored() {
+ final String clientId = clientIdIgnoreInvalidRedirectUri;
+ final SignedJWT idTokenHint;
+ try {
+ idTokenHint = createPrivateKeyJWT(JWTClaimsSet.parse(
+ getIdTokenHintPayload(issuer, subject, clientId, Instant.now().plusSeconds(300), Instant.now(),
+ sessionId)),
+ (RSAPrivateKey) loadRSSigningCredential().getPrivateKey(), JWSAlgorithm.RS256);
+ } catch (JOSEException | ParseException e) {
+ Assert.fail();
+ return;
+ }
+ setRequestParameters(List.of(new Pair<>("id_token_hint", idTokenHint.serialize()),
+ new Pair<>("post_logout_redirect_uri", postLogoutRedirectUri)));
+ request.setMethod("GET");
+ storeMetadata(storageService, clientId, clientSecret);
+
+ initializeThreadLocals();
+
+ final IdPSession session = buildIdPSessionWithSP(clientId);
+ 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_withPostLogoutRedirection() {
final SignedJWT idTokenHint;
@@ -367,8 +402,12 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
};
}
- @SuppressWarnings("null")
protected IdPSession buildIdPSessionWithDefaultSP() {
+ return buildIdPSessionWithSP(clientId);
+ }
+
+ @SuppressWarnings("null")
+ protected IdPSession buildIdPSessionWithSP(final String clientId) {
return buildIdPSession(sessionManager, new OIDCRPSession.Builder()
.serviceId(clientId)
.issuer(issuer)
@@ -418,6 +457,7 @@ public class EndSessionFlowTest extends AbstractOidcFlowTest {
@AfterMethod
public void removeMetadata() throws IOException {
removeMetadata(storageService, clientId);
+ removeMetadata(storageService, clientIdIgnoreInvalidRedirectUri);
}
protected boolean isSessionValid(final IdPSession session) {
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index 367270e9..3ad00f80 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -274,6 +274,13 @@
</list>
</property>
</bean>
+ <bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdIgnoreInvalidRedirectUri">
+ <property name="profileConfigurations">
+ <list>
+ <bean parent="OIDC.Logout.MDDriven" p:ignoreInvalidPostLogoutRedirectUri="true"/>
+ </list>
+ </property>
+ </bean>
<bean parent="RelyingPartyByName" c:relyingPartyIds="mockClientIdLogoutFrontSuccessWithRevoke">
<property name="profileConfigurations">
<list>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list