[java-idp-plugin-oidc-op-oidfed] branch main updated: Store trust anchor, trust chain and trust mark data during explicit registration

Henri Mikkonen henri.mikkonen at iki.fi
Thu Nov 6 15:20:29 UTC 2025


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

hjmikkon pushed a commit to branch main
in repository java-idp-plugin-oidc-op-oidfed.

View the commit online:
https://git.shibboleth.net/view/?p=java-idp-plugin-oidc-op-oidfed.git;a=commit;h=e9a0f750b005ec635ca811e5786435cbc1d8ef41

The following commit(s) were added to refs/heads/main by this push:
     new e9a0f75  Store trust anchor, trust chain and trust mark data during explicit registration
e9a0f75 is described below

commit e9a0f750b005ec635ca811e5786435cbc1d8ef41
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Nov 6 17:20:14 2025 +0200

    Store trust anchor, trust chain and trust mark data during explicit registration
    
    - New AddExtensionsToClientInformation SWF action adds the extensions during explicit registration
    - Modified ValidateAutomaticRegistrationProfileConfiguration to add them during automatic registration
    - ClientInformationExtensionSupport contains helper methods for fetching them
    - Refactored TrustAnchorIdLookupFunction to exploit the client information extensions
      - Enables RelyingPartyByTrustAnchor RP override to work with explicit registration too
    - Improved flow tests
      - Also simplified the relying-party.xml used for flow tests
---
 .../navigate/TrustAnchorIdLookupFunction.java      |  40 +++--
 .../support/ClientInformationExtensionSupport.java | 101 +++++++++++++
 .../impl/AddExtensionsToClientInformation.java     | 167 +++++++++++++++++++++
 ...eAutomaticRegistrationProfileConfiguration.java |  14 +-
 .../AbstractTrustChainContextLookupFunction.java   |   2 +-
 .../idp/flows/oidfed/register/register-beans.xml   |   4 +
 .../idp/flows/oidfed/register/register-flow.xml    |   1 +
 ...shedAuthorizeFlowAutomaticRegistrationTest.java |  66 +++++++-
 .../profile/flow/oidfed/RegistrationFlowTest.java  |  41 ++++-
 .../shibboleth/idp/module/conf/relying-party.xml   |  26 +---
 10 files changed, 405 insertions(+), 57 deletions(-)

diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/navigate/TrustAnchorIdLookupFunction.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/navigate/TrustAnchorIdLookupFunction.java
index e72c07e..3eab558 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/navigate/TrustAnchorIdLookupFunction.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/navigate/TrustAnchorIdLookupFunction.java
@@ -19,56 +19,50 @@ import java.util.function.Function;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
 
-import org.opensaml.messaging.context.navigate.ChildContextLookup;
 import org.opensaml.profile.context.ProfileRequestContext;
-import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
 
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClientInformationExtensionSupport;
+import net.shibboleth.idp.plugin.oidc.op.profile.context.navigate.DefaultOIDCMetadataContextLookupFunction;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.shared.logic.Constraint;
 
 /**
- * A function that returns selected trust anchor ID from a {@link RelyingPartyTrustChainContext} obtained via a lookup
- * function.
+ * A function that returns {@link ClientInformationExtensionSupport#KEY_VALIDATED_TRUST_ANCHOR} if found from the
+ * client information resolved via {@link OIDCMetadataContext}.
  * 
  * <p>If a specific setting is unavailable, a null value is returned.</p>
  */
+ at ThreadSafe
 public class TrustAnchorIdLookupFunction implements Function<ProfileRequestContext, String> {
 
-    /** Strategy used to lookup the trust chain context. */
-    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+    /** Strategy used to lookup the OIDC metadata context. */
+    @Nonnull private Function<ProfileRequestContext, OIDCMetadataContext> oidcMetadataContextLookupStrategy;
 
     /**
      * Constructor.
      */
     public TrustAnchorIdLookupFunction() {
-        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
-                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
-                        new InboundMessageContextLookup());
-        assert tcls != null;
-        trustChainContextLookupStrategy = tcls;
+        oidcMetadataContextLookupStrategy = new DefaultOIDCMetadataContextLookupFunction();
     }
 
     /**
      * Constructor.
      *
-     * @param strategy strategy used to lookup the trust chain context
+     * @param oidcMetadataStrategy strategy used to lookup the OIDC metadata context
      */
     public TrustAnchorIdLookupFunction(
-            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
-        trustChainContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+            @Nonnull final Function<ProfileRequestContext, OIDCMetadataContext> oidcMetadataStrategy) {
+        oidcMetadataContextLookupStrategy = Constraint.isNotNull(oidcMetadataStrategy,
+                "OIDC metadata context lookup strategy cannot be null");
     }
 
     /** {@inheritDoc} */
     @Nullable public String apply(@Nullable final ProfileRequestContext profileRequestContext) {
-        return Optional.ofNullable(profileRequestContext)
-                .map(profileCtx -> trustChainContextLookupStrategy.apply(profileCtx))
-                .map(trustChainCtx -> trustChainCtx.getSelectedTrustChain())
-                .filter(pair -> pair != null && pair.getFirst() != null)
-                .map(pair -> pair.getFirst())
-                .filter(list -> !list.isEmpty())
-                .map(chain -> chain.get(chain.size() - 1))
-                .map(statement -> statement.getEntityID().getValue())
+        return Optional.ofNullable(oidcMetadataContextLookupStrategy.apply(profileRequestContext))
+                .map(oidcContext -> oidcContext.getClientInformation())
+                .map(clientInfo -> ClientInformationExtensionSupport.parseValidatedTrustAnchor(clientInfo))
                 .orElse(null);
     }
 }
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClientInformationExtensionSupport.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClientInformationExtensionSupport.java
new file mode 100644
index 0000000..c3b70c1
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClientInformationExtensionSupport.java
@@ -0,0 +1,101 @@
+/*
+ * 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.support;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Helper methods for our client information extensions related to OpenID federation.
+ */
+public class ClientInformationExtensionSupport {
+
+    /** Identifier for validated trust anchor within client information. */
+    @Nonnull @NotEmpty public static final String KEY_VALIDATED_TRUST_ANCHOR = "oidfed_validated_trust_anchor";
+
+    /** Identifier for validated trust chain within client information. */
+    @Nonnull @NotEmpty public static final String KEY_VALIDATED_TRUST_CHAIN = "oidfed_validated_trust_chain";
+
+    /** Identifier for validated trust mark IDs within client information. */
+    @Nonnull @NotEmpty public static final String KEY_VALIDATED_TRUST_MARK_IDS = "oidfed_validated_trust_mark_ids";
+
+    /** Class logger. */
+    @Nonnull private static Logger log = LoggerFactory.getLogger(ClientInformationExtensionSupport.class);
+
+    /**
+     * Parse validated trust anchor from the given client information.
+     * 
+     * @param clientInformation client information
+     * @return validated trust anchor
+     */
+    @Nullable
+    public static String parseValidatedTrustAnchor(@Nonnull final OIDCClientInformation clientInformation) {
+        return Optional.ofNullable(clientInformation.getOIDCMetadata().getCustomField(KEY_VALIDATED_TRUST_ANCHOR))
+                .filter(String.class::isInstance)
+                .map(obj -> obj.toString())
+                .orElse(null);
+    }
+
+    /**
+     * Parse validated trust chain from the given client information.
+     * 
+     * @param clientInformation client information
+     * @return validated trust chain
+     */
+    @Nullable
+    public static List<String> parseValidatedTrustChain(@Nonnull final OIDCClientInformation clientInformation) {
+        return Optional.ofNullable(clientInformation.getOIDCMetadata().getCustomField(KEY_VALIDATED_TRUST_CHAIN))
+                .filter(List.class::isInstance)
+                .map(obj -> (List<?>) obj)
+                .map(list -> list.stream().map(String.class::cast).toList())
+                .orElse(null);
+    }
+
+    /**
+     * Parse validated trust mark IDs from the given client information..
+     * 
+     * @param clientInformation client information
+     * @return validated trust mark IDs
+     */
+    @Nullable public static Map<String, List<String>> parseValidatedTrustMarkIds(
+            @Nonnull final OIDCClientInformation clientInformation) {
+        return Optional.ofNullable(clientInformation.getOIDCMetadata().getCustomField(KEY_VALIDATED_TRUST_MARK_IDS))
+                .filter(Map.class::isInstance)
+                .map(obj -> (Map<?,?>) obj)
+                .map(map -> map.entrySet().stream()
+                        .filter(entry -> entry.getKey() instanceof String)
+                        .filter(entry -> entry.getValue() instanceof List<?>)
+                        .collect(Collectors.toMap(entry -> entry.getKey().toString(),
+                                entry -> ((List<?>) entry.getValue()).stream()
+                                .filter(Objects::nonNull)
+                                .map(Objects::toString)
+                                .toList())))
+                .orElse(null);
+    }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
new file mode 100644
index 0000000..485b0e5
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AddExtensionsToClientInformation.java
@@ -0,0 +1,167 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+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.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.messaging.context.OIDCClientRegistrationResponseContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClientInformationExtensionSupport;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Adds OpenID Federation specific extensions to the {@link OIDCClientInformation} to be stored.
+ */
+public class AddExtensionsToClientInformation extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private final Logger log = LoggerFactory.getLogger(AddExtensionsToClientInformation.class);
+    
+    /** Strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given request. */
+    @Nonnull private Function<ProfileRequestContext,OIDCClientRegistrationResponseContext>
+        oidcResponseContextLookupStrategy;
+
+    /** Strategy used to lookup the trust chain context. */
+    @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+    /** Client information to operate on. */
+    @NonnullBeforeExec private OIDCClientInformation clientInformation;
+
+    /** Trust chain context to operate on. */
+    @NonnullBeforeExec private  RelyingPartyTrustChainContext trustChainContext;
+
+    /** Selected trust chain to operate on. */
+    @NonnullBeforeExec private Pair<List<EntityStatement>, Map<String,Map<String,Object>>> selectedTrustChain;
+
+    /** Constructor. */
+    public AddExtensionsToClientInformation() {
+        final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> orcls =
+                new ChildContextLookup<>(OIDCClientRegistrationResponseContext.class).compose(
+                        new OutboundMessageContextLookup());
+        assert orcls != null;
+        oidcResponseContextLookupStrategy = orcls;
+        final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+                new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+                        new InboundMessageContextLookup());
+        assert tcls != null;
+        trustChainContextLookupStrategy = tcls;
+    }
+
+    /**
+     * Set the strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a given
+     * {@link MessageContext}.
+     * 
+     * @param strategy strategy used to locate the {@link OIDCClientRegistrationResponseContext} associated with a 
+     *         given {@link MessageContext}
+     */
+    public void setOidcResponseContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext,OIDCClientRegistrationResponseContext> strategy) {
+        checkSetterPreconditions();
+        
+        oidcResponseContextLookupStrategy = Constraint.isNotNull(strategy,
+                "OIDCClientRegistrationResponseContext lookup strategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to lookup the trust chain context.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setTrustChainContextLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+        checkSetterPreconditions();
+        trustChainContextLookupStrategy =
+                Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        
+        final OIDCClientRegistrationResponseContext oidcResponseCtx =
+                oidcResponseContextLookupStrategy.apply(profileRequestContext);
+        if (oidcResponseCtx == null || oidcResponseCtx.getClientInformation() == null) {
+            log.debug("{} No OIDC client informationt could be resolved with this profile request", 
+                    getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;            
+        }
+        clientInformation = oidcResponseCtx.getClientInformation();
+
+        trustChainContext = trustChainContextLookupStrategy.apply(profileRequestContext);
+        if (trustChainContext == null || trustChainContext.getSelectedTrustChain() == null) {
+            log.error("{} Unable to locate selected trust chain", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        selectedTrustChain = trustChainContext.getSelectedTrustChain();
+        assert selectedTrustChain != null;
+        if (selectedTrustChain.getFirst() == null || selectedTrustChain.getSecond() == null) {
+            log.error("{} Selected trust chain contents is not populated", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+        final List<EntityStatement> trustChain = selectedTrustChain.getFirst();
+        assert trustChain != null;
+
+        clientInformation.getOIDCMetadata().setCustomField(
+                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_ANCHOR,
+                trustChain.get(trustChain.size() - 1).getEntityID().getValue());
+        clientInformation.getOIDCMetadata().setCustomField(
+                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_CHAIN,
+                trustChain.stream().map(es -> es.getSignedStatement().serialize()).toList());
+        final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
+        if (verifiedTrustMarks != null) {
+            clientInformation.getOIDCMetadata().setCustomField(
+                    ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
+        }
+
+    
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index 7807242..cd61365 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -38,6 +38,7 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.LocalMet
 import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.MandatoryTrustMarksLookupFunction;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClientInformationExtensionSupport;
 import net.shibboleth.idp.profile.AbstractProfileAction;
 import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
 import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
@@ -219,10 +220,10 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
 
         }
         final String clientId = clientInformation.getID().getValue();
+        final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
         final List<String> mandatoryTrustMarks = mandatoryTrustMarksLookupStrategy.apply(profileRequestContext);
         if (mandatoryTrustMarks != null && !mandatoryTrustMarks.isEmpty()) {
             log.debug("{} Verifying the mandatory trust marks {}", getLogPrefix(), mandatoryTrustMarks);
-            final Map<String,List<String>> verifiedTrustMarks = trustChainContext.getVerifiedTrustMarkIds();
             if (verifiedTrustMarks == null || verifiedTrustMarks.get(clientId) == null 
                     || !verifiedTrustMarks.get(clientId).containsAll(mandatoryTrustMarks)) {
                 log.info("{} Rejecting registration as some of the following mandatory trust marks are missing: {}",
@@ -245,6 +246,17 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
         trustChainContext.setSelectedMetadataExpiration(resolveTrustChainExpiration(trustChain));
 
         final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+        clientInformation.getOIDCMetadata().setCustomField(
+                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_ANCHOR,
+                trustChain.get(trustChain.size() - 1).getEntityID().getValue());
+        clientInformation.getOIDCMetadata().setCustomField(
+                ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_CHAIN,
+                trustChain.stream().map(es -> es.getSignedStatement().serialize()).toList());
+        if (verifiedTrustMarks != null) {
+            clientInformation.getOIDCMetadata().setCustomField(
+                    ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
+        }
+        log.debug("{} Client information after adding custom extension {}", getLogPrefix(), clientInformation.toJSONObject().toJSONString());
         oidcCtx.setClientInformation(clientInformation);
         profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
         log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java
index 67677a5..ab090c1 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractTrustChainContextLookupFunction.java
@@ -67,7 +67,7 @@ public abstract class AbstractTrustChainContextLookupFunction<T> implements Func
     @Override @Nullable public T apply(@Nullable final ProfileRequestContext input) {
         final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(input);
         if (trustChainContext == null) {
-            log.error("Could not resolve trust chain context");
+            log.debug("Could not resolve trust chain context, returning null");
             return null;
         }
         return doApply(trustChainContext);
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 0781d06..831a06a 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -386,6 +386,10 @@
         </constructor-arg>
     </bean>
 
+    <bean id="AddExtensionsToClientInformation"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AddExtensionsToClientInformation"
+        scope="prototype" />
+
     <bean id="FormOutboundMessage"
         class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.FormExplicitRegistrationResponse"
         scope="prototype" />
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
index 7ab6c00..45dcd6a 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-flow.xml
@@ -79,6 +79,7 @@
         <evaluate expression="PopulateEntityStatementSignatureSigningParameters" />
         <evaluate expression="BuildEntityStatement" />
         <evaluate expression="SignEntityStatement" />
+        <evaluate expression="AddExtensionsToClientInformation" />
         <evaluate expression="'proceed'" />
         <transition on="proceed" to="BuildResponseMessage"/>
     </action-state>
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index 2bea9e1..108e208 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -14,6 +14,7 @@
 
 package net.shibboleth.idp.plugin.oidc.op.profile.flow.oidfed;
 
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
@@ -27,8 +28,11 @@ import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
 
+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.mock.web.MockHttpServletRequest;
 import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
@@ -36,13 +40,16 @@ import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.Test;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.JOSEException;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
 import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.OAuth2Error;
 import com.nimbusds.oauth2.sdk.PushedAuthorizationSuccessResponse;
 import com.nimbusds.oauth2.sdk.ResponseType;
+import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod;
 import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
 
@@ -50,12 +57,17 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSuppor
 import net.shibboleth.idp.plugin.oidc.op.profile.flow.PushedAuthorizeFlowTest;
 import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultPushedAuthorizationRequestUriDeserializationFunction;
 import net.shibboleth.idp.plugin.oidc.op.token.support.AuthorizeCodeClaimsSet;
+import net.shibboleth.idp.session.SessionException;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.component.ComponentInitializationException;
 import net.shibboleth.shared.security.DataSealerException;
 
 public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFlowTest {
 
+    @Autowired
+    @Qualifier("shibboleth.StorageService")
+    StorageService storageService;
+
     private DefaultPushedAuthorizationRequestUriDeserializationFunction statelessDeserializer;
 
     public PushedAuthorizeFlowAutomaticRegistrationTest() {
@@ -115,7 +127,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
         setHttpFormRequest("POST", createRequestParameters(clientId));
         final FlowExecutionResult result =
                 flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
     }
 
     @Test
@@ -230,7 +242,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
                 "redirect_uri", redirectUri)).serialize()));
         final FlowExecutionResult result =
                 flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
     }
 
     @Test
@@ -253,7 +265,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
                 "redirect_uri", redirectUri)).serialize()));
         final FlowExecutionResult result =
                 flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
     }
 
     @Test
@@ -345,7 +357,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
                 "redirect_uri", redirectUri)).serialize()));
         final FlowExecutionResult result =
                 flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
-        assertErrorCode(result, OAuth2Error.INVALID_CLIENT_CODE);
+        assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
     }
 
     @Test
@@ -375,6 +387,52 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
         }
     }
 
+    @Test
+    public void testWithValinnaOIDCSignedRequestObject() throws IOException, ParseException,
+            SessionException, JOSEException {
+        final String clientId = "mockClientId";
+        final String clientSecret = "mockClientSecretmockClientSecretmockClientSecretmockClientSecretmockClientSecret";
+
+        final JWTClaimsSet ro = new JWTClaimsSet.Builder()
+                .audience(issuer)
+                .issuer(clientId)
+                .claim("client_id", clientId)
+                .claim("redirect_uri", redirectUri)
+                .claim("scope", "openid profile")
+                .claim("response_type", "code")
+                .build();
+        assertVanillaClient(clientId, clientSecret, createSecretJWT(ro, clientSecret));
+    }
+
+    protected void assertVanillaClient(final String clientId, final String clientSecret, final JWT requestObject)
+            throws IOException {
+        request.setMethod("GET");
+        final Map<String,String> requestParameters = Map.of("client_id", clientId,
+                "request", requestObject.serialize(),
+                "redirect_uri", redirectUri,
+                "scope", "openid profile");
+        setHttpFormRequest("POST", requestParameters);
+        storeMetadata(storageService, clientId, clientSecret, Scope.parse("openid profile"), redirectUri);
+
+        setBasicAuth(clientId, clientSecret);
+        initializeThreadLocals();
+
+        final FlowExecutionResult result =
+                flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+        final PushedAuthorizationSuccessResponse response =
+                parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+        Assert.assertNotNull(response);
+        Assert.assertNotNull(response.getRequestURI());
+        Assert.assertNotNull(response.getLifetime());
+        final String requestUri = response.getRequestURI().toString();
+        Assert.assertTrue(requestUri.startsWith("urn:ietf:params:oauth:request_uri:"));
+        final Map<String,Object> claims = statelessDeserializer.apply(null, response.getRequestURI());
+        Assert.assertNull(claims.get(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN));
+        verifyAuthorizeEndpoint(clientId, requestUri);
+
+        removeMetadata(storageService, clientId);
+    }
+
     protected void verifyAuthorizeEndpoint(final String clientId, final String requestUri) {
         verifyAuthorizeEndpoint(clientId, requestUri, null);
     }
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
index e3551ec..cac0f41 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/RegistrationFlowTest.java
@@ -24,6 +24,7 @@ import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URLEncoder;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -35,7 +36,10 @@ import org.springframework.webflow.executor.FlowExecutionResult;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.oauth2.sdk.ParseException;
 import com.nimbusds.oauth2.sdk.Scope;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
@@ -114,6 +118,19 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
+    @Test
+    public void testValidEntityConfiguration_verifyPar() throws Exception {
+        final String clientId = uniqueClientId();
+        rpConfigureMockHttpClient(clientId);
+        setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+        final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+        verify(federationHttpClient, times(0)).executeOpen(any(),
+                argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
+        assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyParEndpoint(clientId, redirectUri);
+    }
+
+
     @Test
     public void testValidEntityConfiguration_resolveApi() throws Exception {
         final String clientId = uniqueClientId();
@@ -179,7 +196,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         final OIDCClientMetadata providedMetadata = assertResponseStatement(
-                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId).getOIDCMetadata();
         Assert.assertEquals(providedMetadata.getScope(), scope);
         verifyAuthorizeEndpoint(clientId, redirectUri);
     }
@@ -199,7 +216,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         final OIDCClientMetadata providedMetadata = assertResponseStatement(
-                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId).getOIDCMetadata();
         Assert.assertEquals(providedMetadata.getScope(), scope);
         verifyAuthorizeEndpoint(clientId, redirectUri);
     }
@@ -412,7 +429,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         setRequest("POST", trustChain, "application/trust-chain+json");
         final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
         final OIDCClientMetadata providedMetadata = assertResponseStatement(
-                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+                parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId).getOIDCMetadata();
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         verify(federationHttpClient, times(0)).executeOpen(any(),
@@ -435,7 +452,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         }
     }
 
-    protected OIDCClientMetadata assertResponseStatement(final ExplicitClientRegistrationResponse response,
+    protected OIDCClientInformation assertResponseStatement(final ExplicitClientRegistrationResponse response,
             final String expectedClientId) throws IOException, ParseException, net.minidev.json.parser.ParseException {
         final EntityStatement entityStatement = response.getEntityStatement();
         final EntityStatementClaimsSet statementClaims = entityStatement.getClaimsSet();
@@ -464,7 +481,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         Assert.assertEquals(storedInfo.getOIDCMetadata().getRedirectionURIStrings(),
                 metadata.getRedirectionURIStrings());
         Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
-        return metadata;
+        return storedInfo;
     }
 
     protected void verifyAuthorizeEndpoint(final String clientId, final String redirectUri) {
@@ -492,4 +509,18 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
     }
 
+    protected void verifyParEndpoint(final String clientId, final String redirectUri) throws JOSEException {
+        initializeMocks();
+        initializeThreadLocals();
+
+        final SignedJWT jwt = createPrivateKeyJWT(validJwtAuthenticationClaimsSet(clientId, issuer),
+                rpKey.toRSAKey().toRSAPrivateKey(), JWSAlgorithm.RS512);
+        final Map<String, String> requestParameters = new HashMap<>(Map.of("client_id", clientId,
+                "response_type", "code", "scope", "openid", "redirect_uri", redirectUri));
+        populateClientAssertionParams(requestParameters, jwt);
+        setHttpFormRequest("POST", requestParameters);
+        final FlowExecutionResult result = flowExecutor.launchExecution("oauth2/pushed-authorization", null,
+                externalContext);
+        Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
+    }
 }
diff --git a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index a6e7cd7..8466c66 100644
--- a/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidfed-op-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -39,13 +39,6 @@
                 <ref bean="OIDC.Keyset" />
                 <ref bean="OIDC.Registration" />
                 <ref bean="OIDC.Configuration" />
-                <bean parent="OIDC.SSO" />
-                <bean parent="OAUTH2.Token" />
-                <bean parent="OAUTH2.TokenAudience" p:encryptionOptional="true" /> 
-                <bean parent="OIDC.UserInfo" />
-                <bean parent="OAUTH2.Introspection" />
-                <bean parent="OAUTH2.Revocation" />
-                <bean parent="OAUTH2.PAR" />
                 <bean parent="OIDFED.Configuration" p:cachedSuccessResponseLifetime="PT2S" />
                 <bean parent="OIDFED.ResolveEntity" />
             </list>
@@ -57,22 +50,9 @@
     <bean id="shibboleth.DefaultRelyingParty" parent="RelyingParty.MDDriven">
         <property name="profileConfigurations">
             <list>
-                <ref bean="Shibboleth.SSO.MDDriven" />
-                <ref bean="SAML1.AttributeQuery.MDDriven" />
-                <ref bean="SAML1.ArtifactResolution.MDDriven" />
-                <ref bean="SAML2.SSO.MDDriven" />
-                <ref bean="SAML2.ECP.MDDriven" />
-                <ref bean="SAML2.Logout.MDDriven" />
-                <ref bean="SAML2.AttributeQuery.MDDriven" />
-                <ref bean="SAML2.ArtifactResolution.MDDriven" />
+                <!-- Enabled as both authorize and PAR flow tests contain vanilla OIDC tests -->
                 <ref bean="OIDC.SSO.MDDriven" />
-                <ref bean="OIDC.UserInfo.MDDriven" />
-                <ref bean="OIDC.Registration.MDDriven" />
-                <ref bean="OIDC.Logout.MDDriven" />
-                <ref bean="OAUTH2.Token.MDDriven" />
-                <ref bean="OAUTH2.Introspection.MDDriven" />
-                <ref bean="OAUTH2.Revocation.MDDriven" />
-                <bean parent="OAUTH2.PAR.MDDriven" />
+                <bean parent="OAUTH2.PAR.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic"/>
             </list>
         </property>
     </bean>
@@ -91,7 +71,7 @@
                 <list>
                     <ref bean="OIDC.SSO.MDDriven" />
                     <bean parent="OAUTH2.Token.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
-                    <bean parent="OAUTH2.PAR.MDDriven" p:tokenEndpointAuthMethods="client_secret_basic,client_secret_post,client_secret_jwt,private_key_jwt,none"/>
+                    <bean parent="OAUTH2.PAR.MDDriven" p:tokenEndpointAuthMethods="private_key_jwt,none"/>
                     <ref bean="OIDC.UserInfo.MDDriven" />
                 </list>
             </property>

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


More information about the commits mailing list