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