[java-idp-oidc] branch main updated: JOIDC-63 Missing required PKCE code challenges should raise an error in the authorization endpoint

Henri Mikkonen henri.mikkonen at iki.fi
Tue Feb 22 16:02:34 UTC 2022


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=b140fd4ff9fbac3f84949789b55d0ee8d3ac71d1

The following commit(s) were added to refs/heads/main by this push:
     new b140fd4f JOIDC-63 Missing required PKCE code challenges should raise an error in the authorization endpoint
b140fd4f is described below

commit b140fd4ff9fbac3f84949789b55d0ee8d3ac71d1
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Feb 22 18:01:32 2022 +0200

    JOIDC-63 Missing required PKCE code challenges should raise an error in the authorization endpoint
    
    https://shibboleth.atlassian.net/browse/JOIDC-63
    
    Added a new validation action (ValidateCodeChallenge) to the authorize flow.
---
 .../op/profile/impl/ValidateCodeChallenge.java     | 170 ++++++++++++++
 .../idp/flows/oidc/authorize/authorize-beans.xml   |   7 +
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   1 +
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 115 ++++++++-
 .../op/profile/impl/ValidateCodeChallengeTest.java | 258 +++++++++++++++++++++
 5 files changed, 550 insertions(+), 1 deletion(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallenge.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallenge.java
new file mode 100644
index 00000000..29e236b4
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallenge.java
@@ -0,0 +1,170 @@
+/*
+ * 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.oidc.op.profile.impl;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.ActionSupport;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeMethodLookupFunction;
+import net.shibboleth.oidc.profile.config.logic.AllowPKCEPlainPredicate;
+import net.shibboleth.oidc.profile.config.logic.ForcePKCEPredicate;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.utilities.java.support.component.ComponentSupport;
+import net.shibboleth.utilities.java.support.logic.Constraint;
+
+/**
+ * Validates the presence of PKCE code challenge parameter from the incoming authentication request.
+ */
+public class ValidateCodeChallenge  extends AbstractOIDCAuthenticationResponseAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateCodeChallenge.class);
+
+    /** Strategy used to determine whether to require PKCE. */
+    @Nonnull private Predicate<ProfileRequestContext> forcePKCECondition;
+
+    /** Strategy used to determine whether to allow plaintext PKCE. */
+    @Nonnull private Predicate<ProfileRequestContext> allowPKCEPlainCondition;
+    
+    /** Strategy used to locate the code challenge. */
+    @Nonnull private Function<ProfileRequestContext, String> codeChallengeLookupStrategy;
+    
+    /** Strategy used to locate the code challenge method. */
+    @Nonnull private Function<ProfileRequestContext, String> codeChallengeMethodLookupStrategy;
+
+    /** Whether PKCE is mandatory. */
+    private boolean forcePKCE;
+
+    /** Whether plain PKCE is allowed. */
+    private boolean plainPKCE;
+
+    /** PKCE code challenge. */
+    @Nullable private String codeChallenge;
+
+    /** PKCE code challenge method. */
+    @Nullable private String codeChallengeMethod;
+
+    /**
+     * Constructor.
+     */
+    public ValidateCodeChallenge() {
+        forcePKCECondition = new ForcePKCEPredicate();
+        allowPKCEPlainCondition = new AllowPKCEPlainPredicate();
+        codeChallengeLookupStrategy = new DefaultRequestCodeChallengeLookupFunction();
+        codeChallengeMethodLookupStrategy = new DefaultRequestCodeChallengeMethodLookupFunction();
+    }
+
+    /**
+     * Set the condition used to determine whether to require PKCE.
+     * 
+     * @param condition condition to apply
+     */
+    public void setForcePKCECondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        forcePKCECondition = Constraint.isNotNull(condition, "Condition cannot be null");
+    }
+
+    /**
+     * Set the condition used to determine whether to allow plaintext PKCE.
+     * 
+     * @param condition condition to apply
+     */
+    public void setAllowPKCEPlainCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+
+        allowPKCEPlainCondition = Constraint.isNotNull(condition, "Condition cannot be null");
+    }
+
+    /**
+     * Set the strategy used to locate the Code Challenge of the request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setCodeChallengeLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        codeChallengeLookupStrategy =
+                Constraint.isNotNull(strategy, "Code challenge lookup strategy cannot be null");
+    }
+    
+    /**
+     * Set the strategy used to locate the Code Challenge Method of the request.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setCodeChallengeMethodLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);
+        codeChallengeMethodLookupStrategy =
+                Constraint.isNotNull(strategy, "Code challenge method lookup strategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        forcePKCE = forcePKCECondition.test(profileRequestContext);
+        plainPKCE = allowPKCEPlainCondition.test(profileRequestContext);
+
+        codeChallenge = codeChallengeLookupStrategy.apply(profileRequestContext);
+
+        if ((codeChallenge == null || codeChallenge.isEmpty()) && !forcePKCE) {
+            log.debug("{} No PKCE code challenge in request, nothing to do", getLogPrefix());
+            return false;
+        }
+        
+        codeChallengeMethod = codeChallengeMethodLookupStrategy.apply(profileRequestContext);
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (codeChallenge == null || codeChallenge.isEmpty()) {
+            log.warn("{} No PKCE code challenge presented in authentication request" +
+                            " even though required by the profile configuration", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.MISSING_PKCE_CODE_CHALLENGE);
+            return;
+        }
+        if (codeChallengeMethod == null || codeChallengeMethod.isEmpty() || "plain".equals(codeChallengeMethod)) {
+            if (!plainPKCE) {
+                log.warn("{} Plain PKCE code challenge method not allowed", getLogPrefix());
+                ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+                return;
+            }
+            log.debug("{} Plain code challenge exists in the request and is accepted", getLogPrefix());
+        } else if ("S256".equals(codeChallengeMethod)) {
+            log.debug("{} S256 code challenge exists in the request", getLogPrefix());
+        } else {
+            log.warn("{} Unknown code challenge method: {}", getLogPrefix(), codeChallengeMethod);
+            ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+            return;
+        }        
+    }
+}
\ No newline at end of file
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 cf91cf11..85a953b8 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
@@ -172,6 +172,9 @@
     <bean id="ValidateResponseType" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateResponseType"
         scope="prototype" />
 
+    <bean id="ValidateCodeChallenge" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateCodeChallenge"
+        scope="prototype" />
+
     <bean id="ValidateScope" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.ValidateScope" scope="prototype"
         p:allowedScopeLookupStrategy="#{getObject('shibboleth.oidc.AllowedScopeStrategy') ?: getObject('shibboleth.oidc.DefaultAllowedScopeStrategy')}" />
 
@@ -387,6 +390,10 @@
                     value="#{T(com.nimbusds.oauth2.sdk.OAuth2Error).UNSUPPORTED_RESPONSE_TYPE}" />
                 <entry key="#{T(net.shibboleth.idp.authn.AuthnEventIds).NO_PASSIVE}"
                     value="#{T(com.nimbusds.openid.connect.sdk.OIDCError).LOGIN_REQUIRED}" />
+                <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).MISSING_PKCE_CODE_CHALLENGE}"
+                    value="#{T(net.shibboleth.oidc.profile.core.OidcError).MISSING_PKCE_CODE_CHALLENGE}" />
+                <entry key="#{T(net.shibboleth.oidc.profile.core.OidcEventIds).INVALID_PKCE_TRANSFORMATION_METHOD}"
+                    value="#{T(net.shibboleth.oidc.profile.core.OidcError).INVALID_PKCE_TRANSFORMATION_METHOD}" />
             </map>
         </property>
     </bean>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index b3c49f15..7d2e6483 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -37,6 +37,7 @@
         <evaluate expression="ValidateRequestObject" />
         <evaluate expression="ValidateRedirectURI" />
         <evaluate expression="ValidateResponseType" />
+        <evaluate expression="ValidateCodeChallenge" />
         <evaluate expression="ValidateScope" />
         <evaluate expression="SetRequestedClaimsToResponseContext" />
         <evaluate expression="SetRequestedSubjectToResponseContext" />
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
index 01bf40b5..5ab419ae 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/AuthorizeFlowTest.java
@@ -36,13 +36,13 @@ import com.nimbusds.jwt.JWT;
 import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.PlainJWT;
 import com.nimbusds.jwt.SignedJWT;
-import com.nimbusds.oauth2.sdk.ErrorResponse;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
 import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
 
 import net.shibboleth.idp.session.SessionException;
+import net.shibboleth.oidc.profile.core.OidcError;
 
 /**
  * Tests for the authorize-flow.
@@ -86,6 +86,116 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         Assert.assertNull(successResponse.getAccessToken());
         Assert.assertNotNull(successResponse.getAuthorizationCode());
     }
+    
+    @Test
+    public void testWithAuthorizationCodeFlowUnforcedPKCE() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCEPlainUnforced&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri);
+        storeMetadata(storageService, "mockClientIdPKCEPlainUnforced", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedPlainPKCEMissingChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCEPlain&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri);
+        storeMetadata(storageService, "mockClientIdPKCEPlain", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_request");
+        assertErrorDescriptionContains(result, OidcError.MISSING_PKCE_CODE_CHALLENGE.getDescription());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedPlainPKCEUnknownChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCEPlain&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&code_challenge=osdfojsfod&code_challenge_method=unsupported");
+        storeMetadata(storageService, "mockClientIdPKCEPlain", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_request");
+        assertErrorDescriptionContains(result, OidcError.INVALID_PKCE_TRANSFORMATION_METHOD.getDescription());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedPlainPKCEValidChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCEPlain&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&code_challenge=osdfojsfod&code_challenge_method=plain");
+        storeMetadata(storageService, "mockClientIdPKCEPlain", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedS256PKCEPlainChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCES256&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&code_challenge=osdfojsfod&code_challenge_method=plain");
+        storeMetadata(storageService, "mockClientIdPKCES256", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_request");
+        assertErrorDescriptionContains(result, OidcError.INVALID_PKCE_TRANSFORMATION_METHOD.getDescription());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedS256PKCEUnknownChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCES256&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&code_challenge=osdfojsfod&code_challenge_method=unknown");
+        storeMetadata(storageService, "mockClientIdPKCES256", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, "invalid_request");
+        assertErrorDescriptionContains(result, OidcError.INVALID_PKCE_TRANSFORMATION_METHOD.getDescription());
+    }
+
+    @Test
+    public void testWithAuthorizationCodeFlowForcedS256PKCEValidChallenge() throws IOException, ParseException, SessionException {
+        request.setMethod("GET");
+        request.setQueryString("client_id=mockClientIdPKCES256&response_type=code&scope=openid%20profile&redirect_uri="
+                + redirectUri + "&code_challenge=osdfojsfod&code_challenge_method=S256");
+        storeMetadata(storageService, "mockClientIdPKCES256", clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+        
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+        final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+        Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+        Assert.assertNull(successResponse.getIDToken());
+        Assert.assertNull(successResponse.getAccessToken());
+        Assert.assertNotNull(successResponse.getAuthorizationCode());
+    }
 
     @Test
     public void testWithAuthorizationCodeFlowNoScopes() throws IOException, ParseException, SessionException {
@@ -287,6 +397,9 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     @AfterMethod
     public void removeMetadata() throws IOException {
         removeMetadata(storageService, clientId);
+        removeMetadata(storageService, "mockClientIdPKCEPlainUnforced");
+        removeMetadata(storageService, "mockClientIdPKCEPlain");
+        removeMetadata(storageService, "mockClientIdPKCES256");
     }
 
 }
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallengeTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallengeTest.java
new file mode 100644
index 00000000..8983c4bc
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/impl/ValidateCodeChallengeTest.java
@@ -0,0 +1,258 @@
+/*
+ * 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.oidc.op.profile.impl;
+
+import java.net.URISyntaxException;
+
+import net.shibboleth.idp.profile.testing.ActionTestingSupport;
+import net.shibboleth.oidc.profile.core.OidcEventIds;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+import org.springframework.webflow.execution.Event;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+
+/** {@link ValidateCodeChallenge} unit test. */
+public class ValidateCodeChallengeTest extends BaseOIDCResponseActionTest {
+
+    private ValidateCodeChallenge action;
+
+    private boolean forcePkce;
+    private boolean allowPlain;
+    
+    @BeforeMethod
+    private void init() throws ComponentInitializationException, URISyntaxException, ParseException {
+        action = new ValidateCodeChallenge();
+        action.setForcePKCECondition(prc -> forcePkce);
+        action.setAllowPKCEPlainCondition(prc -> allowPlain);
+        action.initialize();
+    }
+
+    @Test
+    public void testUnforcedAllowMissingChallenge() throws ComponentInitializationException {
+        forcePkce = false;
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForcedFailsWhenMissingChallenge() throws ComponentInitializationException {
+        forcePkce = true;
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.MISSING_PKCE_CODE_CHALLENGE);
+    }
+
+    @Test
+    public void testForcedFailsWhenEmptyChallenge() throws ComponentInitializationException, ParseException {
+        forcePkce = true;
+        setAuthenticationRequest(populateCodeChallenge("", null));        
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.MISSING_PKCE_CODE_CHALLENGE);
+    }
+
+    @Test
+    public void testUnforcedPNolainAllowedFailsWithUnknownMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "unsupported"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testUnforcedNoPlainAllowedFailsWithDefaultMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", null));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testUnforcedNoPlainAllowedFailsWithEmptyMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", ""));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testUnforcedNoPlainAllowedFailsWithPlainMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "plain"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testUnforcedNoPlainAllowedSuccessWithS256Method() throws ParseException {
+        forcePkce = false;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "S256"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForcedNoPlainAllowedFailsWithUnknownMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "unsupported"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testForcedNoPlainAllowedFailsWithDefaultMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", null));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testForcedNoPlainAllowedFailsWithEmptyMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", ""));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testForcedNoPlainAllowedFailsWithPlainMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "plain"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testForcedNoPlainAllowedSuccessWithS256Method() throws ParseException {
+        forcePkce = true;
+        allowPlain = false;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "S256"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testUnforcedPlainAllowedFailsWithUnknownMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "unsupported"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testUnforcedPlainAllowedSuccessWithDefaultMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", null));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testUnforcedPlainAllowedSuccessWithEmptyMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", ""));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testUnforcedPlainAllowedSuccesssWithPlainMethod() throws ParseException {
+        forcePkce = false;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "plain"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testUnforcedPlainAllowedSuccessWithS256Method() throws ParseException {
+        forcePkce = false;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "S256"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForcedPlainAllowedFailsWithUnknownMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "unsupported"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertEvent(event, OidcEventIds.INVALID_PKCE_TRANSFORMATION_METHOD);
+    }
+
+    @Test
+    public void testForcedPlainAllowedSuccessWithDefaultMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", null));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForcedPlainAllowedSuccessWithEmptyMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", ""));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForcedPlainAllowedSuccessWithPlainMethod() throws ParseException {
+        forcePkce = true;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "plain"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    @Test
+    public void testForceoPlainAllowedSuccessWithS256Method() throws ParseException {
+        forcePkce = true;
+        allowPlain = true;
+        setAuthenticationRequest(populateCodeChallenge("asdsadasddsa", "S256"));
+        final Event event = action.execute(requestCtx);
+        ActionTestingSupport.assertProceedEvent(event);
+    }
+
+    protected AuthenticationRequest populateCodeChallenge(final String challenge, final String challengeMethod)
+            throws ParseException {
+        final String authnRequest = request.toQueryString() + "&code_challenge=" + challenge;
+        return AuthenticationRequest.parse(challengeMethod == null ? authnRequest : authnRequest 
+                + "&code_challenge_method=" + challengeMethod);
+    }
+
+}
\ 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