[java-idp-oidc] 08/35: JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)

Henri Mikkonen henri.mikkonen at iki.fi
Thu Oct 24 14:01:33 UTC 2024


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

hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.

View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=1a1822486174648af2960a92f38037ef653f7100

commit 1a1822486174648af2960a92f38037ef653f7100
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Sep 12 17:50:13 2024 +0300

    JOIDC-200 - Support for OAuth2 Pushed Authorization Requests (PAR)
    
    https://shibboleth.atlassian.net/browse/JOIDC-200
    
    Modified the request object logic to strictly follow the JAR spec, as mandated by the PAR RFC.
    - The request object must be signed or signed and encrypted - i.e. no plain JWT allowed
    - If the request object is involved, the parameters are solely read from there - i.e. form-parameters are ignored
    - client_id is a mandatory claim in the request object
      - The chain of claims validators may be customized via shibboleth.oidc.par.SignedRequestObjectClaimsValidation
---
 ...mOutbounPushedAuthorizationResponseMessage.java |  64 +---
 .../ValidatePushedAuthorizationClientIDMatch.java  |   2 +-
 .../oauth2/profile/impl/ValidateRequestObject.java |  16 +-
 .../pushed-authorization-beans.xml                 | 116 +++---
 .../op/profile/flow/PushedAuthorizeFlowTest.java   | 424 ++++++++++++++++++++-
 .../flow/PushedAuthorizeRequestObjectJWETest.java  |  45 +--
 6 files changed, 518 insertions(+), 149 deletions(-)

diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
index 147a38dc..141cbe11 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/FormOutbounPushedAuthorizationResponseMessage.java
@@ -17,8 +17,6 @@ package net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl;
 import java.net.URI;
 import java.text.ParseException;
 import java.time.Duration;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.function.BiFunction;
 import java.util.function.Function;
@@ -32,7 +30,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
 import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWT;
-import com.nimbusds.oauth2.sdk.AuthorizationRequest;
 import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
 import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
 
@@ -47,7 +44,7 @@ import net.shibboleth.shared.primitive.LoggerFactory;
 import net.shibboleth.shared.primitive.StringSupport;
 
 /**
- * Action that forms outbound token introspection success message. Formed message is set to
+ * Action that forms outbound pushed authorization response message. Formed message is set to
  * {@link ProfileRequestContext#getOutboundMessageContext()}.
  */
 public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuthAuthorizationResponseAction {
@@ -136,12 +133,19 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
     /** {@inheritDoc} */
     @Override protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
 
-        final Map<String, Object> claimsSet = buildClaimsSet(profileRequestContext);
+        final Map<String, Object> claimsSet;
+        try {
+            claimsSet = buildClaimsSet(profileRequestContext);
+        } catch (final ParseException e) {
+            log.error("{} Could not parse the claims set from the request object", getLogPrefix(), e);
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return;
+        }
         
         final BiFunction<ProfileRequestContext,Map<String,Object>,URI> serializationStrategy =
                 requestUriClaimsSetSerializationStrategies.get(requestUriType == null ? "" : requestUriType);
         if (serializationStrategy == null) {
-            log.error("{} Could not find a seralization strategy for request URI type {}", getLogPrefix(),
+            log.error("{} Could not find a serialization strategy for request URI type {}", getLogPrefix(),
                     requestUriType);
             ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
             return;
@@ -168,27 +172,26 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
     }
 
     /**
-     * Build the claims set by combining the contents of the pushed authorization request form parameters with the ones
-     * defined in request object (if included).
+     * Build the claims set by exploiting the contents of the pushed authorization request form parameters or request
+     * object. Finally, if defined, the manipulation strategy is applied against the claims set. 
      * 
      * @param profileRequestContext the profile request context to operate on
      * @return the claims set
+     * @throws ParseException if the request object cannot be parsed into the claims set
      */
     @Nonnull
-    protected Map<String,Object> buildClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext) {
+    protected Map<String,Object> buildClaimsSet(@Nonnull final ProfileRequestContext profileRequestContext) 
+            throws ParseException {
         final OIDCAuthenticationResponseContext oidcContext = getOidcResponseContext();
         assert oidcContext != null;
 
-        assert requestMessage != null;
-        final Map<String, Object> claimsSet = getRequestClaimsSetWithoutRequestObject(requestMessage);
+        final Map<String, Object> claimsSet;
         final JWT requestObject = oidcContext.getRequestObject();
-        if (requestObject != null) {
-            try {
-                claimsSet.putAll(requestObject.getJWTClaimsSet().getClaims());
-                log.debug("{} Request object claims set successfully merged", getLogPrefix());
-            } catch (ParseException e) {
-                log.error("{} Could not parse request object claims set", getLogPrefix());
-            }
+        if (requestObject == null) {
+            assert requestMessage != null;
+            claimsSet = requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims();
+        } else {
+            claimsSet = requestObject.getJWTClaimsSet().getClaims();
         }
 
         if (manipulationStrategy != null) {
@@ -210,29 +213,4 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
         assert claimsSet != null;
         return claimsSet;
     }
-
-    /**
-     * Get the claims set included in the pushed authorization request form parameters, not including the request
-     * object if it exists.
-     * 
-     * @param requestMessage the request message to operate on
-     * @return the claims set
-     */
-    @Nonnull protected Map<String,Object> getRequestClaimsSetWithoutRequestObject(
-            @Nonnull final PushedAuthorizationRequest requestMessage) {
-        final AuthorizationRequest authorizationRequest = requestMessage.getAuthorizationRequest();
-        final Map<String, Object> result = new HashMap<>();
-        if (authorizationRequest.specifiesRequestObject()) {
-            final Map<String, List<String>> parameters = authorizationRequest.toParameters();
-            parameters.remove("request");
-            try {
-                result.putAll(AuthorizationRequest.parse(parameters).toJWTClaimsSet().getClaims());
-            } catch (final com.nimbusds.oauth2.sdk.ParseException e) {
-                log.error("{} Could not rebuild authorization request without request object", getLogPrefix());
-            }
-        } else {
-            result.putAll(authorizationRequest.toJWTClaimsSet().getClaims());
-        }
-        return result;
-    }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
index 8cabcd0a..eaeaa2db 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidatePushedAuthorizationClientIDMatch.java
@@ -79,7 +79,7 @@ public class ValidatePushedAuthorizationClientIDMatch  extends AbstractOAuthAuth
         final ClientID requestClientId = authorizationRequest.getClientID();
         if (!authenticatedClientId.equals(requestClientId)) {
             log.warn("{} The client ID used in authentication {} did not match with one in request {}",
-                    authenticatedClientId, requestClientId, getLogPrefix());
+                    getLogPrefix(), authenticatedClientId, requestClientId);
             ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MESSAGE);
             return;
         }
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
index b1c777e1..16df6fb2 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/ValidateRequestObject.java
@@ -62,8 +62,12 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
     /** The claims validator to be applied for validating the plain/unsigned request object. */
     @NonnullAfterInit private ClaimsValidator plainClaimsValidator;
 
+    /** The flag to control whether to use the PAR-endpoint logic for the validation. */
+    private boolean parEndpointLogic;
+
     /** Constructor. */
     public ValidateRequestObject() {
+        parEndpointLogic = false;
     }
 
     /**
@@ -86,6 +90,16 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
         plainClaimsValidator = Constraint.isNotNull(validator, "Plain claims validator cannot be null");
     }
 
+    /**
+     * Set the flag to control whether to use the PAR-endpoint logic for the validation.
+     * 
+     * @param flag What to set
+     */
+    public void setParEndpointLogic(final boolean flag) {
+        ifInitializedThrowUnmodifiabledComponentException();
+        parEndpointLogic = flag;
+    }
+
     /** {@inheritDoc} */
     @Override
     protected void doInitialize() throws ComponentInitializationException {
@@ -173,7 +187,7 @@ public class ValidateRequestObject extends AbstractOAuthAuthorizationResponseAct
                 ActionSupport.buildEvent(profileRequestContext, OidcEventIds.INVALID_REQUEST_OBJECT);
                 return;
             }
-            if (authorizationRequest instanceof AuthenticationRequest authenticationRequest) {
+            if (!parEndpointLogic && authorizationRequest instanceof AuthenticationRequest authenticationRequest) {
                 final ResponseType requestedType = authenticationRequest.getResponseType();
                 if (requestedType == null) {
                     log.error("{} mandatory response_type is missing from the request", getLogPrefix());
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
index f8f5669c..8171ed79 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oauth2/pushed-authorization/pushed-authorization-beans.xml
@@ -150,13 +150,8 @@
         </property>
     </bean>
 
-    <bean id="RequestObjectSignedCondition" parent="shibboleth.Conditions.Expression"
-        c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() instanceof T(com.nimbusds.jwt.SignedJWT)" />
-
     <bean id="UseRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
 
-    <bean id="SignRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.SignRequestObjectPredicate" />
-
     <bean id="ValidateRequestObjectSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
         scope="prototype" c:executionDirection="INBOUND">
         <constructor-arg>
@@ -205,55 +200,25 @@
             </bean>
         </constructor-arg>
         <property name="activationCondition">
-            <bean parent="shibboleth.Conditions.OR">
-                <constructor-arg>
-                    <ref bean="RequestObjectSignedCondition" />
-                </constructor-arg>
-                <constructor-arg>
-                    <bean parent="shibboleth.Conditions.OR">
-                        <constructor-arg>
-                            <bean parent="shibboleth.Conditions.AND">
-                                <constructor-arg>
-                                    <ref bean="UseRequestObjectPredicate" />
-                                </constructor-arg>
-                                <constructor-arg>
-                                    <ref bean="SignRequestObjectPredicate" />
-                                </constructor-arg>
-                            </bean>
-                        </constructor-arg>
-                        <constructor-arg>
-                            <bean parent="shibboleth.Conditions.AND">
-                                <constructor-arg>
-                                    <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
-                                        c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
-                                </constructor-arg>
-                                <constructor-arg>
-                                    <bean parent="shibboleth.Conditions.NOT">
-                                        <constructor-arg>
-                                            <bean id="ScopeContainsOpenid" parent="shibboleth.Conditions.Expression" p:customObject-ref="shibboleth.HttpServletRequestSupplier"
-                                                c:expression="#custom.get().getParameter('scope') != null and #custom.get().getParameter('scope').contains('openid')" />
-                                        </constructor-arg>
-                                    </bean>
-                                </constructor-arg>
-                            </bean>
-                        </constructor-arg>
-                    </bean>
-                </constructor-arg>
-            </bean>
+            <bean id="RequestObjectInvolved" parent="shibboleth.Conditions.Expression"
+                c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).getRequestObject() != null" />
         </property>
     </bean>
 
     <bean id="ValidateRequestObject" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRequestObject"
         scope="prototype"
-        p:plainClaimsValidator="#{getObject('shibboleth.oidc.PlainRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.DefaultPlainRequestObjectClaimsValidation')}"
-        p:signedClaimsValidator="#{getObject('shibboleth.oidc.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.DefaultSignedRequestObjectClaimsValidation')}">
+        p:plainClaimsValidator="#{getObject('shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation')}"
+        p:signedClaimsValidator="#{getObject('shibboleth.oidc.par.SignedRequestObjectClaimsValidation') ?: getObject('shibboleth.oidc.par.DefaultSignedRequestObjectClaimsValidation')}"
+        p:parEndpointLogic="true"/>
+
+    <bean id="shibboleth.oidc.par.DefaultPlainRequestObjectClaimsValidation"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+        <property name="claimValidators">
+            <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator"/>
+        </property>
     </bean>
 
-    <bean id="shibboleth.oidc.DefaultPlainRequestObjectClaimsValidation"
-        class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
-        p:claimValidators-ref="PlainClaimsValidators" />
-
-    <bean id="shibboleth.oidc.DefaultSignedRequestObjectClaimsValidation"
+    <bean id="shibboleth.oidc.par.DefaultSignedRequestObjectClaimsValidation"
         class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator"
         p:claimValidators-ref="SignedClaimsValidators" />
 
@@ -275,6 +240,16 @@
         </property>
     </bean>
 
+    <bean id="ClientIdClaimsValidator"
+        class="net.shibboleth.oidc.security.jwt.claims.impl.ExactMatchClaimsValidator"
+        p:claimName="client_id">
+        <property name="valueToMatchLookupStrategy">
+            <bean parent="shibboleth.BiFunctions.Expression"
+                c:expression="#custom.apply(#input1.getInboundMessageContext()) == null ? null : #custom.apply(#input1.getInboundMessageContext()).toString()"
+                p:customObject-ref="shibboleth.ClientIDLookupStrategy" />
+        </property>
+    </bean>
+
     <bean id="AudienceClaimsValidator"
         class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
          <property name="audienceLookupStrategy">
@@ -284,35 +259,62 @@
         </property>
     </bean>
 
-    <util:list id="PlainClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
-        <ref bean="ExpiryClaimsValidator" />
-        <ref bean="NotBeforeClaimsValidator" />
-    </util:list>
-
     <util:list id="SignedClaimsValidators" value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
         <ref bean="ExpiryClaimsValidator" />
         <ref bean="NotBeforeClaimsValidator" />
         <ref bean="IssuerClaimsValidator" />
+        <ref bean="ClientIdClaimsValidator" />
         <ref bean="AudienceClaimsValidator" />
     </util:list>
 
+    <bean id="UseOnlyRequestObjectCondition" parent="shibboleth.BiConditions.Expression" c:expression="#input1 != null and #input1.specifiesRequestObject()" />
+
     <bean id="ValidateRedirectURI" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateRedirectURI"
         scope="prototype"
         p:requireRequestedValue="true"
-        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}">
+        <property name="redirectURILookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestRedirectURILookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+    </bean>
 
     <bean id="ValidateResponseType" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateResponseType"
         scope="prototype"
-        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}"/>
+        p:unregisteredClientPolicyEnforcer="#{getObject('shibboleth.oidc.UnregisteredClientPolicyEnforcer') ?: getObject('shibboleth.oidc.DefaultUnregisteredClientPolicyEnforcer')}">
+        <property name="requestedResponseTypeLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseTypeLookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+    </bean>
 
     <bean id="ValidateResponseMode" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateResponseMode"
-        scope="prototype" />
+        scope="prototype">
+        <property name="requestedResponseModeLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestResponseModeLookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+    </bean>
 
     <bean id="ValidateCodeChallenge" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.ValidateCodeChallenge"
-        scope="prototype" />
+        scope="prototype">
+        <property name="codeChallengeLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeLookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+        <property name="codeChallengeMethodLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestCodeChallengeMethodLookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+    </bean>
 
     <bean id="StoreDPoPProofKeyThumbprint" class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.StoreDPoPProofKeyThumbprint"
-        scope="prototype" />
+        scope="prototype">
+        <property name="dpopJktLookupStrategy">
+            <bean class="net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultRequestDPoPJktLookupFunction"
+                p:useOnlyRequestObjectPredicate-ref="UseOnlyRequestObjectCondition"/>
+        </property>
+    </bean>
 
     <bean id="FormOutboundMessage"
         class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.FormOutbounPushedAuthorizationResponseMessage"
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
index 0258494d..82714ccf 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeFlowTest.java
@@ -18,20 +18,25 @@ import java.io.IOException;
 import java.net.URISyntaxException;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
+import java.time.Instant;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
 import org.opensaml.storage.RevocationCache;
 import org.opensaml.storage.StorageService;
+import org.opensaml.storage.impl.MemoryStorageService;
+import org.opensaml.storage.impl.StorageServiceReplayCache;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jwt.JWT;
@@ -43,6 +48,7 @@ import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriDeserializationFunction;
 import net.shibboleth.idp.session.SessionException;
 import net.shibboleth.oidc.security.credential.JWKCredential;
 import net.shibboleth.shared.collection.Pair;
@@ -72,10 +78,25 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
     @Qualifier("shibboleth.oidc.RevocationCache")
     private RevocationCache revocationCache;
 
+    private DefaultPushedAuthorizationRequestUriDeserializationFunction statelessDeserializer;
+
     public PushedAuthorizeFlowTest() {
         super(FLOW_ID);
     }
 
+    @BeforeMethod
+    public void initDeserializer() throws ComponentInitializationException {
+        final StorageServiceReplayCache replayCache = new StorageServiceReplayCache();
+        final MemoryStorageService storageService = new MemoryStorageService();
+        storageService.setId("mockId");
+        storageService.initialize();
+        replayCache.setStorage(storageService);
+        statelessDeserializer = new DefaultPushedAuthorizationRequestUriDeserializationFunction();
+        statelessDeserializer.setDataSealer(getDataSealer());
+        statelessDeserializer.setObjectMapper(new ObjectMapper());
+        statelessDeserializer.setReplayCache(replayCache);
+    }
+
     @AfterMethod
     public void tearDown() throws IOException {
         removeMetadata(storageService, clientId);
@@ -102,7 +123,6 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         storeMetadata(storageService, clientId, clientSecret, scope);
         setBasicAuth(clientId, clientSecret + "X");
         final Map<String, String> requestParams = createRequestParameters(clientId);
-
         setHttpFormRequest("POST", requestParams);
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
@@ -279,8 +299,31 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
         setBasicAuth(clientId, clientSecret);
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "profile")
+                .build();
+        final PlainJWT requestObject = new PlainJWT(ro);
+        setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null, requestObject.serialize()));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCPlainRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
                 .claim("redirect_uri", "https://example.org/cb")
                 .claim("response_type", "code")
+                .claim("scope", "openid profile")
                 .build();
         final PlainJWT requestObject = new PlainJWT(ro);
         setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null, requestObject.serialize()));
@@ -300,32 +343,63 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
 
     @Test
     public void testOIDCNoResponseTypeRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
-            DataSealerException, ComponentInitializationException {
+            DataSealerException, ComponentInitializationException, JOSEException {
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
         setBasicAuth(clientId, clientSecret);
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
                 .claim("redirect_uri", "https://example.org/cb")
+                .claim("scope", "openid profile")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
         setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", null,
                 requestObject.serialize()));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
     }
 
+    @Test
+    public void testOIDCResponseTypeInParamsWithRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", "code",
+                requestObject.serialize()));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
     @Test
     public void testOIDCResponseTypeInRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
-            DataSealerException, ComponentInitializationException {
+            DataSealerException, ComponentInitializationException, JOSEException {
         storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
         setBasicAuth(clientId, clientSecret);
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
                 .claim("response_type", "code")
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("scope", "openid profile")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
-        setHttpFormRequest("POST", createRequestParameters(clientId, "openid profile", null,
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        setHttpFormRequest("POST", createRequestParameters(clientId, null, null,
                 requestObject.serialize()));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+        assertSuccessResponse(result, clientId);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
     }
 
     @Test
@@ -345,6 +419,7 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         setBasicAuth(clientId, clientSecret);
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
                 .claim("iss", clientId)
+                .claim("client_id", clientId)
                 .claim("aud", issuer)
                 .claim("redirect_uri", "https://example.org/cb")
                 .build();
@@ -354,6 +429,24 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
     }
 
+    @Test
+    public void testOAuth2ResponseTypeInParamsWithRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        setHttpFormRequest("POST", createRequestParameters(clientId, "profile", "code",
+                requestObject.serialize()));
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
     @Test
     public void testOAuth2ResponseTypeInRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
             DataSealerException, ComponentInitializationException, JOSEException {
@@ -361,29 +454,328 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         setBasicAuth(clientId, clientSecret);
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
                 .claim("iss", clientId)
+                .claim("client_id", clientId)
                 .claim("aud", issuer)
                 .claim("redirect_uri", "https://example.org/cb")
                 .claim("response_type", "code")
                 .build();
         final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
-        setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null,
+        setHttpFormRequest("POST", createRequestParameters(clientId, "profile", "code",
                 requestObject.serialize()));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
-        assertSuccessResponse(result, clientId);
         final PushedAuthorizationSuccessResponse response =
                 parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
         verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
     }
 
+    @SuppressWarnings("null")
+    @Test
+    public void testOAuth2ROClaimsSetContents() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("custom2", "custom2Value")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertSuccessResponse(result, clientId);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        final Map<String,Object> object = statelessDeserializer.apply(null, response.getRequestURI());
+        Assert.assertNotNull(object);
+        Assert.assertEquals(object.get("redirect_uri"), "https://example.org/cb");
+        Assert.assertEquals(object.get("response_type"), "code");
+        Assert.assertNull(object.get("scope"));
+        Assert.assertNull(object.get("custom1"));
+        Assert.assertEquals(object.get("custom2"), "custom2Value");
+    }
+
+    @SuppressWarnings("null")
+    @Test
+    public void testOIDCROClaimsSetContents() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .claim("custom2", "custom2Value")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertSuccessResponse(result, clientId);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        final Map<String,Object> object = statelessDeserializer.apply(null, response.getRequestURI());
+        Assert.assertNotNull(object);
+        Assert.assertEquals(object.get("redirect_uri"), "https://example.org/cb");
+        Assert.assertEquals(object.get("response_type"), "code");
+        Assert.assertEquals(object.get("scope"), "openid profile");
+        Assert.assertNull(object.get("custom1"));
+        Assert.assertEquals(object.get("custom2"), "custom2Value");
+    }
+
+    @Test
+    public void testOAuth2ROClientIdMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId + "2", "profile", null,
+                requestObject.serialize());
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOAuth2ROClientIdMatchRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId + "2")
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROClientIdMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId + "2", "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROClientIdMatchRO() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId + "2")
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROClientIdMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROIssMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId + "2")
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROAudMatch() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer + "2")
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROIssMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROAudMissing() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCROExpired() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("exp", Instant.now().minusSeconds(300).toEpochMilli() / 1000)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
+    @Test
+    public void testOIDCRONotYetValid() throws IOException, NoSuchAlgorithmException, URISyntaxException,
+            DataSealerException, ComponentInitializationException, JOSEException {
+        storeMetadata(storageService, clientId, clientSecret, scope, "https://example.org/cb");
+        setBasicAuth(clientId, clientSecret);
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("client_id", clientId)
+                .claim("aud", issuer)
+                .claim("nbf", Instant.now().plusSeconds(300).toEpochMilli() / 1000)
+                .claim("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .claim("scope", "openid profile")
+                .build();
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
+        final Map<String, String> requestParams = createRequestParameters(clientId, "profile", null,
+                requestObject.serialize());
+        requestParams.put("custom1", "custom1Value");
+        setHttpFormRequest("POST", requestParams);
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+    }
+
     @Factory
     public Object[] createRequestObjectSecurityTests() {
         return new Object[] {
-                new PushedAuthorizeRequestObjectJWSTest(true),
                 new PushedAuthorizeRequestObjectJWSTest(false),
-                new PushedAuthorizeRequestObjectJWETest(false, false),
-                new PushedAuthorizeRequestObjectJWETest(false, true),
-                new PushedAuthorizeRequestObjectJWETest(true, false),
-                new PushedAuthorizeRequestObjectJWETest(true, true)
+                new PushedAuthorizeRequestObjectJWETest(false),
+                new PushedAuthorizeRequestObjectJWETest(true)
                 };
     }
 
@@ -444,7 +836,9 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         if (responseType != null) {
             result.put("response_type", responseType);
         }
-        result.put("scope", scope);
+        if (scope != null) {
+            result.put("scope", scope);
+        }
         result.put("redirect_uri", "https://example.org/cb");
         if (requestObject != null) {
             result.put("request", requestObject);
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
index a2c0840e..a6b1de98 100644
--- a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/PushedAuthorizeRequestObjectJWETest.java
@@ -49,19 +49,15 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
 
     String defaultClientIdEncryptionEnforced = "mockClientIdRequestObjectEncryptionEnforced";
 
-    public PushedAuthorizeRequestObjectJWETest(final boolean testSigned, final boolean encryptionOptional) {
-        super(JWT_FETCHING_TYPE.REQUEST_OBJECT, PushedAuthorizeFlowTest.FLOW_ID, testSigned, encryptionOptional);
+    public PushedAuthorizeRequestObjectJWETest(final boolean encryptionOptional) {
+        super(JWT_FETCHING_TYPE.REQUEST_OBJECT, PushedAuthorizeFlowTest.FLOW_ID, true, encryptionOptional);
     }
 
     @Override @Test
     public void testJwtEncryption_noSigAlgNorEncSpecified() throws Exception {
         // use plain request object
         final JWT jwt = obtainRequestObject(null, null, null, null, null, null);
-        if (encryptionOptional) {
-            assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
-        } else {
-            assertErrorRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
-        }
+        assertErrorRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
     }
 
     @Test
@@ -85,23 +81,13 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
 
     @Test
     public void testJwtEncryption_noSigAlgNorEncSpecified_encryptedRequestObject() throws Exception {
-        if (testSignedJwt) {
-            for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
-                for (final JWEAlgorithm jwe : JWE_ALGORITHMS) {
-                    for (final EncryptionMethod method : ENCRYPTION_METHODS) {
-                        final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe),
-                                getSigningKey(jwsAlgorithm), jwsAlgorithm, jwe, method);
-                        assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B,
-                                getSignatureVerificationKey(jwsAlgorithm));
-                    }
-                }
-            }
-        } else {
+        for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
             for (final JWEAlgorithm jwe : JWE_ALGORITHMS) {
                 for (final EncryptionMethod method : ENCRYPTION_METHODS) {
-                    final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe), null,
-                            null, jwe, method);
-                    assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B, null);
+                    final JWT jwt = obtainRequestObject(defaultClientSecret64B, getProviderEncryptionKey(jwe),
+                            getSigningKey(jwsAlgorithm), jwsAlgorithm, jwe, method);
+                    assertSuccessRequestObjectResponse(jwt.serialize(), null, null, null, defaultClientSecret64B,
+                            getSignatureVerificationKey(jwsAlgorithm));
                 }
             }
         }
@@ -118,16 +104,11 @@ public class PushedAuthorizeRequestObjectJWETest extends IssuedEncryptedJWTTest
     }
 
     protected void assertSecretBasedEncryption(final JWEAlgorithm jweAlgorithm, final EncryptionMethod method) {
-        if (testSignedJwt) {
-            for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
-                final JWT jwt = obtainRequestObject(defaultClientSecret64B, null, getSigningKey(jwsAlgorithm),
-                        jwsAlgorithm, jweAlgorithm, method);
-                assertSuccessRequestObjectResponse(jwt.serialize(), jwsAlgorithm, jweAlgorithm, method,
-                        defaultClientSecret64B, getSignatureVerificationKey(jwsAlgorithm));
-            }
-        } else {
-            final JWT jwt = obtainRequestObject(defaultClientSecret, null, null, null, jweAlgorithm, method);
-            assertSuccessRequestObjectResponse(jwt.serialize(), null, jweAlgorithm, method, defaultClientSecret, null);
+        for (final JWSAlgorithm jwsAlgorithm : JWS_ALGORITHMS) {
+            final JWT jwt = obtainRequestObject(defaultClientSecret64B, null, getSigningKey(jwsAlgorithm),
+                    jwsAlgorithm, jweAlgorithm, method);
+            assertSuccessRequestObjectResponse(jwt.serialize(), jwsAlgorithm, jweAlgorithm, method,
+                    defaultClientSecret64B, getSignatureVerificationKey(jwsAlgorithm));
         }
     }
     

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


More information about the commits mailing list