[java-plugin-shibd-oidc] branch main updated: Fix configuration of form_post response decoder
Codeberg
noreply at shibboleth.net
Thu Apr 16 16:10:03 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-plugin-shibd-oidc.
View the commit online:
https://codeberg.org/Shibboleth/java-plugin-shibd-oidc/commit/c0dbabf15a24132d9a3f3ca768c6cecafd30eabf
The following commit(s) were added to refs/heads/main by this push:
new c0dbabf Fix configuration of form_post response decoder
c0dbabf is described below
commit c0dbabf15a24132d9a3f3ca768c6cecafd30eabf
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Apr 16 16:38:33 2026 +0100
Fix configuration of form_post response decoder
- The form_post decoder was not wired up correctly. This is not the
default response mode.
---
.../sp/consumer/oidc/code/post/post-beans.xml | 12 +-
.../sp/oidc/flows/OIDCTokenConsumerFlowTest.java | 133 ++++++++++++++++++++-
.../shibboleth/sp/oidc/flows/TestConstants.java | 3 +
.../idp/module/conf/sp/oidc-test-agents.xml | 11 ++
.../impl/DecodeStateAsJsonObjectConsumer.java | 2 +-
5 files changed, 156 insertions(+), 5 deletions(-)
diff --git a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
index e5743d9..3f5e9b7 100644
--- a/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
+++ b/sp-oidc-conf-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/sp/consumer/oidc/code/post/post-beans.xml
@@ -8,12 +8,18 @@
default-init-method="initialize" default-destroy-method="destroy">
<bean id="DecodeMessage" class="net.shibboleth.sp.profile.impl.DecodeMessage" scope="prototype"
- p:messageType="org.opensaml.saml.saml2.core.Response">
+ p:messageType="com.nimbusds.openid.connect.sdk.AuthenticationResponse">
<constructor-arg>
<bean class="net.shibboleth.oidc.profile.decoding.impl.HTTPPostAuthnResponseDecoder" scope="prototype"
- p:decodeStateAsJSON="true"
p:checkDuringInit="false"
- p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier"/>
+ p:postDecodeStrategyFailureIsError="true"
+ p:httpServletRequestSupplier-ref="shibboleth.RemotedHttpServletRequestSupplier">
+ <property name="postDecodeStrategy">
+ <bean class="net.shibboleth.sp.oidc.profile.decoding.impl.DecodeStateAsJsonObjectConsumer"
+ p:dataSealer-ref="#{'%{sp.dataSealer:shibboleth.DataSealer}'.trim()}"
+ p:unsealStatePredicate="#{getObject('%{sp.oidc.sealOAuthStatePredicate:}') ?: ((%{sp.oidc.sealOAuthState:true}) ? getObject('shibboleth.Conditions.TRUE') : getObject('shibboleth.Conditions.FALSE'))}"/>
+ </property>
+ </bean>
</constructor-arg>
</bean>
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
index f0414d6..479f0a2 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/OIDCTokenConsumerFlowTest.java
@@ -84,7 +84,9 @@ import net.shibboleth.idp.spring.IdPPropertiesApplicationContextInitializer;
import net.shibboleth.idp.test.PreferFileSystemApplicationContextInitializer;
import net.shibboleth.oidc.security.credential.JWKCredential;
import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.net.URISupport;
import net.shibboleth.sp.context.AgentRequestContext;
import net.shibboleth.sp.ddf.DDF;
import net.shibboleth.sp.flows.AbstractSPFlowTest;
@@ -191,6 +193,45 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
TestConstants.RESOURCE_URL);
}
+ /**
+ * Test successful flow with response_mode=form_post, a signed id_token, and a plain user info response.
+ *
+ * @throws IOException on error
+ */
+ @Test
+ public void testSuccess_Post_SignedIDToken_PlainUserInfo() throws Exception {
+
+ mockOIDCEndpoints(constructSuccessfulTokenResponseSigned(
+ Instant.now().plusSeconds(3600), Instant.now(), null, null),
+ constructJSONUserInfoResponse());
+
+ final AuthenticationSuccessResponse response =
+ buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.FORM_POST,
+ TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_RFP));
+ final DDF input = buildRemotedFormPostResponse(response);
+
+ // Add cookies
+ input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_STATE_TOKEN,
+ TestConstants.APPLICATION_ID_POST_RESPONSE_MODE,
+ TestConstants.AUTHENTICATION_RFP,
+ TestConstants.TARGET_URL,
+ TestConstants.buildAuthenticationState(null, false, null)));
+
+ setApplicationRequest(TestConstants.APPLICATION_ID_POST_RESPONSE_MODE, input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, TestConstants.FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ final DDF output = assertOutputMessageSuccess(result);
+ assert output != null;
+ System.out.println("test output: " + output.toString());
+ validateOutputMessage(result, CollectionSupport.setOf("sub","mail","displayName","eduPersonScopedAffiliation"),
+ TestConstants.RESOURCE_URL);
+ }
+
/**
* Test successful flow with a signed id_token and a plain user info response. Use private_key_jwt client
* authentication.
@@ -705,7 +746,7 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
/**
- * Test successful flow.
+ * Test error response from the OP.
*
* @throws IOException on error
*/
@@ -733,6 +774,67 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
System.out.println("DDF: "+output.toString());
validateCookiesAreUnset(output);
}
+
+ /**
+ * Test error response from OP followed by a replayed success response. It should fail as the state has been
+ * replayed.
+ *
+ * @throws IOException on error
+ */
+ //@Test
+ public void testFail_ErrorResponse_FollowedByReplayedSuccess() throws Exception {
+ final DDF input = buildRemotedQueryStringResponse(buildErrorResponse(
+ TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_RFP)));
+
+ // Add cookies
+ input.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_STATE_TOKEN,
+ TestConstants.APPLICATION_ID,
+ TestConstants.AUTHENTICATION_RFP,
+ TestConstants.TARGET_URL,
+ TestConstants.buildAuthenticationState(null, false, null)));
+
+ setApplicationRequest(TestConstants.APPLICATION_ID, input);
+
+ final FlowExecutionResult result = flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(result, TestConstants.FLOW_ID);
+ assertFlowExecutionOutcome(result.getOutcome());
+ final DDF output = assertOutputMessageEvent(result, EventIds.MESSAGE_PROC_ERROR);
+ System.out.println("DDF: "+output.toString());
+ validateCookiesAreUnset(output);
+
+ // Now construct a valid response, as the state is re-used this should fail early, and will not need the
+ // mocked endpoints etc.
+
+ final AuthenticationSuccessResponse response =
+ buildOIDCAuthorizationCodeResponse(TestConstants.RESPONSE_URL, ResponseMode.QUERY,
+ TestConstants.buildOAuthStateString(TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_RFP));
+ final DDF inputSuccessResponse = buildRemotedQueryStringResponse(response);
+
+ // Add cookies
+ inputSuccessResponse.addmember("http.headers.Cookie").unsafe_string(TestConstants.buildCookieHeader(
+ TestConstants.STATE_TOKEN,
+ TestConstants.AUTHENTICATION_STATE_TOKEN,
+ TestConstants.APPLICATION_ID,
+ TestConstants.AUTHENTICATION_RFP,
+ TestConstants.TARGET_URL,
+ TestConstants.buildAuthenticationState(null, false, null)));
+
+ setApplicationRequest(TestConstants.APPLICATION_ID, inputSuccessResponse);
+
+ final FlowExecutionResult resultReplayFail =
+ flowExecutor.launchExecution(TestConstants.FLOW_ID, null, externalContext);
+ assertFlowExecutionResult(resultReplayFail, TestConstants.FLOW_ID);
+ assertFlowExecutionOutcome(resultReplayFail.getOutcome());
+ // TODO change to appropriate for replay error
+ assertOutputMessageEvent(resultReplayFail, EventIds.ACCESS_DENIED);
+ System.out.println("DDF: "+output.toString());
+ validateCookiesAreUnset(output);
+
+ }
/**
@@ -1104,5 +1206,34 @@ public class OIDCTokenConsumerFlowTest extends AbstractSPFlowTest {
return obj;
}
+ /**
+ * Encodes an OIDC response into a form_post embedded in a remoted message.
+ *
+ * @param response OIDC response to encode
+ *
+ * @return input object suitable for token consumer flow
+ *
+ * @throws IOException on error
+ */
+ @Nonnull private DDF buildRemotedFormPostResponse(@Nonnull final AuthorizationResponse response) {
+
+ final DDF obj = new DDF(null).structure();
+ obj.addmember(ConsumerConstants.BASE_URL).unsafe_string(TestConstants.RESOURCE_URL.getBytes(StandardCharsets.UTF_8));
+ final DDF http = obj.addmember(RemotedHttpServletRequest.STRUCTURE_NAME).structure();
+
+ http.addmember(RemotedHttpServletRequest.METHOD).string("POST");
+ http.addmember(RemotedHttpServletRequest.REMOTE_ADDR).string("192.168.1.1");
+ http.addmember(RemotedHttpServletRequest.REQUEST_URL).unsafe_string(
+ TestConstants.RESPONSE_URL.getBytes(StandardCharsets.UTF_8));
+ http.addmember(RemotedHttpServletRequest.CONTENT_TYPE).string("application/x-www-form-urlencoded");
+ if (response instanceof final AuthenticationSuccessResponse successResponse) {
+ http.addmember(RemotedHttpServletRequest.BODY).unsafe_string(Constraint.isNotNull(URISupport.buildQuery(
+ CollectionSupport.listOf(new Pair<>("code", successResponse.getAuthorizationCode().getValue()),
+ new Pair<>("state", response.getState().getValue()))), "Query string is null").getBytes(StandardCharsets.UTF_8));
+ }
+
+ return obj;
+ }
+
}
\ No newline at end of file
diff --git a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
index 9883156..480edda 100644
--- a/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
+++ b/sp-oidc-conf-impl/src/test/java/net/shibboleth/sp/oidc/flows/TestConstants.java
@@ -90,6 +90,9 @@ public final class TestConstants {
/** Application ID used when private_key_jwt has been configured.*/
public static final String APPLICATION_ID_PRIVATE_KEY_JWT = "test-oidc-application-with-ro-private-key-jwt";
+
+ /** Application ID used when the form_post response mode has been configured.*/
+ public static final String APPLICATION_ID_POST_RESPONSE_MODE = "test-oidc-application-with-post-responsemode";
/** Target URL encoded in cookie. */
public static final String TARGET_URL = "https://sp.example.org/secure";
diff --git a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
index 0587107..60c0576 100644
--- a/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
+++ b/sp-oidc-conf-impl/src/test/resources/net/shibboleth/idp/module/conf/sp/oidc-test-agents.xml
@@ -29,6 +29,9 @@
<bean p:id="test-oidc-application-with-pkce" parent="shibboleth.sp.Application"
p:profileConfigurations-ref="test.WithPKCE"/>
+
+ <bean p:id="test-oidc-application-with-post-responsemode" parent="shibboleth.sp.Application"
+ p:profileConfigurations-ref="test.WithPostResponseMode"/>
<!-- Test using a profile that requires a request object, also override the authenticatingAuthority just to check that -->
<bean p:id="test-oidc-application-with-ro" parent="shibboleth.sp.Application"
@@ -67,6 +70,14 @@
</bean>
</util:list>
+ <util:list id="test.WithPostResponseMode">
+ <bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:responseMode="form_post">
+ <property name="clientCredential">
+ <bean parent="shibboleth.oidc.ClientSecretCredential" p:secret="secret"/>
+ </property>
+ </bean>
+ </util:list>
+
<util:list id="test.RequestObjectProfileConfigurations">
<bean parent="OIDC.SSO" p:securityConfiguration-ref="testSecConfig" p:useRequestObject="true" p:signRequestObject="true" p:encryptRequestObject="false">
<property name="clientCredential">
diff --git a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
index dcfa3b4..530e365 100644
--- a/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
+++ b/sp-oidc-impl/src/main/java/net/shibboleth/sp/oidc/profile/decoding/impl/DecodeStateAsJsonObjectConsumer.java
@@ -14,7 +14,6 @@
package net.shibboleth.sp.oidc.profile.decoding.impl;
-import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -126,6 +125,7 @@ public class DecodeStateAsJsonObjectConsumer extends AbstractIdentifiableInitial
}
try {
+
final String stateValue = responseState.getValue();
assert stateValue != null;
Object stateObjectParsed = null;
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list