[java-oidc-common] branch main updated: JCOMOIDC-187 - Add Custom Request Encoder Param Hook

Codeberg noreply at shibboleth.net
Wed Sep 30 14:28:58 UTC 2026


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

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

View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/d9089e5aafea331b7ed5281387e6dcd15cdf4f68

The following commit(s) were added to refs/heads/main by this push:
     new d9089e5a JCOMOIDC-187 - Add Custom Request Encoder Param Hook
d9089e5a is described below

commit d9089e5aafea331b7ed5281387e6dcd15cdf4f68
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Wed Sep 30 15:03:41 2026 +0100

    JCOMOIDC-187 - Add Custom Request Encoder Param Hook
    
     - Added a hook to allow 'new' parameters to be added to the query
    string, not the request object.
    
    https://shibboleth.atlassian.net/browse/JCOMOIDC-187
---
 .../encoding/impl/AbstractOIDCMessageEncoder.java  | 68 +++++++++++++++++++++-
 .../impl/HTTPRedirectRequestEncoderTest.java       | 64 ++++++++++++++++++++
 2 files changed, 131 insertions(+), 1 deletion(-)

diff --git a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractOIDCMessageEncoder.java b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractOIDCMessageEncoder.java
index fc46b040..6afe4959 100644
--- a/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractOIDCMessageEncoder.java
+++ b/oidc-common-profile-impl/src/main/java/net/shibboleth/oidc/profile/encoding/impl/AbstractOIDCMessageEncoder.java
@@ -19,6 +19,7 @@ import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
@@ -47,6 +48,8 @@ import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
 import net.shibboleth.oidc.profile.encoding.AuthenticationContextClassReferenceSupport;
 import net.shibboleth.oidc.profile.encoding.OIDCMessageEncoder;
 import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
 import net.shibboleth.shared.logic.PredicateSupport;
 import net.shibboleth.shared.net.URLBuilder;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -66,10 +69,17 @@ public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResp
     /** A hook to allow additional checking of the authorization parameters after they are built.*/
     @Nonnull private Predicate<List<Pair<String, String>>> authorizationParamsAreValidPredicate;
     
+    /** 
+     * A hook to allow additional parameters to be added to the authorisation request, but not the request object 
+     * (if one exists).
+     */
+    @Nonnull private Function<OIDCAuthenticationRequest, List<Pair<String, String>>> customParamsLookupStrategy;
+    
     /** Constructor. */
     protected AbstractOIDCMessageEncoder() {
         authorizationParamsAreValidPredicate = PredicateSupport.alwaysTrue();
         setProtocolMessageLoggerSubCategory("OAUTH2");
+        customParamsLookupStrategy = FunctionSupport.constant(null);
     }
     
     /**
@@ -89,6 +99,25 @@ public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResp
         }
     }
     
+    /**
+     * Set a hook that generates a list of parameters to add to the authorization request.
+     * 
+     * <p>Will not add parameters to the Request Object if used.</p>
+     * 
+     * <p>Will not override system set parameters, e.g., client_id can not be duplicated and appended to the request.
+     * </p>
+     * 
+     * @param strategy the strategy to set.
+     * 
+     * @since 3.4.0
+     */
+    public void setCustomParamsLookupStrategy(
+			@Nonnull final Function<OIDCAuthenticationRequest, List<Pair<String, String>>> strategy) {
+    	checkSetterPreconditions();
+ 
+		customParamsLookupStrategy = Constraint.isNotNull(strategy, "Custom parameters lookup strategy cannot be null");
+	}
+    
     /**
      * Serialize OAuth 2.0 authorization parameters from the authentication request to the query string 
      * of the URL.
@@ -272,7 +301,10 @@ public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResp
             } catch (final IllegalStateException e) {
                 throw new MessageEncodingException("Couldn't serialize request object to JWT: " + e.getMessage(), e);
             }
-        }      
+        }   
+        
+        // Add custom params
+        addCustomParameters(req, params);
 
         if (!validateParams(params)) {            
             throw new MessageEncodingException("Authorization parameters are not valid");
@@ -352,6 +384,9 @@ public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResp
         }
         //TODO: requestURI, includedGrantedScopes?, resource_uris?
         
+        // Add custom params
+        addCustomParameters(req, params);
+        
         if (!validateParams(params)) {            
             throw new MessageEncodingException("Authorization parameters are not valid");
         }
@@ -359,6 +394,37 @@ public abstract class AbstractOIDCMessageEncoder extends AbstractHttpServletResp
     }
     
 // Checkstyle: CyclomaticComplexity ON
+    
+    /**
+     * Adds any custom request parameters to the list of parameters providing the new parameter does not conflict
+     * with an existing, system set, parameter.
+     * 
+     * @param request the request that contains the system constructed parameters
+     * @param params the current list of parameters to add the custom parameters
+     */
+    private void addCustomParameters(@Nonnull final OIDCAuthenticationRequest request, 
+    		@Nonnull final List<Pair<String, String>> params) {
+    	final List<Pair<String,String>> customParams = customParamsLookupStrategy.apply(request);
+    	
+    	// Snapshot the existing params to check for duplicates so that duplicates can be set from the lookup strategy
+    	// but are not allowed in the original params set.
+    	final List<String> existingParams = params.stream()
+    			.map(Pair::getFirst)
+    			.toList();
+    	
+    	if (customParams != null) {
+    		for (final Pair<String, String> customParam : customParams) {
+    			if (customParam != null && customParam.getFirst() != null) {
+	    			if (existingParams.contains(customParam.getFirst())) {
+	    				log.warn("Ignoring custom authorization parameter '{}' that duplicates a system set parameter ",
+	    						customParam.getFirst());
+					} else {
+						params.add(customParam);
+					}
+    			}
+    		}
+    	}
+    }
 
     /**
      * Ensure the authorization parameters are valid.
diff --git a/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoding/impl/HTTPRedirectRequestEncoderTest.java b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoding/impl/HTTPRedirectRequestEncoderTest.java
index 604a8f3b..1e389da4 100644
--- a/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoding/impl/HTTPRedirectRequestEncoderTest.java
+++ b/oidc-common-profile-impl/src/test/java/net/shibboleth/oidc/profile/encoding/impl/HTTPRedirectRequestEncoderTest.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.oidc.profile.encoding.impl;
 
+import static org.testng.Assert.assertFalse;
 import static org.testng.Assert.assertTrue;
 
 import java.net.URI;
@@ -50,6 +51,7 @@ import com.nimbusds.openid.connect.sdk.assurance.claims.VerifiedClaimsSetRequest
 import net.shibboleth.oidc.profile.core.OIDCAuthenticationRequest;
 import net.shibboleth.oidc.profile.core.OIDCLogoutRequest;
 import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.component.UninitializedComponentException;
 import net.shibboleth.shared.servlet.impl.HttpServletRequestResponseContext;
 import net.shibboleth.shared.servlet.impl.ThreadLocalHttpServletResponseSupplier;
@@ -148,6 +150,68 @@ public class HTTPRedirectRequestEncoderTest {
        
     }
     
+    @Test
+    public void testSuccesfullEncoding_WithCustomParams() throws Exception {
+        context.setMessage(request);
+        encoder.setCustomParamsLookupStrategy(req -> 
+        	CollectionSupport.listOf(new Pair<String,String>("custom", "custom-value")));
+        encoder.initialize();
+        encoder.encode();
+        final String response = mockResponse.getRedirectedUrl();
+        assert response != null;
+        // These are all required
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("response_type"));
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("scope"));
+        assertTrue(response.contains("custom"));
+        assertTrue(response.contains("custom-value"));
+       
+    }
+    
+    @Test
+    public void testSuccesfullEncoding_WithDuplicateCustomParams() throws Exception {
+        context.setMessage(request);
+        // Should be ignored, can not override a system set parameter
+        encoder.setCustomParamsLookupStrategy(req -> 
+        	CollectionSupport.listOf(new Pair<String,String>("client_id", "custom-client_id")));
+        encoder.initialize();
+        encoder.encode();
+        final String response = mockResponse.getRedirectedUrl();
+        assert response != null;
+        // These are all required
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("response_type"));
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("scope"));
+        assertFalse(response.contains("custom-client_id"));
+       
+    }
+    
+    @Test
+    public void testSuccesfullEncoding_WithCustomParams_WithDuplicateCustomParams() throws Exception {
+        context.setMessage(request);
+        // client_id should be ignored, can not override a system set parameter
+        // allowed to set more than one param with the same value
+        encoder.setCustomParamsLookupStrategy(req -> 
+        	CollectionSupport.listOf(new Pair<String,String>("client_id", "custom-client_id"),
+        			new Pair<String,String>("resource", "resource-a"),
+        			new Pair<String,String>("resource", "resource-b")));
+        encoder.initialize();
+        encoder.encode();
+        final String response = mockResponse.getRedirectedUrl();
+        assert response != null;
+        // These are all required
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("response_type"));
+        assertTrue(response.contains("client_id"));
+        assertTrue(response.contains("scope"));
+        assertTrue(response.contains("resource-a"));
+        assertTrue(response.contains("resource-b"));
+        assertFalse(response.contains("custom-client_id"));
+       
+    }
+    
     @Test
     public void testSuccesfullLogoutRequestEncoding_WithPostRedirect() throws Exception {
         logoutRequest.setPostLogoutRedirectURI(new URI("https://localhost:8080/end-session-callback"));

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


More information about the commits mailing list