This is an automated email from the ASF dual-hosted git repository.
coheigea pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git
The following commit(s) were added to refs/heads/main by this push:
new faf40ba0c98 CXF-9225 - Enforce nonce for the hybrid case (#3328)
faf40ba0c98 is described below
commit faf40ba0c98c1621522cf8d9c1fff64a8cdb126d
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Jul 23 09:28:56 2026 +0100
CXF-9225 - Enforce nonce for the hybrid case (#3328)
---
.../oidc/rp/OidcClientCodeRequestFilter.java | 84 ++++-
.../rp/OidcClientCodeRequestFilterNonceTest.java | 402 +++++++++++++++++++++
2 files changed, 476 insertions(+), 10 deletions(-)
diff --git
a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java
b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java
index 0299217564c..1d9fa450ef5 100644
---
a/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java
+++
b/rt/rs/security/sso/oidc/src/main/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilter.java
@@ -26,6 +26,7 @@ import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriBuilder;
import jakarta.ws.rs.core.UriInfo;
+import org.apache.cxf.common.util.Base64UrlUtility;
import org.apache.cxf.jaxrs.json.basic.JsonMapObjectReaderWriter;
import org.apache.cxf.jaxrs.utils.ExceptionUtils;
import org.apache.cxf.rs.security.oauth2.client.ClientCodeRequestFilter;
@@ -35,6 +36,8 @@ import
org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
import org.apache.cxf.rs.security.oidc.common.ClaimsRequest;
import org.apache.cxf.rs.security.oidc.common.IdToken;
+import org.apache.cxf.rs.security.oidc.utils.OidcUtils;
+import org.apache.cxf.rt.security.crypto.CryptoUtils;
public class OidcClientCodeRequestFilter extends ClientCodeRequestFilter {
@@ -51,6 +54,14 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
private String claims;
private String claimsLocales;
private String roleClaim;
+ /**
+ * The OAuth 2.0 / OIDC response_type to request from the Authorization
Endpoint.
+ * Defaults to {@code code} (Authorization Code Flow).
+ * Set to an Implicit or Hybrid value (e.g. {@code id_token}, {@code code
id_token})
+ * to enable those flows; a nonce will be auto-generated and enforced for
any
+ * response type that contains {@code id_token}, per OIDC Core §3.2.2.1
and §3.3.2.1.
+ */
+ private String responseType;
public OidcClientCodeRequestFilter() {
super();
@@ -61,6 +72,27 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
this.authenticationContextRef = Arrays.asList(acr.split(" "));
}
+ /**
+ * Set the {@code response_type} sent to the Authorization Endpoint.
+ * When set to a value that contains {@code id_token} (Implicit or Hybrid
flows),
+ * the filter will automatically generate a nonce for each authorization
request
+ * and enforce its presence in the returned ID Token.
+ * @param responseType e.g. {@code id_token}, {@code code id_token},
{@code id_token token}
+ */
+ public void setResponseType(String responseType) {
+ this.responseType = responseType;
+ }
+
+ /**
+ * Returns {@code true} when the configured response type causes an ID
Token to be
+ * delivered directly from the Authorization Endpoint (Implicit or Hybrid
flows),
+ * meaning a nonce is REQUIRED per OIDC Core §3.2.2.1 and §3.3.2.1.
+ */
+ private boolean isNonceRequired() {
+ return responseType != null
+ && responseType.contains(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+ }
+
@Override
protected ClientTokenContext createTokenContext(ContainerRequestContext rc,
ClientAccessToken at,
@@ -77,8 +109,13 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
IdToken idToken = idTokenReader.getIdToken(at,
requestParams.getFirst(OAuthConstants.AUTHORIZATION_CODE_VALUE),
getConsumer());
+ // Hybrid flow can be detected from context: when the
Authorization Endpoint
+ // returns an id_token directly in the callback parameters
(response_type
+ // contains "id_token" together with "code"), a nonce is REQUIRED
even if
+ // the caller has not explicitly configured a responseType on this
filter.
+ boolean hybridFlowDetected =
requestParams.containsKey(OidcUtils.ID_TOKEN);
// Validate the properties set up at the redirection time.
- validateIdToken(idToken, state);
+ validateIdToken(idToken, state, hybridFlowDetected);
ctx.setIdToken(idToken);
if (userInfoClient != null) {
@@ -100,12 +137,29 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
if (maxAgeOffset != null) {
state.putSingle(MAX_AGE_PARAMETER,
Long.toString(System.currentTimeMillis() + maxAgeOffset));
}
+ // Per OIDC Core §3.2.2.1 and §3.3.2.1, a nonce is REQUIRED for
Implicit and Hybrid flows
+ // (any response_type containing "id_token"). Auto-generate one if the
caller has not
+ // already supplied it, so replay protection is always active for
these flows.
+ if (isNonceRequired() && state.getFirst(IdToken.NONCE_CLAIM) == null) {
+ state.putSingle(IdToken.NONCE_CLAIM,
+
Base64UrlUtility.encode(CryptoUtils.generateSecureRandomBytes(16)));
+ }
return state;
}
- private void validateIdToken(IdToken idToken, MultivaluedMap<String,
String> state) {
+ private void validateIdToken(IdToken idToken, MultivaluedMap<String,
String> state,
+ boolean hybridFlowDetected) {
- String nonce = state.getFirst(IdToken.NONCE_CLAIM);
+ String nonce = state != null ? state.getFirst(IdToken.NONCE_CLAIM) :
null;
+ // A nonce is REQUIRED (OIDC Core §3.2.2.1 / §3.3.2.1) when:
+ // (a) the configured responseType contains "id_token" (Implicit or
Hybrid), OR
+ // (b) an id_token was observed in the authorization callback
parameters, which
+ // indicates a Hybrid flow even without explicit responseType
configuration.
+ // In either case, reject the response if no nonce was round-tripped —
this means
+ // the authorization request was sent without one, removing replay
protection.
+ if ((isNonceRequired() || hybridFlowDetected) && nonce == null) {
+ throw new OAuthServiceException(OAuthConstants.INVALID_REQUEST);
+ }
String tokenNonce = idToken.getNonce();
if (nonce != null && (tokenNonce == null ||
!nonce.equals(tokenNonce))) {
throw new OAuthServiceException(OAuthConstants.INVALID_REQUEST);
@@ -145,13 +199,18 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
protected void setAdditionalCodeRequestParams(UriBuilder ub,
MultivaluedMap<String,
String> redirectState,
MultivaluedMap<String,
String> codeRequestState) {
- if (redirectState != null) {
- if (redirectState.getFirst(IdToken.NONCE_CLAIM) != null) {
- ub.queryParam(IdToken.NONCE_CLAIM,
redirectState.getFirst(IdToken.NONCE_CLAIM));
- }
- if (redirectState.getFirst(MAX_AGE_PARAMETER) != null) {
- ub.queryParam(MAX_AGE_PARAMETER,
redirectState.getFirst(MAX_AGE_PARAMETER));
- }
+ // Prefer the nonce from redirectState (managed by the
ClientCodeStateManager).
+ // Fall back to codeRequestState to cover the auto-generated nonce
path used when the
+ // state manager does not copy the nonce into its redirect map.
+ String nonce = redirectState != null ?
redirectState.getFirst(IdToken.NONCE_CLAIM) : null;
+ if (nonce == null && codeRequestState != null) {
+ nonce = codeRequestState.getFirst(IdToken.NONCE_CLAIM);
+ }
+ if (nonce != null) {
+ ub.queryParam(IdToken.NONCE_CLAIM, nonce);
+ }
+ if (redirectState != null && redirectState.getFirst(MAX_AGE_PARAMETER)
!= null) {
+ ub.queryParam(MAX_AGE_PARAMETER,
redirectState.getFirst(MAX_AGE_PARAMETER));
}
if (codeRequestState != null &&
codeRequestState.getFirst(LOGIN_HINT_PARAMETER) != null) {
ub.queryParam(LOGIN_HINT_PARAMETER,
codeRequestState.getFirst(LOGIN_HINT_PARAMETER));
@@ -168,6 +227,11 @@ public class OidcClientCodeRequestFilter extends
ClientCodeRequestFilter {
if (promptLogin != null) {
ub.queryParam(PROMPT_PARAMETER, promptLogin);
}
+ // Override the response_type set by the base filter (which defaults
to "code").
+ // This is required to support Implicit (id_token) and Hybrid (code
id_token, etc.) flows.
+ if (responseType != null &&
!OAuthConstants.CODE_RESPONSE_TYPE.equals(responseType)) {
+ ub.replaceQueryParam(OAuthConstants.RESPONSE_TYPE, responseType);
+ }
}
diff --git
a/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilterNonceTest.java
b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilterNonceTest.java
new file mode 100644
index 00000000000..598edae2d35
--- /dev/null
+++
b/rt/rs/security/sso/oidc/src/test/java/org/apache/cxf/rs/security/oidc/rp/OidcClientCodeRequestFilterNonceTest.java
@@ -0,0 +1,402 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF 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 org.apache.cxf.rs.security.oidc.rp;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+
+import jakarta.ws.rs.container.ContainerRequestContext;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.UriInfo;
+import org.apache.cxf.jaxrs.impl.MetadataMap;
+import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
+import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
+import org.apache.cxf.rs.security.oidc.common.IdToken;
+import org.apache.cxf.rs.security.oidc.utils.OidcUtils;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Unit tests for nonce enforcement in {@link OidcClientCodeRequestFilter}.
+ *
+ * OIDC Core §3.2.2.1 (Implicit Flow) and §3.3.2.1 (Hybrid Flow) require the
+ * {@code nonce} parameter whenever an ID Token may be returned directly from
+ * the Authorization Endpoint (i.e., the response_type contains {@code
id_token}).
+ */
+public class OidcClientCodeRequestFilterNonceTest {
+
+ private static final URI ABSOLUTE_PATH =
URI.create("https://app.example.com/rp/callback");
+
+ // -----------------------------------------------------------------------
+ // validateIdToken – direct tests via reflection
+ // -----------------------------------------------------------------------
+
+ /**
+ * Code Flow (default): nonce is optional. A token with no nonce claim
must be
+ * accepted even when the state carries no nonce.
+ */
+ @Test
+ public void testCodeFlowAcceptsTokenWithoutNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ // no responseType set → code flow
+
+ IdToken token = new IdToken();
+ // no nonce in token
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ // no nonce in state
+
+ invokeValidateIdToken(filter, token, state); // must not throw
+ }
+
+ /**
+ * Code Flow: if the RP sent a nonce it MUST be echoed back by the IdP.
+ */
+ @Test
+ public void testCodeFlowRejectsNonceMismatch() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+
+ IdToken token = new IdToken();
+ token.setNonce("wrong-nonce");
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ state.putSingle(IdToken.NONCE_CLAIM, "correct-nonce");
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException for nonce mismatch");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Code Flow: token nonce absent when state carries one must be rejected.
+ */
+ @Test
+ public void testCodeFlowRejectsAbsentTokenNonceWhenStateHasOne() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+
+ IdToken token = new IdToken();
+ // no nonce in token
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ state.putSingle(IdToken.NONCE_CLAIM, "expected-nonce");
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException: token nonce absent");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Implicit Flow (response_type=id_token): nonce is REQUIRED.
+ * A null state (no ClientCodeStateManager configured) must be rejected.
+ */
+ @Test
+ public void testImplicitFlowRejectsNullState() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+
+ try {
+ invokeValidateIdToken(filter, token, null);
+ fail("Expected OAuthServiceException: nonce required for implicit
flow");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Implicit Flow: nonce is REQUIRED; state without one must be rejected.
+ */
+ @Test
+ public void testImplicitFlowRejectsStateWithoutNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ // no nonce in state
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException: nonce required for implicit
flow");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Implicit Flow: valid nonce round-trip must be accepted.
+ */
+ @Test
+ public void testImplicitFlowAcceptsMatchingNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+ token.setNonce("session-nonce-abc");
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ state.putSingle(IdToken.NONCE_CLAIM, "session-nonce-abc");
+
+ invokeValidateIdToken(filter, token, state); // must not throw
+ }
+
+ /**
+ * Implicit Flow: nonce mismatch must be rejected.
+ */
+ @Test
+ public void testImplicitFlowRejectsNonceMismatch() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+ token.setNonce("attacker-nonce");
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ state.putSingle(IdToken.NONCE_CLAIM, "session-nonce-abc");
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException for nonce mismatch");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Hybrid Flow (response_type=code id_token): nonce is REQUIRED.
+ */
+ @Test
+ public void testHybridFlowRejectsStateWithoutNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.CODE_ID_TOKEN_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException: nonce required for hybrid
flow");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Hybrid Flow (response_type=id_token token): nonce is REQUIRED.
+ */
+ @Test
+ public void testImplicitWithAccessTokenFlowRejectsStateWithoutNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_AT_RESPONSE_TYPE);
+
+ IdToken token = new IdToken();
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+
+ try {
+ invokeValidateIdToken(filter, token, state);
+ fail("Expected OAuthServiceException: nonce required for id_token
token flow");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ // -----------------------------------------------------------------------
+ // toCodeRequestState – nonce auto-generation
+ // -----------------------------------------------------------------------
+
+ /**
+ * For Implicit Flow, {@code toCodeRequestState} must auto-generate a
nonce when the
+ * caller has not supplied one.
+ */
+ @Test
+ public void testImplicitFlowAutoGeneratesNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ MultivaluedMap<String, String> state =
invokeToCodeRequestState(filter, new MultivaluedHashMap<>());
+
+ String nonce = state.getFirst(IdToken.NONCE_CLAIM);
+ assertNotNull("A nonce must be auto-generated for implicit flow",
nonce);
+ }
+
+ /**
+ * For Implicit Flow, a caller-supplied nonce must be preserved (not
replaced).
+ */
+ @Test
+ public void testImplicitFlowPreservesCallerNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ filter.setResponseType(OidcUtils.ID_TOKEN_RESPONSE_TYPE);
+
+ MultivaluedHashMap<String, String> query = new MultivaluedHashMap<>();
+ query.putSingle(IdToken.NONCE_CLAIM, "my-app-nonce");
+
+ MultivaluedMap<String, String> state =
invokeToCodeRequestState(filter, query);
+
+ assertEquals("Caller-supplied nonce must not be overwritten",
+ "my-app-nonce", state.getFirst(IdToken.NONCE_CLAIM));
+ }
+
+ /**
+ * For Code Flow (default), no nonce should be auto-generated.
+ */
+ @Test
+ public void testCodeFlowDoesNotAutoGenerateNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ // no responseType set → code flow
+
+ MultivaluedMap<String, String> state =
invokeToCodeRequestState(filter, new MultivaluedHashMap<>());
+
+ assertNull("Code flow must not auto-generate a nonce",
state.getFirst(IdToken.NONCE_CLAIM));
+ }
+
+ /**
+ * Hybrid flow detected from context: an id_token in the callback
requestParams means
+ * nonce is required even when responseType has NOT been explicitly
configured.
+ */
+ @Test
+ public void testHybridFlowContextDetectionRequiresNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ // No responseType configured — the filter is code-flow by default.
+
+ IdToken token = new IdToken();
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ // No nonce in state.
+
+ // Simulate a hybrid callback: id_token was returned in the
authorization response.
+ MultivaluedMap<String, String> requestParams = new MetadataMap<>();
+ requestParams.putSingle(OidcUtils.ID_TOKEN, "some.jwt.value");
+
+ try {
+ invokeValidateIdToken(filter, token, state, requestParams);
+ fail("Expected OAuthServiceException: nonce required for hybrid
flow (context-detected)");
+ } catch (OAuthServiceException ex) {
+ assertEquals(OAuthConstants.INVALID_REQUEST, ex.getMessage());
+ }
+ }
+
+ /**
+ * Hybrid flow detected from context: valid nonce round-trip must be
accepted.
+ */
+ @Test
+ public void testHybridFlowContextDetectionAcceptsMatchingNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ // No responseType configured.
+
+ IdToken token = new IdToken();
+ token.setNonce("session-nonce");
+
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ state.putSingle(IdToken.NONCE_CLAIM, "session-nonce");
+
+ MultivaluedMap<String, String> requestParams = new MetadataMap<>();
+ requestParams.putSingle(OidcUtils.ID_TOKEN, "some.jwt.value");
+
+ invokeValidateIdToken(filter, token, state, requestParams); // must
not throw
+ }
+
+ /**
+ * Pure code flow (no id_token in callback) must not require a nonce even
when
+ * context detection is active.
+ */
+ @Test
+ public void testCodeFlowContextDetectionDoesNotRequireNonce() {
+ OidcClientCodeRequestFilter filter = new OidcClientCodeRequestFilter();
+ // No responseType configured.
+
+ IdToken token = new IdToken();
+ MultivaluedMap<String, String> state = new MetadataMap<>();
+ // No id_token in the callback — plain code flow.
+ MultivaluedMap<String, String> requestParams = new MetadataMap<>();
+ requestParams.putSingle("code", "auth-code");
+
+ invokeValidateIdToken(filter, token, state, requestParams); // must
not throw
+ }
+
+ /** Convenience overload: no id_token in callback (non-hybrid). */
+ private static void invokeValidateIdToken(OidcClientCodeRequestFilter
filter,
+ IdToken idToken,
+ MultivaluedMap<String, String>
state) {
+ invokeValidateIdToken(filter, idToken, state, new MetadataMap<>());
+ }
+
+ private static void invokeValidateIdToken(OidcClientCodeRequestFilter
filter,
+ IdToken idToken,
+ MultivaluedMap<String, String>
state,
+ MultivaluedMap<String, String>
requestParams) {
+ try {
+ Method method =
OidcClientCodeRequestFilter.class.getDeclaredMethod(
+ "validateIdToken", IdToken.class, MultivaluedMap.class,
boolean.class);
+ method.setAccessible(true);
+ boolean hybridFlowDetected = requestParams != null
+ && requestParams.containsKey(OidcUtils.ID_TOKEN);
+ method.invoke(filter, idToken, state, hybridFlowDetected);
+ } catch (java.lang.reflect.InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof OAuthServiceException) {
+ throw (OAuthServiceException) cause;
+ }
+ throw new IllegalStateException(cause);
+ } catch (ReflectiveOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static MultivaluedMap<String, String> invokeToCodeRequestState(
+ OidcClientCodeRequestFilter filter, MultivaluedMap<String, String>
queryParams) {
+ UriInfo uriInfo = mock(UriInfo.class);
+ when(uriInfo.getQueryParameters(anyBoolean())).thenReturn(queryParams);
+ when(uriInfo.getAbsolutePath()).thenReturn(ABSOLUTE_PATH);
+
+ ContainerRequestContext rc = mock(ContainerRequestContext.class);
+ when(rc.getUriInfo()).thenReturn(uriInfo);
+ when(rc.getMediaType()).thenReturn(null);
+
+ try {
+ Method method =
OidcClientCodeRequestFilter.class.getDeclaredMethod(
+ "toCodeRequestState", ContainerRequestContext.class,
UriInfo.class);
+ method.setAccessible(true);
+ return (MultivaluedMap<String, String>) method.invoke(filter, rc,
uriInfo);
+ } catch (java.lang.reflect.InvocationTargetException ex) {
+ Throwable cause = ex.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ throw new IllegalStateException(cause);
+ } catch (ReflectiveOperationException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+}