[java-idp-oidc] branch dev/JOIDC-222 updated: JOIDC-222 - Support for OpenID Federation
Henri Mikkonen
henri.mikkonen at iki.fi
Fri Sep 6 11:25:11 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=62e69dbff8bfb524166b4cb774acb40b6d7c7168
The following commit(s) were added to refs/heads/dev/JOIDC-222 by this push:
new 62e69dbf JOIDC-222 - Support for OpenID Federation
62e69dbf is described below
commit 62e69dbff8bfb524166b4cb774acb40b6d7c7168
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Fri Sep 6 14:24:46 2024 +0300
JOIDC-222 - Support for OpenID Federation
https://shibboleth.atlassian.net/browse/JOIDC-222
Initial implementation of the well-known/openid-federation endpoint
- New OIDFED.Configuration profile
- shibboleth.oidc.federation.DefaultSecurityConfiguration
- shibboleth.oidc.federation.SigningConfiguration used for signing entity configuration (JWT)
- By default all the asymmetric algorithms enabled
- Credentials from 'shibboleth.oidfed.SigningCredentials'
- New oidfed/entity-configuration flow
- Exploits dynamic creation of openid-configuration as implemented in oidc/configuration
- Moved 'shibboleth.oidc.DefaultOpenIdConfigurationResolver' from configuration flow into global beans
- Caching and entity statement (JWT) lifetime configurable via profile configuration settings
- property idp.oidfed.entity.configurationCache for Storage Service (in-memory by default)
---
.../DefaultOIDFederationEntityConfiguration.java | 239 ++++++++++++++
.../config/OIDFederationEntityConfiguration.java | 94 ++++++
.../oidfed/profile/impl/BuildEntityStatement.java | 359 +++++++++++++++++++++
.../profile/impl/EntityStatementContext.java | 109 +++++++
.../impl/EntityStatementUpdateStrategy.java | 59 ++++
...ormOutboundFederationConfigurationResponse.java | 139 ++++++++
.../impl/InitializeEntityStatementContext.java | 144 +++++++++
...ClaimsSetFromEntityStatementLookupFunction.java | 87 +++++
.../navigate/AuthorityHintsLookupFunction.java | 48 +++
...achedEntityStatementLifetimeLookupFunction.java | 52 +++
...laimsSetManipulationStrategyLookupFunction.java | 53 +++
.../EntityStatementLifetimeLookupFunction.java | 51 +++
.../META-INF/net.shibboleth.idp/postconfig.xml | 11 +
.../oidc-abstract-api-info-flow.xml | 7 +-
.../oidc/configuration/configuration-beans.xml | 10 -
.../entity-configuration-beans.xml | 98 ++++++
.../entity-configuration-flow.xml | 54 ++++
.../idp/service/relying-party/postconfig.xml | 57 ++++
.../flow/FederationConfigurationFlowTest.java | 101 ++++++
.../resources/credentials/fed-signing-es256.jwk | 10 +
.../resources/credentials/fed-signing-es384.jwk | 9 +
.../resources/credentials/fed-signing-es521.jwk | 9 +
.../test/resources/credentials/fed-signing-rs.jwk | 8 +
.../idp/module/conf/oidc-credentials.xml | 23 ++
.../shibboleth/idp/module/conf/relying-party.xml | 1 +
25 files changed, 1820 insertions(+), 12 deletions(-)
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfiguration.java
new file mode 100644
index 00000000..44612cf4
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/DefaultOIDFederationEntityConfiguration.java
@@ -0,0 +1,239 @@
+/*
+ * 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.config;
+
+import java.time.Duration;
+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.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.AbstractConditionalProfileConfiguration;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+
+/**
+ * Implementation of a profile configuration for the OpenID Federation Entity Configuration.
+ */
+public class DefaultOIDFederationEntityConfiguration extends AbstractConditionalProfileConfiguration
+ implements OIDFederationEntityConfiguration {
+
+ /** OIDC provider information profile counter name. */
+ @Nonnull @NotEmpty public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.configuration";
+
+ /** Lookup function to override issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Lookup function to authority hints. */
+ @Nonnull private Function<ProfileRequestContext,List<String>> authorityHintsLookupStrategy;
+
+ /** Lookup function to supply entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply cached entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> cachedEntityStatementLifetimeLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public DefaultOIDFederationEntityConfiguration() {
+ this(PROFILE_ID);
+ }
+
+ /**
+ * Creates a new configuration instance.
+ *
+ * @param profileId Unique profile identifier.
+ */
+ public DefaultOIDFederationEntityConfiguration(@Nonnull @NotEmpty final String profileId) {
+ super(profileId);
+ issuerLookupStrategy = FunctionSupport.constant(null);
+ authorityHintsLookupStrategy = FunctionSupport.constant(CollectionSupport.emptyList());
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofHours(24));
+ cachedEntityStatementLifetimeLookupStrategy = FunctionSupport.constant(Duration.ofSeconds(10));
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(null);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable @NotEmpty public String getIssuer(@Nullable final ProfileRequestContext profileRequestContext) {
+ return issuerLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set overridden issuer value.
+ *
+ * @param issuer issuer value
+ */
+ public void setIssuer(@Nullable @NotEmpty final String issuer) {
+ issuerLookupStrategy = FunctionSupport.constant(issuer);
+ }
+
+ /**
+ * Sets lookup strategy for overridden issuer value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull @NonnullElements @NotLive @Unmodifiable
+ public List<String> getAuthorityHints(@Nullable final ProfileRequestContext profileRequestContext) {
+ final List<String> authorityHints = authorityHintsLookupStrategy.apply(profileRequestContext);
+ if (authorityHints != null) {
+ return CollectionSupport.copyToList(authorityHints);
+ }
+ return CollectionSupport.emptyList();
+ }
+
+ /**
+ * Set authority hints value.
+ *
+ * @param hints authority hints
+ */
+ public void setAuthorityHints(@Nonnull @NonnullElements @NotLive @Unmodifiable final List<String> hints) {
+ authorityHintsLookupStrategy = FunctionSupport.constant(hints);
+ }
+
+ /**
+ * Sets lookup strategy for authority hints value.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAuthorityHintsLookupStrategy(@Nonnull final Function<ProfileRequestContext,List<String>> strategy) {
+ authorityHintsLookupStrategy = Constraint.isNotNull(strategy, "Authority hints lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of an entity statement.
+ *
+ * @param lifetime lifetime of an entity statement
+ */
+ public void setEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime, "Entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+
+ entityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ entityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Positive @Nonnull
+ public Duration getCachedEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext) {
+ final Duration lifetime = cachedEntityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+
+ Constraint.isTrue(lifetime != null && !lifetime.isZero() && !lifetime.isNegative(),
+ "Entity statement lifetime must be greater than 0");
+ assert lifetime != null;
+ return lifetime;
+ }
+
+ /**
+ * Set the lifetime of a cached entity statement.
+ *
+ * @param lifetime lifetime of a cached entity statement
+ */
+ public void setCachedEntityStatementLifetime(@Positive @Nonnull final Duration lifetime) {
+ final Duration statementLifetime = Constraint.isNotNull(lifetime,
+ "Cached entity statement lifetime cannot be null");
+ Constraint.isTrue(!statementLifetime.isZero() && !statementLifetime.isNegative(),
+ "Cached entity statement lifetime must be greater than 0");
+
+ cachedEntityStatementLifetimeLookupStrategy = FunctionSupport.constant(statementLifetime);
+ }
+
+ /**
+ * Set a lookup strategy for the cached entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setCachedEntityStatementLifetimeLookupStrategy(
+ @Nullable final Function<ProfileRequestContext,Duration> strategy) {
+ cachedEntityStatementLifetimeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable
+ public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+ }
+
+ /**
+ * Set the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy bi-function for manipulating entity statement claims set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = FunctionSupport.constant(strategy);
+ }
+
+ /**
+ * Set a lookup strategy for the bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ entityStatementClaimsSetManipulationStrategyLookupStrategy = Constraint.isNotNull(strategy,
+ "Lookup strategy cannot be null");
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfiguration.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfiguration.java
new file mode 100644
index 00000000..f797e681
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/config/OIDFederationEntityConfiguration.java
@@ -0,0 +1,94 @@
+/*
+ * 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.config;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.oidc.profile.config.OIDCProfileConfiguration;
+import net.shibboleth.profile.config.OverriddenIssuerProfileConfiguration;
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+
+/**
+ * Profile configuration for an OpenID Federation Entity Configuration.
+ */
+public interface OIDFederationEntityConfiguration extends OverriddenIssuerProfileConfiguration,
+ OIDCProfileConfiguration {
+
+ /** OIDC base protocol URI. Section 4 is relevant. */
+ public static final String PROTOCOL_URI = "https://openid.net/specs/openid-federation-1_0.html";
+
+ /** ID for this profile configuration. */
+ public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/configuration";
+
+ /**
+ * Get the authority hints to be included to the entity configuration.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return authority hints to be included to the entity configuration
+ */
+ @ConfigurationSetting(name="authorityHints")
+ @Nonnull @NonnullElements @NotLive @Unmodifiable List<String> getAuthorityHints(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get entity statement lifetime.
+ *
+ * <p>Defaults to 24 hours.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return entity statement lifetime
+ */
+ @ConfigurationSetting(name="entityStatementLifetime")
+ @Positive @Nonnull Duration getEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get cached entity statement lifetime.
+ *
+ * <p>Defaults to 10 seconds.</p>
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return cached entity statement lifetime
+ */
+ @ConfigurationSetting(name="cachedEntityStatementLifetime")
+ @Positive @Nonnull
+ Duration getCachedEntityStatementLifetime(@Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get the bi-function for manipulating entity statement claims set.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return the bi-function for manipulating entity statement claims set
+ */
+ @ConfigurationSetting(name="entityStatementClaimsSetManipulationStrategy")
+ @Nullable BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>
+ getEntityStatementClaimsSetManipulationStrategy(
+ @Nullable final ProfileRequestContext profileRequestContext);
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildEntityStatement.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildEntityStatement.java
new file mode 100644
index 00000000..a677aed5
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/BuildEntityStatement.java
@@ -0,0 +1,359 @@
+/*
+ * 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.text.ParseException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Date;
+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.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.opensaml.security.credential.Credential;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.PlainJWT;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.encoding.impl.ResponseUtil;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.AuthorityHintsLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.EntityStatementClaimsSetManipulationStrategyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.EntityStatementLifetimeLookupFunction;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.idp.profile.IdPEventIds;
+import net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction;
+import net.shibboleth.oidc.security.CredentialConversionUtil;
+import net.shibboleth.oidc.security.jose.SignatureSigningConfiguration;
+import net.shibboleth.profile.context.navigate.IssuerLookupFunction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.security.IdentifierGenerationStrategy;
+import net.shibboleth.shared.security.impl.SecureRandomIdentifierGenerationStrategy;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.messaging.encoder.AbstractMessageEncoder;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Action that creates an Entity Statement, and stores it to an {@link EntityStatementContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#IO_ERROR}
+ * @event {@link IdPEventIds#INVALID_PROFILE_CONFIG}
+ *
+ * @since 4.3.0
+ */
+public class BuildEntityStatement extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(BuildEntityStatement.class);
+
+ /** Used to log protocol messages. */
+ @Nonnull private Logger protocolMessageLog =
+ LoggerFactory.getLogger(AbstractMessageEncoder.BASE_PROTOCOL_MESSAGE_LOGGER_CATEGORY + ".OIDFED");
+
+ /** Strategy used to obtain the response issuer value. */
+ @Nonnull private Function<ProfileRequestContext,String> issuerLookupStrategy;
+
+ /** Strategy used to obtain the entity statement lifetime. */
+ @Nonnull private Function<ProfileRequestContext,Duration> entityStatementLifetimeLookupStrategy;
+
+ /** Strategy used to locate the {@link IdentifierGenerationStrategy} to use. */
+ @Nonnull private Function<ProfileRequestContext,IdentifierGenerationStrategy> idGeneratorLookupStrategy;
+
+ /** Strategy used to create the subcontext to hold the statement. */
+ @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextCreationStrategy;
+
+ /** Strategy used to locate the {@link SignatureSigningConfiguration}s to fetch JWK set from. */
+ @Nonnull private
+ Function<ProfileRequestContext,List<SignatureSigningConfiguration>> signingConfigurationsLookupStrategy;
+
+ /** Strategy used to locate authority hints. */
+ @Nonnull private Function<ProfileRequestContext,List<String>> authorityHintsLookupStrategy;
+
+ /** Lookup function to supply strategy bi-function for manipulating entity statement claims set. */
+ @Nonnull
+ private Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ entityStatementClaimsSetManipulationStrategyLookupStrategy;
+
+ /** The strategy used for manipulating the entity statement claims set. */
+ @Nullable private BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> manipulationStrategy;
+
+ /** Object mapper used for pretty-printing JWT contents. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** The generator to use. */
+ @Nullable private IdentifierGenerationStrategy idGenerator;
+
+ /** Entity statement context. */
+ @Nullable private EntityStatementContext entityStatementCtx;
+
+ /** OIDC provider metadata to publish. */
+ @Nullable private OIDCProviderMetadata metadata;
+
+ /** Constructor. */
+ public BuildEntityStatement() {
+ entityStatementLifetimeLookupStrategy = new EntityStatementLifetimeLookupFunction();
+ issuerLookupStrategy = new IssuerLookupFunction();
+
+ idGeneratorLookupStrategy = FunctionSupport.constant(new SecureRandomIdentifierGenerationStrategy());
+
+ final Function<ProfileRequestContext,EntityStatementContext> esccs =
+ new ChildContextLookup<>(EntityStatementContext.class, true).compose(
+ new OutboundMessageContextLookup());
+ assert esccs != null;
+ entityStatementContextCreationStrategy = esccs;
+
+ signingConfigurationsLookupStrategy = new JWTSignatureSigningConfigurationLookupFunction();
+ authorityHintsLookupStrategy = new AuthorityHintsLookupFunction();
+
+ entityStatementClaimsSetManipulationStrategyLookupStrategy =
+ new EntityStatementClaimsSetManipulationStrategyLookupFunction();
+ }
+
+ /**
+ * Set the strategy used to obtain the entity statement lifetime.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementLifetimeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,Duration> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ entityStatementLifetimeLookupStrategy =
+ Constraint.isNotNull(strategy, "Entity statement lifetime lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the {@link IdentifierGenerationStrategy} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIdentifierGeneratorLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,IdentifierGenerationStrategy> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ idGeneratorLookupStrategy =
+ Constraint.isNotNull(strategy, "Identifier generation strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to locate the issuer value to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setIssuerLookupStrategy(@Nonnull final Function<ProfileRequestContext,String> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ issuerLookupStrategy = Constraint.isNotNull(strategy, "Issuer lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to create the {@link EntityStatementContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setEntityStatementContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ entityStatementContextCreationStrategy =
+ Constraint.isNotNull(strategy, "EntityStatementContext creation strategy cannot be null");
+ }
+
+ /**
+ * Set the lookup function to supply strategy bi-function for manipulating entity statement claims set.
+ *
+ * @param strategy What to set
+ */
+ public void setEntityStatementClaimsSetManipulationStrategyLookupStrategy(@Nonnull final
+ Function<ProfileRequestContext,BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>>
+ strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ entityStatementClaimsSetManipulationStrategyLookupStrategy =
+ Constraint.isNotNull(strategy, "Manipulation strategy lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the object mapper used for pretty-printing JWT contents.
+ *
+ * @param mapper What to set.
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("Object mapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ idGenerator = idGeneratorLookupStrategy.apply(profileRequestContext);
+ if (idGenerator == null) {
+ log.error("{} No identifier generation strategy", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ entityStatementCtx = entityStatementContextCreationStrategy.apply(profileRequestContext);
+ if (entityStatementCtx == null) {
+ log.error("{} Unable to create EntityStatementContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ assert entityStatementCtx != null;
+ metadata = entityStatementCtx.getOPMetadata();
+ if (metadata == null) {
+ log.error("{} Could not resolve provider metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return false;
+ }
+
+ final Duration lifetime = entityStatementLifetimeLookupStrategy.apply(profileRequestContext);
+ if (lifetime == null) {
+ log.error("{} No lifetime supplied for entity statement", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return false;
+ }
+ assert entityStatementCtx != null;
+ entityStatementCtx.setLifetime(lifetime);
+
+ manipulationStrategy =
+ entityStatementClaimsSetManipulationStrategyLookupStrategy.apply(profileRequestContext);
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final String issuer = issuerLookupStrategy.apply(profileRequestContext);
+
+ final Instant now = Instant.now();
+ assert entityStatementCtx != null;
+ final Instant dateExp = now.plus(entityStatementCtx.getLifetime());
+ assert dateExp != null;
+
+ final List<SignatureSigningConfiguration> signingConfigurations =
+ signingConfigurationsLookupStrategy.apply(profileRequestContext);
+ if (signingConfigurations == null || signingConfigurations.isEmpty()) {
+ log.error("{} Could not fetch any signature signing configurations", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+
+ final List<JWK> jwks = new ArrayList<>();
+ for (final SignatureSigningConfiguration signingConfiguration : signingConfigurations) {
+ for (final Credential credential : signingConfiguration.getSigningCredentials()) {
+ final JWK jwk = CredentialConversionUtil.credentialToKey(credential);
+ if (jwk != null) {
+ jwks.add(jwk);
+ log.debug("{} Included {} to the keyset", getLogPrefix(), jwk.toJSONString());
+ }
+ }
+ }
+ final JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
+ .issuer(issuer)
+ .subject(issuer)
+ .issueTime(Date.from(now))
+ .expirationTime(Date.from(dateExp))
+ .claim("jwks", new JWKSet(jwks).toJSONObject(true))
+ .claim("authority_hints", authorityHintsLookupStrategy.apply(profileRequestContext))
+ .claim("metadata", buildOpenIDProviderClaim())
+ .build();
+ assert claimsSet != null;
+ if (manipulationStrategy != null) {
+ log.debug("{} Manipulation strategy has been set, applying it to the claims set {}", getLogPrefix(),
+ claimsSet.toJSONObject());
+ assert manipulationStrategy != null;
+ final Map<String, Object> result = manipulationStrategy.apply(profileRequestContext,
+ claimsSet.toJSONObject());
+ if (result == null) {
+ log.debug("{} Manipulation strategy returned null, leaving statement claims set untouched.",
+ getLogPrefix());
+ } else {
+ log.debug("{} Applying the manipulated claims into the entity statement claims set", getLogPrefix());
+ try {
+ final JWTClaimsSet parsedSet = JWTClaimsSet.parse(result);
+ assert parsedSet != null;
+ logAndConstructEntityStatement(parsedSet);
+ return;
+ } catch (final ParseException e) {
+ log.error("{} The resulted claims set could not be transformed into ", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, IdPEventIds.INVALID_PROFILE_CONFIG);
+ return;
+ }
+ }
+ } else {
+ log.debug("{} No manipulation strategy configured", getLogPrefix());
+ }
+ logAndConstructEntityStatement(claimsSet);
+ }
+
+ @Nonnull protected Map<String, Object> buildOpenIDProviderClaim() {
+ assert metadata != null;
+ return CollectionSupport.singletonMap("openid_provider", metadata.toJSONObject());
+ }
+
+ protected void logAndConstructEntityStatement(@Nonnull final JWTClaimsSet claimsSet) {
+ log.trace("{} Building JWT from the claims set {}", getLogPrefix(), claimsSet);
+ final JWT jwt = new PlainJWT(claimsSet);
+ assert objectMapper != null;
+ try {
+ protocolMessageLog.trace("Entity statement payload contents:\n{}",
+ ResponseUtil.getJwtProtocolMessage(jwt, objectMapper));
+ } catch (final ParseException e) {
+ log.error("{} Could not construct protocol log message", getLogPrefix(), e);
+ }
+ assert entityStatementCtx != null;
+ entityStatementCtx.setJWT(jwt);
+ }
+
+}
\ 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/EntityStatementContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java
new file mode 100644
index 00000000..7b6af76e
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementContext.java
@@ -0,0 +1,109 @@
+/*
+ * 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.Duration;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+/**
+ * Subcontext carrying information used to produce entity statements.
+ *
+ * @since 4.3.0
+ */
+public final class EntityStatementContext extends BaseContext {
+
+ /** OIDC provider metadata. */
+ @Nullable private OIDCProviderMetadata opMetadata;
+
+ /** Lifetime of the statement. */
+ @Nullable private Duration lifetime;
+
+ /** The entity statement. */
+ @Nullable private JWT jwt;
+
+ /**
+ * Get the OIDC provider metadata.
+ *
+ * @return the metadata
+ */
+ @Nullable public OIDCProviderMetadata getOPMetadata() {
+ return opMetadata;
+ }
+
+ /**
+ * Set the OIDC provider metadata.
+ *
+ * @param metadata the metadata
+ *
+ * @return this context
+ */
+ @Nonnull public EntityStatementContext setOPMetadata(@Nullable final OIDCProviderMetadata metadata) {
+ opMetadata = metadata;
+ return this;
+ }
+
+ /**
+ * Get the entity statement JWT.
+ *
+ * <p>May be in various states prior to signing.</p>
+ *
+ * @return the JWT
+ */
+ @Nullable public JWT getJWT() {
+ return jwt;
+ }
+
+ /**
+ * Set the entity statement JWT.
+ *
+ * <p>May be in various states prior to signing.</p>
+ *
+ * @param token the JWT
+ *
+ * @return this context
+ */
+ @Nonnull public EntityStatementContext setJWT(@Nullable final JWT token) {
+ jwt = token;
+ return this;
+ }
+
+ /**
+ * Get the statement lifetime.
+ *
+ * @return lifetime
+ */
+ @Nullable public Duration getLifetime() {
+ return lifetime;
+ }
+
+ /**
+ * Set the statement lifetime.
+ *
+ * @param lt lifetime
+ *
+ * @return this context
+ */
+ @Nonnull public EntityStatementContext setLifetime(@Nullable final Duration lt) {
+ lifetime = lt;
+ 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/EntityStatementUpdateStrategy.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementUpdateStrategy.java
new file mode 100644
index 00000000..79312677
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/EntityStatementUpdateStrategy.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+
+import com.nimbusds.jwt.JWT;
+
+/**
+ * Add the {@link JWT} back to the {@link EntityStatementContext}.
+ *
+ * @since 4.3.0
+ */
+public class EntityStatementUpdateStrategy implements BiConsumer<JWT, MessageContext> {
+
+ /** Strategy used to locate the subcontext with the statement. */
+ @Nonnull private Function<MessageContext,EntityStatementContext> entityStatementContextLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public EntityStatementUpdateStrategy() {
+ final Function<MessageContext,EntityStatementContext> escls =
+ new ChildContextLookup<>(EntityStatementContext.class);
+ assert escls != null;
+ entityStatementContextLookupStrategy = escls;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(final JWT jwt, final MessageContext messageContext) {
+ if (messageContext == null) {
+ return;
+ }
+ final EntityStatementContext entityStatementCtx = entityStatementContextLookupStrategy.apply(messageContext);
+ if (entityStatementCtx == null) {
+ return;
+ }
+ entityStatementCtx.setJWT(jwt);
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java
new file mode 100644
index 00000000..c9bd012b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/FormOutboundFederationConfigurationResponse.java
@@ -0,0 +1,139 @@
+/*
+ * 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.io.IOException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+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.OutboundMessageContextLookup;
+import org.opensaml.storage.StorageService;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.CachedEntityStatementLifetimeLookupFunction;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * This action builds a response for the OpenID federation configuration request. The response contains an
+ * {@link EntityStatement}.
+ *
+ * @since 4.3.0
+ */
+public class FormOutboundFederationConfigurationResponse extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(FormOutboundFederationConfigurationResponse.class);
+
+ /** Strategy used to locate the subcontext to hold the statement. */
+ @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextLookupStrategy;
+
+ @Nonnull private Function<ProfileRequestContext,Duration> storageRecordLifetimeLookupStrategy;
+ @NonnullAfterInit private StorageService storageService;
+
+ /** JWT used to build entity statement. */
+ @Nullable private SignedJWT jwt;
+
+ public FormOutboundFederationConfigurationResponse() {
+ final Function<ProfileRequestContext,EntityStatementContext> escls =
+ new ChildContextLookup<>(EntityStatementContext.class).compose(
+ new OutboundMessageContextLookup());
+ assert escls != null;
+ entityStatementContextLookupStrategy = escls;
+ storageRecordLifetimeLookupStrategy = new CachedEntityStatementLifetimeLookupFunction();
+ }
+
+ /**
+ * Set the strategy used to locate the subcontext to hold the statement
+ *
+ * @param strategy What to set.
+ */
+ public void setMetadataResolver(@Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+ checkSetterPreconditions();
+ entityStatementContextLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null!");
+ }
+
+ public void setStorageService(@Nonnull final StorageService service) {
+ checkSetterPreconditions();
+ storageService = Constraint.isNotNull(service, "Storage service cannot be null!");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+ final EntityStatementContext entityStatementContext =
+ entityStatementContextLookupStrategy.apply(profileRequestContext);
+ if (entityStatementContext == null) {
+ log.error("{} Could not resolve entity statement context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+ if (entityStatementContext.getJWT() instanceof SignedJWT signedJwt) {
+ jwt = signedJwt;
+ } else {
+ log.error("{} No signed JWT found from the entity statement context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ final EntityStatement entityStatement;
+ try {
+ entityStatement = EntityStatement.parse(jwt);
+ } catch (ParseException e) {
+ log.error("{} Could not parse entity statement from JWT", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
+ final Duration lifetime = storageRecordLifetimeLookupStrategy.apply(profileRequestContext);
+ final FederationEntityConfigurationSuccessResponse response =
+ new FederationEntityConfigurationSuccessResponse(entityStatement);
+ try {
+ assert jwt != null;
+ final String serializedJwt = jwt.serialize();
+ assert serializedJwt != null;
+ storageService.create(OIDFederationEntityConfiguration.PROFILE_ID, "entityStatement", serializedJwt, Instant.now().plus(lifetime).toEpochMilli());
+ } catch (final IOException e) {
+ log.warn("{} Could not store the entity statement into storage service", getLogPrefix(), e);
+ }
+ log.debug("{} Response stored into the cache", getLogPrefix());
+ profileRequestContext.ensureOutboundMessageContext().setMessage(response);
+ }
+}
\ 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/InitializeEntityStatementContext.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/InitializeEntityStatementContext.java
new file mode 100644
index 00000000..ec17c6eb
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/InitializeEntityStatementContext.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.action.EventIds;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+import net.shibboleth.idp.plugin.oidc.op.metadata.resolver.ProviderMetadataResolver;
+import net.shibboleth.idp.profile.AbstractProfileAction;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.ResolverException;
+
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.opensaml.profile.action.ActionSupport;
+
+/**
+ * Action that creates and initializes the {@link EntityStatementContext}.
+ *
+ * @event {@link EventIds#PROCEED_EVENT_ID}
+ * @event {@link EventIds#INVALID_PROFILE_CTX}
+ * @event {@link EventIds#IO_ERROR}
+ *
+ * @since 4.3.0
+ */
+public class InitializeEntityStatementContext extends AbstractProfileAction {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(InitializeEntityStatementContext.class);
+
+ /** The resolver for the metadata that is being distributed. */
+ @NonnullAfterInit private ProviderMetadataResolver metadataResolver;
+
+ /** Strategy used to create the subcontext to hold the statement. */
+ @Nonnull private Function<ProfileRequestContext,EntityStatementContext> entityStatementContextCreationStrategy;
+
+ /** Entity statement context. */
+ @Nullable private EntityStatementContext entityStatementCtx;
+
+ /** Constructor. */
+ public InitializeEntityStatementContext() {
+ final Function<ProfileRequestContext,EntityStatementContext> esccs =
+ new ChildContextLookup<>(EntityStatementContext.class, true).compose(
+ new OutboundMessageContextLookup());
+ assert esccs != null;
+ entityStatementContextCreationStrategy = esccs;
+ }
+
+ /**
+ * Set the resolver for the metadata that is being distributed.
+ *
+ * @param resolver What to set.
+ */
+ public void setMetadataResolver(@Nonnull final ProviderMetadataResolver resolver) {
+ metadataResolver = Constraint.isNotNull(resolver, "The metadata resolver cannot be null!");
+ }
+
+ /**
+ * Set the strategy used to create the {@link EntityStatementContext} to use.
+ *
+ * @param strategy creation strategy
+ */
+ public void setEntityStatementContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext,EntityStatementContext> strategy) {
+ ifInitializedThrowUnmodifiabledComponentException();
+
+ entityStatementContextCreationStrategy =
+ Constraint.isNotNull(strategy, "EntityStatementContext creation strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (metadataResolver == null) {
+ throw new ComponentInitializationException("The metadata resolver cannot be null!");
+ }
+ }
+
+ // Checkstyle: CyclomaticComplexity|MethodLength OFF
+ /** {@inheritDoc} */
+ @Override
+ protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+ if (!super.doPreExecute(profileRequestContext)) {
+ return false;
+ }
+
+ entityStatementCtx = entityStatementContextCreationStrategy.apply(profileRequestContext);
+ if (entityStatementCtx == null) {
+ log.error("{} Unable to create EntityStatementContext", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return false;
+ }
+
+ return true;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
+
+ final OIDCProviderMetadata metadata;
+ try {
+ metadata = metadataResolver.resolveSingle(profileRequestContext);
+ } catch (final ResolverException e) {
+ log.error("{} Could not resolve provider metadata", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return;
+ }
+ if (metadata == null) {
+ log.error("{} Could not resolve provider metadata", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.IO_ERROR);
+ return;
+ }
+
+ assert entityStatementCtx != null;
+ entityStatementCtx.setOPMetadata(metadata);
+ }
+
+}
\ 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/JWTClaimsSetFromEntityStatementLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/JWTClaimsSetFromEntityStatementLookupFunction.java
new file mode 100644
index 00000000..b81ae0f7
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/JWTClaimsSetFromEntityStatementLookupFunction.java
@@ -0,0 +1,87 @@
+/*
+ * 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.text.ParseException;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.MessageContext;
+import org.opensaml.messaging.context.navigate.ChildContextLookup;
+import org.slf4j.Logger;
+
+import com.nimbusds.jwt.JWT;
+import com.nimbusds.jwt.JWTClaimsSet;
+
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Extract the {@link JWTClaimsSet} from the JWT in {@link EntityStatementContext}.
+ *
+ * @since 4.3.0
+ */
+public class JWTClaimsSetFromEntityStatementLookupFunction implements Function<MessageContext, JWTClaimsSet> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(JWTClaimsSetFromEntityStatementLookupFunction.class);
+
+ /** Strategy used to locate the subcontext with the token. */
+ @Nonnull private Function<MessageContext,EntityStatementContext> entityStatementContextLookupStrategy;
+
+ /**
+ * Constructor.
+ */
+ public JWTClaimsSetFromEntityStatementLookupFunction() {
+ // message context -> OIDC response context -> ATC
+ final Function<MessageContext,EntityStatementContext> escl = new ChildContextLookup<>(EntityStatementContext.class);
+ assert escl != null;
+ entityStatementContextLookupStrategy = escl;
+ }
+
+ /**
+ * Set the strategy used to lookup the {@link EntityStatementContext} to use.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setEntityStatementContextCreationStrategy(
+ @Nonnull final Function<MessageContext,EntityStatementContext> strategy) {
+ entityStatementContextLookupStrategy =
+ Constraint.isNotNull(strategy, "EntityStatementContext lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public JWTClaimsSet apply(@Nullable final MessageContext messageContext) {
+ if (messageContext == null) {
+ return null;
+ }
+ final EntityStatementContext entityStatementCtx = entityStatementContextLookupStrategy.apply(messageContext);
+ if (entityStatementCtx == null) {
+ return null;
+ }
+ final JWT jwt = entityStatementCtx.getJWT();
+ try {
+ if (jwt != null) {
+ return jwt.getJWTClaimsSet();
+ }
+ } catch (final ParseException e) {
+ log.error("Could not fetch the claims set from entity statement", e);
+ }
+ return null;
+ }
+}
diff --git a/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AuthorityHintsLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AuthorityHintsLookupFunction.java
new file mode 100644
index 00000000..a516768b
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AuthorityHintsLookupFunction.java
@@ -0,0 +1,48 @@
+/*
+ * 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 javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration;
+
+/**
+ * A function that obtains {@link OIDFederationEntityConfiguration#getAuthorityHints(ProfileRequestContext)}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class AuthorityHintsLookupFunction extends AbstractRelyingPartyLookupFunction<List<String>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public List<String> apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityConfiguration ofec) {
+ return ofec.getAuthorityHints(input);
+ }
+ }
+ return null;
+ }
+
+}
\ 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/CachedEntityStatementLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/CachedEntityStatementLifetimeLookupFunction.java
new file mode 100644
index 00000000..c3bd31a8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/CachedEntityStatementLifetimeLookupFunction.java
@@ -0,0 +1,52 @@
+/*
+ * 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.time.Duration;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration;
+
+/**
+ * A function that returns
+ * {@link OIDFederationEntityConfiguration#getCachedEntityStatementLifetime(ProfileRequestContext)} if such a profile
+ * is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child of the
+ * {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class CachedEntityStatementLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityConfiguration ofec) {
+ return ofec.getCachedEntityStatementLifetime(input);
+ }
+ }
+
+ return null;
+ }
+
+}
\ 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/EntityStatementClaimsSetManipulationStrategyLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/EntityStatementClaimsSetManipulationStrategyLookupFunction.java
new file mode 100644
index 00000000..3cdb4397
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/EntityStatementClaimsSetManipulationStrategyLookupFunction.java
@@ -0,0 +1,53 @@
+/*
+ * 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.Map;
+import java.util.function.BiFunction;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationEntityConfiguration#getEntityStatementClaimsSetManipulationStrategy(ProfileRequestContext)} if
+ * such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child of
+ * the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class EntityStatementClaimsSetManipulationStrategyLookupFunction extends
+ AbstractRelyingPartyLookupFunction<BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>>> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public BiFunction<ProfileRequestContext,Map<String,Object>,Map<String,Object>> apply(
+ @Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityConfiguration ofec) {
+ return ofec.getEntityStatementClaimsSetManipulationStrategy(input);
+ }
+ }
+ return null;
+ }
+}
\ 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/EntityStatementLifetimeLookupFunction.java b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/EntityStatementLifetimeLookupFunction.java
new file mode 100644
index 00000000..5163a85f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/EntityStatementLifetimeLookupFunction.java
@@ -0,0 +1,51 @@
+/*
+ * 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.time.Duration;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration;
+
+/**
+ * A function that returns {@link OIDFederationEntityConfiguration#getEntityStatementLifetime(ProfileRequestContext)}
+ * if such a profile is available from a {@link RelyingPartyContext} obtained via a lookup function, by default a child
+ * of the {@link ProfileRequestContext}.
+ *
+ * <p>If a specific setting is unavailable, a null value is returned.</p>
+ */
+public class EntityStatementLifetimeLookupFunction extends AbstractRelyingPartyLookupFunction<Duration> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public Duration apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationEntityConfiguration ofec) {
+ return ofec.getEntityStatementLifetime(input);
+ }
+ }
+
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 1a456b6a..5137d750 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -872,4 +872,15 @@
p:customMetadataPolicyOperators="#{getObject('shibboleth.oidc.RedirectUriValidator.MetadataPolicyCustomOperators') ?: getObject('shibboleth.oidc.DefaultMetadataPolicyCustomOperators')}"/>
</property>
</bean>
+
+ <bean id="shibboleth.oidc.DefaultOpenIdConfigurationResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.DynamicFilesystemProviderMetadataResolver"
+ p:minRefreshDelay="%{idp.oidc.config.minRefreshDelay:PT5M}"
+ p:maxRefreshDelay="%{idp.oidc.config.maxRefreshDelay:PT4H}"
+ c:metadata="#{getObject('shibboleth.oidc.OpenIDConfiguration') ?: getObject('DefaultMetadataSkeleton')}"
+ p:dynamicValueResolvers-ref="#{'%{idp.oidc.discovery.resolver.values:shibboleth.oidc.discovery.DefaultDynamicValueResolvers}'.trim()}"/>
+
+ <bean id="DefaultMetadataSkeleton" class="org.springframework.core.io.FileSystemResource" lazy-init="true"
+ c:path="%{idp.oidc.discovery.template:%{idp.home}/static/openid-configuration.json}" />
+
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api-info/oidc-abstract-api-info-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api-info/oidc-abstract-api-info-flow.xml
index 1f6bd85c..7ed5e680 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api-info/oidc-abstract-api-info-flow.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/abstract-api-info/oidc-abstract-api-info-flow.xml
@@ -17,13 +17,16 @@
</action-state>
<decision-state id="CheckInboundInterceptContext">
+ <on-entry>
+ <evaluate expression="flowRequestContext.getFlowScope().get('transitionAfterInboundIntercept') != null ? flowRequestContext.getFlowScope().get('transitionAfterInboundIntercept') : 'BuildResponseMessage'" result="flowScope.transitionTargetAfterInboundIntercept"/>
+ </on-entry>
<if test="opensamlProfileRequestContext.ensureSubcontext(T(net.shibboleth.idp.profile.context.ProfileInterceptorContext)).getAvailableFlows().isEmpty()"
- then="BuildResponseMessage" else="DoInboundInterceptSubflow" />
+ then="#{transitionTargetAfterInboundIntercept}" else="DoInboundInterceptSubflow" />
</decision-state>
<subflow-state id="DoInboundInterceptSubflow" subflow="intercept">
<input name="calledAsSubflow" value="true" />
- <transition on="proceed" to="BuildResponseMessage" />
+ <transition on="proceed" to="#{transitionTargetAfterInboundIntercept}" />
</subflow-state>
<bean-import resource="classpath:/META-INF/net/shibboleth/idp/flows/oidc/abstract-api-info/oidc-abstract-api-info-beans.xml" />
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/configuration/configuration-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/configuration/configuration-beans.xml
index e67bcd5e..58ab20ea 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/configuration/configuration-beans.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/configuration/configuration-beans.xml
@@ -28,14 +28,4 @@
<bean id="FormOutboundMessage" class="net.shibboleth.idp.plugin.oidc.op.profile.impl.FormOutboundDiscoveryResponse"
scope="prototype" p:metadataResolver-ref="#{'%{idp.oidc.discovery.resolver:shibboleth.oidc.DefaultOpenIdConfigurationResolver}'.trim()}" />
- <bean id="shibboleth.oidc.DefaultOpenIdConfigurationResolver"
- class="net.shibboleth.idp.plugin.oidc.op.metadata.impl.DynamicFilesystemProviderMetadataResolver"
- p:minRefreshDelay="%{idp.oidc.config.minRefreshDelay:PT5M}"
- p:maxRefreshDelay="%{idp.oidc.config.maxRefreshDelay:PT4H}"
- c:metadata="#{getObject('shibboleth.oidc.OpenIDConfiguration') ?: getObject('DefaultMetadataSkeleton')}"
- p:dynamicValueResolvers-ref="#{'%{idp.oidc.discovery.resolver.values:shibboleth.oidc.discovery.DefaultDynamicValueResolvers}'.trim()}"/>
-
- <bean id="DefaultMetadataSkeleton" class="org.springframework.core.io.FileSystemResource" lazy-init="true"
- c:path="%{idp.oidc.discovery.template:%{idp.home}/static/openid-configuration.json}" />
-
</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
new file mode 100644
index 00000000..a4dbb7d6
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-beans.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans" xmlns:c="http://www.springframework.org/schema/c"
+ xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
+ xmlns:util="http://www.springframework.org/schema/util" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
+ http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"
+ default-init-method="initialize" default-destroy-method="destroy">
+
+ <bean id="shibboleth.oidc.profileId" class="java.lang.String"
+ c:_0="#{T(net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration).PROFILE_ID}" />
+
+ <bean id="shibboleth.oidc.loggingId" class="java.lang.String" c:_0="%{idp.service.logging.oidfedconfig:OIDFED.Configuration}" />
+
+ <util:constant id="shibboleth.metrics.ProfileCounter"
+ static-field="net.shibboleth.idp.plugin.oidc.op.oidfed.config.DefaultOIDFederationEntityConfiguration.PROFILE_COUNTER" />
+
+ <bean id="BuildErrorResponseFromEvent"
+ class="net.shibboleth.idp.plugin.oidc.op.profile.impl.BuildJSONErrorResponseFromEvent" scope="prototype"
+ p:defaultStatusCode="500" p:defaultCode="server_error"
+ p:httpServletResponseSupplier-ref="shibboleth.HttpServletResponseSupplier">
+ <property name="eventContextLookupStrategy">
+ <bean class="net.shibboleth.idp.profile.context.navigate.WebFlowCurrentEventLookupFunction" />
+ </property>
+ </bean>
+
+ <alias alias="ResponseCacheStorageService" name="%{idp.oidfed.entity.configurationCache:shibboleth.StorageService}" />
+
+ <bean id="InitializeEntityStatementContext"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.InitializeEntityStatementContext"
+ p:metadataResolver-ref="#{'%{idp.oidc.discovery.resolver:shibboleth.oidc.DefaultOpenIdConfigurationResolver}'.trim()}"/>
+
+ <bean id="PopulateEntityStatementSignatureSigningParameters"
+ class="net.shibboleth.oidc.profile.impl.PopulateJWTSignatureSigningParameters" scope="prototype"
+ c:strategy-ref="shibboleth.MessageContextLookup.Outbound"
+ p:securityParametersContextLookupStrategy-ref="EntityStatementSecurityParametersContextLookupStrategy">
+ <property name="configurationLookupStrategy">
+ <bean lazy-init="true"
+ class="net.shibboleth.oidc.profile.config.navigate.JWTSignatureSigningConfigurationLookupFunction" />
+ </property>
+ <property name="signatureSigningParametersResolver">
+ <bean class="net.shibboleth.oidc.security.jose.impl.ClientInformationSignatureSigningParametersResolver">
+ <constructor-arg name="signatureAlgorithmLookupStrategy">
+ <bean parent="shibboleth.Functions.Constant" c:target="" />
+ </constructor-arg>
+ <constructor-arg name="defaultAlgorithmValue" value="%{idp.oidfed.entity.sigalg:RS256}" />
+ </bean>
+ </property>
+ </bean>
+
+ <bean id="EntityStatementSecurityParametersContextLookupStrategy" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.oidc.ChildLookupOrCreate.JWTSecurityParameters"
+ c:f-ref="shibboleth.ChildLookup.RelyingParty" />
+
+ <bean id="EntityStatementSecurityParametersCreationViaMessageContextStrategy" parent="shibboleth.Functions.Compose">
+ <constructor-arg name="g" ref="EntityStatementSecurityParametersContextLookupStrategy" />
+ <constructor-arg name="f">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input.getParent()" />
+ </constructor-arg>
+ </bean>
+
+ <bean id="SelectOidcConfigurationProfileConfiguration"
+ class="net.shibboleth.idp.profile.impl.SelectProfileConfiguration" scope="prototype"
+ p:profileId="#{T(net.shibboleth.oidc.profile.config.OIDCProviderInformationConfiguration).PROFILE_ID}" />
+
+ <bean id="ConfigurationRelyingPartyCreationStrategy" parent="shibboleth.Functions.Compose"
+ c:g-ref="shibboleth.ChildLookupOrCreate.RelyingPartyContext"
+ c:f-ref="shibboleth.MessageContextLookup.Outbound" />
+
+ <bean id="BuildEntityStatement"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.BuildEntityStatement" scope="prototype"
+ p:identifierGeneratorLookupStrategy-ref="shibboleth.oidc.DefaultIdentifierGenerationStrategy"
+ p:objectMapper-ref="#{'%{idp.oidc.logging.objectMapper:shibboleth.oidc.JSONObjectMapper}'.trim()}" />
+
+ <bean id="SignEntityStatement" class="net.shibboleth.idp.profile.impl.WebFlowMessageHandlerAdaptor"
+ scope="prototype" c:executionDirection="OUTBOUND ">
+ <constructor-arg name="messageHandler">
+ <bean id="SignEntityStatementHandler"
+ class="net.shibboleth.oidc.security.impl.SignJWTHandler" scope="prototype" p:logName="Entity Statement"
+ p:securityParametersLookupStrategy-ref="EntityStatementSecurityParametersCreationViaMessageContextStrategy"
+ p:typeHeader="entity-statement+jwt">
+ <property name="claimsToSignLookupStrategy">
+ <bean
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.JWTClaimsSetFromEntityStatementLookupFunction" />
+ </property>
+ <property name="jwtUpdateConsumer">
+ <bean
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.EntityStatementUpdateStrategy" />
+ </property>
+ </bean>
+ </constructor-arg>
+ </bean>
+
+ <bean id="FormOutboundMessage" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.FormOutboundFederationConfigurationResponse"
+ scope="prototype" p:storageService-ref="ResponseCacheStorageService">
+ </bean>
+
+</beans>
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
new file mode 100644
index 00000000..b89baf62
--- /dev/null
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/entity-configuration/entity-configuration-flow.xml
@@ -0,0 +1,54 @@
+<flow xmlns="http://www.springframework.org/schema/webflow"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow.xsd"
+ parent="oidc/abstract-api-info">
+
+ <action-state id="InitializeMandatoryContexts">
+ <on-entry>
+ <set name="flowScope.transitionAfterInboundIntercept" value="'CheckIfValidCachedResponseExists'" />
+ </on-entry>
+ </action-state>
+
+ <decision-state id="CheckIfValidCachedResponseExists">
+ <on-entry>
+ <set name="requestScope.storageRecord"
+ value="ResponseCacheStorageService.read(T(net.shibboleth.idp.plugin.oidc.op.oidfed.config.OIDFederationEntityConfiguration).PROFILE_ID, 'entityStatement')"/>
+ <set name="requestScope.cachedEntityStatementJwt"
+ value="storageRecord == null ? null : T(com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement).parse(storageRecord.getValue())" />
+ </on-entry>
+ <if test="cachedEntityStatementJwt != null"
+ then="UseCachedEntityStatement"
+ else="InitializeEntityStatementContext" />
+ </decision-state>
+
+ <action-state id="UseCachedEntityStatement">
+ <on-entry>
+ <set name="requestScope.cachedResponseMessage"
+ value="new com.nimbusds.openid.connect.sdk.federation.config.FederationEntityConfigurationSuccessResponse(cachedEntityStatementJwt)" />
+ <evaluate expression="opensamlProfileRequestContext.ensureOutboundMessageContext().setMessage(cachedResponseMessage)"/>
+ </on-entry>
+ <evaluate expression="'proceed'"/>
+ <transition on="proceed" to="PopulateOutboundInterceptContext"/>
+ </action-state>
+
+ <action-state id="InitializeEntityStatementContext">
+ <evaluate expression="SelectOidcConfigurationProfileConfiguration" />
+ <evaluate expression="InitializeEntityStatementContext"/>
+ <evaluate expression="SelectProfileConfiguration" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="BuildEntityConfiguration" />
+ </action-state>
+
+ <action-state id="BuildEntityConfiguration">
+ <evaluate expression="PopulateEntityStatementSignatureSigningParameters" />
+ <evaluate expression="BuildEntityStatement" />
+ <evaluate expression="SignEntityStatement" />
+ <evaluate expression="'proceed'" />
+
+ <transition on="proceed" to="BuildResponseMessage"/>
+ </action-state>
+
+ <bean-import resource="entity-configuration-beans.xml" />
+
+</flow>
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 4f8bfe96..6063ec50 100644
--- a/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidc-extension-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -102,6 +102,12 @@
p:requestUriType="%{idp.oauth2.par.requestUriType:}"
p:requestUriLifetime="%{idp.oauth2.par.requestUriLifetime:PT1M}"/>
+ <bean id="OIDFED.Configuration" parent="AbstractOIDCProfile" lazy-init="true"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.config.DefaultOIDFederationEntityConfiguration"
+ p:issuer-ref="shibboleth.oidc.issuer"
+ p:authorityHints="%{idp.oidfed.entity.authorityHints:https://example.org}"
+ p:securityConfiguration-ref="shibboleth.oidc.federation.DefaultSecurityConfiguration" />
+
<bean id="DefaultLogoutHintMatchingPredicate"
class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultLogoutHintMatchingPredicate"/>
@@ -1113,4 +1119,55 @@
c:sealer-ref="DefaultDPoPNonceSealer">
</bean>
+
+ <bean id="shibboleth.oidc.federation.SigningConfiguration"
+ parent="shibboleth.oidc.BasicSignatureSigningConfiguration"
+ p:signingCredentials-ref="shibboleth.oidc.federation.SigningCredentialsFactory">
+ <property name="signatureAlgorithms">
+ <list>
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_RS_256" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_RS_384" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_RS_512" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_256" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_384" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_ES_512" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_PS_256" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_PS_384" />
+ <util:constant
+ static-field="net.shibboleth.oidc.jwa.support.SignatureConstants.ALGO_ID_SIGNATURE_PS_512" />
+ </list>
+ </property>
+ </bean>
+
+ <bean id="shibboleth.oidc.federation.SigningCredentialsFactory"
+ class="net.shibboleth.oidc.profile.config.CredentialsListFactory"
+ c:_0="#{getObject('shibboleth.oidfed.SigningCredentials') ?: getObject('shibboleth.oidc.SigningCredentials')}" />
+
+ <bean id="shibboleth.oidc.federation.DefaultSecurityConfiguration"
+ class="net.shibboleth.oidc.profile.config.JSONSecurityConfiguration" c:clockSkew="%{idp.policy.clockSkew:PT1M}">
+ <constructor-arg name="idGenerator">
+ <bean
+ class="net.shibboleth.shared.security.IdentifierGenerationStrategy" factory-method="getInstance">
+ <constructor-arg>
+ <util:constant
+ static-field="net.shibboleth.shared.security.IdentifierGenerationStrategy.ProviderType.SECURE" />
+ </constructor-arg>
+ </bean>
+ </constructor-arg>
+ <property name="jwtSignatureSigningConfiguration">
+ <ref bean="#{'%{idp.oidfed.signing.config:shibboleth.oidc.federation.SigningConfiguration}'.trim()}" />
+ </property>
+ <property name="jwtSignatureValidationConfiguration">
+ <ref bean="#{'%{idp.oidfed.validation.config:shibboleth.oidc.SignatureValidationConfiguration}'.trim()}" />
+ </property>
+ </bean>
+
</beans>
diff --git a/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/FederationConfigurationFlowTest.java b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/FederationConfigurationFlowTest.java
new file mode 100644
index 00000000..58be9d19
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/FederationConfigurationFlowTest.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.profile.flow;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.springframework.webflow.executor.FlowExecutionResult;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.nimbusds.jose.Algorithm;
+import com.nimbusds.oauth2.sdk.ParseException;
+import com.nimbusds.oauth2.sdk.Response;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+import com.nimbusds.openid.connect.sdk.federation.entities.EntityStatement;
+import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
+
+/**
+ * Unit test for the entity configuration flow.
+ */
+public class FederationConfigurationFlowTest extends AbstractOidcFlowTest {
+
+ public static final String FLOW_ID = "oidfed/entity-configuration";
+
+ protected FederationConfigurationFlowTest() {
+ super(FLOW_ID);
+ }
+
+ @Test
+ public void testOutputAndCaching() throws ParseException, IOException, InterruptedException {
+ final FlowExecutionResult result = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response = parseResponse(result);
+ Assert.assertTrue(response.indicatesSuccess());
+ assertEntityStatement(response);
+
+ final FlowExecutionResult result2 = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response2 = parseResponse(result2);
+ Assert.assertEquals(response2.toHTTPResponse().getContent(), response.toHTTPResponse().getContent());
+
+ Thread.sleep(2000);
+ final FlowExecutionResult result3 = flowExecutor.launchExecution(FLOW_ID, null, externalContext);
+ final Response response3 = parseResponse(result3);
+ assertEntityStatement(response3);
+ Assert.assertNotEquals(response3.toHTTPResponse().getContent(), response.toHTTPResponse().getContent());
+ }
+
+ protected void assertEntityStatement(final Response response) throws ParseException {
+ final EntityStatement entityStatement = EntityStatement.parse(response.toHTTPResponse().getContent());
+ final OIDCProviderMetadata metadata = entityStatement.getClaimsSet().getOPMetadata();
+ Assert.assertEquals(metadata.getIssuer(), new Issuer("https://op.example.org"));;
+ // all but RSA-OAEP-384 as it's excluded in test relying-party.xml
+ final List<String> jweAlgs = Arrays.asList("RSA1_5", "RSA-OAEP", "RSA-OAEP-256", "RSA-OAEP-512",
+ "A128KW", "A192KW", "A256KW", "A128GCMKW", "A192GCMKW", "A256GCMKW", "ECDH-ES", "ECDH-ES+A128KW",
+ "ECDH-ES+A192KW", "ECDH-ES+A256KW");
+ // all but A192CBC-HS384 as it's excluded in test relying-party.xml
+ final List<String> jweEncs = Arrays.asList("A128CBC-HS256", "A256CBC-HS512", "A128GCM", "A192GCM", "A256GCM");
+ // all but ES384 as it's excluded in test relying-party.xml
+ final List<String> jwsAlgs = Arrays.asList("RS256", "RS384", "RS512", "ES256", "ES512", "HS256", "HS384",
+ "HS512", "PS256", "PS384", "PS512");
+ Assert.assertNotNull(metadata.getIDTokenJWEAlgs());
+ Assert.assertTrue(containsAll(metadata.getIDTokenJWEAlgs(), jweAlgs));
+ Assert.assertNotNull(metadata.getIDTokenJWEAlgs());
+ Assert.assertTrue(containsAll(metadata.getIDTokenJWEEncs(), jweEncs));
+ Assert.assertNotNull(metadata.getIDTokenJWEEncs());
+ Assert.assertTrue(containsAll(metadata.getIDTokenJWSAlgs(), jwsAlgs));
+ Assert.assertNotNull(metadata.getUserInfoJWEAlgs());
+ Assert.assertTrue(containsAll(metadata.getUserInfoJWEAlgs(), jweAlgs));
+ Assert.assertNotNull(metadata.getUserInfoJWEAlgs());
+ Assert.assertTrue(containsAll(metadata.getUserInfoJWEEncs(), jweEncs));
+ Assert.assertNotNull(metadata.getUserInfoJWEEncs());
+ Assert.assertTrue(containsAll(metadata.getUserInfoJWSAlgs(), jwsAlgs));
+ Assert.assertNotNull(metadata.getCustomParameter("STATIC_TEST_ATTRIBUTE"));
+ Assert.assertEquals(metadata.getCustomParameter("STATIC_TEST_ATTRIBUTE"), "TestValue");
+
+ }
+
+ protected boolean containsAll(Collection<? extends Algorithm> algs, Collection<String> strings) {
+ final List<String> algStrings = new ArrayList<>();
+ for (final Algorithm alg : algs) {
+ algStrings.add(alg.toString());
+ }
+ return strings.size() == algStrings.size() ? strings.containsAll(strings) : false;
+ }
+
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es256.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es256.jwk
new file mode 100644
index 00000000..a70c96c8
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es256.jwk
@@ -0,0 +1,10 @@
+{
+ "kty": "EC",
+ "d": "CO-ctmQcB-hS042i2omOIPpaaAaKkBAU6s_v4W09oA0",
+ "use": "sig",
+ "crv": "P-256",
+ "kid": "fedtestkeyES256",
+ "x": "2uzfE1oK0cf1_c11SFc9vFdGLnJoH3e0AKTrGPAmUis",
+ "y": "14410NGKqwLM58b26ZcvGOruFixpHt_SJTw8I5wwgLQ",
+ "alg": "ES256"
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es384.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es384.jwk
new file mode 100644
index 00000000..c42f913f
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es384.jwk
@@ -0,0 +1,9 @@
+{
+ "kty": "EC",
+ "d": "e65hCxxbNq5gubmkgZD73A1cDf_GfGzkl4KZtbRg0GxAktztyDg4pI4bcxXaUNOb",
+ "use": "sig",
+ "crv": "P-384",
+ "kid": "fedtestkeyES384",
+ "x": "uVsAjiFw4Hv0Kcwl2532baUKPTzDht2966ar_pJ8ZdAzquFwJPdRjCfpbkqZUi46",
+ "y": "yp3W3Cmc1QQptLC3s072Iy69l1ubx_WSFRivMYqCpK4Ec89HKvYh3mTKcfjHvk2l"
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es521.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es521.jwk
new file mode 100644
index 00000000..b42d56a4
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-es521.jwk
@@ -0,0 +1,9 @@
+{
+ "kty": "EC",
+ "d": "ADaJK1sgPtlu4xAFGmb8scq8XGujamVjP3z7Xr4xErwuurSynn8sNtZKX8SfoId9syS27VLFHe12CbeBR6nbReFv",
+ "use": "sig",
+ "crv": "P-521",
+ "kid": "fedtestkeyES512",
+ "x": "AKObj9VTXWndDB7RC9dqSEkEsCqYgOHxq9AgvlDA8XBKxPzp39XrnBD0CMFy0C1HFvoiFKh9lPXJewkkruAOLW-6",
+ "y": "AMG6cRDBekWfD8imLDkBCmm-mtI16mFbifxZ06bgI5GwdyRTIMYUaBizmOzRK038Am4h6EjF8RCFr7383iKcqGZt"
+}
\ No newline at end of file
diff --git a/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-rs.jwk b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-rs.jwk
new file mode 100644
index 00000000..a6297ee6
--- /dev/null
+++ b/idp-oidc-extension-impl/src/test/resources/credentials/fed-signing-rs.jwk
@@ -0,0 +1,8 @@
+{
+ "kty": "RSA",
+ "d": "gv7aqFcXV86jDcCn6-JCqEEIRcv1Rh1AEv4dKziFzQal1nROliDdtkJjELpOYlFY9CgI-xAXt8ivwJ4q1eA_G9WTId7qLxPdcQW4QjfRl8VVEPUhka6Gc8y95WUO4VONEwzZnZ4V7KobE0QGADXvXUw3MtIZdGgvRCS-6avQXITjhTnlkUONxeqpy2BE6l0cI8GSM1vlLy66vjsQ06aAizMB-g3yMMpbKNd73oYgrdpEjAtddH3-sLhv_TG7pMlbB_etnPGkWKdIbpvTKr2P2oZN_8Qvq7G4ETIe9nIv7i8T7GXZfTxWspYkszbrpRACM9Ic8fSctvil2j013JeSgQ",
+ "e": "AQAB",
+ "use": "sig",
+ "kid": "fedtestkeyRS",
+ "n": "pNf03ghVzMAw5sWrwDAMAZdSYNY2q7OVlxMInljMgz8XB5mf8XKH3EtP7AKrb8IAf7rGhfuH3T1N1C7F-jwIeYjXxMm2nIAZ0hXApgbccvBpf4n2H7IZflMjt4A3tt587QQSxQ069drCP4sYevxhTcLplJy6RWA0cLj-5CHyWy94zPeeA4GRd6xgHFLz0RNiSF0pF0kE4rmRgQVZ-b4_BmD9SsWnIpwhms5Ihciw36WyAGQUeZqULGsfwAMwlNLIaTCBLAoRgv370p-XsLrgz86pTkNBJqXP5GwI-ZfgiLmJuHjQ9l85KqHM87f-QdsqiV8KoRcslgXPqb6VOTJBVw"
+}
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc-credentials.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc-credentials.xml
index 663dc553..2652e658 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc-credentials.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/oidc-credentials.xml
@@ -29,6 +29,18 @@
<bean id="shibboleth.oidc.DefaultECEncryptionCredential" parent="shibboleth.JWKCredential"
p:resource="/credentials/idp-encryption-ec.jwk" />
+ <bean id="shibboleth.oidfed.DefaultRSSigningCredential" parent="shibboleth.JWKCredential"
+ p:resource="/credentials/fed-signing-rs.jwk" />
+
+ <bean id="shibboleth.oidfed.DefaultES256SigningCredential" parent="shibboleth.JWKCredential"
+ p:resource="/credentials/fed-signing-es256.jwk" />
+
+ <bean id="shibboleth.oidfed.DefaultES384SigningCredential" parent="shibboleth.JWKCredential"
+ p:resource="/credentials/fed-signing-es384.jwk" />
+
+ <bean id="shibboleth.oidfed.DefaultES521SigningCredential" parent="shibboleth.JWKCredential"
+ p:resource="/credentials/fed-signing-es521.jwk" />
+
<!--
Lists ALL of your OP's response signing credentials for the default security configuration.
If you define additional signing credentials make sure to include them within this list.
@@ -49,6 +61,17 @@
<ref bean="shibboleth.oidc.DefaultECEncryptionCredential" />
</util:list>
+ <!--
+ List ALL of your OP's signing credentials for the default federation security configuration.
+ If you define additional signing credentials make sure to include them within this list.
+ -->
+ <util:list id="shibboleth.oidfed.SigningCredentials">
+ <ref bean="shibboleth.oidfed.DefaultRSSigningCredential" />
+ <ref bean="shibboleth.oidfed.DefaultES256SigningCredential" />
+ <ref bean="shibboleth.oidfed.DefaultES384SigningCredential" />
+ <ref bean="shibboleth.oidfed.DefaultES521SigningCredential" />
+ </util:list>
+
<!--
If you need to publish a key set different from shibboleth.oidc.EncryptionCredentials, define
a list bean named "shibboleth.oidc.EncryptionCredentialsToPublish".
diff --git a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
index c75c3c7b..acbced56 100644
--- a/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
+++ b/idp-oidc-extension-impl/src/test/resources/net/shibboleth/idp/module/conf/relying-party.xml
@@ -46,6 +46,7 @@
<bean parent="OAUTH2.Introspection" />
<bean parent="OAUTH2.Revocation" />
<bean parent="OAUTH2.PAR" />
+ <bean parent="OIDFED.Configuration" p:cachedEntityStatementLifetime="PT2S" />
</list>
</property>
</bean>
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list