[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Nov 15 10:35:58 UTC 2024
This is an automated email from the git hooks/post-receive script.
hjmikkon pushed a commit to branch dev/JOIDC-222
in repository java-idp-oidc.
View the commit online:
http://git.shibboleth.net/view/?p=java-idp-oidc.git;a=commit;h=9df87708eb52f7b985bca8cd90403c1b11a8cb77
The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
new 9df87708 JOIDC-222 - Support for OpenID Federation
9df87708 is described below
commit 9df87708eb52f7b985bca8cd90403c1b11a8cb77
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Nov 15 12:35:21 2024 +0200
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Initial implementation for the automatic registration in the authorization flow
- If the feature is enabled (TBD in the profile configurations), the trust chains are resolved
- Metadata policy merging & encforcing is done via configurable strategies
- Trust chain is selected via configurable strategy
- By default, the shortest is selected
- After successful authentication, the metadata is registered via configurable ClientInformationManager
- Similar to dynamic registration flow
---
.../impl/RelyingPartyTrustChainContext.java | 135 +++++++++++
.../op/oidfed/profile/impl/ResolveTrustChains.java | 255 +++++++++++++++++++++
.../op/oidfed/profile/impl/SelectTrustChain.java | 156 +++++++++++++
.../profile/impl/StoreAutomaticRegistration.java | 154 +++++++++++++
...ultTrustChainMetadataPolicyMergingStrategy.java | 174 ++++++++++++++
.../DefaultTrustChainSelectionStrategy.java | 100 ++++++++
.../idp/flows/oidc/authorize/authorize-beans.xml | 52 +++++
.../idp/flows/oidc/authorize/authorize-flow.xml | 37 ++-
8 files changed, 1060 insertions(+), 3 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
new file mode 100644
index 00000000..8948c452
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/RelyingPartyTrustChainContext.java
@@ -0,0 +1,135 @@
+/*
+ * 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.time.Instant;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.shared.collection.Pair;
+
+/**
+ * Subcontext carrying information for trust chains related to a relying party.
+ *
+ * @since 4.3.0
+ */
+public final class RelyingPartyTrustChainContext extends BaseContext {
+
+ /** All resolved trust chains for the relying party. */
+ @Nullable private List<List<EntityStatement>> resolvedTrustChains;
+
+ /** Policy-compliant trust chains for the relying party. */
+ @Nullable private List<Pair<List<EntityStatement>,OIDCClientInformation>> policyCompliantTrustChains;
+
+ /** Selected trust chain for the relying party. */
+ @Nullable private Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain;
+
+ /** Expiration instant for the selected metadata. */
+ @Nullable private Instant selectedMetadataExpiration;
+
+ /**
+ * Get the resolved trust chains for the relying party.
+ *
+ * @return the trust chains
+ */
+ @Nullable public List<List<EntityStatement>> getResolvedTrustChains() {
+ return resolvedTrustChains;
+ }
+
+ /**
+ * Set the resolved trust chains for the relying party.
+ *
+ * @param trustChains the trust chains
+ *
+ * @return this context
+ */
+ @Nonnull public RelyingPartyTrustChainContext setResolvedTrustChains(
+ @Nullable final List<List<EntityStatement>> trustChains) {
+ resolvedTrustChains = trustChains;
+ return this;
+ }
+
+ /**
+ * Get the policy-compliant trust chains for the relying party.
+ *
+ * @return the trust chains
+ */
+ @Nullable public List<Pair<List<EntityStatement>,OIDCClientInformation>> getPolicyCompliantTrustChains() {
+ return policyCompliantTrustChains;
+ }
+
+ /**
+ * Set the policy-compliant trust chains for the relying party.
+ *
+ * @param chains the trust chains and client informations
+ *
+ * @return this context
+ */
+ @Nonnull public RelyingPartyTrustChainContext setPolicyCompliantTrustChains(
+ @Nullable final List<Pair<List<EntityStatement>,OIDCClientInformation>> chains) {
+ policyCompliantTrustChains = chains;
+ return this;
+ }
+
+ /**
+ * Get the selected trust chain for the relying party.
+ *
+ * @return the trust chain
+ */
+ @Nullable public Pair<List<EntityStatement>,OIDCClientInformation> getSelectedTrustChain() {
+ return selectedTrustChain;
+ }
+
+ /**
+ * Set the selected trust chain for the relying party.
+ *
+ * @param chais the selected trust chain
+ *
+ * @return this context
+ */
+ @Nonnull public RelyingPartyTrustChainContext setSelectedTrustChains(
+ @Nullable final Pair<List<EntityStatement>,OIDCClientInformation> chain) {
+ selectedTrustChain = chain;
+ return this;
+ }
+
+ /**
+ * Get the expiration instant for the selected metadata
+ *
+ * @return the expiration instant
+ */
+ @Nullable public Instant getSelectedMetadataExpiration() {
+ return selectedMetadataExpiration;
+ }
+
+ /**
+ * Set the expiration instant for the selected metadata.
+ *
+ * @param expiration the expiration instant
+ *
+ * @return this context
+ */
+ @Nonnull public RelyingPartyTrustChainContext setSelectedMetadataExpiration(@Nullable final Instant expiration) {
+ selectedMetadataExpiration = expiration;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
new file mode 100644
index 00000000..d01db3d9
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ResolveTrustChains.java
@@ -0,0 +1,255 @@
+/*
+ * 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.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.id.ClientID;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
+
+import net.minidev.json.JSONObject;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubjectEntityIDCriterion;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.CriteriaSet;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Resolves metadata policy-compliant trust chains from the configurable trust chain cache, metadata policy merging
+ * strategy and enforcer. The data is populated to the {@link RelyingPartyTrustChainContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ *
+ * @since 4.3.0
+ */
+public class ResolveTrustChains extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(ResolveTrustChains.class);
+
+ /** Metadata cache for trust chains. */
+ @NonnullAfterInit private MetadataCache<List<List<EntityStatement>>> trustChainCache;
+
+ /** Strategy used to obtain the client id value for authorize/token request. */
+ @NonnullAfterInit private Function<MessageContext, ClientID> clientIDLookupStrategy;
+
+ /** Strategy used to create the trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextCreationStrategy;
+
+ /** Strategy used to merge metadata policies in trust chain for specific entity type. */
+ @NonnullAfterInit private BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>>
+ metadataPolicyMergingStrategy;
+
+ /** Enforcer function for applying metadata policy for an item. */
+ @NonnullAfterInit private BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> metadataPolicyEnforcer;
+
+ /** OAuth2 client id. */
+ @NonnullBeforeExec private String clientId;
+
+ /**
+ * Constructor.
+ */
+ public ResolveTrustChains() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tccs =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert tccs != null;
+ trustChainContextCreationStrategy = tccs;
+ }
+
+ /**
+ * Set the metadata cache for trust chains.
+ *
+ * @param cache metadata cache
+ */
+ public void setTrustChainCache(@Nonnull final MetadataCache<List<List<EntityStatement>>> cache) {
+ checkSetterPreconditions();
+ trustChainCache = Constraint.isNotNull(cache, "TrustChainCache cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the client id of the request.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClientIDLookupStrategy(@Nonnull final Function<MessageContext, ClientID> strategy) {
+ checkSetterPreconditions();
+ clientIDLookupStrategy =
+ Constraint.isNotNull(strategy, "ClientIDLookupStrategy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to merge metadata policies in trust chain for specific entity type.
+ *
+ * @param strategy merging strategy
+ */
+ public void setMetadataPolicyMergingStrategy(@Nonnull final
+ BiFunction<List<EntityStatement>,String,Map<String, MetadataPolicy>> strategy) {
+ checkSetterPreconditions();
+ metadataPolicyMergingStrategy =
+ Constraint.isNotNull(strategy, "MetadataPolicyMergingStrategy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the enforcer function for applying metadata policy for an item.
+ *
+ * @param enforcer policy enforcer
+ */
+ public void setMetadataPolicyEnforcer(
+ @Nonnull final BiFunction<Object, MetadataPolicy, Pair<Object, Boolean>> enforcer) {
+ checkSetterPreconditions();
+ metadataPolicyEnforcer = Constraint.isNotNull(enforcer, "Metadata policy enforcer cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (trustChainCache == null) {
+ throw new ComponentInitializationException("TrustChainCache cannot be null");
+ }
+ if (clientIDLookupStrategy == null) {
+ throw new ComponentInitializationException("ClientIDLookupStrategy cannot be null");
+ }
+ if (metadataPolicyEnforcer == null) {
+ throw new ComponentInitializationException("MetadataPolicyEnforcer cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final ClientID id = clientIDLookupStrategy.apply(profileRequestContext.getInboundMessageContext());
+ if (id == null) {
+ log.error("{} Unable to obtain client ID", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ clientId = id.getValue();
+ try {
+ new URL(clientId).toURI();
+ } catch (final URISyntaxException | MalformedURLException e) {
+ log.debug("{} The client ID {} is not a valid URL, nothing to do", getLogPrefix(), clientId);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ log.debug("Resolving trust chain for {)", clientId);
+ final List<List<List<EntityStatement>>> cacheResult;
+ try {
+ assert clientId != null;
+ cacheResult = trustChainCache.get(new CriteriaSet(new SubjectEntityIDCriterion(clientId)));
+ } catch (final MetadataCacheException e) {
+ log.warn("{} Could fetch trust chains for {}", getLogPrefix(), clientId);
+ return;
+ }
+ if (cacheResult.isEmpty()) {
+ log.debug("{} No trust chains resolved for {}", getLogPrefix(), clientId);
+ return;
+ }
+
+ final RelyingPartyTrustChainContext trustChainContext =
+ trustChainContextCreationStrategy.apply(profileRequestContext);
+ trustChainContext.setResolvedTrustChains(cacheResult.get(0));
+ final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains = new ArrayList<>();
+
+ for (final List<EntityStatement> chain : cacheResult.get(0)) {
+ final Map<String, MetadataPolicy> mergedPolicies =
+ metadataPolicyMergingStrategy.apply(chain, EntityType.OPENID_RELYING_PARTY.getValue());
+ log.debug("{} Merged policy for chain {}", getLogPrefix(), mergedPolicies);
+ final OIDCClientMetadata metadata = chain.get(0).getClaimsSet().getRPMetadata();
+ if (metadata != null) {
+ final OIDCClientInformation clientInformation = new OIDCClientInformation(
+ new ClientID(chain.get(0).getEntityID().getValue()), metadata);
+ boolean compliant = true;
+ final JSONObject requestMetadata = clientInformation.toJSONObject();
+ for (final String claim : mergedPolicies.keySet()) {
+ final MetadataPolicy policy = mergedPolicies.get(claim);
+ final Object value = requestMetadata.get(claim);
+ log.debug("{} Claim {} set in policy included in the request: {}", getLogPrefix(), claim,
+ value == null);
+ final Pair<Object,Boolean> mergeResult = metadataPolicyEnforcer.apply(value, policy);
+ final Boolean enforcerResult = mergeResult != null ? mergeResult.getSecond() : null;
+ if (enforcerResult == null || !enforcerResult.booleanValue()) {
+ log.warn("{} Metadata claim {} is not compliant with the policy", getLogPrefix(), claim);
+ compliant = false;
+ } else {
+ log.trace("{} Validation result is OK for claim {}", getLogPrefix(), claim);
+ final Object enforcedValue = mergeResult != null ? mergeResult.getFirst() : null;
+ requestMetadata.put(claim, enforcedValue);
+ }
+ }
+
+ if (!compliant) {
+ log.warn("{} The requested metadata is not compliant with the policy", getLogPrefix());
+ } else {
+ log.debug("{} The requested metadata is compliant with the policy", getLogPrefix());
+ try {
+ policyCompliantChains.add(new Pair<>(chain, OIDCClientInformation.parse(requestMetadata)));
+ } catch (final ParseException e) {
+ log.error("{} Could not parse the metadata object", getLogPrefix(), e);
+ }
+ }
+ log.debug("{} Policy-enforced metadata {}", getLogPrefix(), requestMetadata.toJSONString());
+ } else {
+ log.error("{} No client information found from the entity statement", getLogPrefix());
+ }
+ }
+
+ log.debug("{} Setting the policy compliant trust chains into the context: {}", getLogPrefix(),
+ policyCompliantChains);
+ trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
new file mode 100644
index 00000000..048638ad
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/SelectTrustChain.java
@@ -0,0 +1,156 @@
+/*
+ * 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.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.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.oidfed.profile.navigate.DefaultTrustChainSelectionStrategy;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Selects the trust chain to be used for automatic registration via configurable lookup strategy and stores it to the
+ * {@link RelyingPartyTrustChainContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_MSG_CTX}
+ *
+ * @since 4.3.0
+ */
+public class SelectTrustChain extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(SelectTrustChain.class);
+
+ /** Strategy used to lookup the trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+ /** Strategy used to fetch the selected trust chain and metadata. */
+ @NonnullAfterInit private Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>>
+ selectedTrustChainLookupStrategy;
+
+ /** Trust chain context to operate on. */
+ @NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
+
+ /**
+ * Constructor.
+ */
+ public SelectTrustChain() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+ new InboundMessageContextLookup());
+ assert tcls != null;
+ trustChainContextLookupStrategy = tcls;
+ selectedTrustChainLookupStrategy = new DefaultTrustChainSelectionStrategy();
+ }
+
+ /**
+ * 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");
+ }
+
+ /**
+ * Set the strategy used to fetch the selected trust chain and metadata.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setSelectedTrustChainLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> strategy) {
+ checkSetterPreconditions();
+ selectedTrustChainLookupStrategy =
+ Constraint.isNotNull(strategy, "SelectedTrustChainLookupStrategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+ if (selectedTrustChainLookupStrategy == null) {
+ throw new ComponentInitializationException("Trust chain selection strategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ trustChainContext = trustChainContextLookupStrategy.apply(profileRequestContext);
+ if (trustChainContext == null || trustChainContext.getPolicyCompliantTrustChains() == null) {
+ log.error("{} Unable to locate policy-compliant trust chains", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final Pair<List<EntityStatement>, OIDCClientInformation> selectedChain =
+ selectedTrustChainLookupStrategy.apply(profileRequestContext);
+
+ if (selectedChain == null || selectedChain.getFirst() == null || selectedChain.getSecond() == null) {
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ log.error("{} No selected trust chain could be resolved", getLogPrefix());
+ return;
+ }
+
+ trustChainContext.setSelectedTrustChains(selectedChain);
+ final List<EntityStatement> selectedTrustChain = selectedChain.getFirst();
+ assert selectedTrustChain != null;
+ Instant metadataExpiration = null;
+ for (final EntityStatement statement : selectedTrustChain) {
+ final Instant statementExpiration = statement.getClaimsSet().getExpirationTime().toInstant();
+ metadataExpiration = metadataExpiration == null ? statementExpiration :
+ statementExpiration.isBefore(metadataExpiration) ? statementExpiration : metadataExpiration;
+ }
+ trustChainContext.setSelectedMetadataExpiration(metadataExpiration);
+
+ final OIDCMetadataContext oidcCtx = new OIDCMetadataContext();
+ oidcCtx.setClientInformation(selectedChain.getSecond());
+ profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
+ log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
new file mode 100644
index 00000000..5074365e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/StoreAutomaticRegistration.java
@@ -0,0 +1,154 @@
+/*
+ * 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.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+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.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.oidc.metadata.ClientInformationManager;
+import net.shibboleth.oidc.metadata.ClientInformationManagerException;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Stores the automatically registered client information via configurable {@link ClientInformationManager}.
+ * The client information and its expiration time are fetched from {@link RelyingPartyTrustChainContext}.
+ */
+public class StoreAutomaticRegistration extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(StoreAutomaticRegistration.class);
+
+ /** The client information manager used for storing the information. */
+ @NonnullAfterInit private ClientInformationManager clientInformationManager;
+
+ /** Strategy used to lookup the trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+ /** The client information to be stored. */
+ @NonnullBeforeExec private OIDCClientInformation clientInformation;
+
+ /** The expiration instant for the client informatiom. */
+ @NonnullBeforeExec private Instant expiration;
+
+ /**
+ * Constructor.
+ */
+ public StoreAutomaticRegistration() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+ new InboundMessageContextLookup());
+ assert tcls != null;
+ trustChainContextLookupStrategy = tcls;
+ }
+
+ /**
+ * Get the client information manager used for storing the information.
+ *
+ * @return The client information manager used for storing the information
+ */
+ @NonnullAfterInit public ClientInformationManager getClientInformationManager() {
+ return clientInformationManager;
+ }
+
+ /**
+ * Set the client information manager used for storing the information.
+ * @param manager The client information manager used for storing the information.
+ */
+ public void setClientInformationManager(@Nonnull final ClientInformationManager manager) {
+ ifInitializedThrowUnmodifiabledComponentException();
+ clientInformationManager = Constraint.isNotNull(manager, "The client information manager cannot be null!");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (clientInformationManager == null) {
+ throw new ComponentInitializationException("ClientInformationManager cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ final RelyingPartyTrustChainContext 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;
+ }
+
+ final Pair<List<EntityStatement>,OIDCClientInformation> selectedTrustChain =
+ trustChainContext.getSelectedTrustChain();
+ assert selectedTrustChain != null;
+ clientInformation = selectedTrustChain.getSecond();
+ if (clientInformation == null) {
+ log.error("{} Unable to locate selected metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+
+ expiration = trustChainContext.getSelectedMetadataExpiration();
+ if (expiration == null) {
+ log.error("{} Unable to resolve expiration time for the selected metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return false;
+ }
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ log.debug("{} Storing automatically registered client information", getLogPrefix());
+
+ try {
+ log.debug("{} Registration will expire on {}", getLogPrefix(), expiration);
+ assert clientInformation != null;
+ clientInformationManager.storeClientInformation(clientInformation, expiration, true);
+ } catch (final ClientInformationManagerException e) {
+ log.error("{} Could not store the client information", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return;
+ }
+ log.info("{} Client information successfully stored for {}", getLogPrefix(),
+ clientInformation.getID().getValue());
+ }
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
new file mode 100644
index 00000000..513e307a
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainMetadataPolicyMergingStrategy.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityType;
+import com.nimbusds.openid.connect.sdk.federation.policy.language.PolicyViolationException;
+
+import net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataPolicyMergingStrategy;
+import net.shibboleth.oidc.metadata.policy.MetadataPolicy;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.primitive.StringSupport;
+
+/**
+ * Default strategy to merge the metadata policies for the given entity type from the given trust chain. Finally,
+ * a configurable local metadata policy is merged to the resulting map of metadata policies.
+ */
+public class DefaultTrustChainMetadataPolicyMergingStrategy extends AbstractIdentifiableInitializableComponent
+ implements BiFunction<List<EntityStatement>,String, Map<String, MetadataPolicy>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainMetadataPolicyMergingStrategy.class);
+
+ /** The strategy used for merging two metadata policies. */
+ @Nonnull private BiFunction<Map<String, MetadataPolicy>, Map<String, MetadataPolicy>,
+ Pair<Map<String, MetadataPolicy>, Boolean>> metadataPolicyMergingStrategy;
+
+ /** The strategy used for local (additional) metadata policy. */
+ @NonnullAfterInit private Function<List<EntityStatement>, Map<String, MetadataPolicy>>
+ localMetadataPolicyStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultTrustChainMetadataPolicyMergingStrategy() {
+ metadataPolicyMergingStrategy = new DefaultMetadataPolicyMergingStrategy();
+ }
+
+ /**
+ * Set the strategy used for merging two metadata policies.
+ *
+ * @param strategy What to set.
+ */
+ public void setMetadataPolicyMergingStrategy(@Nonnull final BiFunction<Map<String,MetadataPolicy>,
+ Map<String,MetadataPolicy>, Pair<Map<String, MetadataPolicy>, Boolean>> strategy) {
+ checkSetterPreconditions();
+ metadataPolicyMergingStrategy = Constraint.isNotNull(strategy,
+ "Metadata policy merging strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used for local (additional) metadata policy.
+ *
+ * @param strategy What to set.
+ */
+ public void setLocalMetadataPolicyStrategy(
+ @Nonnull final Function<List<EntityStatement>, Map<String, MetadataPolicy>> strategy) {
+ checkSetterPreconditions();
+ localMetadataPolicyStrategy = Constraint.isNotNull(strategy,
+ "Local metadata policy strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Map<String, MetadataPolicy> apply(@Nullable final List<EntityStatement> trustChain,
+ @Nullable final String entityType) throws ConstraintViolationException {
+ checkComponentActive();
+ if (trustChain == null || trustChain.isEmpty()) {
+ return CollectionSupport.emptyMap();
+ }
+ final Map<String, MetadataPolicy> result = new HashMap<>();
+ for (int i = trustChain.size(); i > 0; i--) {
+ final EntityStatement entityStatement = trustChain.get(i - 1);
+ final Map<String, Object> policy = entityStatement.getClaimsSet().getMetadataPolicyJSONObject();
+ if (policy == null || policy.isEmpty()) {
+ continue;
+ }
+
+ if (StringSupport.trimOrNull(entityType) != null) {
+ try {
+ assert entityType != null;
+ result.putAll(mergePolicies(result, parseMetadataPolicy(entityStatement, entityType)));
+ } catch (final ConstraintViolationException e) {
+ throw new ConstraintViolationException("Could not merge federation metadata policies");
+ }
+ }
+ }
+ try {
+ result.putAll(mergePolicies(result, localMetadataPolicyStrategy.apply(trustChain)));
+ } catch (final ConstraintViolationException e) {
+ throw new ConstraintViolationException("Could not merge local metadata policy in the federation policy");
+ }
+ return result;
+ }
+
+ /**
+ * Merges the two maps of metadata policies.
+ *
+ * @param first the first map of policies
+ * @param second the second map of policies
+ * @return the map containing merged policies
+ * @throws ConstraintViolationException if the merging fails
+ */
+ @Nonnull
+ protected Map<String, MetadataPolicy> mergePolicies(@Nullable final Map<String, MetadataPolicy> first,
+ @Nullable final Map<String, MetadataPolicy> second) throws ConstraintViolationException {
+ final Pair<Map<String, MetadataPolicy>, Boolean> result = metadataPolicyMergingStrategy.apply(first, second);
+ if (result != null && Boolean.TRUE.equals(result.getSecond())) {
+ final Map<String, MetadataPolicy> mergedMap = result.getFirst();
+ return mergedMap == null ? CollectionSupport.emptyMap() : mergedMap;
+ }
+ throw new ConstraintViolationException("Merge failed");
+ }
+
+ /**
+ * Parse a map of metadata policies from the given entity statement for a specified entity type.
+ *
+ * @param entityStatement the source for the map of metadata policies
+ * @param entityType the entity type to use
+ * @return the map of metadata policies for the entity type or null if not found
+ * @throws ConstraintViolationException if the map of policies could not be parsed from the entity statement
+ */
+ @Nullable
+ protected Map<String, MetadataPolicy> parseMetadataPolicy(@Nonnull final EntityStatement entityStatement,
+ @Nonnull final String entityType) throws ConstraintViolationException {
+ final ObjectMapper objectMapper = new ObjectMapper();
+ final TypeReference<HashMap<String,MetadataPolicy>> typeRef =
+ new TypeReference<HashMap<String,MetadataPolicy>>() {};
+ final EntityType type = new EntityType(entityType);
+ try {
+ if (entityStatement.getClaimsSet().getMetadataPolicy(type) != null) {
+ return objectMapper.readValue(
+ entityStatement.getClaimsSet().getMetadataPolicy(type).toJSONString(), typeRef);
+ }
+ } catch (final JsonProcessingException | PolicyViolationException e) {
+ log.debug("Could not parse metadata policy of type {} from the claims set", entityType, e);
+ throw new ConstraintViolationException("Could not parse metadata policy of type " + entityType +
+ " from the claims set");
+ }
+ return null;
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
new file mode 100644
index 00000000..702d373d
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultTrustChainSelectionStrategy.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+
+import java.util.List;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
+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.oidfed.profile.impl.RelyingPartyTrustChainContext;
+import net.shibboleth.shared.collection.Pair;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default strategy for choosing a specific trust chain: it simply selects the first one in the list whose size is the
+ * shortest.
+ */
+public class DefaultTrustChainSelectionStrategy implements
+ Function<ProfileRequestContext,Pair<List<EntityStatement>, OIDCClientInformation>> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainSelectionStrategy.class);
+
+ /** Strategy used to locate the trust chain context. */
+ @Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultTrustChainSelectionStrategy() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert tcls != null;
+ trustChainContextLookupStrategy = tcls;
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param strategy the strategy used to locate the trust chain context
+ */
+ public DefaultTrustChainSelectionStrategy(
+ @Nonnull final Function<ProfileRequestContext, RelyingPartyTrustChainContext> strategy) {
+ trustChainContextLookupStrategy =
+ Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public Pair<List<EntityStatement>, OIDCClientInformation> apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyTrustChainContext trustChainContext = trustChainContextLookupStrategy.apply(input);
+ if (trustChainContext == null) {
+ log.debug("No trust chain context located");
+ return null;
+ }
+ final List<Pair<List<EntityStatement>, OIDCClientInformation>> policyCompliantChains =
+ trustChainContext.getPolicyCompliantTrustChains();
+
+ if (policyCompliantChains == null || policyCompliantChains.isEmpty()) {
+ log.debug("No policy compliant chains located");
+ return null;
+ }
+
+ int shortestIndex = 0;
+ if (policyCompliantChains.size() > 1) {
+ for (int i = 0; i < policyCompliantChains.size(); i++) {
+ final List<EntityStatement> candidate = policyCompliantChains.get(i).getFirst();
+ final List<EntityStatement> shortest = policyCompliantChains.get(shortestIndex).getFirst();
+ if (candidate != null && shortest != null && candidate.size() < shortest.size()) {
+ shortestIndex = i;
+ }
+ }
+ }
+ return policyCompliantChains.get(shortestIndex);
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
index a2369b79..2491fd0f 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-beans.xml
@@ -1146,4 +1146,56 @@
</constructor-arg>
</bean>
+ <alias alias="AutomaticRegistrationCondition" name="%{idp.oidfed.authorize.automaticRegistrationCondition:shibboleth.Conditions.FALSE}" />
+
+ <bean id="ResolveTrustChains" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ResolveTrustChains"
+ scope="prototype"
+ p:trustChainCache-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataCache:shibboleth.oidfed.TrustChainMetadataCache}'.trim()}"
+ p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy"
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.authorize.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.authorize.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"/>
+
+ <bean id="DefaultMetadataPolicyEnforcer"
+ class="net.shibboleth.oidc.metadata.policy.impl.DefaultMetadataPolicyEnforcer" />
+
+ <bean id="DefaultTrustChainMetadataPolicyMergingStrategy"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultTrustChainMetadataPolicyMergingStrategy"
+ p:localMetadataPolicyStrategy-ref="#{'%{idp.oidfed.authorize.LocalMetadataPolicyStrategy:DefaultLocalMetadataPolicyStrategy}'.trim()}"/>
+
+ <bean id="DefaultLocalMetadataPolicyStrategy"
+ parent="shibboleth.Functions.Constant">
+ <constructor-arg name="target">
+ <util:map>
+ <entry key="scope">
+ <bean class="net.shibboleth.oidc.metadata.policy.MetadataPolicy" p:defaultValue="openid" />
+ </entry>
+ <entry key="token_endpoint_auth_method">
+ <bean class="net.shibboleth.oidc.metadata.policy.MetadataPolicy" p:defaultValue="private_key_jwt" />
+ </entry>
+ </util:map>
+ </constructor-arg>
+ </bean>
+
+ <bean id="SelectTrustChain" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.SelectTrustChain"
+ scope="prototype">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext))" />
+ </property>
+ </bean>
+
+ <bean id="InitializeRelyingPartyContext"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.InitializeRelyingPartyContext" scope="prototype"
+ p:clientIDLookupStrategy-ref="shibboleth.ClientIDLookupStrategy">
+ <property name="activationCondition">
+ <bean parent="shibboleth.Conditions.Expression"
+ c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)) and #input.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)).getSelectedTrustChain() != null" />
+ </property>
+ </bean>
+
+ <bean id="StoreAutomaticRegistration" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.StoreAutomaticRegistration"
+ scope="prototype"
+ p:clientInformationManager-ref="#{'%{idp.oidc.dynreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}">
+ </bean>
+
</beans>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
index 4ffc9741..413675da 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/authorize/authorize-flow.xml
@@ -36,7 +36,25 @@
<transition on="proceed" to="DoMetadataLookup" />
</action-state>
- <action-state id="SelectConfiguration">
+ <decision-state id="SelectConfiguration">
+ <on-entry>
+ <set name="flowScope.automaticallyRegistered" value="'false'" />
+ </on-entry>
+ <if test="AutomaticRegistrationCondition.test(opensamlProfileRequestContext) and !opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))"
+ then="DoAutomaticRegistration" else="DoSelectConfiguration" />
+ </decision-state>
+
+ <action-state id="DoAutomaticRegistration">
+ <evaluate expression="ResolveTrustChains" />
+ <evaluate expression="SelectTrustChain" />
+ <evaluate expression="InitializeRelyingPartyContext" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="DoSelectConfiguration">
+ <set name="flowScope.automaticallyRegistered" value="opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))" />
+ </transition>
+ </action-state>
+
+ <action-state id="DoSelectConfiguration">
<evaluate expression="SelectRelyingPartyConfiguration" />
<evaluate expression="SelectProfileConfiguration" />
<evaluate expression="PostLookupPopulateAuditContext" />
@@ -299,16 +317,29 @@
<evaluate expression="PopulateClientStorageSaveContext" />
<evaluate expression="'proceed'" />
<transition on="proceed" to="ClientStorageSave" />
- <transition on="NoSaveNeeded" to="BuildResponseMessage" />
+ <transition on="NoSaveNeeded" to="CheckIfAutomaticRegistration" />
<transition to="HandleError" />
</action-state>
<subflow-state id="ClientStorageSave" subflow="client-storage/write">
<input name="calledAsSubflow" value="true" />
- <transition on="proceed" to="BuildResponseMessage"/>
+ <transition on="proceed" to="CheckIfAutomaticRegistration"/>
<transition to="HandleError" />
</subflow-state>
+ <decision-state id="CheckIfAutomaticRegistration">
+ <if test="flowScope.automaticallyRegistered and opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)) and opensamlProfileRequestContext.ensureInboundMessageContext().getSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.RelyingPartyTrustChainContext)).getSelectedTrustChain() != null"
+ then="StoreAutomaticRegistration"
+ else="BuildResponseMessage" />
+ </decision-state>
+
+ <action-state id="StoreAutomaticRegistration">
+ <evaluate expression="StoreAutomaticRegistration" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="BuildResponseMessage" />
+ <transition to="HandleError" />
+ </action-state>
+
<!-- Error views, handling and end states -->
<!-- Passthrough state if an exception is thrown. -->
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list