[java-idp-plugin-oidc-op-oidfed] branch dev/CACHE-REFACTOR updated: Harmonised explicit registration request (statement and trust chain) validation
Codeberg
noreply at shibboleth.net
Thu Jan 29 16:28:15 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/0021ff1020e74f53431f9b5077e7dcaa89512866
The following commit(s) were added to refs/heads/dev/CACHE-REFACTOR by this push:
new 0021ff1 Harmonised explicit registration request (statement and trust chain) validation
0021ff1 is described below
commit 0021ff1020e74f53431f9b5077e7dcaa89512866
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Thu Jan 29 18:27:52 2026 +0200
Harmonised explicit registration request (statement and trust chain) validation
- By default fetch-through metadata caches are exploited for both provided entity configurations and trust chains
- Content validation can be shared with the other existing metadata caches
- Explicit registration specific claims validation: shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator (includes audience)
- This way also the custom cache container filter strategies may be wired via 'idp.oidfed.cache.<cacheType>.customFilterStrategies'
- new cacheTypes: explicitRegistration.trustChain and explicitRegistration.entityConfiguration
---
...rustChainSignatureValidationFilterStrategy.java | 125 ------------------
...efaultProvidedTrustChainValidationStrategy.java | 119 -----------------
.../ProvidedEntityConfigurationContainer.java | 55 ++++++++
...videdEntityConfigurationContainerCriterion.java | 88 +++++++++++++
.../cache/local/ProvidedTrustChainContainer.java | 50 ++++++++
.../ProvidedTrustChainContainerCriterion.java | 81 ++++++++++++
...videdEntityConfigurationValidationStrategy.java | 97 ++++++++++++++
...efaultProvidedTrustChainValidationStrategy.java | 96 ++++++++++++++
.../META-INF/net.shibboleth.idp/postconfig.xml | 38 ++++++
.../idp/flows/oidfed/register/register-beans.xml | 142 +++++++++++++--------
...shedAuthorizeFlowAutomaticRegistrationTest.java | 2 +
.../profile/flow/oidfed/RegistrationFlowTest.java | 86 +++++++++++--
.../oidfed/TokenFlowAutomaticRegistrationTest.java | 19 ++-
13 files changed, 686 insertions(+), 312 deletions(-)
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
deleted file mode 100644
index 04b2901..0000000
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainSignatureValidationFilterStrategy.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * 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.metadata.cache;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.function.BiFunction;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.security.trust.TrustEngine;
-import org.slf4j.Logger;
-
-import com.nimbusds.jwt.SignedJWT;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-import net.shibboleth.shared.resolver.CriteriaSet;
-
-/**
- * Default signature validating filter for trust chains. The signature for each entity statement within trust chain is
- * verified by using the contents of the jwks-claim of the payload of the next entity statement in the chain until the
- * final subordinate statement of the chain. The configurable {@link TrustEngine} is used for validating the signature.
- * The trust engine is fed with the JWT to be verified and the {@link SignatureValidationKeyContainerJwtCriterion}
- * containing the JWT with the keyset used for validation.
- *
- * The trust anchor signature validation is performed by passing the trust anchor entity statement of each trust chain
- * to the configurable validation filter strategy.
- *
- * All the chains that don't pass the full validation are filtered out from the result.
- */
- at ThreadSafeAfterInit
-public class DefaultProvidedTrustChainSignatureValidationFilterStrategy
- extends AbstractTrustEngineSignatureValidationComponent
- implements BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>> {
-
- /** Class logger. */
- @Nonnull private Logger log =
- LoggerFactory.getLogger(DefaultProvidedTrustChainSignatureValidationFilterStrategy.class);
-
- /** Signature validation filter strategy for trust anchor entity statements. */
- @NonnullAfterInit private BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>>
- entityStatementSignatureValidationFilterStrategy;
-
- /**
- * Set the signature validation filter strategy for trust anchor entity statements.
- *
- * @param strategy signature validation filter strategy
- */
- public void setEntityStatementSignatureValidationFilterStrategy(@Nonnull final
- BiFunction<EntityStatement<?>, MetadataFilterContext, EntityStatement<?>> strategy) {
- checkSetterPreconditions();
- entityStatementSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
- "EntityStatementSignatureValidationFilterStrategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
- if (entityStatementSignatureValidationFilterStrategy == null) {
- throw new ComponentInitializationException(
- "EntityStatementSignatureValidationFilterStrategy cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override @Nullable
- public List<List<EntityStatement<?>>> apply(@Nullable final List<List<EntityStatement<?>>> trustChains,
- @Nullable final MetadataFilterContext filterContext) {
- checkComponentActive();
- if (trustChains == null) {
- return null;
- }
- final List<List<EntityStatement<?>>> result = new ArrayList<>();
-
- trustChainLoop: for (final List<EntityStatement<?>> trustChain : trustChains) {
- for (int i = 0; i < trustChain.size() - 1; i++) {
- final EntityStatement<?> trustChainEntry = trustChain.get(i);
- final SignedJWT keyContainer = trustChain.get(i + 1).getJwt();
- final CriteriaSet criteria =
- new CriteriaSet(new SignatureValidationKeyContainerJwtCriterion(keyContainer));
- if (!validateJwt(trustChainEntry.getJwt(), criteria,
- trustChain.get(i).getSubject())) {
- log.warn("The signature check for {} failed, ignoring trust chain anchored by {}",
- i == 0 ? "leaf entity configuration" : "subordinate statement",
- trustChain.get(trustChain.size() - 1).getSubject());
- log.trace("Key container {} could not validate the trust chain entry {}", keyContainer.serialize(),
- trustChainEntry.getJwt().serialize());
- continue trustChainLoop;
- }
- }
- final EntityStatement<?> trustAnchorStatement = trustChain.get(trustChain.size() - 1);
- final String entityId = trustAnchorStatement.getSubject();
- final EntityStatement<?> filteredStatement =
- entityStatementSignatureValidationFilterStrategy.apply(trustAnchorStatement, filterContext);
- if (filteredStatement != null) {
- log.debug("Successfully validated entity statement for trust anchor {}", entityId);
- result.add(trustChain);
- } else {
- log.debug("Signature validation failed for trust anchor {}", entityId);
- }
- }
- return result;
- }
-
-}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
deleted file mode 100644
index 2580a24..0000000
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultProvidedTrustChainValidationStrategy.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * 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.metadata.cache;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.BiFunction;
-import java.util.function.BiPredicate;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-import org.slf4j.Logger;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.constraints.FederationPolicyConstraint;
-import net.shibboleth.oidc.metadata.filter.MetadataFilterContext;
-import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.component.ComponentInitializationException;
-import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.primitive.LoggerFactory;
-
-/**
- * Default strategy for validating provided trust chain signatures via configurable signature validation filter and
- * applying the constraints.
- */
-public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent
- implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
-
- /** Class logger. */
- @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
-
- /** Strategy for validating trust anchor's entity configuration signature. */
- @NonnullAfterInit BiFunction<List<List<EntityStatement<?>>>, MetadataFilterContext, List<List<EntityStatement<?>>>>
- trustChainSignatureValidationFilterStrategy;
-
- /** Map of supported federation policy constraints. */
- @NonnullAfterInit private Map<String, FederationPolicyConstraint> federationPolicyConstraints;
-
- /**
- * Set the strategy for validating trust chain signatures.
- *
- * @param strategy validation strategy
- */
- public void setTrustChainSignatureValidationFilterStrategy(
- @Nonnull final BiFunction<List<List<EntityStatement<?>>>,
- MetadataFilterContext, List<List<EntityStatement<?>>>> strategy) {
- checkSetterPreconditions();
- trustChainSignatureValidationFilterStrategy = Constraint.isNotNull(strategy,
- "TrustChainSignatureValidationFilterStrategy cannot be null");
- }
-
- /**
- * Set the map of supported federation policy constraints.
- *
- * @param constraints map of supported federation policy constraints.
- */
- public void setFederationPolicyConstraints(@Nonnull final Map<String, FederationPolicyConstraint> constraints) {
- checkSetterPreconditions();
- federationPolicyConstraints = Constraint.isNotNull(constraints, "Map of policy constraints cannot be null");
- }
-
- /**
- * Get the map of supported federation policy constraints.
- *
- * @return map of supported federation policy constraints.
- */
- @Nonnull public Map<String, FederationPolicyConstraint> getFederationPolicyConstraints() {
- checkComponentActive();
- assert federationPolicyConstraints != null;
- return federationPolicyConstraints;
- }
-
- /** {@inheritDoc} */
- @Override
- protected void doInitialize() throws ComponentInitializationException {
- super.doInitialize();
- if (trustChainSignatureValidationFilterStrategy == null) {
- throw new ComponentInitializationException("TrustChainSignatureValidationFilterStrategy cannot be null");
- }
- if (federationPolicyConstraints == null) {
- throw new ComponentInitializationException("Map of policy constraints cannot be null");
- }
- }
-
- /** {@inheritDoc} */
- @Override
- public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
- @Nullable final List<EntityStatement<?>> trustChain) {
- checkComponentActive();
- if (trustChain == null || trustChain.size() < 3 || trustChain.contains(null)) {
- log.error("No satisfactory trust chain provided");
- return false;
- }
-
- final List<List<EntityStatement<?>>> validatedChains =
- trustChainSignatureValidationFilterStrategy.apply(List.of(trustChain), null);
- if (validatedChains == null || validatedChains.size() != 1) {
- log.debug("The trust chain did not pass the signature validation");
- return false;
- }
-
- return true;
- }
-}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java
new file mode 100644
index 0000000..a7186d8
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainer.java
@@ -0,0 +1,55 @@
+/*
+ * 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.metadata.cache.local;
+
+import java.time.Instant;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
+
+/**
+ * Metadata cache container for provided entity configuration.
+ */
+public class ProvidedEntityConfigurationContainer extends RemoteEntityConfigurationContainer {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -6322735237721678470L;
+
+ /**
+ * Constructor.
+ *
+ * @param configuration provided entity configuration
+ */
+ public ProvidedEntityConfigurationContainer(@Nonnull final EntityConfiguration configuration) {
+ super(configuration.getSubject(), configuration, parseExpirationInstant(configuration),
+ parseExpirationInstant(configuration));
+ }
+
+ /**
+ * Parse expiration instant from entity configuration.
+ *
+ * @param configuration entity configuration
+ * @return expiration set in configuration, or 5 minutes from now if it was not set
+ */
+ @Nonnull private static Instant parseExpirationInstant(@Nonnull final EntityConfiguration configuration) {
+ final Instant expiration = Optional.ofNullable(configuration.getParsedPayload().getExpiration())
+ .orElse(Instant.now().plusSeconds(300));
+ assert expiration != null;
+ return expiration;
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java
new file mode 100644
index 0000000..81f5b37
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedEntityConfigurationContainerCriterion.java
@@ -0,0 +1,88 @@
+/*
+ * 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.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided entity configuration container.
+ */
+public class ProvidedEntityConfigurationContainerCriterion implements Criterion {
+
+ /** The entity configuration container value. */
+ @Nonnull final ProvidedEntityConfigurationContainer value;
+
+ /**
+ * Constructor.
+ *
+ * @param container the entity configuration container value, must not be null
+ */
+ public ProvidedEntityConfigurationContainerCriterion(
+ @Nonnull final ProvidedEntityConfigurationContainer container) {
+ value = Constraint.isNotNull(container, "Entity configuration container cannot be null");
+ }
+
+ /**
+ * Get the entity configuration container value.
+ *
+ * @return the container value
+ */
+ @Nonnull public ProvidedEntityConfigurationContainer getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final var statement = value.getStatement();
+ return "ProvidedEntityConfigurationContainerCriterion [value="
+ + (statement == null ? "null" : statement.getJwt().serialize())
+ + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ /** {@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 ProvidedEntityConfigurationContainerCriterion other = (ProvidedEntityConfigurationContainerCriterion) obj;
+ final var statement = value.getStatement();
+ final var otherStatement = other.value.getStatement();
+ if (statement == null) {
+ return otherStatement == null;
+ } else {
+ return statement.getJwt().serialize().equals(otherStatement == null ?
+ null : otherStatement.getJwt().serialize());
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java
new file mode 100644
index 0000000..1ecce44
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainer.java
@@ -0,0 +1,50 @@
+/*
+ * 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.metadata.cache.local;
+
+import java.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+
+/**
+ * Metadata cache container for provided trust chain. The expiration is hardcoded to five minutes as it's assumed
+ * that the container is solely used with fetch-through caches and the expiration validation is done during the
+ * filtering phase.
+ */
+public class ProvidedTrustChainContainer extends RemoteTrustChainsContainer {
+
+ /** Serial version UID. */
+ private static final long serialVersionUID = -6322735111721678470L;
+
+ /**
+ * Constructor.
+ *
+ * @param trustChain trust chain
+ * @param entity entity ID for the trust chain subject
+ */
+ public ProvidedTrustChainContainer(@Nonnull final List<EntityStatement<?>> trustChain,
+ @Nonnull @NotEmpty final String entity) {
+ super(Constraint.isNotNull(Instant.now().plusSeconds(300), "Expiration instant cannot be null"),
+ entity, CollectionSupport.listOf(trustChain));
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java
new file mode 100644
index 0000000..86fee10
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/local/ProvidedTrustChainContainerCriterion.java
@@ -0,0 +1,81 @@
+/*
+ * 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.metadata.cache.local;
+
+import java.util.Objects;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.Criterion;
+
+/**
+ * A {@link Criterion} representing a provided trust chain container.
+ */
+public class ProvidedTrustChainContainerCriterion implements Criterion {
+
+ /** The trust chain container value. */
+ @Nonnull final ProvidedTrustChainContainer value;
+
+ /**
+ * Constructor.
+ *
+ * @param container the trust chain container value, must not be null
+ */
+ public ProvidedTrustChainContainerCriterion(
+ @Nonnull final ProvidedTrustChainContainer container) {
+ value = Constraint.isNotNull(container, "Trust chain container cannot be null");
+ }
+
+ /**
+ * Get the trust chain container value.
+ *
+ * @return the container value
+ */
+ @Nonnull public ProvidedTrustChainContainer getValue() {
+ return value;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ return "ProvidedTrustChainContainerCriterion [value=" + value + "]";
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public int hashCode() {
+ return Objects.hash(value);
+ }
+
+ /** {@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 ProvidedTrustChainContainerCriterion other = (ProvidedTrustChainContainerCriterion) obj;
+ final var chains = value.getTrustChains();
+ final var otherChains = other.value.getTrustChains();
+ return value.getEntityId().equals(other.value.getEntityId()) && chains.containsAll(otherChains)
+ && otherChains.containsAll(chains);
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java
new file mode 100644
index 0000000..fd42bd6
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedEntityConfigurationValidationStrategy.java
@@ -0,0 +1,97 @@
+/*
+ * 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.logic;
+
+import java.util.List;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteEntityConfigurationContainer;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default validation strategy for provided entity configuration. A {@link MetadataCache} supporting
+ * {@link ProvidedEntityConfigurationContainerCriterion} is exploited for validation.
+ */
+public class DefaultProvidedEntityConfigurationValidationStrategy extends AbstractIdentifiableInitializableComponent
+ implements BiPredicate<ProfileRequestContext, EntityStatement<?>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedEntityConfigurationValidationStrategy.class);
+
+ /** Metadata cache for entity configuration validation. */
+ @NonnullAfterInit private MetadataCache<RemoteEntityConfigurationContainer> metadataCache;
+
+ /**
+ * Set the metadata cache for entity configuration validation.
+ *
+ * @param cache What to set.
+ */
+ public void setMetadataCache(@Nonnull final MetadataCache<RemoteEntityConfigurationContainer> cache) {
+ checkSetterPreconditions();
+ metadataCache = Constraint.isNotNull(cache, "Metadata cache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (metadataCache == null) {
+ throw new ComponentInitializationException("Metadata cache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nullable final EntityStatement<?> entityStatement) {
+ if (entityStatement instanceof EntityConfiguration entityConfiguration) {
+ final ProvidedEntityConfigurationContainer container =
+ new ProvidedEntityConfigurationContainer(entityConfiguration);
+ final ProvidedEntityConfigurationContainerCriterion criterion =
+ new ProvidedEntityConfigurationContainerCriterion(container);
+ try {
+ final List<RemoteEntityConfigurationContainer> result =
+ metadataCache.get(new CriteriaSet(criterion));
+ if (result.size() == 1 && result.get(0).getStatement() != null) {
+ return true;
+ }
+ } catch (final MetadataCacheException e) {
+ log.debug("{} Exception catched when validating entty configuration", e);
+ }
+
+ } else {
+ log.error("No entity configuration provided, returning false");
+ }
+ return false;
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
new file mode 100644
index 0000000..2ff1715
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/logic/DefaultProvidedTrustChainValidationStrategy.java
@@ -0,0 +1,96 @@
+/*
+ * 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.logic;
+
+import java.util.List;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.RemoteTrustChainsContainer;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+/**
+ * Default validation strategy for provided trust chain. A {@link MetadataCache} supporting
+ * {@link ProvidedTrustChainContainerCriterion} is exploited for validation.
+ */
+public class DefaultProvidedTrustChainValidationStrategy extends AbstractIdentifiableInitializableComponent
+ implements BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainValidationStrategy.class);
+
+ /** Metadata cache for trust chain validation. */
+ @NonnullAfterInit private MetadataCache<RemoteTrustChainsContainer> metadataCache;
+
+ /**
+ * Set the metadata cache for trust chain validation.
+ *
+ * @param cache What to set.
+ */
+ public void setMetadataCache(@Nonnull final MetadataCache<RemoteTrustChainsContainer> cache) {
+ checkSetterPreconditions();
+ metadataCache = Constraint.isNotNull(cache, "Metadata cache cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (metadataCache == null) {
+ throw new ComponentInitializationException("Metadata cache cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final ProfileRequestContext profileRequestContext,
+ @Nullable final List<EntityStatement<?>> trustChain) {
+ if (trustChain != null && !trustChain.isEmpty()) {
+ final ProvidedTrustChainContainer container = new ProvidedTrustChainContainer(trustChain,
+ trustChain.get(0).getSubject());
+ final ProvidedTrustChainContainerCriterion criterion =
+ new ProvidedTrustChainContainerCriterion(container);
+ try {
+ final List<RemoteTrustChainsContainer> result = metadataCache.get(new CriteriaSet(criterion));
+ if (result.size() == 1 && !result.get(0).getTrustChains().isEmpty()) {
+ return true;
+ }
+ } catch (final MetadataCacheException e) {
+ log.debug("Exception catched when validating trust chain", e);
+ }
+
+ } else {
+ log.error("No trust chain provided, returning false");
+ }
+ return false;
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 12762ff..3b5b4ce 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -1066,6 +1066,44 @@
p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
p:trustMarkCache-ref="shibboleth.oidfed.TrustMarkMetadataCache"/>
+ <bean id="shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator"
+ class="net.shibboleth.oidc.security.jwt.claims.impl.ChainingJWTClaimsValidator">
+ <property name="claimValidators">
+ <util:list value-type="net.shibboleth.oidc.jwt.claims.ClaimsValidator">
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.IssuedAtClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}"
+ p:messageLifetime="%{idp.policy.messageLifetime:PT1M}"
+ p:requiredRule="true" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="iss" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="sub" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="jwks" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ExpiryClaimsValidator"
+ p:clockSkew="%{idp.policy.clockSkew:PT1M}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.AudienceClaimsValidator">
+ <property name="audienceLookupStrategy">
+ <bean parent="shibboleth.BiFunctions.Constant"
+ c:target-ref="shibboleth.oidc.issuer" />
+ </property>
+ </bean>
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="metadata_policy_crit" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="constraints" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.ProhibitedClaimsValidator"
+ p:prohibitedClaims="source_endpoint" />
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.jwt.claims.impl.CritClaimsValidator"
+ p:recognizedClaims="%{idp.oidfed.cache.explicitRegistration.critClaims:%{idp.oidfed.cache.default.critClaims:}}" />
+ <bean class="net.shibboleth.oidc.security.jwt.claims.impl.RequiredClaimsValidator"
+ p:requiredClaims="authority_hints" />
+ </util:list>
+ </property>
+ </bean>
+
<import resource="${idp.home}/conf/oidfed/oidfed-trustchain-resolver.xml"/>
</beans>
\ 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 6cce525..6ad1232 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
@@ -46,42 +46,61 @@
p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}"
p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}">
<property name="providedTrustChainValidationStrategy">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultProvidedTrustChainValidationStrategy"
- p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}">
- <property name="trustChainSignatureValidationFilterStrategy">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultProvidedTrustChainSignatureValidationFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
+ p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedTrustChainMetadataCache:FetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+ </property>
+ <property name="providedTrustChainLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
+ </property>
+ </bean>
+
+ <bean id="FetchThroughProvidedTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="FetchThroughProvidedTrustChainMetadataCache" parent="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.FetchThroughMetadataCacheBuilderSpec">
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion))?.getValue()?.getEntityId()" />
+ </property>
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+ </property>
+ <property name="fetchStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedTrustChainContainerCriterion))?.getValue()" />
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.trustchain.DefaultRemoteTrustChainSignatureValidationFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.explicitRegistration.trustChain.customFilterStrategies:}'.trim())}">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="entityStatementSignatureValidationFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
<property name="trustEngine">
<bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
<constructor-arg index="0">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
- </constructor-arg>
- <constructor-arg index="1">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
- </constructor-arg>
- </bean>
- </property>
- <property name="entityStatementSignatureValidationFilterStrategy">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
- <property name="trustEngine">
- <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
- <constructor-arg index="0">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
- c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
- </constructor-arg>
- <constructor-arg index="1">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
- </constructor-arg>
- </bean>
- </property>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
+ c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
</bean>
</property>
</bean>
</property>
</bean>
</property>
- <property name="providedTrustChainLookupStrategy">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
- </property>
</bean>
<bean id="ExplicitClientRegistrationRequestEntityConfigurationLookupFunction"
@@ -91,11 +110,43 @@
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.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy"
- p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedEntityConfigurationValidationStrategy"
+ p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedEntityConfigurationMetadataCache:FetchThroughProvidedEntityConfigurationMetadataCache}'.trim()}" />
+ </property>
+ </bean>
+
+ <bean id="FetchThroughProvidedEntityConfigurationMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="FetchThroughProvidedEntityConfigurationMetadataCache" parent="FetchThroughProvidedEntityConfigurationMetadataCacheBuilderSpec"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="FetchThroughProvidedEntityConfigurationMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.FetchThroughMetadataCacheBuilderSpec">
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion))?.getValue()?.getEntityId()" />
+ </property>
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+ </property>
+ <property name="fetchStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.ProvidedEntityConfigurationContainerCriterion))?.getValue()" />
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementMetadataFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.explicitRegistration.entityConfiguration.customFilterStrategies:}'.trim())}">
+ <property name="validationConditions">
+ <util:list value-type="java.util.function.BiPredicate">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementSignatureValidationCondition"
+ p:trustEngine-ref="shibboleth.oidfed.DefaultEntityConfigurationTrustEngine">
+ <property name="criteriaSetLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.configuration.DefaultEntityConfigurationCriteriaSetLookupFunction"/>
+ </property>
+ </bean>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.remote.DefaultEntityStatementContentValidationCondition"
+ p:claimsValidator-ref="%{idp.oidfed.cache.explicitRegistrationRequest.claimsValidator:shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator}"
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ </util:list>
</property>
</bean>
</property>
@@ -150,27 +201,18 @@
p:criteriaToSubjectEntityIdStrategy-ref="shibboleth.oidfed.DefaultSubjectEntityIDCriteriaToIdentifierStrategy"
p:entityConfigurationCache-ref="shibboleth.oidfed.EntityConfigurationMetadataCache"
p:subordinateStatementCache-ref="shibboleth.oidfed.SubordinateEntityStatementMetadataCache"
- p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache">
- <property name="criteriaToValidContainerLifetimeStrategy">
- <bean parent="shibboleth.Functions.Constant">
- <constructor-arg>
- <!-- Doesn't matter for the fetch-through cache -->
- <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
- </constructor-arg>
- </bean>
- </property>
- <property name="criteriaToInvalidContainerLifetimeStrategy">
- <bean parent="shibboleth.Functions.Constant">
- <constructor-arg>
- <!-- Doesn't matter for the fetch-through cache -->
- <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
- </constructor-arg>
- </bean>
- </property>
- </bean>
+ p:localTrustAnchorsCache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache"
+ p:criteriaToValidContainerLifetimeStrategy-ref="ConstantFiveMinutes"
+ p:criteriaToInvalidContainerLifetimeStrategy-ref="ConstantFiveMinutes" />
</property>
</bean>
+ <bean id="ConstantFiveMinutes" parent="shibboleth.Functions.Constant">
+ <constructor-arg>
+ <bean class="java.time.Duration" factory-method="parse" c:_0="PT5M" />
+ </constructor-arg>
+ </bean>
+
<bean id="DefaultMetadataPolicyEnforcer"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.policy.impl.DefaultFederationMetadataPolicyEnforcer"
p:metadataPolicyOperators-ref="#{'%{idp.oidfed.register.MetadataPolicyOperatorsy:shibboleth.oidfed.StandardMetadataPolicyOperators}'.trim()}"/>
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 108e208..d9b8b8f 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
@@ -427,6 +427,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
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 claims != null;
Assert.assertNull(claims.get(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN));
verifyAuthorizeEndpoint(clientId, requestUri);
@@ -523,6 +524,7 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
final String requestUri = resp.getRequestURI().toString();
Assert.assertTrue(requestUri.startsWith("urn:ietf:params:oauth:request_uri:"));
final Map<String,Object> claims = statelessDeserializer.apply(null, resp.getRequestURI());
+ assert claims != null;
Assert.assertNotNull(claims.get(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN));
}
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 18c0314..d25233d 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
@@ -22,8 +22,11 @@ import static org.mockito.Mockito.verify;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URLEncoder;
+import java.time.Instant;
import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -38,7 +41,9 @@ import org.testng.annotations.Test;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.ParseException;
import com.nimbusds.oauth2.sdk.Scope;
@@ -102,7 +107,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidEntityConfiguration_invalidType() throws Exception {
final String clientId = uniqueClientId();
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/json");
+ setRequest("POST", explicitRegistrationRequest(clientId), "application/json");
final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
assertErrorCode(result, "invalid_request");
}
@@ -111,7 +116,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidEntityConfiguration() throws Exception {
final String clientId = uniqueClientId();
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -123,18 +128,33 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
public void testValidEntityConfiguration_emptyMetadataPolicyCrit() throws Exception {
final String clientId = uniqueClientId();
rpConfigureMockHttpClientWithMetadataCrit(clientId, CollectionSupport.emptyList());
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
assertErrorCode(result, "invalid_client");
}
+ @Test
+ public void testValidEntityConfiguration_emptyCritical() throws Exception {
+ final String clientId = uniqueClientId();
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ rpConfigureMockHttpClient(clientId, metadata);
+ setRequest("POST", explicitRegistrationRequest(clientId, metadata, CollectionSupport.emptyList(),
+ rpKey), "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());
+ assertErrorCode(result, "invalid_request");
+ }
+
@Test
public void testValidEntityConfiguration_verifyPar() throws Exception {
final String clientId = uniqueClientId();
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -148,7 +168,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final String clientId = uniqueClientId();
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
rpResolveEntityConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -161,7 +181,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final String clientId = uniqueClientId();
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -173,7 +193,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final String clientId = uniqueClientId();
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -186,7 +206,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
request.addHeader(USE_CUSTOM_RESOLVER_API_CONDITION, "true");
request.addHeader(USE_CUSTOM_FAILBACK_TO_LOCAL_CONDITION, "true");
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -203,7 +223,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
final Scope scope = Scope.parse("openid profile email custom");
metadata.setScope(scope);
- setRequest("POST", rpEntityConfiguration(clientId, metadata), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(clientId, metadata), "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());
@@ -223,7 +243,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final Scope scope = Scope.parse("openid profile email custom");
metadata.setScope(scope);
rpResolveEntityConfigureMockHttpClient(clientId, metadata);
- setRequest("POST", rpEntityConfiguration(clientId, metadata), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(clientId, metadata), "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());
@@ -238,7 +258,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
final String clientId = uniqueClientId();
for (int i = 0; i < 2; i++) {
rpConfigureMockHttpClient(clientId);
- setRequest("POST", rpEntityConfiguration(clientId), "application/entity-statement+jwt");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -253,7 +273,7 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
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");
+ setRequest("POST", explicitRegistrationRequest(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());
@@ -554,4 +574,46 @@ public class RegistrationFlowTest extends AbstractFederationFlowTest {
externalContext);
Assert.assertEquals(result.getOutcome().getId(), END_STATE_ID);
}
+
+ protected String explicitRegistrationRequest(final String clientId, final String... authorityHints)
+ throws URISyntaxException {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
+ }
+
+ protected String explicitRegistrationRequest(final String clientId, final OIDCClientMetadata metadata,
+ final String... authorityHints) throws URISyntaxException {
+ return explicitRegistrationRequest(clientId, leafKey, null, List.of(issuer), metadata, authorityHints);
+ }
+
+ protected String explicitRegistrationRequest(final String clientId, final OIDCClientMetadata metadata,
+ final List<String> critical, final JWK leafKey, final String... authorityHints)
+ throws URISyntaxException {
+ return explicitRegistrationRequest(clientId, leafKey, critical, List.of(issuer), metadata, authorityHints);
+ }
+
+ protected String explicitRegistrationRequest(final String clientId, final JWK leafKey,
+ final List<String> critical, final List<String> audience, final OIDCClientMetadata metadata,
+ final String... authorityHints) throws URISyntaxException {
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(clientId).subject(clientId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .audience(audience)
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("openid_relying_party", metadata.toJSONObject()))
+ .claim("authority_hints", authorityHints == null || authorityHints.length == 0 ?
+ new String[] { anchorId } : authorityHints);
+ if (audience != null) {
+ builder.audience(audience);
+ }
+ if (critical != null) {
+ builder.claim("crit", critical);
+ }
+ final EntityStatement<?> rpConfiguration =
+ TrustChainTestUtil.entityStatement(JWSAlgorithm.RS256, leafKey, builder.build());
+ return rpConfiguration.getJwt().serialize();
+ }
+
}
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
index 4cbd501..ead63e7 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/TokenFlowAutomaticRegistrationTest.java
@@ -30,6 +30,7 @@ import org.testng.annotations.Test;
import com.nimbusds.jose.JWSAlgorithm;
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.Scope;
@@ -71,8 +72,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
Assert.assertNotNull(response.getTokens().getAccessToken());
final AccessTokenClaimsSet tokenClaims =
AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
- Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
- .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+ final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+ assert claimsSet != null;
+ Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+ List.of(clientId, anchorId));
Assert.assertNotNull(response.getOIDCTokens().getIDToken());
Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
}
@@ -95,8 +98,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
Assert.assertNotNull(response.getTokens().getAccessToken());
final AccessTokenClaimsSet tokenClaims =
AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
- Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
- .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+ final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+ assert claimsSet != null;
+ Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+ List.of(clientId, anchorId));
Assert.assertNotNull(response.getOIDCTokens().getIDToken());
Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
}
@@ -155,8 +160,10 @@ public class TokenFlowAutomaticRegistrationTest extends AbstractFederationFlowTe
Assert.assertNotNull(response.getTokens().getAccessToken());
final AccessTokenClaimsSet tokenClaims =
AccessTokenClaimsSet.parse(response.getTokens().getAccessToken().getValue(), getDataSealer());
- Assert.assertEquals(tokenClaims.getClaimsSet().getStringListClaim(ClaimsSetExtensionSupport
- .KEY_AUTO_REGISTERED_TRUST_CHAIN), List.of(clientId, anchorId));
+ final JWTClaimsSet claimsSet = tokenClaims.getClaimsSet();
+ assert claimsSet != null;
+ Assert.assertEquals(claimsSet.getStringListClaim(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN),
+ List.of(clientId, anchorId));
Assert.assertNotNull(response.getOIDCTokens().getIDToken());
Assert.assertNotNull(response.getOIDCTokens().getIDToken().getJWTClaimsSet().getClaim("at_hash"));
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list