[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