[java-idp-oidc] branch main updated: JOIDC-206 - Provide method to avoid Nimbus message parsing restrictions

Henri Mikkonen henri.mikkonen at iki.fi
Mon May 13 15:00:24 UTC 2024


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=949455a07cac3a321d76199b73a6ebb3fbadcbb3

The following commit(s) were added to refs/heads/main by this push:
     new 949455a0 JOIDC-206 - Provide method to avoid Nimbus message parsing restrictions
949455a0 is described below

commit 949455a07cac3a321d76199b73a6ebb3fbadcbb3
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon May 13 18:00:07 2024 +0300

    JOIDC-206 - Provide method to avoid Nimbus message parsing restrictions
    
    https://shibboleth.atlassian.net/browse/JOIDC-206
    
    Included setter for customRequestParser to the abstract Nimbus request parser: it's null by
    default, but custom parsers may be wired via message-specific beans:
    
    - IntrospectionRequestParser
    - PushedAuthorizationRequestParser
    - RevocationRequestParser
    - AuthenticationRequestParser
    - AuthorizationRequestParser
    - EndSessionRequestParser
    - RegisterRequestParser
    - TokenRequestParser
    - UserInfoRequestParser
---
 .../decoding/impl/BaseOAuth2RequestDecoder.java    | 24 +++++++-
 .../decoding/impl/CustomNimbusRequestParser.java   | 45 +++++++++++++++
 .../oauth2/introspection/introspection-beans.xml   |  3 +-
 .../pushed-authorization-beans.xml                 |  3 +-
 .../flows/oauth2/revocation/revocation-beans.xml   |  3 +-
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  6 +-
 .../flows/oidc/end-session/end-session-beans.xml   |  3 +-
 .../idp/flows/oidc/register/register-beans.xml     |  3 +-
 .../idp/flows/oidc/token/token-beans.xml           |  3 +-
 .../idp/flows/oidc/userinfo/userinfo-beans.xml     |  3 +-
 .../decoding/impl/OIDCTokenRequestDecoderTest.java | 66 ++++++++++++++++++++++
 11 files changed, 152 insertions(+), 10 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/BaseOAuth2RequestDecoder.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/BaseOAuth2RequestDecoder.java
index c9f256f3..24b90e87 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/BaseOAuth2RequestDecoder.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/BaseOAuth2RequestDecoder.java
@@ -49,6 +49,9 @@ public abstract class BaseOAuth2RequestDecoder<T extends Request> extends Abstra
     /** A flag to remove the IP address from the endpoint URI. */
     private boolean removeIpAddressFromEndpointUri;
 
+    /** A custom extension for parsing Nimbus object out of the servlet request. */
+    @Nullable protected CustomNimbusRequestParser<T> customRequestParser;
+
     /** Constructor. */
     public BaseOAuth2RequestDecoder() {
         super();
@@ -67,11 +70,30 @@ public abstract class BaseOAuth2RequestDecoder<T extends Request> extends Abstra
         removeIpAddressFromEndpointUri = flag;
     }
 
+    /**
+     * Set the custom extension for parsing Nimbus object out of the servlet request.
+     * 
+     * @param strategy What to set.
+     */
+    public synchronized void setCustomRequestParser(@Nullable final CustomNimbusRequestParser<T> customParser) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        ifDestroyedThrowDestroyedComponentException();
+
+        customRequestParser = customParser;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doDecode() throws MessageDecodingException {
         final MessageContext messageContext = new MessageContext();
-        final T requestMessage = parseMessage();
+        final HttpServletRequest httpServletRequest = getHttpServletRequest();
+        assert httpServletRequest != null;
+        final T requestMessage;
+        if (customRequestParser != null) {
+            requestMessage = customRequestParser.parse(httpServletRequest);
+        } else {
+            requestMessage = parseMessage();
+        }
         messageContext.setMessage(requestMessage);
         setMessageContext(messageContext);
     }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/CustomNimbusRequestParser.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/CustomNimbusRequestParser.java
new file mode 100644
index 00000000..6ef0e4f1
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/decoding/impl/CustomNimbusRequestParser.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.decoder.MessageDecodingException;
+
+import com.nimbusds.oauth2.sdk.Request;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+/**
+ * An interface for custom parsers for transforming {@link HttpServletRequest} into a Nimbus request object.
+ * Implementing classes may be wired to decoders that extends {@link BaseOAuth2RequestDecoder} with the corresponding
+ * Nimbus request object type.
+ *
+ * @param <T> The Nimbus request object type.
+ * 
+ * @since 4.2.0
+ */
+public interface CustomNimbusRequestParser<T extends Request> {
+
+    /**
+     * Parse the Nimbus request object from the given servlet request.
+     * 
+     * @param httpServletRequest the servlet request used as an input
+     * @return the Nimbus request object
+     * @throws MessageDecodingException if the Nimbus object cannot be parsed
+     */
+    public T parse(@Nonnull final HttpServletRequest httpServletRequest) throws MessageDecodingException;
+
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
index ddd835d9..c0628664 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/introspection/introspection-beans.xml
@@ -19,7 +19,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.OAuth2IntrospectionRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('IntrospectionRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
index 37311a58..820d4032 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
@@ -19,7 +19,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.OAuth2PushedAuthorizationRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('PushedAuthorizationRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
index 28236519..61409af9 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/revocation/revocation-beans.xml
@@ -19,7 +19,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.OAuth2RevocationRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('RevocationRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index a6a14945..8cb6f71c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -21,7 +21,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCAuthenticationRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('AuthenticationRequestParser')}"/>
         </constructor-arg>
     </bean>
 
@@ -29,7 +30,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.OAuth2AuthorizationRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('AuthorizationRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-beans.xml
index 08c7d336..51f0e6d6 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/end-session/end-session-beans.xml
@@ -21,7 +21,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCLogoutRequestDecoder"
                 scope="prototype" p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('EndSessionRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
index a7969f1c..8108d70e 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/register/register-beans.xml
@@ -31,7 +31,8 @@
                 scope="prototype"
                 p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
                 p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
-                p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"/>
+                p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
+                p:customRequestParser="#{getObject('RegisterRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
index 1c48dfd2..157d0308 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/token/token-beans.xml
@@ -22,7 +22,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCTokenRequestDecoder" scope="prototype"
                 p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('TokenRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
index 4f54c57d..940b6b7c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/userinfo/userinfo-beans.xml
@@ -22,7 +22,8 @@
         <constructor-arg>
             <bean class="net.shibboleth.idp.plugin.oidc.op.decoding.impl.OIDCUserInfoRequestDecoder" scope="prototype"
                 p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"
-                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"/>
+                p:removeIpAddressFromEndpointUri="%{idp.oidc.logging.removeIpAddressFromProtocolMessage:false}"
+                p:customRequestParser="#{getObject('UserInfoRequestParser')}"/>
         </constructor-arg>
     </bean>
 
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/decoding/impl/OIDCTokenRequestDecoderTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/decoding/impl/OIDCTokenRequestDecoderTest.java
index 4044d245..3bc9f06c 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/decoding/impl/OIDCTokenRequestDecoderTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/decoding/impl/OIDCTokenRequestDecoderTest.java
@@ -16,6 +16,9 @@ package net.shibboleth.idp.plugin.oidc.op.decoding.impl;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
 
 import org.opensaml.messaging.context.MessageContext;
 import org.opensaml.messaging.decoder.MessageDecodingException;
@@ -24,11 +27,16 @@ import org.testng.Assert;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
+import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.TokenRequest;
+import com.nimbusds.oauth2.sdk.http.HTTPRequest;
+import com.nimbusds.oauth2.sdk.http.JakartaServletUtils;
 import com.nimbusds.oauth2.sdk.http.HTTPRequest.Method;
 
 import jakarta.servlet.http.HttpServletRequest;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.CustomNimbusRequestParser;
 import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestAudienceLookupFunction;
+import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.primitive.NonnullSupplier;
 
 /**
@@ -109,4 +117,62 @@ public class OIDCTokenRequestDecoderTest {
                 List.of("resource.example.org"));
     }
 
+    @Test
+    public void testRequestDecodingWithCustomAssertion()
+            throws MessageDecodingException, IOException, ComponentInitializationException {
+        final CustomNimbusRequestParser<TokenRequest> customParser = new CustomNimbusRequestParser<TokenRequest>() {
+
+            @Override
+            public TokenRequest parse(@Nonnull final HttpServletRequest httpServletRequest)
+                    throws MessageDecodingException {
+                try {
+                    final HTTPRequest httpReq = JakartaServletUtils.createHTTPRequest(httpServletRequest);
+                    assert httpReq != null;
+                    switchIntoCustomClientAssertion(httpReq);
+                    return TokenRequest.parse(httpReq);
+                } catch (final ParseException | IOException e) {
+                    throw new MessageDecodingException(e);
+                }
+            }
+        };
+        httpRequest = new MockHttpServletRequest();
+        httpRequest.setMethod(Method.POST.toString());
+        decoder = new OIDCTokenRequestDecoder();
+        decoder.setHttpServletRequestSupplier(new NonnullSupplier<> () {
+            public HttpServletRequest get() { assert httpRequest != null; return httpRequest;}
+            });
+        decoder.setCustomRequestParser(customParser);
+        decoder.initialize();
+        final String customAssertion = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0." +
+                "eyJpc3Mi.cC4hiUPo~eyJzI1NiIsImtphbGciOimtpZCI6IjIyIn0.IjIyIn0.iOiJSUzI1";
+        httpRequest.setContentType("application/x-www-form-urlencoded");
+        httpRequest.addParameter("grant_type", "authorization_code");
+        httpRequest.addParameter("code", "SplxlOBeZQQYbYS6WxSbIA");
+        httpRequest.addParameter("redirect_uri", "https://client.example.org/cb");
+        httpRequest.addParameter("client_id", "mockClientId");
+        httpRequest.addParameter("client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:mock-custom");
+        httpRequest.addParameter("client_assertion", customAssertion);
+        decoder.decode();
+        final MessageContext messageContext = decoder.getMessageContext();
+        assert messageContext != null;
+        final TokenRequest message = (TokenRequest) messageContext.getMessage();
+        assert message != null;
+        Assert.assertEquals(message.getCustomParameter("custom_client_assertion"), List.of(customAssertion));
+    }
+
+    protected void switchIntoCustomClientAssertion(@Nonnull final HTTPRequest httpRequest) {
+        final Map<String,List<String>> params = httpRequest.getQueryParameters();
+        if (params != null && !params.isEmpty() && params.get("client_assertion_type") != null) {
+            final List<String> assertionTypes = params.get("client_assertion_type");
+            String query = httpRequest.getQuery();
+            for (final String assertionType : assertionTypes) {
+                if (assertionType.equals("urn:ietf:params:oauth:client-assertion-type:mock-custom")) {
+                    query = query.replace("client_assertion=", "custom_client_assertion=");
+
+                }
+            httpRequest.setQuery(query);
+            }
+        }
+    }
+
 }
\ No newline at end of file

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


More information about the commits mailing list