[java-idp-plugin-oidc-rp] branch main updated: Add HTTP POST support to the UserInfo request decoder

Phil Smart philip.smart at jisc.ac.uk
Tue Mar 14 14:42:10 UTC 2023


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

philsmart pushed a commit to branch main
in repository java-idp-plugin-oidc-rp.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-plugin-oidc-rp.git;a=commit;h=66cb0ed9214052c8d8d1511df4a11c9cc3bfc599

The following commit(s) were added to refs/heads/main by this push:
     new 66cb0ed  Add HTTP POST support to the UserInfo request decoder
66cb0ed is described below

commit 66cb0ed9214052c8d8d1511df4a11c9cc3bfc599
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Tue Mar 14 14:42:07 2023 +0000

    Add HTTP POST support to the UserInfo request decoder
    
     - Set via a strategy which defaults to GET
     - There is no proper implementation of the strategy at present
---
 .../impl/DefaultAccessTokenResponseDecoder.java    |   2 +-
 .../impl/AbstractRequestEncoderFunction.java       |   6 +-
 .../impl/DefaultAuthCodeTokenRequestEncoder.java   | 142 ---------------------
 .../impl/DefaultUserInfoRequestEncoder.java        | 101 ++++++++++++---
 .../impl/NimbusAuthCodeTokenRequestEncoder.java    |   2 +-
 5 files changed, 85 insertions(+), 168 deletions(-)

diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java
index f744175..3bdf42c 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/decoding/impl/DefaultAccessTokenResponseDecoder.java
@@ -40,7 +40,7 @@ import net.minidev.json.JSONObject;
 
 /** 
  * Default access token response decoder, which converts a successful HTTP response into an 
- * {@link OIDCTokenResponse}.
+ * {@link OIDCTokenResponse}. Any decoding error is logged and {@code null} is returned.
  */
 public class DefaultAccessTokenResponseDecoder extends AbstractJSONResponseDecoderFunction<TokenResponse> {
     
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
index 77e9e76..809b3e7 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/AbstractRequestEncoderFunction.java
@@ -40,7 +40,6 @@ import net.shibboleth.utilities.java.support.component.AbstractInitializableComp
 import net.shibboleth.utilities.java.support.component.ComponentSupport;
 import net.shibboleth.utilities.java.support.logic.Constraint;
 
-//TODO is the same as the AbstractOIDCAuthenticationAction.
 /** Abstract request encoder function that pulls out various contexts and request/response messages.*/
 public abstract class AbstractRequestEncoderFunction extends AbstractInitializableComponent 
                                 implements Function<ProfileRequestContext, HttpUriRequest> {
@@ -84,8 +83,7 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
     }
     
     /**
-     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext} 
-     * from the {@link ProfileRequestContext}.
+     * Set the strategy to lookup the {@link OAuth2ClientAuthenticationContext}.
      * 
      * @param strgy the strategy.
      */
@@ -147,8 +145,6 @@ public abstract class AbstractRequestEncoderFunction extends AbstractInitializab
         return providerMetadataContext;
     }
 
-    //TODO these come from the outbound message context, so are not general to other actions
-
     @Override
     @Nullable public HttpUriRequest apply(@Nonnull final ProfileRequestContext profileRequestContext) {
 
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
deleted file mode 100644
index 526956e..0000000
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultAuthCodeTokenRequestEncoder.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Licensed to the University Corporation for Advanced Internet Development,
- * Inc. (UCAID) under one or more contributor license agreements.  See the
- * NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The UCAID licenses this file to You 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.authn.oidc.rp.encoding.impl;
-
-import java.net.URI;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.methods.RequestBuilder;
-import org.apache.http.client.utils.URIBuilder;
-import org.apache.http.entity.ContentType;
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.nimbusds.jose.util.Base64;
-import com.nimbusds.jose.util.StandardCharset;
-
-import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
-
-/** A token request encoder that builds an OAuth2.0 Access Token Request for an authorization code grant request. */
-//TODO either only applies to authorization_code grant, or can handle more. If specific, must gurantee that.
-//TODO just remove in favour of the Nimbus version?
- at Deprecated
-public class DefaultAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFunction {
-    
-    /** The HTTPS scheme.*/
-    @Nonnull @NotEmpty private static final String HTTPS = "https";
-    
-    /** Class logger. */
-    @Nonnull
-    private final Logger log = LoggerFactory.getLogger(DefaultAuthCodeTokenRequestEncoder.class);
-    
-    @Override
-    @Nullable public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
-
-        try {
-            if (getClientAuthenticationContext() == null || getClientAuthenticationContext().getClientAuthentication()
-                    == null) {
-                log.warn("No client authentication context to base token request off");
-                return null;
-            }
-            // Mandate HTTPS, so construct the URL from that.
-            final URI uri = new URIBuilder().setScheme(HTTPS)
-                    .setPort(getProviderMetadataContext().getProviderInformation().getTokenEndpointURI().getPort())
-                    .setHost(getProviderMetadataContext()
-                            .getProviderInformation()
-                            .getTokenEndpointURI().getHost())
-                    .setPath(getProviderMetadataContext()
-                            .getProviderInformation()
-                            .getTokenEndpointURI().getPath())
-                    .build();                 
-            
-            // Add headers and create request.
-            final RequestBuilder rb = RequestBuilder.post().setUri(uri)
-                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
-                    .setCharset(StandardCharset.UTF_8);
-            
-            // Add mandatory fields 
-            addRequiredFields(rb);
-            // Add fields that are required if certain conditions are met. 
-            addRequiredOnConditionFields(rb);            
-            // Build client_secret_basic authentication. TODO support others.
-            buildSecretBasicAuthentication(rb);            
-            
-            final HttpUriRequest request = rb.build();                  
-            log.debug("Token URL '{}'",request);          
-            return request;
-            
-        } catch (final Exception e) {
-            log.warn("Unable to encode token request", e);
-        }
-        return null;
-    }
-    
-    /**
-     * Add all required fields for the given grant_type to the request.
-     * 
-     * @param rb the request builder. 
-     */
-    private void addRequiredFields(@Nonnull final RequestBuilder rb) {
-        
-        rb.addParameter("grant_type", "authorization_code")
-        .addParameter("code",getAuthenticationResponse().getAuthorizationCode().getValue());
-    }
-    
-    
-    /**
-     * Add all fields to the request that are required if a given condition is met.
-     * 
-     * @param rb the request builder. 
-     */
-    private void addRequiredOnConditionFields(@Nonnull final RequestBuilder rb) {
-        
-        // Check redirect_uri was sent in authz request and add if so
-        if (getAuthenticationRequest().getRedirectURI() != null) {
-            rb.addParameter("redirect_uri", getAuthenticationRequest().getRedirectURI().toString());
-        }
-    }
-
-    /** 
-     * Build the client_secret_basic authentication using pre-emptive HTTP Basic auth.
-     * 
-     * @param rb the request builder to use.
-     */
-    //TODO taken from Nimbus ClientSecretBasic. Find best way to apply this. Use the authn method here?
-    private void buildSecretBasicAuthentication(final RequestBuilder rb) {
-        final StringBuilder sb = new StringBuilder();
-
-        sb.append(URLEncoder.encode(
-                getClientAuthenticationContext().getClientAuthentication()
-                .getClientID().getValue(), StandardCharsets.UTF_8));
-        sb.append(':');
-        sb.append(URLEncoder.encode(
-                getClientAuthenticationContext().
-                getClientAuthentication().getMethod().getValue(), StandardCharsets.UTF_8));
-
-        final String authHeader =  "Basic " + Base64.encode(sb.toString().getBytes(StandardCharsets.UTF_8));
-        rb.addHeader("Authorization", authHeader);
-        
-    }
-
-}
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
index af8889f..3287e2d 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/DefaultUserInfoRequestEncoder.java
@@ -31,6 +31,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.springframework.http.HttpMethod;
 
 import com.nimbusds.jose.util.StandardCharset;
 import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
@@ -38,12 +39,12 @@ import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
 import net.shibboleth.idp.plugin.authn.oidc.rp.OIDCRPException;
 import net.shibboleth.idp.plugin.authn.oidc.rp.context.AccessTokenResponseContext;
 import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
 
 /**
- * Default encoder for UserInfo requests.
+ * Default encoder for UserInfo requests. Supports either GET or POST requests.
  */
-//TODO This could be GET or POST - how to signal that? profile config, client metadata etc.
-//TODO move to commons?
 public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunction {
     
     /** The HTTPS scheme.*/
@@ -54,30 +55,62 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
     private final Logger log = LoggerFactory.getLogger(DefaultUserInfoRequestEncoder.class);
     
     /** Strategy used to look up the {@link AccessTokenResponseContext} to set the parameters for. */
-    @Nonnull private final Function<ProfileRequestContext, AccessTokenResponseContext> 
+    @Nonnull private Function<ProfileRequestContext, AccessTokenResponseContext> 
             tokenResponseContextLookupStrategy;
-
+   
+    /** Strategy used to look up the {@link HttpMethod} used for this request. Defaults to GET.*/
+    @Nonnull private Function<ProfileRequestContext, HttpMethod> httpMethodLookupStrategy;
     
     /** Constructor.*/
     public DefaultUserInfoRequestEncoder() {
         tokenResponseContextLookupStrategy =
                 new ChildContextLookup<>(AccessTokenResponseContext.class, true).compose(
                         new InboundMessageContextLookup());
+        
+        httpMethodLookupStrategy = prc -> HttpMethod.GET;
+    }
+    
+    /**
+     * Set the strategy used to lookup the {@link AccessTokenResponseContext}.
+     * 
+     * @param strategy the strategy
+     */
+    public void setTokenResponseContextLookupStrategy(
+            final Function<ProfileRequestContext, AccessTokenResponseContext> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        tokenResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "tokenResponseContextLookupStrategy can not be null");
+    }
+    
+    /**
+     * Set the strategy used to lookup the HTTP method to use in this request.
+     * 
+     * @param strategy the strategy
+     */
+    public void setHttpMethodLookupStrategy(final Function<ProfileRequestContext, HttpMethod> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        ComponentSupport.ifDestroyedThrowDestroyedComponentException(this);
+
+        httpMethodLookupStrategy = Constraint.isNotNull(strategy,
+                "httpMethodLookupStrategy can not be null");
     }
+   
     
     @Override
     public HttpUriRequest doApply(@Nonnull final ProfileRequestContext profileRequestContext) {
         
         try {
-
+            final HttpMethod requestMethod = httpMethodLookupStrategy.apply(profileRequestContext);
+            
             final AccessTokenResponseContext responseCtx = 
                     tokenResponseContextLookupStrategy.apply(profileRequestContext);
             if (responseCtx == null) {
                 log.debug("No TokenResponseContext returned by lookup strategy");
                 return null;
-            }
+            }            
             
-            // Mandate HTTPS GET, so construct the URL from that.
             final URI uri = new URIBuilder().setScheme(HTTPS)
                     .setPort(getProviderMetadataContext().getProviderInformation().getUserInfoEndpointURI().getPort())
                     .setHost(getProviderMetadataContext()
@@ -88,16 +121,25 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
                             .getUserInfoEndpointURI().getPath())
                     .build();                 
             
-            // Add headers and create request.
-            final RequestBuilder rb = RequestBuilder.get().setUri(uri)
-                    .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
-                    .setCharset(StandardCharset.UTF_8);
-             
-            // Add bearer token as authorization header.
-            addBearerToken(rb, responseCtx);            
-            
-            //If POST request add access_token= to query URL (unitutively)
-            
+            // Add headers and create request. 
+            RequestBuilder rb = null;
+            if (requestMethod == HttpMethod.GET) {
+                rb = RequestBuilder.get().setUri(uri)
+                        .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                        .setCharset(StandardCharset.UTF_8);
+                 
+                addBearerTokenToGet(rb, responseCtx);      
+            } else if (requestMethod == HttpMethod.POST) {
+
+                rb = RequestBuilder.post().setUri(uri)
+                        .setHeader("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType())
+                        .setCharset(StandardCharset.UTF_8);
+
+                addBearerTokenToPost(rb, responseCtx);   
+            } else {
+                log.error("Unable to construct UserInfo request, unknown request method: "+requestMethod);
+            }
+
             final HttpUriRequest request = rb.build();                  
             log.debug("UserInfo request URL '{}'",request);            
             return request;
@@ -108,6 +150,27 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
         return null;
     }
     
+    /**
+     * Add the bearer token to the 'access_token' parameter, used when issuing HTTP POST requests.
+     * 
+     * @param rb the request builder to use.
+     * @param responseCtx the response context to find the access_token from.
+     * 
+     * @throws OIDCRPException if there is an issue adding the bearer token to the 'access_token' parameter.
+     */
+    private void addBearerTokenToPost(@Nonnull final RequestBuilder rb, 
+        @Nonnull final AccessTokenResponseContext responseCtx) throws OIDCRPException {
+    
+        if (responseCtx.getTokenResponse() == null) {
+            throw new OIDCRPException("No access token response found");
+        }
+        final BearerAccessToken bearer = responseCtx.getTokenResponse().getTokens().getBearerAccessToken();
+        if (bearer == null) {
+            throw new OIDCRPException("Access token was not Bearer type");
+        }
+        rb.addParameter("access_token", bearer.getValue());
+    }
+    
     /** 
      * Add the bearer token to the Authorization header, used when issuing HTTP GET requests.
      * 
@@ -116,7 +179,7 @@ public class DefaultUserInfoRequestEncoder extends AbstractRequestEncoderFunctio
      * 
      * @throws OIDCRPException if there is an issue adding the bearer token to the Authorization header.
      */
-    private void addBearerToken(@Nonnull final RequestBuilder rb, 
+    private void addBearerTokenToGet(@Nonnull final RequestBuilder rb, 
             @Nonnull final AccessTokenResponseContext responseCtx) throws OIDCRPException {
         
         if (responseCtx.getTokenResponse() == null) {
diff --git a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
index c2d8f94..5daa5a4 100644
--- a/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
+++ b/idp-oidc-rp-impl/src/main/java/net/shibboleth/idp/plugin/authn/oidc/rp/encoding/impl/NimbusAuthCodeTokenRequestEncoder.java
@@ -77,7 +77,7 @@ public class NimbusAuthCodeTokenRequestEncoder extends AbstractRequestEncoderFun
     @Nullable private HttpUriRequest convertHttpRequest(@Nonnull final HTTPRequest request) {
         
         if (request.getMethod() != HTTPRequest.Method.POST) {
-            // Should never happen as Nimbus should always use POST
+            // Should never happen as HTTPRequest should always use POST
             log.warn("Token Request must use the HTTP POST method, is trying to use '{}'", request.getMethod());
             return null;
         }

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


More information about the commits mailing list