[java-idp-oidc] branch main updated: JOIDC-217 - Support for OAuth2 JWT-Secured Authorization Request (JAR)

Henri Mikkonen henri.mikkonen at iki.fi
Wed Jun 19 12:00:17 UTC 2024


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=95928d3e0997dc23db156fb4c43c6303895d80c6

The following commit(s) were added to refs/heads/main by this push:
     new 95928d3e JOIDC-217 - Support for OAuth2 JWT-Secured Authorization Request (JAR)
95928d3e is described below

commit 95928d3e0997dc23db156fb4c43c6303895d80c6
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Wed Jun 19 14:59:48 2024 +0300

    JOIDC-217 - Support for OAuth2 JWT-Secured Authorization Request (JAR)
    
    https://shibboleth.atlassian.net/browse/JOIDC-217
    
    - Changed authorization/authentication request lookup functions to exploit a predicate for only using request object values
     - By default the predicate returns true if the request is not OIDC authentication request and contains request object
    - Updated authorize and PAR flows to enforce request object signing whenever non-OIDC authorization request contains request object
    - Improved flow tests
---
 ...AbstractAuthorizationRequestLookupFunction.java | 47 +++++++++++++++++++++-
 .../DefaultRequestAudienceLookupFunction.java      |  2 +-
 .../DefaultRequestCodeChallengeLookupFunction.java |  3 +-
 ...ltRequestCodeChallengeMethodLookupFunction.java |  3 +-
 .../DefaultRequestDPoPJktLookupFunction.java       |  5 ++-
 .../DefaultRequestLoginHintLookupFunction.java     |  2 +-
 .../DefaultRequestMaxAgeLookupFunction.java        |  3 +-
 .../DefaultRequestNonceLookupFunction.java         |  2 +-
 .../DefaultRequestRedirectURILookupFunction.java   |  4 +-
 .../DefaultRequestResponseModeLookupFunction.java  |  2 +-
 .../DefaultRequestResponseTypeLookupFunction.java  |  2 +-
 .../DefaultRequestStateLookupFunction.java         |  2 +-
 .../DefaultRequestedAcrLookupFunction.java         |  2 +-
 .../DefaultRequestedClaimsLookupFunction.java      |  2 +-
 .../DefaultRequestedPromptLookupFunction.java      |  2 +-
 .../DefaultRequestedScopeLookupFunction.java       |  2 +-
 .../DefaultUseOnlyRequestObjectPredicate.java      | 39 ++++++++++++++++++
 .../DefaultRequestedScopeLookupFunctionTest.java   |  3 +-
 .../pushed-authorization-beans.xml                 | 26 ++++++++++--
 .../idp/flows/oidc/authorize/authorize-beans.xml   | 33 +++++++++++++--
 .../oidc/op/profile/flow/AuthorizeFlowTest.java    | 34 +++++++++++++---
 .../op/profile/flow/PushedAuthorizeFlowTest.java   | 30 ++++++++++++--
 22 files changed, 216 insertions(+), 34 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractAuthorizationRequestLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractAuthorizationRequestLookupFunction.java
index 5d0577fb..1915146c 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractAuthorizationRequestLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/AbstractAuthorizationRequestLookupFunction.java
@@ -14,11 +14,14 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.context.navigate;
 
+import java.util.function.BiPredicate;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
 
 import com.nimbusds.jwt.JWT;
 import com.nimbusds.oauth2.sdk.AuthorizationRequest;
@@ -26,6 +29,7 @@ import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
 import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
 
 /**
  * A Abstract function extended by lookups searching fields from authorization request.
@@ -37,12 +41,18 @@ import net.shibboleth.shared.logic.Constraint;
 public abstract class AbstractAuthorizationRequestLookupFunction<T>
         implements ContextDataLookupFunction<ProfileRequestContext,T> {
 
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(AbstractAuthorizationRequestLookupFunction.class);
+
     /** Request. */
     @Nullable private JWT requestObject;
 
     /** The request message class to verify that the actual request is an instance of. */
     @Nonnull private final Class<? extends AuthorizationRequest> messageClass;
 
+    /** The predicate to decide if the parameter values may only be set in the request object. */
+    @Nonnull private BiPredicate<AuthorizationRequest, String> useOnlyRequestObjectPredicate;
+
     /**
      * Constructor.
      */
@@ -57,6 +67,19 @@ public abstract class AbstractAuthorizationRequestLookupFunction<T>
      */
     protected AbstractAuthorizationRequestLookupFunction(@Nonnull final Class<? extends AuthorizationRequest> clazz) {
         messageClass = Constraint.isNotNull(clazz, "Message class cannot be null");
+        useOnlyRequestObjectPredicate = new DefaultUseOnlyRequestObjectPredicate();
+    }
+
+    /**
+     * Set the predicate to decide if the parameter values may only be set in the request object.
+     * 
+     * @param predicate the predicate to decide if the parameter values may only be set in the request object.
+     * 
+     * @since 4.2.0
+     */
+    public void setUseOnlyRequestObjectPredicate(@Nonnull final BiPredicate<AuthorizationRequest, String> predicate) {
+        useOnlyRequestObjectPredicate = Constraint.isNotNull(predicate,
+                "Use only request object predicate cannot be null");
     }
 
     /**
@@ -102,5 +125,27 @@ public abstract class AbstractAuthorizationRequestLookupFunction<T>
     @Nullable public JWT getRequestObject() {
         return requestObject;
     }
-    
+
+    /**
+     * Check if the given {@link AuthorizationRequest} and parameter should only be set in the request object. It's
+     * tested with {@link #useOnlyRequestObjectPredicate}: if it returns true, the default result is returned
+     * instead of the parameter value.
+     * 
+     * @param authorizationRequest the authorization request being processed.
+     * @param parameterName the parameter name.
+     * @param parameterValue the value found from the parameters.
+     * @param defaultResult the default value to be used if the request contained OAuth2 JAR request object.
+     * @return the parameter value to be used, depending on the authorization request message contents.
+     * 
+     * @since 4.2.0
+     */
+    @Nullable protected T checkRequestObject(@Nonnull final AuthorizationRequest authorizationRequest,
+            @Nonnull final String parameterName, @Nullable final T parameterValue, @Nullable T defaultResult) {
+        if (useOnlyRequestObjectPredicate.test(authorizationRequest, parameterName)) {
+            log.debug("Ignoring value {}={} from authorization request as request object is defined", parameterName,
+                    parameterValue);
+            return defaultResult;
+        }
+        return parameterValue;
+    }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java
index 0b7a0776..51ade30d 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestAudienceLookupFunction.java
@@ -66,7 +66,7 @@ public class DefaultRequestAudienceLookupFunction extends AbstractAuthorizationR
             return null;
         }
 
-        return req.getCustomParameter(CUSTOM_RESOURCE_PARAM_NAME);
+        return checkRequestObject(req, "resource", req.getCustomParameter(CUSTOM_RESOURCE_PARAM_NAME), null);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeLookupFunction.java
index 4ecd3e94..8ff2d1ba 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeLookupFunction.java
@@ -54,7 +54,8 @@ public class DefaultRequestCodeChallengeLookupFunction extends AbstractAuthoriza
             log.error("Unable to parse code challenge from request object code_challenge value");
             return null;
         }
-        return req.getCodeChallenge() == null ? null : req.getCodeChallenge().getValue();
+        return req.getCodeChallenge() == null ? null : 
+            checkRequestObject(req, "code_challenge", req.getCodeChallenge().getValue(), null);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeMethodLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeMethodLookupFunction.java
index e68e0e55..be2ea11f 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeMethodLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestCodeChallengeMethodLookupFunction.java
@@ -55,7 +55,8 @@ public class DefaultRequestCodeChallengeMethodLookupFunction
             log.error("Unable to parse code challenge method  from request object code_challenge_method value");
             return null;
         }
-        return req.getCodeChallengeMethod() == null ? null : req.getCodeChallengeMethod().getValue();
+        return req.getCodeChallengeMethod() == null ? null : 
+            checkRequestObject(req, "code_challenge_method", req.getCodeChallengeMethod().getValue(), null);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestDPoPJktLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestDPoPJktLookupFunction.java
index 68bc7463..ec4f8c59 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestDPoPJktLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestDPoPJktLookupFunction.java
@@ -56,8 +56,9 @@ public class DefaultRequestDPoPJktLookupFunction extends AbstractAuthorizationRe
             log.error("Unable to parse dpop_jkt from request object dpop_jkt value");
             return null;
         }
-        return req.getDPoPJWKThumbprintConfirmation() == null ?
-                null : req.getDPoPJWKThumbprintConfirmation().getValue().toString();
+        return req.getDPoPJWKThumbprintConfirmation() == null ? null :
+            checkRequestObject(req, "dpop_jkt", req.getDPoPJWKThumbprintConfirmation().getValue().toString(),
+                    null);
     }
 
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestLoginHintLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestLoginHintLookupFunction.java
index 99628838..cb83710c 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestLoginHintLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestLoginHintLookupFunction.java
@@ -52,6 +52,6 @@ public class DefaultRequestLoginHintLookupFunction extends AbstractAuthenticatio
             log.error("Unable to parse login hint from request object login_hint value");
             return null;
         }
-        return req.getLoginHint();
+        return checkRequestObject(req, "login_hint", req.getLoginHint(), null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestMaxAgeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestMaxAgeLookupFunction.java
index 17892346..2f3f398c 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestMaxAgeLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestMaxAgeLookupFunction.java
@@ -48,6 +48,7 @@ public class DefaultRequestMaxAgeLookupFunction extends AbstractAuthenticationRe
             log.error("Unable to parse state from request object state value");
             return null;
         }
-        return req.getMaxAge() == -1 ? null : Duration.ofSeconds(req.getMaxAge());
+        return req.getMaxAge() == -1 ? null :
+            checkRequestObject(req, "max_age", Duration.ofSeconds(req.getMaxAge()), null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestNonceLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestNonceLookupFunction.java
index e7fb2d28..2a060f6b 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestNonceLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestNonceLookupFunction.java
@@ -54,7 +54,7 @@ public class DefaultRequestNonceLookupFunction extends AbstractAuthenticationReq
             return null;
         }
         if (req.getNonce() != null) {
-            return new Nonce(req.getNonce().getValue());
+            return checkRequestObject(req, "nonce", new Nonce(req.getNonce().getValue()), null);
         }
         return null;
     }
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestRedirectURILookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestRedirectURILookupFunction.java
index d252849a..a77b41ac 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestRedirectURILookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestRedirectURILookupFunction.java
@@ -35,7 +35,7 @@ public class DefaultRequestRedirectURILookupFunction extends AbstractAuthorizati
     /** Class logger. */
     @Nonnull
     private Logger log = LoggerFactory.getLogger(DefaultRequestRedirectURILookupFunction.class);
-    
+
     /** {@inheritDoc} */
     @Override
     protected URI doLookup(@Nonnull final AuthorizationRequest req) {
@@ -54,6 +54,6 @@ public class DefaultRequestRedirectURILookupFunction extends AbstractAuthorizati
             log.error("Unable to parse redirect URI from request object redirect_uri value");
             return null;
         }
-        return req.getRedirectionURI();
+        return checkRequestObject(req, "redirect_uri", req.getRedirectionURI(), null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseModeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseModeLookupFunction.java
index 3083bf8c..38971a64 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseModeLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseModeLookupFunction.java
@@ -58,6 +58,6 @@ public class DefaultRequestResponseModeLookupFunction
         if (req.getResponseMode() == null) {
             return null;
         }
-        return new ResponseMode(req.getResponseMode().getValue());
+        return checkRequestObject(req, "response_mode", new ResponseMode(req.getResponseMode().getValue()), null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseTypeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseTypeLookupFunction.java
index c086ab97..4fb936c1 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseTypeLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestResponseTypeLookupFunction.java
@@ -53,6 +53,6 @@ public class DefaultRequestResponseTypeLookupFunction
         if (requestedType != null) {
             result.addAll(requestedType);
         }
-        return result;
+        return checkRequestObject(req, "response_type", result, new ResponseType());
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestStateLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestStateLookupFunction.java
index 36eb00bc..7b7e40c2 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestStateLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestStateLookupFunction.java
@@ -56,6 +56,6 @@ public class DefaultRequestStateLookupFunction extends AbstractAuthorizationRequ
         if (req.getState() == null) {
             return null;
         }
-        return new State(req.getState().getValue());
+        return checkRequestObject(req, "state", new State(req.getState().getValue()), null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedAcrLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedAcrLookupFunction.java
index 2fcbd20c..4643618d 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedAcrLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedAcrLookupFunction.java
@@ -59,6 +59,6 @@ public class DefaultRequestedAcrLookupFunction extends AbstractAuthenticationReq
         }
         final List<ACR> requestParameterAcr = new ArrayList<ACR>();
         requestParameterAcr.addAll(req.getACRValues());
-        return requestParameterAcr;
+        return checkRequestObject(req, "acr_values", requestParameterAcr, null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedClaimsLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedClaimsLookupFunction.java
index a4b5969b..1dc0d6d2 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedClaimsLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedClaimsLookupFunction.java
@@ -60,7 +60,7 @@ public class DefaultRequestedClaimsLookupFunction
             log.error("unable to parse claims claim {}", e.getMessage());
             return null;
         }
-        return req.getOIDCClaims();
+        return checkRequestObject(req, "claims", req.getOIDCClaims(), null);
     }
     
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedPromptLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedPromptLookupFunction.java
index 0edf0e53..98770ac7 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedPromptLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedPromptLookupFunction.java
@@ -52,6 +52,6 @@ public class DefaultRequestedPromptLookupFunction extends AbstractAuthorizationR
         }
         final Prompt requestParameterPrompt = new Prompt();
         requestParameterPrompt.addAll(req.getPrompt());
-        return requestParameterPrompt;
+        return checkRequestObject(req, "prompt", requestParameterPrompt, null);
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunction.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunction.java
index d6acbef5..20545fcb 100644
--- a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunction.java
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunction.java
@@ -52,6 +52,6 @@ public class DefaultRequestedScopeLookupFunction extends AbstractAuthorizationRe
         if (requestParameterScope != null) {
             result.addAll(requestParameterScope);
         }
-        return result;
+        return checkRequestObject(req, "scope", result, new Scope());
     }
 }
\ No newline at end of file
diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultUseOnlyRequestObjectPredicate.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultUseOnlyRequestObjectPredicate.java
new file mode 100644
index 00000000..0822c6d4
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultUseOnlyRequestObjectPredicate.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed 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.context.navigate;
+
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.oauth2.sdk.AuthorizationRequest;
+import com.nimbusds.openid.connect.sdk.AuthenticationRequest;
+
+/**
+ * Default predicate for using only request object parameters if the authorization request (1) contains request object
+ * and (2) is not OIDC authentication request.
+ */
+public class DefaultUseOnlyRequestObjectPredicate implements BiPredicate<AuthorizationRequest, String> {
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean test(@Nullable final AuthorizationRequest authorizationRequest, @Nullable final String parameter) {
+        if (authorizationRequest == null) {
+            return false;
+        }
+        return (!(authorizationRequest instanceof AuthenticationRequest)
+                && authorizationRequest.specifiesRequestObject());
+    }
+}
diff --git a/idp-oidc-extension-api/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunctionTest.java b/idp-oidc-extension-api/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunctionTest.java
index bbc3a49e..c3f778cf 100644
--- a/idp-oidc-extension-api/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunctionTest.java
+++ b/idp-oidc-extension-api/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/context/navigate/DefaultRequestedScopeLookupFunctionTest.java
@@ -35,6 +35,7 @@ public class DefaultRequestedScopeLookupFunctionTest extends BaseDefaultRequestL
     @BeforeMethod
     protected void setUp() throws Exception {
         lookup = new DefaultRequestedScopeLookupFunction();
+        oidcCtx.setRequestObject(null);
     }
 
     @Test
@@ -102,7 +103,7 @@ public class DefaultRequestedScopeLookupFunctionTest extends BaseDefaultRequestL
 
     @Test
     public void testWithEmptyScopeInRequest() {
-        AuthorizationRequest req = new AuthorizationRequest.Builder(URI.create("https://example.com/callback"),
+        AuthorizationRequest req = new AuthorizationRequest.Builder(ResponseType.CODE,
                 new ClientID("000123")).state(new State()).scope(new Scope()).build();
         msgCtx.setMessage(req);
         final Scope result = lookup.apply(prc);
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 4d9c4461..213e9c47 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
@@ -210,12 +210,32 @@
                     <ref bean="RequestObjectSignedCondition" />
                 </constructor-arg>
                 <constructor-arg>
-                    <bean parent="shibboleth.Conditions.AND">
+                    <bean parent="shibboleth.Conditions.OR">
                         <constructor-arg>
-                            <ref bean="UseRequestObjectPredicate" />
+                            <bean parent="shibboleth.Conditions.AND">
+                                <constructor-arg>
+                                    <ref bean="UseRequestObjectPredicate" />
+                                </constructor-arg>
+                                <constructor-arg>
+                                    <ref bean="SignRequestObjectPredicate" />
+                                </constructor-arg>
+                            </bean>
                         </constructor-arg>
                         <constructor-arg>
-                            <ref bean="SignRequestObjectPredicate" />
+                            <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>
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 2c359a4d..2ce554a8 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
@@ -300,12 +300,39 @@
                     <ref bean="RequestObjectSignedCondition" />
                 </constructor-arg>
                 <constructor-arg>
-                    <bean parent="shibboleth.Conditions.AND">
+                    <bean parent="shibboleth.Conditions.OR">
                         <constructor-arg>
-                            <ref bean="UseRequestObjectPredicate" />
+                            <bean parent="shibboleth.Conditions.AND">
+                                <constructor-arg>
+                                    <ref bean="UseRequestObjectPredicate" />
+                                </constructor-arg>
+                                <constructor-arg>
+                                    <ref bean="SignRequestObjectPredicate" />
+                                </constructor-arg>
+                            </bean>
                         </constructor-arg>
                         <constructor-arg>
-                            <ref bean="SignRequestObjectPredicate" />
+                            <bean parent="shibboleth.Conditions.AND">
+                                <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 id="RequestObjectNotVerified" parent="shibboleth.Conditions.Expression"
+                                                c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectValidated() == false" />
+                                        </constructor-arg>
+                                    </bean>
+                                </constructor-arg>
+                                <constructor-arg>
+                                    <bean parent="shibboleth.Conditions.NOT">
+                                        <constructor-arg>
+                                            <ref bean="ScopeContainsOpenid" />
+                                        </constructor-arg>
+                                    </bean>
+                                </constructor-arg>
+                            </bean>
                         </constructor-arg>
                     </bean>
                 </constructor-arg>
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 86aa61d2..608ec2ea 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
@@ -1811,6 +1811,25 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
         assertRequestObjectError(new PlainJWT(ro));
     }
 
+    @Test
+    public void testWithPlainOAuth2ReqObject() throws IOException, SessionException, JOSEException {
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("redirect_uri", redirectUri)
+                .claim("response_type", "code")
+                .claim("scope", "profile")
+                .build();
+        final PlainJWT requestObject = new PlainJWT(ro);
+        request.setMethod("GET");
+        setRequestParameters(List.of(new Pair<>("client_id", "mockClientId"),
+                new Pair<>("request", requestObject.serialize())));
+        storeMetadata(storageService, clientId, clientSecret, scope, redirectUri);
+
+        initializeThreadLocals();
+
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+    }
+
     @Test
     public void testWitnOpenIDReqObjectResponseTypeParameter() throws IOException, SessionException {
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
@@ -1852,11 +1871,14 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testWithOAuth2ReqObjectResponseTypeParameter() throws IOException, SessionException {
+    public void testWithOAuth2ReqObjectResponseTypeParameter() throws IOException, SessionException, JOSEException {
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .claim("iss", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
                 .claim("response_type", "code")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
         request.setMethod("GET");
         setRequestParameters(List.of(new Pair<>("client_id", "mockClientId"),
                 new Pair<>("scope", "profile"),
@@ -1876,11 +1898,13 @@ public class AuthorizeFlowTest extends AbstractOidcFlowTest {
     }
 
     @Test
-    public void testWithOAuth2ReqObjectNoResponseType() throws IOException, SessionException {
+    public void testWithOAuth2ReqObjectNoResponseType() throws IOException, SessionException, JOSEException {
         final JWTClaimsSet ro = new JWTClaimsSet.Builder()
-                .claim("redirect_uri", redirectUri)
+                .claim("iss", clientId)
+                .claim("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
         request.setMethod("GET");
         setRequestParameters(List.of(new Pair<>("client_id", "mockClientId"),
                 new Pair<>("scope", "profile"),
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 048daadd..3880c86d 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
@@ -32,10 +32,12 @@ import org.testng.annotations.AfterMethod;
 import org.testng.annotations.Factory;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 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.OAuth2Error;
 import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
 import com.nimbusds.oauth2.sdk.Scope;
@@ -271,6 +273,21 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
         verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
     }
 
+    @Test
+    public void testOAuth2PlainRO() 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("redirect_uri", "https://example.org/cb")
+                .claim("response_type", "code")
+                .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 testOIDCNoResponseType() throws IOException, NoSuchAlgorithmException, URISyntaxException,
             DataSealerException, ComponentInitializationException {
@@ -323,13 +340,15 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
 
     @Test
     public void testOAuth2NoResponseTypeRO() 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("aud", issuer)
                 .claim("redirect_uri", "https://example.org/cb")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
         setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null, requestObject.serialize()));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
@@ -337,13 +356,16 @@ public class PushedAuthorizeFlowTest extends AbstractOidcClientAuthenticationFlo
 
     @Test
     public void testOAuth2ResponseTypeInRO() 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("aud", issuer)
+                .claim("redirect_uri", "https://example.org/cb")
                 .claim("response_type", "code")
                 .build();
-        final PlainJWT requestObject = new PlainJWT(ro);
+        final SignedJWT requestObject = createSecretJWT(ro, clientSecret);
         setHttpFormRequest("POST", createRequestParameters(clientId, "profile", null,
                 requestObject.serialize()));
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);

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


More information about the commits mailing list