[java-idp-plugin-oidc-op-oidfed] branch main updated: Improved the explicit registration logic

Henri Mikkonen henri.mikkonen at iki.fi
Thu Oct 30 14:16:25 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=841ac99534429c0126e2663a5a174ea4a53ba187

The following commit(s) were added to refs/heads/main by this push:
     new 841ac99  Improved the explicit registration logic
841ac99 is described below

commit 841ac99534429c0126e2663a5a174ea4a53ba187
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Oct 30 16:16:10 2025 +0200

    Improved the explicit registration logic
    
    - Validate the pushed entity configuration signature before trust chain resolution
    - Improved remote resolution logic
      - Store the already attempted remote entieis in the RelyingPartyTrustChainContext
    - Include authorize-endpoint verification to the flow tests
---
 .../profile/TrustedRemoteResolverEntity.java       |  28 +++++
 .../context/RelyingPartyTrustChainContext.java     |  26 +++++
 .../oidfed/profile/impl/CallResolveEntityApi.java  |  37 +++++--
 .../impl/ValidateProvidedEntityConfiguration.java  | 123 +++++++++++++++++++++
 ...onRequestEntityConfigurationLookupFunction.java |  47 ++++++++
 .../idp/flows/oidfed/register/register-beans.xml   |  48 ++++----
 .../idp/flows/oidfed/register/register-flow.xml    |   8 +-
 .../profile/flow/oidfed/RegistrationFlowTest.java  |  46 +++++++-
 8 files changed, 326 insertions(+), 37 deletions(-)

diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/TrustedRemoteResolverEntity.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/TrustedRemoteResolverEntity.java
index b8b6859..005dd3a 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/TrustedRemoteResolverEntity.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/TrustedRemoteResolverEntity.java
@@ -18,6 +18,8 @@ import java.util.Collection;
 
 import javax.annotation.Nonnull;
 
+import com.google.common.base.MoreObjects;
+
 import net.shibboleth.shared.annotation.ParameterName;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.logic.Constraint;
@@ -62,4 +64,30 @@ public class TrustedRemoteResolverEntity {
     @Nonnull @NotEmpty public Collection<String> getTrustAnchors() {
         return trustAnchors;
     }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return MoreObjects.toStringHelper(this)
+                .add("entityId", getEntityId())
+                .add("trustAnchors", getTrustAnchors())
+                .toString();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final TrustedRemoteResolverEntity other = (TrustedRemoteResolverEntity) obj;
+        return entityId.equals(other.entityId) &&
+                trustAnchors.containsAll(other.trustAnchors) && other.trustAnchors.containsAll(trustAnchors);
+    }
 }
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
index bd503ec..3b2f939 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
@@ -26,6 +26,7 @@ import org.opensaml.messaging.context.BaseContext;
 import com.nimbusds.jwt.SignedJWT;
 import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
 
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.TrustedRemoteResolverEntity;
 import net.shibboleth.shared.collection.Pair;
 
 /**
@@ -54,7 +55,11 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
     /** All previously selected but rejected trust chains. */
     @Nullable private List<List<EntityStatement>> rejectedTrustChains;
 
+    /** All already attempted trusted remote resolver entities. */
+    @Nullable private List<TrustedRemoteResolverEntity> attemptedTrustedRemoteResolverEntities;
+
     /**
+
      * Get the resolved trust chains for the relying party.
      * 
      * @return the trust chains
@@ -207,4 +212,25 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
         return this;
     }
 
+    /**
+     * Get the attempted remote resolver entities for the relying party.
+     * 
+     * @return the trust chains
+     */
+    @Nullable public List<TrustedRemoteResolverEntity> getAttemptedTrustedRemoteResolverEntities() {
+        return attemptedTrustedRemoteResolverEntities;
+    }
+
+    /**
+     * Set the attempted remote resolver entities for the relying party.
+     * 
+     * @param attemptedEntities the attempted remote resolver entities
+     * 
+     * @return this context
+     */
+    @Nonnull public RelyingPartyTrustChainContext setAttemptedTrustedRemoteResolverEntities(
+            @Nullable final List<TrustedRemoteResolverEntity> attemptedEntities) {
+        attemptedTrustedRemoteResolverEntities = attemptedEntities;
+        return this;
+    }
 }
\ 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/CallResolveEntityApi.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
index ee70ea0..a9adf43 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
@@ -69,6 +69,7 @@ import net.shibboleth.shared.logic.Constraint;
 import net.shibboleth.shared.logic.FunctionSupport;
 import net.shibboleth.shared.logic.PredicateSupport;
 import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.NonnullSupplier;
 import net.shibboleth.shared.resolver.CriteriaSet;
 
 import org.opensaml.messaging.context.MessageContext;
@@ -352,11 +353,26 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                 Optional.ofNullable(preSelectedTrustChainIdsLookupStrategy.apply(profileRequestContext))
                 .orElse(CollectionSupport.emptyList());
 
+        final RelyingPartyTrustChainContext trustChainContext =
+                trustChainContextCreationStrategy.apply(profileRequestContext);
+
         for (final TrustedRemoteResolverEntity trustedEntity : trustedEntities) {
             if (trustedEntity == null) {
                 log.warn("{} Ignoring null trusted entity entry", getLogPrefix());
                 continue;
             }
+            final List<TrustedRemoteResolverEntity> alreadyAttemptedEntities =
+                    Optional.ofNullable(trustChainContext.getAttemptedTrustedRemoteResolverEntities())
+                    .map(list -> new ArrayList<>(list))
+                    .orElseGet(NonnullSupplier.of(new ArrayList<>()));
+            if (alreadyAttemptedEntities.contains(trustedEntity)) {
+                log.debug("{} Trusted entity {} has already been attempted", getLogPrefix(), trustedEntity);
+                continue;
+            } else {
+                alreadyAttemptedEntities.add(trustedEntity);
+                trustChainContext.setAttemptedTrustedRemoteResolverEntities(
+                        CollectionSupport.copyToList(alreadyAttemptedEntities));
+            }
             final URI uri = fetchResolveEntityEndpoint(trustedEntity);
             if (uri == null) {
                 log.warn("{} Could not fetch federation resolve endpoint for {}", getLogPrefix(), trustedEntity);
@@ -371,11 +387,11 @@ public class CallResolveEntityApi extends AbstractProfileAction {
             try {
                 cacheResult = resolveEntityTrustChainMetadataCache.get(criteriaSet);
             } catch (final MetadataCacheException e) {
-                log.warn("{} Could not resolve entity for {}", getLogPrefix(), clientId, e);
+                log.warn("{} Could not resolve entity for {} from {}", getLogPrefix(), clientId, trustedEntity, e);
                 continue;
             }
             if (cacheResult.isEmpty()) {
-                log.debug("{} No data resolved for {}", getLogPrefix(), clientId);
+                log.debug("{} No data resolved for {} from {}", getLogPrefix(), clientId, trustedEntity);
                 continue;
             }
             if (cacheResult.get(0).getResponse() instanceof ResolveEntityResponse successResponse) {
@@ -387,11 +403,13 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                     rawMetadata = successResponse.getJWT().getJWTClaimsSet().getJSONObjectClaim("metadata");
                     rawTrustMarks = successResponse.getJWT().getJWTClaimsSet().getListClaim("trust_marks");
                 } catch (final ParseException e) {
-                    log.error("{} Could not parse resolve entity response contents", getLogPrefix(), e);
+                    log.error("{} Could not parse resolve entity response contents from {}", getLogPrefix(),
+                            trustedEntity, e);
                     continue;
                 }
                 if (rawTrustChain == null || rawTrustChain.isEmpty() || rawMetadata == null || rawMetadata.isEmpty()) {
-                    log.warn("{} Could not parse mandatory parameters from the response", getLogPrefix());
+                    log.warn("{} Could not parse mandatory parameters from the response from {}", getLogPrefix(),
+                            trustedEntity);
                     continue;
                 }
                 final List<EntityStatement> chain = rawTrustChain.stream()
@@ -399,7 +417,8 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                         .map(entry -> EntityStatementHelper.deserializeEntityStatement(entry))
                         .toList();
                 if (!preSelectedChain.isEmpty() && !preSelectedChain.equals(trustChainIDsLookupStrategy.apply(chain))) {
-                    log.debug("{} Ignored resolved trust chain that doesn't match with preselected chain", getLogPrefix());
+                    log.debug("{} Ignored resolved trust chain that doesn't match with preselected chain",
+                            getLogPrefix());
                     continue;
                 }
                 final Map<String, Map<String, Object>> metadata = rawMetadata.entrySet().stream()
@@ -409,8 +428,6 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                                 .filter(e -> e.getKey() instanceof String)
                                 .collect(Collectors.toMap(e -> (String) e.getKey(), e -> e.getValue()))));
 
-                final RelyingPartyTrustChainContext trustChainContext =
-                        trustChainContextCreationStrategy.apply(profileRequestContext);
                 final List<Pair<List<EntityStatement>, Map<String,Map<String,Object>>>> policyCompliantChains =
                         new ArrayList<>();
                 policyCompliantChains.add(new Pair<>(chain, metadata));
@@ -457,16 +474,16 @@ public class CallResolveEntityApi extends AbstractProfileAction {
                         
                     }
                 } else {
-                    log.debug("{} No trust marks included in the response", getLogPrefix());
+                    log.debug("{} No trust marks included in the response from {}", getLogPrefix(), trustedEntity);
                 }
                 return;
             } else {
-                log.debug("{} The response was not a success response: {}", getLogPrefix(),
+                log.debug("{} The response from {} was not a success response: {}", getLogPrefix(), trustedEntity,
                         cacheResult.get(0).getResponse());
                 continue;
             }
         }
-        log.debug("{} No previously rejected policy-compliant trust chains resolved", getLogPrefix());
+        log.debug("{} No previously not attempted policy-compliant trust chains resolved", getLogPrefix());
         ActionSupport.buildEvent(profileRequestContext, "CheckFallback");
 
     }
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedEntityConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedEntityConfiguration.java
new file mode 100644
index 0000000..0065b06
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedEntityConfiguration.java
@@ -0,0 +1,123 @@
+/*
+ * 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.function.BiPredicate;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.idp.authn.AuthnEventIds;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Validates the provided entity configuration.
+ * 
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ * @event {@link AuthnEventIds#INVALID_CREDENTIALS}
+ */
+public class ValidateProvidedEntityConfiguration extends AbstractProfileAction {
+
+    /** Class logger. */
+    @Nonnull private Logger log = LoggerFactory.getLogger(ValidateProvidedEntityConfiguration.class);
+
+    /** Strategy used to validate provided trust chain. */
+    @NonnullAfterInit
+    private BiPredicate<ProfileRequestContext, EntityStatement> providedEntityConfigurationValidationStrategy;
+
+    /** Strategy used to locate the provided trust chain. */
+    @NonnullAfterInit
+    private Function<ProfileRequestContext, EntityStatement> providedEntityConfigurationLookupStrategy;
+
+    /** Entity configuration to operate on. */
+    @NonnullBeforeExec private EntityStatement entityConfiguration;
+
+    /**
+     * Set the strategy used to locate the provided entity configuration.
+     * 
+     * @param strategy lookup strategy
+     */
+    public void setProvidedEntityConfigurationLookupStrategy(
+            @Nonnull final Function<ProfileRequestContext, EntityStatement> strategy) {
+        providedEntityConfigurationLookupStrategy =
+                Constraint.isNotNull(strategy, "ProvidedEntityConfigurationLookupStrategy cannot be null");
+    }
+
+    /**
+     * Set the strategy used to validate provided entity configuration.
+     * 
+     * @param strategy validation strategy
+     */
+    public void setProvidedEntityConfigurationValidationStrategy(
+            @Nonnull final BiPredicate<ProfileRequestContext, EntityStatement> strategy) {
+        checkSetterPreconditions();
+        providedEntityConfigurationValidationStrategy =
+                Constraint.isNotNull(strategy, "ProvidedEntityConfigurationValidationStrategy cannot be null");
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doInitialize() throws ComponentInitializationException {
+        super.doInitialize();
+
+        if (providedEntityConfigurationLookupStrategy == null) {
+            throw new ComponentInitializationException("ProvidedEntityConfigurationLookupStrategy cannot be null");
+        }
+        if (providedEntityConfigurationValidationStrategy == null) {
+            throw new ComponentInitializationException("ProvidedEntityConfigurationValidationStrategy cannot be null");
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!super.doPreExecute(profileRequestContext)) {
+            return false;
+        }
+        entityConfiguration = providedEntityConfigurationLookupStrategy.apply(profileRequestContext);
+        if (entityConfiguration == null) {
+            log.error("{} Unable to fetch entity configuration", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+            return false;
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+        if (!providedEntityConfigurationValidationStrategy.test(profileRequestContext, entityConfiguration)) {
+            log.error("{} The entity configuration validation failed", getLogPrefix());
+            ActionSupport.buildEvent(profileRequestContext, AuthnEventIds.INVALID_CREDENTIALS);
+            return;
+        }
+        log.debug("{} The provided entity configuration successfully validated", getLogPrefix());
+    }
+
+}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestEntityConfigurationLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestEntityConfigurationLookupFunction.java
new file mode 100644
index 0000000..6136dae
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestEntityConfigurationLookupFunction.java
@@ -0,0 +1,47 @@
+/*
+ * 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.Optional;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
+
+/**
+ * A function that returns entity configuration set in the explicit registration request.
+ */
+ at ThreadSafe
+public class ExplicitClientRegistrationRequestEntityConfigurationLookupFunction
+    implements ContextDataLookupFunction<ProfileRequestContext, EntityStatement> {
+
+    /** {@inheritDoc} */
+    @Nullable
+    public EntityStatement apply(@Nullable final ProfileRequestContext input) {
+        return Optional.ofNullable(input)
+                .map(profileRequestContext -> profileRequestContext.getInboundMessageContext())
+                .map(messageContext -> messageContext.getMessage())
+                .filter(ExplicitClientRegistrationRequest.class::isInstance)
+                .map(ExplicitClientRegistrationRequest.class::cast)
+                .map(request -> request.getEntityConfiguration())
+                .orElse(null);
+    }
+}
\ No newline at end of file
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 072918b..bfcdba0 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
@@ -71,6 +71,24 @@
         </property>
     </bean>
 
+    <bean id="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction"
+        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestEntityConfigurationLookupFunction" />
+
+    <bean id="ValidateProvidedEntityConfiguration" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateProvidedEntityConfiguration"
+        scope="prototype"
+        p:providedEntityConfigurationLookupStrategy-ref="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction">
+        <property name="providedEntityConfigurationValidationStrategy">
+            <bean parent="shibboleth.BiConditions.Expression"
+                c:expression="#custom.apply(#input2, null) != null">
+                <property name="customObject">
+                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementSignatureValidationFilterStrategy"
+                        p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
+                </property>
+            </bean>
+        </property>
+    </bean>
+
+
     <bean id="DefaultMetadataValidationCondition"
         class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultMetadataValidationCondition" />
 
@@ -78,25 +96,16 @@
         class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultCombinedMetadataFromTrustChainLookupStrategy"
         p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper" />
  
-     <bean id="CallResolveEntityApi"
-        class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.CallResolveEntityApi"
+     <bean id="CallResolveEntityApi" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.CallResolveEntityApi"
+        scope="prototype"
         p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
         p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
         p:resolveEntityTrustChainMetadataCache-ref="shibboleth.oidfed.ResolveEntityTrustChainMetadataCache"
         p:objectMapper-ref="shibboleth.oidc.JSONObjectMapper"
         p:trustedEntitiesLookupStrategy="#{getObject('shibboleth.oidfed.TrustedRemoteResolverEntitiesLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultTrustedRemoteResolverEntitiesLookupStrategy')}"
         p:preSelectedTrustChainIdsLookupStrategy="#{getObject('shibboleth.oidfed.PreSelectedTrustChainIDsLookupStrategy') ?: getObject('shibboleth.oidfed.DefaultPreSelectedTrustChainIDsLookupStrategy')}"
-        p:requireEntityConfigurationCondition-ref="shibboleth.Conditions.TRUE">
-        <property name="entityConfigurationLookupStrategy">
-            <bean parent="shibboleth.Functions.Expression"
-                c:expression="#custom.apply(#input.ensureInboundMessageContext().getMessage().getEntityConfiguration(), null)">
-                <property name="customObject">
-                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementSignatureValidationFilterStrategy"
-                        p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
-                </property>
-            </bean>
-        </property>
-    </bean>
+        p:requireEntityConfigurationCondition-ref="shibboleth.Conditions.TRUE"
+        p:entityConfigurationLookupStrategy-ref="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction"/>
 
     <bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
         scope="prototype"
@@ -108,17 +117,8 @@
         p:requireEntityConfigurationCondition-ref="shibboleth.Conditions.TRUE"
         p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
         p:metadataLookupStrategy-ref="DefaultCombinedMetadataFromTrustChainLookupStrategy"
-        p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}">
-        <property name="entityConfigurationLookupStrategy">
-            <bean parent="shibboleth.Functions.Expression"
-                c:expression="#custom.apply(#input.ensureInboundMessageContext().getMessage().getEntityConfiguration(), null)">
-                <property name="customObject">
-                    <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.DefaultEntityStatementSignatureValidationFilterStrategy"
-                        p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
-                </property>
-            </bean>
-        </property>
-    </bean>
+        p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}"
+        p:entityConfigurationLookupStrategy-ref="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction" />
 
     <bean id="FetchThroughTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
         <constructor-arg>
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 35a5716..7ab6c00 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
@@ -12,9 +12,15 @@
 
     <decision-state id="SelectTrustChainResolution">
         <if test="opensamlProfileRequestContext.ensureInboundMessageContext().getMessage().getTrustChain() != null"
-            then="ValidateProvidedTrustChain" else="ChooseResolutionMethod" />
+            then="ValidateProvidedTrustChain" else="ValidateProvidedEntityConfiguration" />
     </decision-state>
 
+    <action-state id="ValidateProvidedEntityConfiguration">
+        <evaluate expression="ValidateProvidedEntityConfiguration" />
+        <evaluate expression="'proceed'" />
+        <transition on="proceed" to="ChooseResolutionMethod"/>
+    </action-state>
+
     <decision-state id="ChooseResolutionMethod">
         <if test="UseResolverApiCondition.test(opensamlProfileRequestContext)"
             then="CallResolveEntityApi" else="ResolveTrustChains" />
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 387b78e..eddf551 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
@@ -20,7 +20,9 @@ import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
+import java.net.URLEncoder;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +47,7 @@ import net.minidev.json.JSONObject;
 import net.minidev.json.parser.JSONParser;
 import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationResponse;
 import net.shibboleth.oidc.metadata.impl.BaseStorageServiceClientInformationComponent;
+import net.shibboleth.shared.collection.Pair;
 
 /**
  * Flow tests for the OpenID federation explicit registration flow.
@@ -108,6 +111,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -120,6 +124,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -157,6 +162,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -175,6 +181,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final OIDCClientMetadata providedMetadata = assertResponseStatement(
                 parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
         Assert.assertEquals(providedMetadata.getScope(), scope);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -194,6 +201,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         final OIDCClientMetadata providedMetadata = assertResponseStatement(
                 parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
         Assert.assertEquals(providedMetadata.getScope(), scope);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -206,20 +214,22 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
             verify(federationHttpClient, times(0)).executeOpen(any(),
                     argThat(new RequestUriMatcher(entityConfigurationUrl(clientId))), any());
             assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+            verifyAuthorizeEndpoint(clientId, redirectUri);
         }
     }
 
     @Test
     public void testValidEntityConfiguration_repeat_resolveApi() throws Exception {
         final String clientId = uniqueClientId();
-        request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
         for (int i = 0; i < 2; i++) {
+            request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
             rpResolveEntityConfigureMockHttpClient(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);
+            verifyAuthorizeEndpoint(clientId, redirectUri);
         }
     }
 
@@ -236,6 +246,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(super.subordinateStatementUrl(anchorFetchEndpoint, clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -254,6 +265,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(resolveEntityUrl(anchorResolveEndpoint, clientId, anchorId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -269,6 +281,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(super.subordinateStatementUrl(anchorFetchEndpoint, clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -299,6 +312,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(super.subordinateStatementUrl(anchorFetchEndpoint, clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -327,6 +341,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(super.subordinateStatementUrl(anchorFetchEndpoint, clientId))), any());
         assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -392,6 +407,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         verify(federationHttpClient, times(0)).executeOpen(any(),
                 argThat(new RequestUriMatcher(super.subordinateStatementUrl(anchorFetchEndpoint, clientId))), any());
         Assert.assertEquals(providedMetadata.getScope(), scope);
+        verifyAuthorizeEndpoint(clientId, redirectUri);
     }
 
     @Test
@@ -404,6 +420,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
             setRequest("POST", trustChain, "application/trust-chain+json");
             final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
             assertResponseStatement(parseSuccessResponse(result, ExplicitClientRegistrationResponse.class), clientId);
+            verifyAuthorizeEndpoint(clientId, redirectUri);
         }
     }
 
@@ -438,5 +455,30 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
         Assert.assertTrue(metadata.getRedirectionURIStrings().contains(redirectUri));
         return metadata;
     }
-    
+
+    protected void verifyAuthorizeEndpoint(final String clientId, final String redirectUri) {
+        initializeMocks();
+        initializeThreadLocals();
+        setBasicAuth(subject, "changeit");
+        request.setMethod("GET");
+
+        final StringBuffer query = new StringBuffer();
+        for (final Pair<String, String> pair : List.of(new Pair<>("client_id", clientId),
+                new Pair<>("redirect_uri", redirectUri), new Pair<>("response_type", "code"),
+                new Pair<>("scope", "openid"))) {
+            final String first = pair.getFirst();
+            assert first != null;
+            request.addParameter(first, pair.getSecond());
+            try {
+                query.append(pair.getFirst() + "=" + URLEncoder.encode(pair.getSecond(), "UTF-8") + "&");
+            } catch (UnsupportedEncodingException e) {
+                Assert.fail(e.getMessage());
+            }
+        }
+        request.setQueryString(query.toString());
+
+        final FlowExecutionResult result = flowExecutor.launchExecution("oidc/authorize", null, externalContext);
+        Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
+    }
+
 }

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


More information about the commits mailing list