[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