[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation

Henri Mikkonen henri.mikkonen at iki.fi
Fri Feb 21 11:29:08 UTC 2025


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=4b2f71a7aee947c5a5dabbc89e4e432a5705a03c

The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
     new 4b2f71a7 JOIDC-222 - Support for OpenID Federation
4b2f71a7 is described below

commit 4b2f71a7aee947c5a5dabbc89e4e432a5705a03c
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Feb 21 13:28:27 2025 +0200

    JOIDC-222 - Support for OpenID Federation
    
    https://shibboleth.atlassian.net/browse/JOIDC-222
    
    Initial support for automatic registration via PAR endpoint
    - PAR requires either client authentication or signed request object for automatic registration
    - PAR endpoint stores the selected trust chain into the request_uri claims set
    - Authorize-flow was refactored to deserialize PAR-produced request_uri before metadata resolution
      - The previously selected trust chain can then be exploited during the metadata-lookup
      - Deserialization is done by new DeserializePushedRequest -action
        - Claims set is stored in OAuth2PushedAuthorizationRequestContext
      - SetRequestObjectToResponseContext exploits the deserialized claims set
      - Signed request object is always required with automatic registration
---
 .../OAuth2PushedAuthorizationRequestContext.java   |  60 ++++++++++
 .../profile/impl/DeserializedPushedRequest.java    | 128 +++++++++++++++++++++
 ...mOutbounPushedAuthorizationResponseMessage.java |  63 ++++++++--
 .../impl/SetRequestObjectToResponseContext.java    |  50 ++++----
 ...rizePreSelectedTrustChainIDsLookupStrategy.java |  52 +++++++++
 .../pushed-authorization-beans.xml                 |  23 +++-
 .../oidc/abstract-api/oidc-abstract-api-beans.xml  |   3 +
 .../idp/flows/oidc/authorize/authorize-beans.xml   |  54 +++++++--
 .../idp/flows/oidc/authorize/authorize-flow.xml    |   1 +
 .../oidc/metadata-lookup/metadata-lookup-beans.xml |   3 +-
 10 files changed, 395 insertions(+), 42 deletions(-)

diff --git a/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
new file mode 100644
index 00000000..a4e7fc1a
--- /dev/null
+++ b/idp-oidc-extension-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/messaging/context/OAuth2PushedAuthorizationRequestContext.java
@@ -0,0 +1,60 @@
+/*
+ * 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.oauth2.messaging.context;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+import org.opensaml.messaging.context.MessageContext;
+
+/**
+ * Subcontext carrying information for a pushed authorization request.
+ * 
+ * <p>This context appears as a subcontext of an inbound {@link MessageContext}.</p>
+ * 
+ * @since 4.3.0
+ */
+public class OAuth2PushedAuthorizationRequestContext extends BaseContext {
+
+    /** The claim set carried within pushed authorization request. */
+    @Nullable private Map<String,Object> claimsSet;
+
+    /**
+     * Get the claims set carried within pushed authorization request.
+     * 
+     * @return claims set
+     */
+    @Nullable public Map<String,Object> getClaimsSet() {
+        return claimsSet;
+    }
+
+    /**
+     * Set the claims set carried within pushed authorization request.
+     * 
+     * @param claims claims set
+     * 
+     * @return this context
+     */
+    @Nonnull public OAuth2PushedAuthorizationRequestContext setClaimsSet(
+            @Nullable final Map<String,Object> claims) {
+        claimsSet = claims;
+        
+        return this;
+    }
+
+}
\ 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/DeserializedPushedRequest.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializedPushedRequest.java
new file mode 100644
index 00000000..3ea0aee8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/DeserializedPushedRequest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.oauth2.profile.impl;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Attempts to deserialize the possibly existing request_uri value from the authorization request via configurable
+ * list of pushed authorization deserializers. If any of the deserializer is successful, the extracted claims set is
+ * stored to {@link OAuth2PushedAuthorizationRequestContext}.
+ */
+public class DeserializedPushedRequest extends AbstractOAuthAuthorizationRequestAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(DeserializedPushedRequest.class);
+
+    /** Lookup strategy for pushed authorization request context- */
+    @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+        pushedAuthorizationRequestContextLookupStrategy;
+
+    /** List of deserializers for OP-issued request_uri values. */
+    @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
+        pushedAuthorizationRequestUriDeserializers;
+
+    /** Request URI to be deserialized. */
+    @NonnullBeforeExec private URI requestUri;
+
+    /**
+     * Constructor.
+     */
+    public DeserializedPushedRequest() {
+        final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+                new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert parcls != null;
+        pushedAuthorizationRequestContextLookupStrategy = parcls;
+
+        pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+    }
+
+    /**
+     * Set the lookup strategy for pushed authorization request context.
+     * 
+     * @param strategy the lookup strategy for pushed authorization request context
+     */
+    public void setPushedAuthorizationRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
+        checkSetterPreconditions();
+        pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "Pushed authorization request context lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the list of deserializers for OP-issued request_uri values.
+     * 
+     * @param deserializers What to set.
+     */
+    public void setPushedAuthorizationRequestUriDeserializers(
+            @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+        checkSetterPreconditions();
+        pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
+                "List of request_uri deserializers cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+
+        requestUri = Optional.ofNullable(getAuthorizationRequest())
+                .map(request -> request.getRequestURI())
+                .orElse(null);
+
+        if (requestUri == null) {
+            log.debug("{} No request_uri value found, nothing to do", getLogPrefix());
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
+            pushedAuthorizationRequestUriDeserializers) {
+            final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext, requestUri);
+            if (claimsSet != null && !claimsSet.isEmpty()) {
+                pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext)
+                    .setClaimsSet(CollectionSupport.copyToMap(claimsSet));
+                log.debug("{} Pushed authorization request successfully deserialized and stored", getLogPrefix());
+                return;
+            }
+        }
+    }
+
+}
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 141cbe11..cb3916f8 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,6 +17,8 @@ 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;
@@ -34,6 +36,8 @@ import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
 import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
 
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.profile.config.navigate.PushedAuthorizationRequestUriClaimsSetManipulationStrategyLookupFunction;
 import net.shibboleth.oidc.profile.config.navigate.PushedAuthorizationRequestUriLifetimeLookupFunction;
@@ -67,6 +71,9 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
     @Nonnull private Map<String, BiFunction<ProfileRequestContext,Map<String,Object>,URI>>
         requestUriClaimsSetSerializationStrategies;
 
+    /** The strategy used to locate IDs for the selected trust chain. */
+    @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
+
     /** The request URI type to use. */
     @Nullable private String requestUriType;
 
@@ -88,6 +95,7 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
         requestUriClaimsSetManipulationStrategyLookupStrategy =
                 new PushedAuthorizationRequestUriClaimsSetManipulationStrategyLookupFunction();
         requestUriClaimsSetSerializationStrategies = CollectionSupport.emptyMap();
+        selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
     }
 
     /**
@@ -102,6 +110,19 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
                "Request URI serialization strategies cannot be null");
     }
 
+    /**
+     * Set the lookup strategy used to locate IDs for the selected trust chain.
+     * 
+     * @param strategy What to set
+     */
+    public void setSelectedTrustChainIDsLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
+        checkSetterPreconditions();
+
+        selectedTrustChainIDsLookupStrategy  = Constraint.isNotNull(strategy,
+                "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
+    }
+
     /** {@inheritDoc} */
     @Override
     protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -185,14 +206,7 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
         final OIDCAuthenticationResponseContext oidcContext = getOidcResponseContext();
         assert oidcContext != null;
 
-        final Map<String, Object> claimsSet;
-        final JWT requestObject = oidcContext.getRequestObject();
-        if (requestObject == null) {
-            assert requestMessage != null;
-            claimsSet = requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims();
-        } else {
-            claimsSet = requestObject.getJWTClaimsSet().getClaims();
-        }
+        final Map<String, Object> claimsSet = buildClaimsSet(profileRequestContext, oidcContext.getRequestObject());
 
         if (manipulationStrategy != null) {
             log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
@@ -210,7 +224,38 @@ public class FormOutbounPushedAuthorizationResponseMessage extends AbstractOAuth
         } else {
             log.debug("{} No manipulation strategy configured", getLogPrefix());
         }
-        assert claimsSet != null;
         return claimsSet;
     }
+
+    /**
+     * Build the claims set by exploiting the contents of the pushed authorization request form parameters or request
+     * object. If the RP was automatically registered, the trust chain is stored to the claims set via key
+     * {@link TokenClaimsSet#KEY_AUTO_REGISTERED_TRUST_CHAIN}. Otherwise the claim is guaranteed to not exist in the
+     * claims set.
+     * 
+     * @param profileRequestContext the profile request context to operate on
+     * @param requestObject the request object to be exploited if set
+     * @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,
+            @Nullable final JWT requestObject) throws ParseException {
+        final Map<String, Object> claimsSet;
+        if (requestObject == null) {
+            assert requestMessage != null;
+            claimsSet = new HashMap<>(requestMessage.getAuthorizationRequest().toJWTClaimsSet().getClaims());
+        } else {
+            claimsSet = new HashMap<>(requestObject.getJWTClaimsSet().getClaims());
+        }
+        if (claimsSet.containsKey(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN)) {
+            claimsSet.remove(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN);
+            log.warn("{} Removed {} from the request", getLogPrefix(), TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN);
+        }
+        final List<String> selectedTrustChain = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
+        if (selectedTrustChain != null && !selectedTrustChain.isEmpty()) {
+            claimsSet.put(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN, selectedTrustChain);
+            log.debug("{} Included selected trust chain {} the claims set", getLogPrefix(), selectedTrustChain);
+        }
+        return CollectionSupport.copyToMap(claimsSet);
+    }
 }
\ 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/SetRequestObjectToResponseContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
index c6153249..eddd45b4 100644
--- a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oauth2/profile/impl/SetRequestObjectToResponseContext.java
@@ -18,10 +18,10 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.text.ParseException;
-import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.BiFunction;
+import java.util.function.Function;
 import java.util.function.Predicate;
 
 import javax.annotation.Nonnull;
@@ -34,10 +34,12 @@ import org.apache.hc.core5.http.ClassicHttpRequest;
 import org.apache.hc.core5.http.ClassicHttpResponse;
 import org.apache.hc.core5.http.HttpStatus;
 import org.apache.hc.core5.http.io.entity.EntityUtils;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.messaging.encoder.AbstractMessageEncoder;
 import org.opensaml.profile.action.ActionSupport;
 import org.opensaml.profile.action.EventIds;
 import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 import org.opensaml.security.httpclient.HttpClientSecurityParameters;
 import org.opensaml.security.httpclient.HttpClientSecuritySupport;
 import org.slf4j.Logger;
@@ -53,11 +55,11 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
 import net.shibboleth.idp.plugin.oidc.op.encoding.impl.ResponseUtil;
 import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
 import net.shibboleth.idp.profile.IdPEventIds;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.profile.core.OidcEventIds;
 import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.primitive.LoggerFactory;
@@ -90,9 +92,9 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
     /** Object mapper used for pretty-printing JWT contents. */
     @NonnullAfterInit private ObjectMapper objectMapper;
 
-    /** List of deserializers for OP-issued request_uri values. */
-    @Nonnull private List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>>
-        pushedAuthorizationRequestUriDeserializers;
+    /** Lookup strategy for pushed authorization request context. */
+    @Nonnull private Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext>
+        pushedAuthorizationRequestContextLookupStrategy;
 
     /** Whether to require pushed authorization request to be used. */
     private boolean requirePushedAuthorization = false;
@@ -101,7 +103,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      * Constructor.
      */
     public SetRequestObjectToResponseContext() {
-        pushedAuthorizationRequestUriDeserializers = CollectionSupport.emptyList();
+        final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> parcls =
+                new ChildContextLookup<>(OAuth2PushedAuthorizationRequestContext.class, true).compose(
+                        new InboundMessageContextLookup());
+        assert parcls != null;
+        pushedAuthorizationRequestContextLookupStrategy = parcls;
     }
 
     /**
@@ -111,6 +117,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      *            client to use
      */
     public void setHttpClient(@Nonnull final HttpClient client) {
+        checkSetterPreconditions();
         httpClient = Constraint.isNotNull(client, "HttpClient cannot be null");
     }
 
@@ -121,6 +128,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      *            the new client security parameters
      */
     public void setHttpClientSecurityParameters(@Nullable final HttpClientSecurityParameters params) {
+        checkSetterPreconditions();
         httpClientSecurityParameters = params;
     }
 
@@ -130,6 +138,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      * @param predicate the predicate for enforcing the use of request objects
      */
     public void setRequestObjectEnforcedPredicate(@Nonnull final Predicate<ProfileRequestContext> predicate) {
+        checkSetterPreconditions();
         requestObjectEnforcedPredicate = Constraint.isNotNull(predicate,
                 "Request object enforced predicate annot be null");
     }
@@ -143,6 +152,7 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
      */
     public void setPushedAuthorizationRequestEnforcedPredicate(
             @Nonnull final Predicate<ProfileRequestContext> predicate) {
+        checkSetterPreconditions();
         pushedAuthorizationRequestEnforcedPredicate = Constraint.isNotNull(predicate,
                 "Pushed authorization request enforced predicate annot be null");
     }
@@ -160,17 +170,17 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
     }
 
     /**
-     * Set the list of deserializers for OP-issued request_uri values.
+     * Set the lookup strategy for pushed authorization request context.
      * 
-     * @param deserializers What to set.
+     * @param strategy the lookup strategy for pushed authorization request context
      * 
-     * @since 4.2.0
+     * @since 4.3.0
      */
-    public void setPushedAuthorizationRequestUriDeserializers(
-            @Nonnull final List<BiFunction<ProfileRequestContext,URI,Map<String,Object>>> deserializers) {
+    public void setPushedAuthorizationRequestContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, OAuth2PushedAuthorizationRequestContext> strategy) {
         checkSetterPreconditions();
-        pushedAuthorizationRequestUriDeserializers = Constraint.isNotNull(deserializers,
-                "List of request_uri deserializers cannot be null");
+        pushedAuthorizationRequestContextLookupStrategy = Constraint.isNotNull(strategy,
+                "Pushed authorization request context lookup strategy cannot be null");
     }
 
     /**
@@ -286,13 +296,11 @@ public class SetRequestObjectToResponseContext extends AbstractOAuthAuthorizatio
         }
         
         if (!authorized) {
-            for (final BiFunction<ProfileRequestContext,URI,Map<String,Object>> deserializer :
-                pushedAuthorizationRequestUriDeserializers) {
-                final Map<String,Object> claimsSet = deserializer.apply(profileRequestContext,
-                        authorizationRequest.getRequestURI());
-                if (claimsSet == null) {
-                    continue;
-                }
+            final Map<String,Object> claimsSet =
+                    Optional.ofNullable(pushedAuthorizationRequestContextLookupStrategy.apply(profileRequestContext))
+                        .map(ctx -> ctx.getClaimsSet())
+                        .orElse(null);
+            if (claimsSet != null && !claimsSet.isEmpty()) {
                 try {
                     final JWTClaimsSet jwtClaimsSet = JWTClaimsSet.parse(claimsSet);
                     oidcResponseContext.setRequestObject(new PlainJWT(jwtClaimsSet));
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java
new file mode 100644
index 00000000..34e4f74b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy.java
@@ -0,0 +1,52 @@
+/*
+ * 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.oidfed.profile.navigate;
+
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oauth2.messaging.context.OAuth2PushedAuthorizationRequestContext;
+import net.shibboleth.idp.plugin.oidc.op.token.support.TokenClaimsSet;
+
+/**
+ * Default strategy for looking up the entity IDs of the previously selected trust chain to be used in the authorization
+ * endpoint. The selected trust chain is fetched via {@link OAuth2PushedAuthorizationRequestContext#getClaimsSet()}.
+ */
+public class DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy
+    implements Function<ProfileRequestContext,List<String>> {
+
+    /** {@inheritDoc} */
+    @Override @Nullable
+    public List<String> apply(@Nullable final ProfileRequestContext input) {
+        final List<?> result =  Optional.ofNullable(input)
+                .filter(prc -> prc.getInboundMessageContext() != null)
+                .map(prc -> prc.ensureInboundMessageContext())
+                .filter(msgCtx -> msgCtx.containsSubcontext(OAuth2PushedAuthorizationRequestContext.class))
+                .map(msgCtx -> msgCtx.ensureSubcontext(OAuth2PushedAuthorizationRequestContext.class))
+                .map(parCtx -> parCtx.getClaimsSet())
+                .filter(Objects::nonNull)
+                .map(claimsSet -> claimsSet.get(TokenClaimsSet.KEY_AUTO_REGISTERED_TRUST_CHAIN))
+                .filter(List.class::isInstance)
+                .map(List.class::cast)
+                .orElse(null);
+        return result == null ? null : result.stream().filter(Objects::nonNull).map(obj -> obj.toString()).toList();
+    }
+}
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 0f69d394..1707e1ef 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,7 +150,28 @@
         </property>
     </bean>
 
-    <bean id="UseRequestObjectPredicate" class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
+    <bean id="UseRequestObjectPredicate" parent="shibboleth.Conditions.OR">
+        <constructor-arg>
+            <bean class="net.shibboleth.oidc.profile.config.logic.UseRequestObjectPredicate" />
+        </constructor-arg>
+        <constructor-arg>
+            <bean parent="shibboleth.Conditions.AND">
+                <constructor-arg>
+                    <bean id="NoneAuthenticationCondition" parent="shibboleth.Conditions.Expression"
+                        c:expression="#input.ensureInboundMessageContext().getMessage().getClientAuthentication() == null or T(com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod).NONE.equals(#input.ensureInboundMessageContext().getMessage().getClientAuthentication().getMethod())" />
+                </constructor-arg>
+                <constructor-arg>
+                    <bean id="IsAutomaticallyRegistered" parent="shibboleth.Conditions.Expression"
+                        c:expression="#custom.apply(#input) != null">
+                        <property name="customObject">
+                            <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy" />
+                        </property>
+                    </bean>
+                </constructor-arg>
+            </bean>
+        </constructor-arg>
+    </bean>
+
 
     <bean id="ValidateRequestObjectSignature" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
         scope="prototype" c:executionDirection="INBOUND">
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
index 83a13584..c6cce2eb 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api/oidc-abstract-api-beans.xml
@@ -82,4 +82,7 @@
         </property>
     </bean>
 
+    <bean id="shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultPreSelectedTrustChainIDsLookupStrategy" />
+
 </beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index 7bc77ec5..9a09cce2 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
@@ -116,6 +116,10 @@
         </property>
     </bean>
 
+    <bean id="DeserializedPushedRequest"
+        class="net.shibboleth.idp.plugin.oidc.op.oauth2.profile.impl.DeserializedPushedRequest" scope="prototype"
+        p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}"/>
+
     <bean id="InitializeOutboundMessageContext"
         class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeOutboundAuthenticationResponseMessageContext"
         scope="prototype" />
@@ -148,8 +152,7 @@
         p:httpClient="#{getObject('shibboleth.oidc.NonBrowser.HttpClient') ?: getObject('shibboleth.InternalHttpClient')}"
         p:httpClientSecurityParameters="#{getObject('shibboleth.oidc.NonBrowser.HttpClientSecurityParameters')}"
         p:pushedAuthorizationRequestEnforcedPredicate-ref="RequirePushedAuthorizationRequestPredicate"
-        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}"
-        p:pushedAuthorizationRequestUriDeserializers-ref="#{'%{idp.oauth2.par.deserializationStrategies:shibboleth.oidc.DefaultPushedAuthorizationRequestUriDeserializers}'.trim()}">
+        p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}">
         <property name="requestObjectEnforcedPredicate">
             <bean parent="shibboleth.Conditions.OR">
                 <constructor-arg>
@@ -336,23 +339,43 @@
                             </bean>
                         </constructor-arg>
                         <constructor-arg>
-                            <bean parent="shibboleth.Conditions.AND">
+                            <bean parent="shibboleth.Conditions.OR">
                                 <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" />
+                                            <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="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
+                                                        c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+                                                </constructor-arg>
+                                            </bean>
                                         </constructor-arg>
                                         <constructor-arg>
-                                            <bean id="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
-                                                c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+                                            <bean parent="shibboleth.Conditions.NOT">
+                                                <constructor-arg>
+                                                    <ref bean="ScopeContainsOpenid" />
+                                                </constructor-arg>
+                                            </bean>
                                         </constructor-arg>
                                     </bean>
                                 </constructor-arg>
                                 <constructor-arg>
-                                    <bean parent="shibboleth.Conditions.NOT">
+                                    <bean parent="shibboleth.Conditions.AND">
+                                        <constructor-arg>
+                                            <bean id="RequestObjectNotFromPar" parent="shibboleth.Conditions.Expression"
+                                                c:expression="#input.ensureOutboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCAuthenticationResponseContext)).isRequestObjectFromPar() == false" />
+                                        </constructor-arg>
                                         <constructor-arg>
-                                            <ref bean="ScopeContainsOpenid" />
+                                            <bean id="IsAutomaticallyRegistered" parent="shibboleth.Conditions.Expression"
+                                                c:expression="#custom.apply(#input) != null">
+                                                <property name="customObject">
+                                                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy" />
+                                                </property>
+                                            </bean>
                                         </constructor-arg>
                                     </bean>
                                 </constructor-arg>
@@ -1146,6 +1169,17 @@
         </constructor-arg>
     </bean>
 
-    <alias alias="AutomaticRegistrationCondition" name="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}" />
+    <bean id="AutomaticRegistrationCondition" parent="shibboleth.Conditions.AND">
+        <constructor-arg>
+            <ref bean="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}"/>
+        </constructor-arg>
+        <constructor-arg>
+            <bean parent="shibboleth.Conditions.Expression"
+                c:expression="#input.ensureInboundMessageContext().getMessage() instanceof T(com.nimbusds.oauth2.sdk.AuthorizationRequest) and #input.ensureInboundMessageContext().getMessage().specifiesRequestObject()" />
+        </constructor-arg>
+    </bean>
+
+    <bean id="shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultAuthorizePreSelectedTrustChainIDsLookupStrategy" />
 
 </beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 4ffc9741..56e3497c 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -31,6 +31,7 @@
 
     <action-state id="PostDecodeMessage">
         <evaluate expression="PostDecodePopulateAuditContext" />
+        <evaluate expression="DeserializedPushedRequest" />
         <evaluate expression="'proceed'" />
         <!-- DoMetadataLookup is expected to proceed to SelectConfiguration -->
         <transition on="proceed" to="DoMetadataLookup" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
index 2a27ed5b..ac639f57 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup/metadata-lookup-beans.xml
@@ -87,7 +87,8 @@
         p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
         p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
-        p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+        p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
+        p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"/>
 
     <bean id="DefaultMetadataPolicyEnforcer"
         class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />

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


More information about the commits mailing list