[java-idp-oidc] 04/04: JOIDC-256 - Facilitate extending the default set of mapped error responses

Henri Mikkonen henri.mikkonen at iki.fi
Mon Sep 8 10:53:16 UTC 2025


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

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

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=2b8089c38dba622b7dcec6e7884f682417fb8ad1

commit 2b8089c38dba622b7dcec6e7884f682417fb8ad1
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Sep 8 13:51:19 2025 +0300

    JOIDC-256 - Facilitate extending the default set of mapped error responses
    
    https://shibboleth.atlassian.net/browse/JOIDC-256
    
    Tests for the autowired error mappings via metadata-lookup extension flow
---
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 23 +++++++++++
 .../op/profile/flow/IntrospectionFlowTest.java     | 11 ++++++
 .../op/profile/flow/PushedAuthorizeFlowTest.java   | 20 ++++++++++
 .../oidc/op/profile/flow/RevocationFlowTest.java   | 11 ++++++
 .../plugin/oidc/op/profile/flow/TokenFlowTest.java | 10 +++++
 .../plugin/oidc/op/profile/flow/UserInfoTest.java  | 10 +++++
 .../lookupext-error/lookupext-error-flow.xml       | 23 +++++++++++
 .../net/shibboleth/idp/module/conf/global.xml      | 44 ++++++++++++++++++++++
 8 files changed, 152 insertions(+)

diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index cb9d4600..72fdc256 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -2413,6 +2413,29 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getIssuer());
     }
 
+    @Test
+    public void testCustomErrorEvent()throws IOException, ParseException, SessionException, JOSEException {
+        final String clientId = "clientIdForLookupExtensionError";
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience(issuer)
+                .issuer(clientId)
+                .claim("redirect_uri", redirectUri)
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        request.setMethod("GET");
+        setRequestParameters(List.of(new Pair<>("client_id", clientId),
+                new Pair<>("response_type", "code"),
+                new Pair<>("scope", "openid profile"),
+                new Pair<>("redirect_uri", redirectUri),
+                new Pair<>("request", requestObject.serialize())));
+
+        initializeThreadLocals();
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertFlowExecutionResult(result, FLOW_ID);
+        Assert.assertEquals("ErrorView", result.getOutcome().getId());
+    }
+
     @Factory
     public Object[] createIdTokenSecurityTests() {
         return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
index 66c9f7fc..26a20286 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/IntrospectionFlowTest.java
@@ -802,6 +802,17 @@ public class IntrospectionFlowTest extends AbstractOidcClientAuthenticationFlowT
         Assert.assertFalse(resp.isActive());
     }
 
+    @Test
+    public void testCustomErrorEvent() throws NoSuchAlgorithmException, URISyntaxException, DataSealerException,
+            ComponentInitializationException {
+        final String clientId = "clientIdForLookupExtensionError";
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Collections.singletonMap("token",
+                buildToken(clientId, "sub", Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "custom_introspection_error");
+    }
+
     protected FlowExecutionResult launchWithJwtAuthentication(final JWT jwt, final JWSAlgorithm algorithm,
             final ClientAuthenticationMethod method, final PublicKey publicKey) throws Exception {
         // use 'iss' claim from JWT as clientId if set, 'sub' otherwise
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
index bdd730d9..94d7d189 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
@@ -898,6 +898,26 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
     }
 
+    @Test
+    public void testCustomErrorEvent() throws IOException, NoSuchAlgorithmException,
+            URISyntaxException, DataSealerException, ComponentInitializationException, JOSEException {
+        final String clientId = "clientIdForLookupExtensionError";
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("response_type", "code")
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        setHttpFormRequest("POST", createRequestParameters(clientId, null, null,
+                requestObject.serialize()));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "custom_par_error");
+    }
+
     @Factory
     public Object[] createRequestObjectSecurityTests() {
         return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
index b128e127..4ad7fc55 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/RevocationFlowTest.java
@@ -414,6 +414,17 @@ public class RevocationFlowTest extends AbstractOidcClientAuthenticationFlowTest
         parseSuccessResponse(result, OAuth2RevocationSuccessResponse.class);
     }
 
+    @Test
+    public void testCustomErrorEvent() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+        DataSealerException, ComponentInitializationException {
+        final String clientId = "clientIdForLookupExtensionError";
+        setBasicAuth(clientId, clientSecret);
+        setHttpFormRequest("POST", Collections.singletonMap("token", super.buildToken(clientId, "sub", 
+                Scope.parse("openid")).toJSONObject().getAsString("access_token")));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "custom_revocation_error");
+   }
+
     protected void assertSuccessResponse(final FlowExecutionResult result, final String clientId) {
         Assert.assertNotNull(parseSuccessResponse(result, OAuth2RevocationSuccessResponse.class));
     }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
index 1f2b0f55..1f970226 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/TokenFlowTest.java
@@ -1985,6 +1985,16 @@ public class TokenFlowTest extends AbstractOidcClientAuthenticationFlowTest {
         Assert.assertTrue(revocationCache.isRevoked(RevocationCacheContexts.AUTHORIZATION_CODE, id));
     }
 
+    @Test
+    public void testCustomErrorEvent() throws Exception {
+        final String clientId = "clientIdForLookupExtensionError";
+        setHttpFormRequest("POST", createRequestParameters(redirectUri, "authorization_code",
+                buildAuthorizationCode(clientId), clientId));
+        setBasicAuth(clientId, clientSecret);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "custom_token_error");
+    }
+
     @Factory
     public Object[] createIdTokenSecurityTests() {
         return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
index ab653fdc..03b8fc1b 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/UserInfoTest.java
@@ -846,6 +846,16 @@ public class UserInfoTest extends AbstractOidcApiFlowTest {
         assertErrorCode(result, BearerTokenError.INVALID_TOKEN.getCode());
     }
 
+    @Test
+    public void testCustomErrorEvent() throws URISyntaxException, NoSuchAlgorithmException, DataSealerException,
+        ComponentInitializationException, IOException {
+        final String clientId = "clientIdForLookupExtensionError";
+        final BearerAccessToken token = buildToken(clientId, subject, new Scope("openid"));
+        request.addHeader("Authorization", getTokenHeaderValue(token));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "custom_userinfo_error");
+    }
+
     @Factory
     public Object[] createUserInfoAsJwtSecurityTests() {
         return new Object[] {
diff --git a/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext-error/lookupext-error-flow.xml b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext-error/lookupext-error-flow.xml
new file mode 100644
index 00000000..0e099a1e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/lookupext-error/lookupext-error-flow.xml
@@ -0,0 +1,23 @@
+<flow xmlns="http://www.springframework.org/schema/webflow" 
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd">
+
+    <decision-state id="CheckIfClientIdMatch">
+        <if test="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLookupExtensionContext)).getClientId() != null and opensamlProfileRequestContext.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.metadata.MetadataLo [...]
+            then="PublishCustomErrorEventId" else="proceed" />
+    </decision-state>
+
+    <action-state id="PublishCustomErrorEventId">
+        <evaluate expression="'lookpext-error.CustomError'" />
+        <transition on="proceed" to="proceed">
+        </transition>
+    </action-state>
+
+    <end-state id="proceed"/>
+    <end-state id="lookpext-error.CustomError"/>
+
+    <global-transitions>
+        <transition on="lookpext-error.CustomError" to="lookpext-error.CustomError" />
+    </global-transitions>
+
+</flow>
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
index 0614c937..0cdc7aeb 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/global.xml
@@ -123,4 +123,48 @@
         </constructor-arg>
     </bean>
 
+    <bean p:id="oidc/metadata-lookup-ext/lookupext-error" parent="shibboleth.oidc.MetadataLookupExtensionFlow" />
+
+    <bean parent="shibboleth.oidc.AuthorizationErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_authorization_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
+    <bean parent="shibboleth.oidc.PushedAuthorizationErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_par_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
+    <bean parent="shibboleth.oidc.TokenErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_token_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
+    <bean parent="shibboleth.oidc.UserInfoErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_userinfo_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
+    <bean parent="shibboleth.oidc.IntrospectionErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_introspection_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
+    <bean parent="shibboleth.oidc.RevocationErrorMapping"
+        p:eventId="lookpext-error.CustomError">
+        <property name="errorObject">
+            <bean class="com.nimbusds.oauth2.sdk.ErrorObject" c:_0="custom_revocation_error" c:_1="Error from the custom flow" c:_2="400" />
+        </property>
+    </bean>
+
 </beans>

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


More information about the commits mailing list