[java-idp-plugin-oidc-op-oidfed] branch dev/CACHE-REFACTOR updated: Initial support for pushed/provided trust chains
Codeberg
noreply at shibboleth.net
Mon Mar 16 15:34:14 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch dev/CACHE-REFACTOR
in repository java-idp-plugin-oidc-op-oidfed.
View the commit online:
https://codeberg.org/Shibboleth/java-idp-plugin-oidc-op-oidfed/commit/2d5e1255a4a8f43e62cdf77d93ee28f23b2e506f
The following commit(s) were added to refs/heads/dev/CACHE-REFACTOR by this push:
new 2d5e125 Initial support for pushed/provided trust chains
2d5e125 is described below
commit 2d5e1255a4a8f43e62cdf77d93ee28f23b2e506f
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Mon Mar 16 17:33:39 2026 +0200
Initial support for pushed/provided trust chains
- Request objects sent to PAR and authorize endpoint may contain trust_chain header
- OIDFED.AutomaticRegistration can be configured with a decorator type
- p:defaultClaimsSetDecorationType: for requests without provided trust chain
- p:providedChainClaimsSetDecorationType: for requests with provided trust chain
- ValidateAutomaticRegistrationProfileConfiguration sets the configured decorator type into AutomaticRegistrationContext
- The context is exploited by claims set decorators
- Harmonised trust chain deserialization
---
...ration.java => ProvidedTrustChainResolver.java} | 21 ++-
...nAutomaticRegistrationProfileConfiguration.java | 29 +++
.../ClaimsSetDecorationTypeLookupFunction.java | 50 ++++++
...ChainClaimsSetDecorationTypeLookupFunction.java | 50 ++++++
.../context/AutomaticRegistrationContext.java | 51 ++++++
.../context/RelyingPartyTrustChainContext.java | 24 +++
.../oidfed/support/ClaimsSetExtensionSupport.java | 42 +++++
.../ExplicitClientRegistrationRequestDecoder.java | 54 ++----
...tEntityStatementContentValidationCondition.java | 27 +++
...DefaultTrustChainHeaderValidationCondition.java | 196 +++++++++++++++++++++
.../metadata/util/EntityStatementHelper.java | 114 ++++++++++++
...nAutomaticRegistrationProfileConfiguration.java | 69 ++++++++
.../impl/AbstractTokenClaimsSetDecorator.java | 123 +++++++++++++
...edClientInformationStoreClaimsSetDecorator.java | 145 +++++++++++++++
...redTrustChainAccessTokenClaimsSetDecorator.java | 54 +-----
...stChainAuthorizationCodeClaimsSetDecorator.java | 54 +-----
...RegisteredTrustChainIDsClaimsSetDecorator.java} | 16 +-
...edTrustChainRefreshTokenClaimsSetDecorator.java | 54 +-----
...eredTrustChainRequestUriClaimsSetDecorator.java | 52 +-----
.../oidfed/profile/impl/CallResolveEntityApi.java | 33 +---
.../impl/DefaultProvidedTrustChainResolver.java | 116 ++++++++++++
.../oidfed/profile/impl/OidFederationEventIds.java | 12 ++
...eAutomaticRegistrationProfileConfiguration.java | 75 +++++++-
.../profile/impl/ValidateProvidedTrustChain.java | 106 +++++++++--
...actRequestObjectProvidedTrustChainResolver.java | 146 +++++++++++++++
...ionRequestObjectProvidedTrustChainResolver.java | 43 +++++
...ionRequestObjectProvidedTrustChainResolver.java | 45 +++++
...ientRegistrationRequestTrustChainResolver.java} | 28 ++-
.../META-INF/net.shibboleth.idp/postconfig.xml | 79 ++++++++-
.../oidfed/metadata-lookup-ext-oidfed-beans.xml | 77 ++++++++
.../oidfed/metadata-lookup-ext-oidfed-flow.xml | 13 +-
.../idp/flows/oidfed/register/register-beans.xml | 21 ++-
.../idp/service/relying-party/postconfig.xml | 4 +-
.../plugin/oidc/op/oidfed/TrustChainTestUtil.java | 13 +-
.../flow/oidfed/AbstractFederationFlowTest.java | 9 +-
.../AuthorizeFlowAutomaticRegistrationTest.java | 68 +++++++
...shedAuthorizeFlowAutomaticRegistrationTest.java | 75 ++++++++
.../EntityConfigurationMetadataCacheTest.java | 44 +++++
.../SubordinateStatementMetadataCacheTest.java | 50 ++++++
39 files changed, 1950 insertions(+), 332 deletions(-)
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/ProvidedTrustChainResolver.java
similarity index 51%
copy from idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
copy to idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/ProvidedTrustChainResolver.java
index 5339180..15e1732 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/ProvidedTrustChainResolver.java
@@ -12,14 +12,21 @@
* limitations under the License.
*/
-package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config;
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile;
-/**
- * Profile configuration for an OpenID Federation Automatic Registration.
- */
-public interface OIDFederationAutomaticRegistrationProfileConfiguration extends OIDFederationRegistrationProfileConfiguration {
+import java.util.List;
+
+import javax.annotation.concurrent.ThreadSafe;
+
+import org.opensaml.profile.context.ProfileRequestContext;
- /** ID for this profile configuration. */
- public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/automaticregistration";
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.shared.resolver.Resolver;
+
+/**
+ * A resolver that is capable of finding provided trust chains from the {@link ProfileRequestContext}.
+ */
+ at ThreadSafe
+public interface ProvidedTrustChainResolver extends Resolver<List<EntityStatement<?>>, ProfileRequestContext> {
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
index 5339180..7ef25f5 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/OIDFederationAutomaticRegistrationProfileConfiguration.java
@@ -14,6 +14,13 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.shared.annotation.ConfigurationSetting;
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
/**
* Profile configuration for an OpenID Federation Automatic Registration.
*/
@@ -22,4 +29,26 @@ public interface OIDFederationAutomaticRegistrationProfileConfiguration extends
/** ID for this profile configuration. */
public static final String PROFILE_ID = "http://shibboleth.net/ns/profiles/oidfed/automaticregistration";
+ /**
+ * Get claims set decoration type.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return claims set decoration type, or null for default
+ */
+ @ConfigurationSetting(name="defaultClaimsSetDecorationType")
+ @Nullable @NotEmpty String getDefaultClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
+ /**
+ * Get claims set decoration type for a client with request-provided trust chain.
+ *
+ * @param profileRequestContext profile request context
+ *
+ * @return claims set decoration type, or null for default
+ */
+ @ConfigurationSetting(name="providedChainClaimsSetDecorationType")
+ @Nullable @NotEmpty String getProvidedChainClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext);
+
}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java
new file mode 100644
index 0000000..2590ec5
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ClaimsSetDecorationTypeLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationAutomaticRegistrationProfileConfiguration#getDefaultClaimsSetDecorationType(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 ClaimsSetDecorationTypeLookupFunction extends AbstractRelyingPartyLookupFunction<String> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationAutomaticRegistrationProfileConfiguration ofarpc) {
+ return ofarpc.getDefaultClaimsSetDecorationType(input);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java
new file mode 100644
index 0000000..13fd28b
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/navigate/ProvidedChainClaimsSetDecorationTypeLookupFunction.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate;
+
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
+import net.shibboleth.profile.config.ProfileConfiguration;
+import net.shibboleth.profile.context.RelyingPartyContext;
+import net.shibboleth.profile.context.navigate.AbstractRelyingPartyLookupFunction;
+
+/**
+ * A function that returns
+ * {@link OIDFederationAutomaticRegistrationProfileConfiguration#getProvidedChainClaimsSetDecorationType(
+ * 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 ProvidedChainClaimsSetDecorationTypeLookupFunction extends AbstractRelyingPartyLookupFunction<String> {
+
+ /** {@inheritDoc} */
+ @Override
+ @Nullable public String apply(@Nullable final ProfileRequestContext input) {
+ final RelyingPartyContext rpc = getRelyingPartyContextLookupStrategy().apply(input);
+ if (rpc != null) {
+ final ProfileConfiguration pc = rpc.getProfileConfig();
+ if (pc instanceof OIDFederationAutomaticRegistrationProfileConfiguration ofarpc) {
+ return ofarpc.getProvidedChainClaimsSetDecorationType(input);
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/AutomaticRegistrationContext.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/AutomaticRegistrationContext.java
new file mode 100644
index 0000000..fcf4306
--- /dev/null
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/AutomaticRegistrationContext.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.context;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.messaging.context.BaseContext;
+
+/**
+ * Subcontext carrying information for automatic registration.
+ */
+public class AutomaticRegistrationContext extends BaseContext {
+
+ /** Claims set decoration type. */
+ @Nullable private String claimsSetDecorationType;
+
+ /**
+ * Get the claims set decoration typet.
+ *
+ * @return claims set decoration type
+ */
+ @Nullable public String getClaimsSetDecorationType() {
+ return claimsSetDecorationType;
+ }
+
+ /**
+ * Set the claims set decoration type.
+ *
+ * @param type claims set decoration type
+ *
+ * @return this context
+ */
+ @Nonnull public AutomaticRegistrationContext setClaimsSetDecorationType(
+ @Nullable final String type) {
+ claimsSetDecorationType = type;
+ return this;
+ }
+}
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
index 1ccedce..c850ea7 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/context/RelyingPartyTrustChainContext.java
@@ -33,6 +33,9 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.TrustedRemoteResolverEnt
*/
public final class RelyingPartyTrustChainContext extends BaseContext {
+ /** Trust chain provided within the request. */
+ @Nullable private List<EntityStatement<?>> providedTrustChain;
+
/** All resolved trust chains for the relying party. */
@Nullable private List<List<EntityStatement<?>>> resolvedTrustChains;
@@ -58,7 +61,28 @@ public final class RelyingPartyTrustChainContext extends BaseContext {
@Nullable private List<TrustedRemoteResolverEntity> attemptedTrustedRemoteResolverEntities;
/**
+ * Get the trust chain provided within the request.
+ *
+ * @return provided trust chain
+ */
+ @Nullable public List<EntityStatement<?>> getProvidedTrustChain() {
+ return providedTrustChain;
+ }
+ /**
+ * Set the trust chain provided within the request.
+ *
+ * @param trustChain provided trust chain
+ *
+ * @return this context
+ */
+ @Nonnull public RelyingPartyTrustChainContext setProvidedTrustChain(
+ @Nullable final List<EntityStatement<?>> trustChain) {
+ providedTrustChain = trustChain;
+ return this;
+ }
+
+ /**
* Get the resolved trust chains for the relying party.
*
* @return the trust chains
diff --git a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClaimsSetExtensionSupport.java b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClaimsSetExtensionSupport.java
index 82707d5..b09355d 100644
--- a/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClaimsSetExtensionSupport.java
+++ b/idp-oidfed-op-api/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/support/ClaimsSetExtensionSupport.java
@@ -39,6 +39,9 @@ public class ClaimsSetExtensionSupport {
/** Identifier for trust chain used when client was registered via OID federation. */
@Nonnull @NotEmpty public static final String KEY_AUTO_REGISTERED_TRUST_CHAIN = "oidfed_artc";
+ /** Identifier for flag indicating the client was stored. */
+ @Nonnull @NotEmpty public static final String KEY_AUTO_REGISTERED_STORED = "oidfed_sto";
+
/** Class logger. */
@Nonnull private static Logger log = LoggerFactory.getLogger(ClaimsSetExtensionSupport.class);
@@ -67,6 +70,31 @@ public class ClaimsSetExtensionSupport {
return null;
}
+ /**
+ * Parse flag indicating the client was stored from the given token claims set.
+ *
+ * @param claimsSet token claims set
+ * @return flag
+ */
+ public static boolean parseAutoRegisteredStored(@Nonnull final TokenClaimsSet claimsSet) {
+ return parseAutoRegisteredStored(claimsSet.getClaimsSet());
+ }
+
+ /**
+ * Parse flag indicating the client was stored from the given JWT claims set.
+ *
+ * @param claimsSet JWT claims set
+ * @return flag
+ */
+ public static boolean parseAutoRegisteredStored(@Nullable final JWTClaimsSet claimsSet) {
+ try {
+ return claimsSet != null ? claimsSet.getBooleanClaim(KEY_AUTO_REGISTERED_STORED) : false;
+ } catch (final ParseException e) {
+ log.warn("Could not parse auto registered stored flag from the claims set", e);
+ }
+ return false;
+ }
+
/**
* Parse automatically registered trust chain from the given map pf claims
*
@@ -85,4 +113,18 @@ public class ClaimsSetExtensionSupport {
.orElse(null);
}
+ /**
+ * Parse flag indicating the client was stored from the given map pf claims
+ *
+ * @param claimsSet map of claims
+ * @return flag
+ */
+ public static boolean parseAutoRegisteredStored(@Nullable final Map<String,Object> claimsSet) {
+ return Optional.ofNullable(claimsSet)
+ .map(set -> set.get(KEY_AUTO_REGISTERED_STORED))
+ .filter(Boolean.class::isInstance)
+ .map(Boolean.class::cast)
+ .orElse(false);
+ }
+
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
index 7669f97..e261c01 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/decoding/impl/ExplicitClientRegistrationRequestDecoder.java
@@ -16,8 +16,6 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.decoding.impl;
import java.io.IOException;
import java.net.URI;
-import java.text.ParseException;
-import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
@@ -26,11 +24,9 @@ import javax.annotation.Nullable;
import org.opensaml.messaging.decoder.MessageDecodingException;
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.google.common.base.MoreObjects;
-import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.http.HTTPRequest;
import com.nimbusds.oauth2.sdk.http.JakartaServletUtils;
@@ -40,12 +36,11 @@ import net.shibboleth.idp.plugin.oidc.op.oauth2.decoding.impl.BaseOAuth2RequestD
import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.EntityConfigurationImpl;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.SubordinateStatementImpl;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util.EntityStatementHelper;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.ConstraintViolationException;
import net.shibboleth.shared.primitive.LoggerFactory;
/**
@@ -97,22 +92,28 @@ public class ExplicitClientRegistrationRequestDecoder
}
final String contentType = request.getContentType();
if ("application/entity-statement+jwt".equals(contentType)) {
- if (deserializeEntityStatement(httpRequest.getQuery()) instanceof EntityConfiguration configuration) {
- log.trace("Entity configuration claims set: {}", configuration.getParsedPayload());
- return new ExplicitClientRegistrationRequest(uri, configuration);
+ final String query = httpRequest.getQuery();
+ if (query != null) {
+ try {
+ assert objectMapper != null;
+ final EntityStatement<?> statement =
+ EntityStatementHelper.parseEntityStatement(query, objectMapper);
+ if (statement instanceof EntityConfiguration configuration) {
+ log.trace("Entity configuration claims set: {}", configuration.getParsedPayload());
+ return new ExplicitClientRegistrationRequest(uri, configuration);
+ }
+ } catch (final ConstraintViolationException e) {
+ throw new MessageDecodingException("Could not deserialize entity configuration", e);
+ }
}
throw new MessageDecodingException("Could not deserialize entity configuration");
} else if ("application/trust-chain+json".equals(contentType)) {
final TypeReference<List<String>> typeReference = new TypeReference<List<String>>() {};
final List<String> strings = objectMapper.readValue(httpRequest.getQuery(), typeReference);
- final List<EntityStatement<?>> trustChain = new ArrayList<>();
- strings.forEach(item -> {
- final EntityStatement<?> deserialized = deserializeEntityStatement(item);
- if (deserialized != null) {
- trustChain.add(deserialized);
- }
- });
- if (trustChain.isEmpty()) {
+ assert strings != null; assert objectMapper != null;
+ final List<EntityStatement<?>> trustChain =
+ EntityStatementHelper.deserializeTrustChain(strings, objectMapper);
+ if (trustChain == null) {
throw new MessageDecodingException("Could not deserialize trust chain");
}
return new ExplicitClientRegistrationRequest(uri, trustChain);
@@ -126,25 +127,6 @@ public class ExplicitClientRegistrationRequestDecoder
}
}
- @Nullable private EntityStatement<?> deserializeEntityStatement(@Nullable final String input) {
- try {
- final SignedJWT jwt = SignedJWT.parse(input);
- assert jwt != null;
- final String subject = jwt.getJWTClaimsSet().getSubject();
- final String issuer = jwt.getJWTClaimsSet().getIssuer();
- if (subject != null && subject.equals(issuer)) {
- assert objectMapper != null;
- return EntityConfigurationImpl.parse(jwt, objectMapper);
- } else {
- assert objectMapper != null;
- return SubordinateStatementImpl.parse(jwt, objectMapper);
- }
- } catch (final JsonProcessingException | ParseException e) {
- log.warn("Could not parse entity statement", e);
- return null;
- }
- }
-
/** {@inheritDoc} */
@Override
protected String getMessageToLog(@Nullable final ExplicitClientRegistrationRequest message) {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
index 1fa5054..c071569 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultEntityStatementContentValidationCondition.java
@@ -25,6 +25,7 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
import jakarta.servlet.http.HttpServletRequest;
import net.shibboleth.oidc.jwt.claims.ClaimsValidator;
@@ -57,6 +58,9 @@ public class DefaultEntityStatementContentValidationCondition
/** The claims validator to use for validating the entity statement claims. */
@NonnullAfterInit private ClaimsValidator claimsValidator;
+ /** The header validator to use for validating the entity statement header. */
+ @NonnullAfterInit private BiPredicate<SignedJWT, ProfileRequestContext> headerValidator;
+
/** Supplier for the Current HTTP request, if available. */
@NonnullAfterInit private NonnullSupplier<HttpServletRequest> httpServletRequestSupplier;
@@ -70,6 +74,16 @@ public class DefaultEntityStatementContentValidationCondition
claimsValidator = Constraint.isNotNull(validator, "Claims validator cannot be null");
}
+ /**
+ * Set the header validator to use for validating the entity statement header.
+ *
+ * @param validator header validator
+ */
+ public void setHeaderValidator(@Nonnull final BiPredicate<SignedJWT, ProfileRequestContext> validator) {
+ checkSetterPreconditions();
+ headerValidator = Constraint.isNotNull(validator, "Header validator cannot be null");
+ }
+
/**
* Set the current HTTP request Supplier.
*
@@ -87,6 +101,9 @@ public class DefaultEntityStatementContentValidationCondition
if (claimsValidator == null) {
throw new ComponentInitializationException("Claims validator cannot be null");
}
+ if (headerValidator == null) {
+ throw new ComponentInitializationException("Header validator cannot be null");
+ }
if (httpServletRequestSupplier == null) {
throw new ComponentInitializationException("Http servlet request supplier cannot be null");
}
@@ -114,8 +131,18 @@ public class DefaultEntityStatementContentValidationCondition
.filter(ProfileRequestContext.class::isInstance)
.map(ProfileRequestContext.class::cast)
.orElse(null);
+ final ProfileRequestContext nonNullProfileRequestContext;
if (profileRequestContext == null) {
log.warn("Could not resolve profile request context, supplying an empty one to claims validators");
+ nonNullProfileRequestContext = new ProfileRequestContext();
+ } else {
+ nonNullProfileRequestContext = profileRequestContext;
+ }
+
+ //TODO: make validator interface to commons?
+ if (!headerValidator.test(wrapper.getJwt(), nonNullProfileRequestContext)) {
+ log.warn("Header validation failed for entity statement {}", responseContainer.getEntityId());
+ return false;
}
try {
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java
new file mode 100644
index 0000000..32cdee3
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/cache/DefaultTrustChainHeaderValidationCondition.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache;
+
+import java.text.ParseException;
+import java.util.List;
+import java.util.Set;
+import java.util.function.BiPredicate;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util.EntityStatementHelper;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Default validator that validates the syntax of trust_chain and peer_trust_chain headers if they're allowed to be
+ * used with the given entity statement {@link SignedJWT}.
+ */
+public class DefaultTrustChainHeaderValidationCondition extends AbstractIdentifiableInitializableComponent
+ implements BiPredicate<SignedJWT, ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private Logger log = LoggerFactory.getLogger(DefaultTrustChainHeaderValidationCondition.class);
+
+ /** JSON object mapper used for decoding entity statement payload. */
+ @NonnullAfterInit private ObjectMapper objectMapper;
+
+ /** The flag to indicate allowing the use of trust_chain header. */
+ private boolean allowTrustChainHeader = false;
+
+ /** The flag to indicate allowing the use of peer_trust_chain header. */
+ private boolean allowPeerTrustChainHeader = false;
+
+ /** The flag to indicate requiring the same trust anchor if both headers are present. */
+ private boolean requireCommonTrustAnchor = true;
+
+ /**
+ * Set the JSON {@link ObjectMapper} used for decoding entity statement payload.
+ *
+ * @param mapper object mapper
+ */
+ public void setObjectMapper(@Nonnull final ObjectMapper mapper) {
+ checkSetterPreconditions();
+ objectMapper = Constraint.isNotNull(mapper, "Object mapper cannot be null");
+ }
+
+ /**
+ * Set the flag to indicate allowing the use of trust_chain header.
+ *
+ * @param flag value
+ */
+ public void setAllowTrustChainHeader(final boolean flag) {
+ checkSetterPreconditions();
+ allowTrustChainHeader = flag;
+ }
+
+ /**
+ * Set the flag to indicate allowing the use of peer_trust_chain header.
+ *
+ * @param flag value
+ */
+ public void setAllowPeerTrustChainHeader(final boolean flag) {
+ checkSetterPreconditions();
+ allowPeerTrustChainHeader = flag;
+ }
+
+ /**
+ * Set the flag to indicate requiring the same trust anchor if both headers are present.
+ *
+ * @param flag value
+ */
+ public void setRequireCommonTrustAnchor(final boolean flag) {
+ checkSetterPreconditions();
+ requireCommonTrustAnchor = flag;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (objectMapper == null) {
+ throw new ComponentInitializationException("ObjectMapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean test(@Nullable final SignedJWT signedJwt,
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+ if (signedJwt == null || signedJwt.getHeader() == null || profileRequestContext == null) {
+ log.error("Invalid input: signed JWT nor profile request context cannot be null");
+ return false;
+ }
+ final JWTClaimsSet claimsSet;
+ try {
+ claimsSet = signedJwt.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.warn("Could not parse entity statement claims set", e);
+ return false;
+ }
+ final JWSHeader header = signedJwt.getHeader();
+ assert header != null;
+ final Set<String> params = header.getIncludedParams();
+ final List<EntityStatement<?>> trustChain;
+ if (params.contains("trust_chain")) {
+ if (!allowTrustChainHeader) {
+ log.warn("The use of trust_chain header is not allowed for {}", claimsSet.getSubject());
+ return false;
+ }
+ trustChain = parseToList(header.getCustomParam("trust_chain"), profileRequestContext);
+ if (trustChain == null) {
+ log.warn("Could not parse trust_chain contents into a trust chain");
+ return false;
+ }
+ } else {
+ trustChain = null;
+ }
+
+ final List<EntityStatement<?>> peerTrustChain;
+ if (params.contains("peer_trust_chain")) {
+ if (!allowPeerTrustChainHeader) {
+ log.warn("The use of peer_trust_chain header is not allowed for {}", claimsSet.getSubject());
+ return false;
+ }
+ peerTrustChain = parseToList(header.getCustomParam("peer_trust_chain"), profileRequestContext);
+ if (peerTrustChain == null) {
+ log.warn("Could not parse peer_trust_chain contents into a trust chain");
+ return false;
+ }
+ } else {
+ peerTrustChain = null;
+ }
+
+ if (trustChain != null && peerTrustChain != null) {
+ if (requireCommonTrustAnchor) {
+ log.debug("Both trust_chain and peer_trust_chain exists, verifying the trust anchors match");
+ final String trustChainAnchor = trustChain.get(trustChain.size() - 1).getSubject();
+ final String peerTrustChainAnchor = peerTrustChain.get(peerTrustChain.size() - 1).getSubject();
+ if (!trustChainAnchor.equals(peerTrustChainAnchor)) {
+ log.warn("The trust_chain anchor {} does not match with the peer_trust_chain anchor {}",
+ trustChainAnchor, peerTrustChainAnchor);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parse the raw object to trust chain.
+ *
+ * @param input raw object
+ * @param profileRequestContext profile request context
+ * @return trust chain if the input could be parsed, null otherwise
+ */
+ @Nullable protected List<EntityStatement<?>> parseToList(@Nullable final Object input,
+ @Nonnull final ProfileRequestContext profileRequestContext) {
+ if (input instanceof List<?> list) {
+ final List<String> strings = list.stream()
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .toList();
+ assert strings != null; assert objectMapper != null;
+ return EntityStatementHelper.deserializeTrustChain(strings, objectMapper);
+ }
+ return null;
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
index e3ab216..a095edc 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/metadata/util/EntityStatementHelper.java
@@ -15,6 +15,10 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util;
import java.net.URI;
+import java.text.ParseException;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -24,11 +28,22 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityConfiguration;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.SubordinateStatement;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.SubjectEntityIDCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.configuration.EntityConfigurationContainer;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.EntityConfigurationImpl;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.SubordinateStatementImpl;
import net.shibboleth.oidc.metadata.cache.MetadataCache;
import net.shibboleth.oidc.metadata.cache.MetadataCacheException;
+import net.shibboleth.shared.annotation.constraint.NonnullElements;
+import net.shibboleth.shared.logic.ConstraintViolationException;
import net.shibboleth.shared.primitive.LoggerFactory;
import net.shibboleth.shared.resolver.CriteriaSet;
@@ -125,4 +140,103 @@ public class EntityStatementHelper {
.map(statement -> statement.getSubject())
.anyMatch(id -> subject.equals(id));
}
+
+ /**
+ * Deserialize the given list of strings into a list of entity statements. The method also verifies that the
+ * trust chain syntax is correct: it contains three or more items, it begins and ends with an entity configuration,
+ * other items being subordinate statements.
+ *
+ * @param chain serialized trust chain
+ * @param objectMapper object mapper used for deserializing the statement payloads
+ * @return list of entity statements, or null if the input was invalid
+ */
+ @Nullable public static List<EntityStatement<?>> deserializeTrustChain(
+ @Nonnull @NonnullElements final List<String> chain, @Nonnull final ObjectMapper objectMapper) {
+ if (chain.size() < 3) {
+ log.warn("Invalid trust chain: shorter than three items");
+ return null;
+ }
+ final List<EntityStatement<?>> result = new ArrayList<>();
+ for (final String entry : chain) {
+ try {
+ assert entry != null;
+ result.add(parseEntityStatement(entry, objectMapper));
+ } catch (final ConstraintViolationException e) {
+ log.warn("Could not deserialize the given trust chain", e);
+ return null;
+ }
+ }
+ if (!(result.get(0) instanceof EntityConfiguration)) {
+ log.warn("Trust chain does not begin with an entity configuration");
+ return null;
+ }
+ for (int i = 1; i < result.size() - 1; i++) {
+ if (!(result.get(i) instanceof SubordinateStatement)) {
+ log.warn("Trust chain entry {} is not a subordinate statement", i);
+ return null;
+ }
+ }
+ if (!(result.get(result.size() - 1) instanceof EntityConfiguration)) {
+ log.warn("Trust chain does not end with an entity configuration");
+ return null;
+ }
+ return result;
+ }
+
+ /**
+ * Parse an entity statement from the given input. If the subject and issuer matches in the payload, an entity
+ * configuration is returned. Otherwise, a subordinate statement is returned.
+ *
+ * @param input serialized entity statement
+ * @param objectMapper object mapper used for deserializing the statement payload
+ * @return entity configuration or subordinate statement, edpending on the content
+ * @throws ConstraintViolationException if input was invalid
+ */
+ @Nonnull public static EntityStatement<?> parseEntityStatement(@Nonnull final String input,
+ @Nonnull final ObjectMapper objectMapper) throws ConstraintViolationException {
+ final SignedJWT jwt;
+ final JWTClaimsSet claimsSet;
+ try {
+ jwt = SignedJWT.parse(input);
+ assert jwt != null;
+ claimsSet = jwt.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ log.warn("Could not construct a JWT from the entity statement", e);
+ throw new ConstraintViolationException(e.getMessage());
+ }
+ assert objectMapper != null;
+ final String subject = claimsSet.getSubject();
+ if (subject == null) {
+ throw new ConstraintViolationException("Subject is null");
+ }
+ final String issuer = claimsSet.getIssuer();
+ if (issuer == null) {
+ throw new ConstraintViolationException("Issuer is null");
+ }
+ try {
+ if (subject.equals(issuer)) {
+ return EntityConfigurationImpl.parse(jwt, objectMapper);
+ } else {
+ return SubordinateStatementImpl.parse(jwt, objectMapper);
+ }
+ } catch (final JsonProcessingException e) {
+ log.debug("Could not parse the entity statement " + subject + ", issued by " + issuer, e);
+ throw new ConstraintViolationException(e.getMessage());
+ }
+ }
+
+ /**
+ * Parse the expiration time from the trust chain: the lowest expiration time value is taken from the chain.
+ *
+ * @param trustChain trust chain
+ * @return expiration instant
+ */
+ @Nonnull public static Instant parseTrustChainExpiration(@Nonnull final List<EntityStatement<?>> trustChain) {
+ final Instant result = trustChain.stream()
+ .map(statement -> statement.getParsedPayload().getExpiration())
+ .min(Comparator.comparing(Instant::toEpochMilli))
+ .orElse(Instant.now());
+ assert result != null;
+ return result;
+ }
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java
index 3c78977..533b00f 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/config/impl/DefaultOIDFederationAutomaticRegistrationProfileConfiguration.java
@@ -14,10 +14,18 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl;
+import java.util.function.Function;
+
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.OIDFederationAutomaticRegistrationProfileConfiguration;
import net.shibboleth.shared.annotation.constraint.NotEmpty;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.logic.FunctionSupport;
+import net.shibboleth.shared.primitive.StringSupport;
/**
* Implementation of a profile configuration for the OpenID Federation Automatic Registration.
@@ -30,6 +38,12 @@ public class DefaultOIDFederationAutomaticRegistrationProfileConfiguration
@Nonnull @NotEmpty
public static final String PROFILE_COUNTER = "net.shibboleth.idp.profiles.oidfed.automaticregistration";
+ /** Lookup function to supply claims set decoration type. */
+ @Nonnull private Function<ProfileRequestContext,String> claimsSetDecorationTypeLookupStrategy;
+
+ /** Lookup function to supply claims set decoration type for clients with request-provided trust chains. */
+ @Nonnull private Function<ProfileRequestContext,String> providedChainClaimsSetDecorationTypeLookupStrategy;
+
/**
* Constructor.
*/
@@ -44,5 +58,60 @@ public class DefaultOIDFederationAutomaticRegistrationProfileConfiguration
*/
public DefaultOIDFederationAutomaticRegistrationProfileConfiguration(@Nonnull @NotEmpty final String profileId) {
super(profileId);
+ claimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(null);
+ providedChainClaimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(null);
}
+
+ /** {@inheritDoc} */
+ @Override @Nullable @NotEmpty
+ public String getDefaultClaimsSetDecorationType(@Nullable final ProfileRequestContext profileRequestContext) {
+ return StringSupport.trimOrNull(claimsSetDecorationTypeLookupStrategy.apply(profileRequestContext));
+ }
+
+ /**
+ * Set claims set decoration type.
+ *
+ * @param type decoration type, or null for default
+ */
+ public void setDefaultClaimsSetDecorationType(@Nullable @NotEmpty final String type) {
+ claimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(type));
+ }
+
+ /**
+ * Set lookup strategy for claims set decoration type.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setDefaultClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ claimsSetDecorationTypeLookupStrategy = Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable @NotEmpty
+ public String getProvidedChainClaimsSetDecorationType(
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ return StringSupport.trimOrNull(
+ providedChainClaimsSetDecorationTypeLookupStrategy.apply(profileRequestContext));
+ }
+
+ /**
+ * Set claims set decoration type for clients with request-provided trust chain.
+ *
+ * @param type decoration type
+ */
+ public void setProvidedChainClaimsSetDecorationType(@Nullable @NotEmpty final String type) {
+ providedChainClaimsSetDecorationTypeLookupStrategy = FunctionSupport.constant(StringSupport.trimOrNull(type));
+ }
+
+ /**
+ * Set lookup strategy for claims set decoration type for clients with request-provided trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProvidedChainClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext,String> strategy) {
+ providedChainClaimsSetDecorationTypeLookupStrategy =
+ Constraint.isNotNull(strategy, "Lookup strategy cannot be null");
+ }
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTokenClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTokenClaimsSetDecorator.java
new file mode 100644
index 0000000..ca4148a
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AbstractTokenClaimsSetDecorator.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+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 net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.AutomaticRegistrationContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * Abstract base class for the token type specific claims set decorators.
+ */
+ at ThreadSafeAfterInit
+public abstract class AbstractTokenClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+ implements BiConsumer<Map<String, Object>, ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractTokenClaimsSetDecorator.class);
+
+ /** Lookup strategy for automatic registration context. */
+ @Nonnull
+ private Function<ProfileRequestContext, AutomaticRegistrationContext> automaticRegistrationContextLookupStrategy;
+
+ /** Map of claims set decorators. */
+ @NonnullAfterInit
+ private Map<String, List<BiConsumer<Map<String, Object>, ProfileRequestContext>>> claimsSetDecorators;
+
+ /**
+ * Constructor.
+ */
+ public AbstractTokenClaimsSetDecorator() {
+ final Function<ProfileRequestContext, AutomaticRegistrationContext> arcls =
+ new ChildContextLookup<>(AutomaticRegistrationContext.class).compose(
+ new InboundMessageContextLookup());
+ assert arcls != null;
+ automaticRegistrationContextLookupStrategy = arcls;
+ }
+
+ /**
+ * Set the lookup strategy for automatic registration context.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setAutomaticRegistrationContextLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, AutomaticRegistrationContext> strategy) {
+ checkSetterPreconditions();
+ automaticRegistrationContextLookupStrategy =
+ Constraint.isNotNull(strategy, "AutomaticRegistrationContextLookupStrategy cannot be null");
+ }
+
+ /**
+ * Set the map of claims set decorators.
+ *
+ * @param map decorators
+ */
+ public void setClaimsSetDecorators(
+ @Nonnull final Map<String, List<BiConsumer<Map<String, Object>, ProfileRequestContext>>> map) {
+ checkSetterPreconditions();
+ claimsSetDecorators = Constraint.isNotNull(map, "ClaimsSetDecorators cannot be null");
+ Constraint.isNotEmpty(map.entrySet(), "ClaimsSetDecorators cannot be empty");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (claimsSetDecorators == null) {
+ throw new ComponentInitializationException("ClaimsSetDecorators cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(@Nullable final Map<String, Object> claimsSet,
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ final AutomaticRegistrationContext registrationContext =
+ automaticRegistrationContextLookupStrategy.apply(profileRequestContext);
+ if (registrationContext == null) {
+ log.error("Could not resolve automatic registration context, cannot proceed");
+ return;
+ }
+ final String decorationType = registrationContext.getClaimsSetDecorationType();
+ final List<BiConsumer<Map<String, Object>, ProfileRequestContext>> decorators =
+ claimsSetDecorators.get(decorationType);
+ if (decorators == null) {
+ log.error("Could not resolve decorators for type {}", decorationType);
+ return;
+ }
+ for (final BiConsumer<Map<String, Object>, ProfileRequestContext> decorator : decorators) {
+ log.trace("Running decorator {}", decorator);
+ decorator.accept(claimsSet, profileRequestContext);
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredClientInformationStoreClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredClientInformationStoreClaimsSetDecorator.java
new file mode 100644
index 0000000..c911d9e
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredClientInformationStoreClaimsSetDecorator.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+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.rp.OIDCClientInformation;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util.EntityStatementHelper;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
+import net.shibboleth.oidc.metadata.ClientInformationManager;
+import net.shibboleth.oidc.metadata.ClientInformationManagerException;
+import net.shibboleth.oidc.metadata.context.OIDCMetadataContext;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+
+/**
+ * A decorator that stores the client information for the configurable {@link ClientInformationManager} and sets a
+ * custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_STORED} as true if the operation was successful.
+ */
+ at ThreadSafeAfterInit
+public class AutoRegisteredClientInformationStoreClaimsSetDecorator
+ extends AbstractIdentifiableInitializableComponent
+ implements BiConsumer<Map<String, Object>, ProfileRequestContext> {
+
+ /** Class logger. */
+ @Nonnull private final Logger log =
+ LoggerFactory.getLogger(AutoRegisteredClientInformationStoreClaimsSetDecorator.class);
+
+ /** Strategy used to lookup the trust chain context. */
+ @Nonnull protected Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+
+ /** The client information manager used for storing the information. */
+ @NonnullAfterInit private ClientInformationManager clientInformationManager;
+
+ /**
+ * Constructor.
+ */
+ public AutoRegisteredClientInformationStoreClaimsSetDecorator() {
+ final Function<ProfileRequestContext, RelyingPartyTrustChainContext> tcls =
+ new ChildContextLookup<>(RelyingPartyTrustChainContext.class).compose(
+ new InboundMessageContextLookup());
+ assert tcls != null;
+ trustChainContextLookupStrategy = tcls;
+ }
+
+ /**
+ * 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 client information manager used for storing the information.
+ *
+ * @param manager client information manager
+ */
+ public void setClientInformationManager(@Nonnull final ClientInformationManager manager) {
+ checkSetterPreconditions();
+ clientInformationManager = Constraint.isNotNull(manager, "ClientInformationManager cannot be null!");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (clientInformationManager == null) {
+ throw new ComponentInitializationException("ClientInformationManager cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void accept(@Nullable final Map<String, Object> claimsSet,
+ @Nullable final ProfileRequestContext profileRequestContext) {
+ checkComponentActive();
+
+ if (claimsSet == null) {
+ log.error("Claims set is null");
+ return;
+ }
+ final List<EntityStatement<?>> selectedTrustChain =
+ Optional.ofNullable(trustChainContextLookupStrategy.apply(profileRequestContext))
+ .map(trustChainContext -> trustChainContext.getSelectedTrustChain())
+ .map(chain -> chain.getTrustChain())
+ .orElse(null);
+ if (selectedTrustChain != null) {
+ final OIDCClientInformation clientInformation = Optional.ofNullable(profileRequestContext)
+ .map(prc -> prc.getInboundMessageContext())
+ .map(msgCtx -> msgCtx.getSubcontext(OIDCMetadataContext.class))
+ .map(oidcCtx -> oidcCtx.getClientInformation())
+ .orElse(null);
+ if (clientInformation != null) {
+ try {
+ clientInformationManager.storeClientInformation(clientInformation,
+ EntityStatementHelper.parseTrustChainExpiration(selectedTrustChain), true);
+ claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_STORED, true);
+ } catch (final ClientInformationManagerException e) {
+ log.warn("Could not store client information record {}", clientInformation.getID().getValue());
+ }
+ } else {
+ log.error("Could not resolve client information, cannot store it");
+ }
+ } else {
+ log.debug("No selected trust chain found, nothing to store");
+ }
+ }
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAccessTokenClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAccessTokenClaimsSetDecorator.java
index 0200d3e..4cf2b8e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAccessTokenClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAccessTokenClaimsSetDecorator.java
@@ -14,62 +14,12 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
import net.shibboleth.idp.plugin.oidc.op.profile.AccessTokenClaimsSetDecorator;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
/**
- * The decorator that adds a custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_TRUST_CHAIN} to the
- * access token claims set if a value was resolved via {@link #selectedTrustChainIDsLookupStrategy}.
+ * Default claims set decorator for access token claims sets.
*/
-public class AutoRegisteredTrustChainAccessTokenClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+public class AutoRegisteredTrustChainAccessTokenClaimsSetDecorator extends AbstractTokenClaimsSetDecorator
implements AccessTokenClaimsSetDecorator {
- /** The strategy used to locate IDs for the selected trust chain. */
- @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
-
- /**
- * Constructor.
- */
- public AutoRegisteredTrustChainAccessTokenClaimsSetDecorator() {
- selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
- }
-
- /**
- * Set the lookup strategy used to locate IDs for the selected trust chain.
- *
- * @param strategy What to set
- */
- public void setSelectedTrustChainIDsLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
- checkSetterPreconditions();
-
- selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
- "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
- }
- /** {@inheritDoc} */
- @Override
- public void accept(@Nullable final Map<String, Object> claimsSet,
- @Nullable final ProfileRequestContext profileRequestContext) {
- checkComponentActive();
- if (claimsSet != null) {
- final List<String> trustChainIds = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
- if (trustChainIds != null && !trustChainIds.isEmpty()) {
- claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChainIds);
- }
-
- }
- }
-
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java
index bb948c0..7108c34 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java
@@ -14,62 +14,12 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
import net.shibboleth.idp.plugin.oidc.op.profile.AuthorizationCodeClaimsSetDecorator;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
/**
- * The decorator that adds a custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_TRUST_CHAIN} to the
- * builder if a value was resolved via {@link #selectedTrustChainIDsLookupStrategy}.
+ * Default claims set decorator for authorization code claims sets.
*/
-public class AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+public class AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator extends AbstractTokenClaimsSetDecorator
implements AuthorizationCodeClaimsSetDecorator {
- /** The strategy used to locate IDs for the selected trust chain. */
- @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
-
- /**
- * Constructor.
- */
- public AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator() {
- selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
- }
-
- /**
- * Set the lookup strategy used to locate IDs for the selected trust chain.
- *
- * @param strategy What to set
- */
- public void setSelectedTrustChainIDsLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
- checkSetterPreconditions();
-
- selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
- "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
- }
- /** {@inheritDoc} */
- @Override
- public void accept(@Nullable final Map<String, Object> claimsSet,
- @Nullable final ProfileRequestContext profileRequestContext) {
- checkComponentActive();
- if (claimsSet != null) {
- final List<String> trustChainIds = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
- if (trustChainIds != null && !trustChainIds.isEmpty()) {
- claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChainIds);
- }
-
- }
- }
-
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainIDsClaimsSetDecorator.java
similarity index 81%
copy from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java
copy to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainIDsClaimsSetDecorator.java
index bb948c0..7bbbc03 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainIDsClaimsSetDecorator.java
@@ -16,6 +16,7 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.util.List;
import java.util.Map;
+import java.util.function.BiConsumer;
import java.util.function.Function;
import javax.annotation.Nonnull;
@@ -25,16 +26,17 @@ import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
-import net.shibboleth.idp.plugin.oidc.op.profile.AuthorizationCodeClaimsSetDecorator;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
import net.shibboleth.shared.logic.Constraint;
/**
- * The decorator that adds a custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_TRUST_CHAIN} to the
- * builder if a value was resolved via {@link #selectedTrustChainIDsLookupStrategy}.
+ * A decorator that adds a custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_TRUST_CHAIN} to the
+ * claims set if a value was resolved via {@link #selectedTrustChainIDsLookupStrategy}.
*/
-public class AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
- implements AuthorizationCodeClaimsSetDecorator {
+ at ThreadSafeAfterInit
+public class AutoRegisteredTrustChainIDsClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+ implements BiConsumer<Map<String, Object>, ProfileRequestContext> {
/** The strategy used to locate IDs for the selected trust chain. */
@Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
@@ -42,7 +44,7 @@ public class AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator extends
/**
* Constructor.
*/
- public AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator() {
+ public AutoRegisteredTrustChainIDsClaimsSetDecorator() {
selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
}
@@ -54,10 +56,10 @@ public class AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator extends
public void setSelectedTrustChainIDsLookupStrategy(
@Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
checkSetterPreconditions();
-
selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
"SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
}
+
/** {@inheritDoc} */
@Override
public void accept(@Nullable final Map<String, Object> claimsSet,
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator.java
index b3c59d7..8480c94 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator.java
@@ -14,62 +14,12 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
import net.shibboleth.idp.plugin.oidc.op.profile.RefreshTokenClaimsSetDecorator;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
/**
- * The decorator that adds a custom claim {@link ClaimsSetExtensionSupport#KEY_AUTO_REGISTERED_TRUST_CHAIN} to the
- * refresh token claims set if a value was resolved via {@link #selectedTrustChainIDsLookupStrategy}.
+ * Default claims set decorator for refresh token claims sets.
*/
-public class AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+public class AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator extends AbstractTokenClaimsSetDecorator
implements RefreshTokenClaimsSetDecorator {
- /** The strategy used to locate IDs for the selected trust chain. */
- @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
-
- /**
- * Constructor.
- */
- public AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator() {
- selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
- }
-
- /**
- * Set the lookup strategy used to locate IDs for the selected trust chain.
- *
- * @param strategy What to set
- */
- public void setSelectedTrustChainIDsLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
- checkSetterPreconditions();
-
- selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
- "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
- }
- /** {@inheritDoc} */
- @Override
- public void accept(@Nullable final Map<String, Object> claimsSet,
- @Nullable final ProfileRequestContext profileRequestContext) {
- checkComponentActive();
- if (claimsSet != null) {
- final List<String> trustChainIds = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
- if (trustChainIds != null && !trustChainIds.isEmpty()) {
- claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChainIds);
- }
-
- }
- }
-
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
index 1185961..0d60c31 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/AutoRegisteredTrustChainRequestUriClaimsSetDecorator.java
@@ -14,60 +14,12 @@
package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Function;
-
-import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
-
-import org.opensaml.profile.context.ProfileRequestContext;
-
-import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainIDsLookupStrategy;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.support.ClaimsSetExtensionSupport;
import net.shibboleth.idp.plugin.oidc.op.profile.RequestUriClaimsSetDecorator;
-import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
-import net.shibboleth.shared.logic.Constraint;
/**
- *
+ * Default claims set decorator for pushed authorization request_uri claims sets.
*/
-public class AutoRegisteredTrustChainRequestUriClaimsSetDecorator extends AbstractIdentifiableInitializableComponent
+public class AutoRegisteredTrustChainRequestUriClaimsSetDecorator extends AbstractTokenClaimsSetDecorator
implements RequestUriClaimsSetDecorator {
- /** The strategy used to locate IDs for the selected trust chain. */
- @Nonnull private Function<ProfileRequestContext,List<String>> selectedTrustChainIDsLookupStrategy;
-
- /**
- * Constructor.
- */
- public AutoRegisteredTrustChainRequestUriClaimsSetDecorator() {
- selectedTrustChainIDsLookupStrategy = new DefaultSelectedTrustChainIDsLookupStrategy();
- }
-
- /**
- * Set the lookup strategy used to locate IDs for the selected trust chain.
- *
- * @param strategy What to set
- */
- public void setSelectedTrustChainIDsLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, List<String>> strategy) {
- checkSetterPreconditions();
-
- selectedTrustChainIDsLookupStrategy = Constraint.isNotNull(strategy,
- "SelectedTrustChainIDsLookupStrategy lookup strategy cannot be null");
- }
-
- /** {@inheritDoc} */
- @Override
- public void accept(@Nullable final Map<String, Object> claimsSet,
- @Nullable final ProfileRequestContext profileRequestContext) {
- checkComponentActive();
-
- final List<String> trustChainIds = selectedTrustChainIDsLookupStrategy.apply(profileRequestContext);
- if (claimsSet != null && trustChainIds != null && !trustChainIds.isEmpty()) {
- claimsSet.put(ClaimsSetExtensionSupport.KEY_AUTO_REGISTERED_TRUST_CHAIN, trustChainIds);
- }
- }
-
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
index 13e3df2..fc0fb5b 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/CallResolveEntityApi.java
@@ -39,7 +39,6 @@ import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.InboundMessageContextLookup;
import org.slf4j.Logger;
-import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jwt.SignedJWT;
import com.nimbusds.oauth2.sdk.id.ClientID;
@@ -55,10 +54,9 @@ import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.configuration.Ent
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.resolver.ResolveEntityCacheContainerIdentifier;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.resolver.ResolveEntityCacheIdentifierCriterion;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.resolver.ResolveEntityResponseContainer;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.EntityConfigurationImpl;
-import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.impl.SubordinateStatementImpl;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.ResolveEntityResponsePayload;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.Metadata;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util.EntityStatementHelper;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.TrustedRemoteResolverEntity;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.VerifiedTrustChain;
@@ -72,7 +70,6 @@ import net.shibboleth.shared.annotation.constraint.NonnullBeforeExec;
import net.shibboleth.shared.collection.CollectionSupport;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.logic.Constraint;
-import net.shibboleth.shared.logic.ConstraintViolationException;
import net.shibboleth.shared.logic.FunctionSupport;
import net.shibboleth.shared.logic.PredicateSupport;
import net.shibboleth.shared.primitive.LoggerFactory;
@@ -438,27 +435,13 @@ public class CallResolveEntityApi extends AbstractProfileAction {
trustedEntity);
continue;
}
- final List<EntityStatement<?>> chain = new ArrayList<>();
- rawTrustChain.stream()
- .filter(Objects::nonNull)
- .forEach(entry -> {
- try {
- final SignedJWT jwt = SignedJWT.parse(entry);
- assert jwt != null;
- assert objectMapper != null;
- final String subject = jwt.getJWTClaimsSet().getSubject();
- final String issuer = jwt.getJWTClaimsSet().getIssuer();
- if (subject != null && subject.equals(issuer)) {
- assert objectMapper != null;
- chain.add(EntityConfigurationImpl.parse(jwt, objectMapper));
- } else {
- assert objectMapper != null;
- chain.add(SubordinateStatementImpl.parse(jwt, objectMapper));
- }
- } catch (final JsonProcessingException | ParseException | ConstraintViolationException e) {
- log.warn("Could not parse the trust chain into list of entity statements", e);
- }
- });
+ assert objectMapper != null;
+ final List<EntityStatement<?>> chain =
+ EntityStatementHelper.deserializeTrustChain(rawTrustChain, objectMapper);
+ if (chain == null) {
+ log.warn("Could not parse the trust chain into list of entity statements");
+ continue;
+ }
if (!preSelectedChain.isEmpty() && !preSelectedChain.equals(trustChainIDsLookupStrategy.apply(chain))) {
log.debug("{} Ignored resolved trust chain that doesn't match with preselected chain",
getLogPrefix());
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultProvidedTrustChainResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultProvidedTrustChainResolver.java
new file mode 100644
index 0000000..c38952e
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/DefaultProvidedTrustChainResolver.java
@@ -0,0 +1,116 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.slf4j.Logger;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.annotation.constraint.ThreadSafeAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.primitive.LoggerFactory;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Resolver for provided trust chains that exploits a map of profile-specific resolvers.
+ */
+ at ThreadSafeAfterInit
+public class DefaultProvidedTrustChainResolver extends AbstractIdentifiableInitializableComponent
+ implements ProvidedTrustChainResolver {
+
+ /** Class logger. */
+ @Nonnull private final Logger log = LoggerFactory.getLogger(DefaultProvidedTrustChainResolver.class);
+
+ /** Strategy to locate the profile ID. */
+ @NonnullAfterInit private Function<ProfileRequestContext, String> profileIdLookupStrategy;
+
+ /** Strategies used to locate profile-specific lookup strategies. */
+ @Nonnull private Map<String,ProvidedTrustChainResolver> profileSpecificResolvers;
+
+ /**
+ * Constructor.
+ */
+ public DefaultProvidedTrustChainResolver() {
+ profileSpecificResolvers = CollectionSupport.emptyMap();
+ }
+
+ /**
+ * Set strategy to locate the profile ID.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProfileIdLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
+ checkSetterPreconditions();
+ profileIdLookupStrategy = Constraint.isNotNull(strategy, "ProfileIdLookupStrategy cannot be null");
+ }
+
+ /**
+ * Set map used to locate profile-specific resolvers.
+ *
+ * @param resolvers map of resolvers
+ */
+ public void setProfileSpecificResolvers(@Nonnull final Map<String,ProvidedTrustChainResolver> resolvers) {
+ checkSetterPreconditions();
+ profileSpecificResolvers = Constraint.isNotNull(resolvers, "ProfileSpecificResolvers cannot be null");
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ protected void doInitialize() throws ComponentInitializationException {
+ super.doInitialize();
+
+ if (profileIdLookupStrategy == null) {
+ throw new ComponentInitializationException("ProfileIdLookupStrategy cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Iterable<List<EntityStatement<?>>> resolve(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ final List<EntityStatement<?>> result = resolveSingle(profileRequestContext);
+ return result == null ? CollectionSupport.emptyList() : CollectionSupport.listOf(result);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public List<EntityStatement<?>> resolveSingle(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ checkComponentActive();
+ final String profileId = profileIdLookupStrategy.apply(profileRequestContext);
+ if (profileId == null) {
+ throw new ResolverException("Could not resolve profile ID via lookup strategy");
+ }
+ final ProvidedTrustChainResolver resolver = profileSpecificResolvers.get(profileId);
+ if (resolver == null) {
+ log.debug("No resolver found for profile {}", profileId);
+ return null;
+ }
+ return resolver.resolveSingle(profileRequestContext);
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
index d8c4c01..82837a4 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/OidFederationEventIds.java
@@ -75,4 +75,16 @@ public class OidFederationEventIds {
*/
@Nonnull @NotEmpty public static final String INVALID_TRUST_CHAIN_AGAINST_CONSTRAINTS =
"InvalidTrustChainAgainstConstraints";
+
+ /**
+ * ID of event returned if the mandatory provided trust chain could not be fetched.
+ */
+ @Nonnull @NotEmpty public static final String MISSING_MANDATORY_PROVIDED_TRUST_CHAIN =
+ "MissingMandatoryProvidedTrustChain";
+
+ /**
+ * ID of event returned if the provided trust chain could not be verified.
+ */
+ @Nonnull @NotEmpty public static final String INVALID_PROVIDED_TRUST_CHAIN = "InvalidProvidedTrustChain";
+
}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
index ce6d5ad..758f368 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateAutomaticRegistrationProfileConfiguration.java
@@ -35,8 +35,11 @@ import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.payload.claim.impl.MetadataImpl;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.ClaimsSetDecorationTypeLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.LocalMetadataPolicyLookupFunction;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.MandatoryTrustMarksLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.ProvidedChainClaimsSetDecorationTypeLookupFunction;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.AutomaticRegistrationContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.VerifiedTrustChain;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultSelectedTrustChainMetadataLookupStrategy;
@@ -73,6 +76,10 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
/** Strategy used to lookup the trust chain context. */
@Nonnull private Function<ProfileRequestContext, RelyingPartyTrustChainContext> trustChainContextLookupStrategy;
+ /** Strategy used to create the automatic registration context. */
+ @Nonnull
+ private Function<ProfileRequestContext, AutomaticRegistrationContext> automaticRegistrationContextCreationStrategy;
+
/** Strategy used to lookup the selected metadata. */
@Nonnull private Function<ProfileRequestContext, OIDCClientMetadata> selectedMetadataLookupStrategy;
@@ -86,6 +93,12 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
@NonnullAfterInit private BiFunction<OIDCClientInformation,Map<String, MetadataPolicy>,OIDCClientInformation>
localMetadataPolicyMergingStrategy;
+ /** Strategy used to lookup claims set decoration type. */
+ @Nonnull private Function<ProfileRequestContext, String> claimsSetDecorationTypeLookupStrategy;
+
+ /** Strategy used to lookup claims set decoration type for clients with request-provided trust chain. */
+ @Nonnull private Function<ProfileRequestContext, String> providedChainClaimsSetDecorationTypeLookupStrategy;
+
/** Trust chain context to operate on. */
@NonnullBeforeExec private RelyingPartyTrustChainContext trustChainContext;
@@ -101,9 +114,16 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
new InboundMessageContextLookup());
assert tcls != null;
trustChainContextLookupStrategy = tcls;
+ final Function<ProfileRequestContext, AutomaticRegistrationContext> arccs =
+ new ChildContextLookup<>(AutomaticRegistrationContext.class, true).compose(
+ new InboundMessageContextLookup());
+ assert arccs != null;
+ automaticRegistrationContextCreationStrategy = arccs;
selectedMetadataLookupStrategy = new DefaultSelectedTrustChainMetadataLookupStrategy();
mandatoryTrustMarksLookupStrategy = new MandatoryTrustMarksLookupFunction();
localMetadataPolicyLookupStrategy = new LocalMetadataPolicyLookupFunction();
+ claimsSetDecorationTypeLookupStrategy = new ClaimsSetDecorationTypeLookupFunction();
+ providedChainClaimsSetDecorationTypeLookupStrategy = new ProvidedChainClaimsSetDecorationTypeLookupFunction();
}
/**
@@ -118,6 +138,18 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
Constraint.isNotNull(strategy, "TrustChainContextLookupStrategy cannot be null");
}
+ /**
+ * Set the strategy used to create automatic registration context.
+ *
+ * @param strategy creation strategy
+ */
+ public void setAutomaticRegistrationContextCreationStrategy(
+ @Nonnull final Function<ProfileRequestContext, AutomaticRegistrationContext> strategy) {
+ checkSetterPreconditions();
+ automaticRegistrationContextCreationStrategy =
+ Constraint.isNotNull(strategy, "AutomaticRegistrationContextCreationStrategy cannot be null");
+ }
+
/**
* Set the strategy used to lookup the selected metadata.
*
@@ -166,6 +198,30 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
Constraint.isNotNull(strategy, "Local metadata policy merging strategy cannot be null");
}
+ /**
+ * Set the strategy used to lookup claims set decoration type.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, String> strategy) {
+ checkSetterPreconditions();
+ claimsSetDecorationTypeLookupStrategy =
+ Constraint.isNotNull(strategy, "Claims set decoration type lookup strategy cannot be null");
+ }
+
+ /**
+ * Set the strategy used to lookup claims set decoration type for clients with request-provided trust chain.
+ *
+ * @param strategy lookup strategy
+ */
+ public void setProvidedChainClaimsSetDecorationTypeLookupStrategy(
+ @Nonnull final Function<ProfileRequestContext, String> strategy) {
+ checkSetterPreconditions();
+ providedChainClaimsSetDecorationTypeLookupStrategy =
+ Constraint.isNotNull(strategy, "Claims set decoration type lookup strategy cannot be null");
+ }
+
/** {@inheritDoc} */
@Override
protected boolean doPreExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
@@ -258,10 +314,27 @@ public class ValidateAutomaticRegistrationProfileConfiguration extends AbstractP
clientInformation.getOIDCMetadata().setCustomField(
ClientInformationExtensionSupport.KEY_VALIDATED_TRUST_MARK_IDS, verifiedTrustMarks);
}
- log.debug("{} Client information after adding custom extension {}", getLogPrefix(), clientInformation.toJSONObject().toJSONString());
+ log.debug("{} Client information after adding custom extension {}", getLogPrefix(),
+ clientInformation.toJSONObject().toJSONString());
oidcCtx.setClientInformation(clientInformation);
profileRequestContext.ensureInboundMessageContext().addSubcontext(oidcCtx);
log.debug("{} Client information attached to the OIDCMetadataContext", getLogPrefix());
+ final AutomaticRegistrationContext automaticRegistrationContext =
+ automaticRegistrationContextCreationStrategy.apply(profileRequestContext);
+ if (automaticRegistrationContext == null) {
+ log.error("{} Could not create automatic registration context", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ return;
+ }
+ final String decorationType;
+ if (trustChainContext.getProvidedTrustChain() != null) {
+ decorationType = providedChainClaimsSetDecorationTypeLookupStrategy.apply(profileRequestContext);
+ log.debug("{} Resolved provided chain claims set decoration type {}", getLogPrefix(), decorationType);
+ } else {
+ decorationType = claimsSetDecorationTypeLookupStrategy.apply(profileRequestContext);
+ log.debug("{} Resolved default claims set decoration type {}", getLogPrefix(), decorationType);
+ }
+ automaticRegistrationContext.setClaimsSetDecorationType(decorationType);
}
/**
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
index 7afe12b..51379a2 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/impl/ValidateProvidedTrustChain.java
@@ -17,15 +17,17 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiPredicate;
-import java.util.function.Function;
+import java.util.function.Predicate;
import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
import org.opensaml.profile.action.EventIds;
import org.opensaml.profile.context.ProfileRequestContext;
import org.slf4j.Logger;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.VerifiedTrustChain;
import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
@@ -34,6 +36,7 @@ import net.shibboleth.shared.collection.CollectionSupport;
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.profile.action.ActionSupport;
@@ -53,22 +56,28 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
@NonnullAfterInit
private BiPredicate<ProfileRequestContext, List<EntityStatement<?>>> providedTrustChainValidationStrategy;
- /** Strategy used to locate the provided trust chain. */
- @NonnullAfterInit
- private Function<ProfileRequestContext, List<EntityStatement<?>>> providedTrustChainLookupStrategy;
+ /** Resolver used to fetch the provided trust chain. */
+ @NonnullAfterInit private ProvidedTrustChainResolver providedTrustChainResolver;
+
+ /** Condition used to define if provided trust chain is mandatory. */
+ @NonnullAfterInit private Predicate<ProfileRequestContext> mandatoryProvidedTrustChainCondition;
+
+ /** Condition used to define if provided trust chain is ignored. */
+ @NonnullAfterInit private Predicate<ProfileRequestContext> ignoreProvidedTrustChainCondition;
+
+ /** Event ID to publish if the provided trust chain was valid and populated into the context. */
+ @Nullable private String validProvidedTrustChainEventId = null;
/** Trust chain to operate on. */
@NonnullBeforeExec private List<EntityStatement<?>> trustChain;
/**
- * Set the strategy used to locate the provided trust chain.
+ * Set the resolver to fetch the provided trust chain.
*
- * @param strategy lookup strategy
+ * @param resolver provided trust chain resolver
*/
- public void setProvidedTrustChainLookupStrategy(
- @Nonnull final Function<ProfileRequestContext, List<EntityStatement<?>>> strategy) {
- providedTrustChainLookupStrategy =
- Constraint.isNotNull(strategy, "ProvidedTrustChainLookupStrategy cannot be null");
+ public void setProvidedTrustChainResolver(@Nonnull final ProvidedTrustChainResolver resolver) {
+ providedTrustChainResolver = Constraint.isNotNull(resolver, "ProvidedTrustChainResolver cannot be null");
}
/**
@@ -83,17 +92,55 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
Constraint.isNotNull(strategy, "ProvidedTrustChainValidationStrategy cannot be null");
}
+ /**
+ * Set the condition used to define if provided trust chain is mandatory.
+ *
+ * @param condition mandatory condition
+ */
+ public void setMandatoryProvidedTrustChainCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+ mandatoryProvidedTrustChainCondition =
+ Constraint.isNotNull(condition, "MandatoryProvidedTrustChainCondition cannot be null");
+ }
+
+ /**
+ * Set the condition used to define if provided trust chain is ignored.
+ *
+ * @param condition ignoring condition
+ */
+ public void setIgnoreProvidedTrustChainCondition(@Nonnull final Predicate<ProfileRequestContext> condition) {
+ checkSetterPreconditions();
+ ignoreProvidedTrustChainCondition =
+ Constraint.isNotNull(condition, "IgnoreProvidedTrustChainCondition cannot be null");
+ }
+
+ /**
+ * Set the event ID to publish if the provided trust chain was valid and populated into the context.
+ *
+ * @param eventId event ID to publish
+ */
+ public void setValidProvidedTrustChainEventId(@Nullable final String eventId) {
+ checkSetterPreconditions();
+ validProvidedTrustChainEventId = eventId;
+ }
+
/** {@inheritDoc} */
@Override
protected void doInitialize() throws ComponentInitializationException {
super.doInitialize();
- if (providedTrustChainLookupStrategy == null) {
- throw new ComponentInitializationException("ProvidedTrustChainLookupStrategy cannot be null");
+ if (providedTrustChainResolver == null) {
+ throw new ComponentInitializationException("ProvidedTrustChainResolver cannot be null");
}
if (providedTrustChainValidationStrategy == null) {
throw new ComponentInitializationException("ProvidedTrustChainValidationStrategy cannot be null");
}
+ if (mandatoryProvidedTrustChainCondition == null) {
+ throw new ComponentInitializationException("MandatoryProvidedTrustChainCondition cannot be null");
+ }
+ if (ignoreProvidedTrustChainCondition == null) {
+ throw new ComponentInitializationException("IgnoreProvidedTrustChainCondition cannot be null");
+ }
}
/** {@inheritDoc} */
@@ -102,10 +149,25 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
if (!super.doPreExecute(profileRequestContext)) {
return false;
}
- trustChain = providedTrustChainLookupStrategy.apply(profileRequestContext);
+ try {
+ trustChain = providedTrustChainResolver.resolveSingle(profileRequestContext);
+ } catch (final ResolverException e) {
+ log.warn("{} Could not parse the provided trust chain", getLogPrefix(), e);
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_PROVIDED_TRUST_CHAIN);
+ return false;
+ }
+ if (ignoreProvidedTrustChainCondition.test(profileRequestContext)) {
+ log.debug("{} Provided trust chains are configurd to be ignored, nothing to do", getLogPrefix());
+ return false;
+ }
if (trustChain == null) {
- log.error("{} Unable to fetch trust chain", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ if (mandatoryProvidedTrustChainCondition.test(profileRequestContext)) {
+ log.warn("{} Unable to fetch mandatory trust chain", getLogPrefix());
+ ActionSupport.buildEvent(profileRequestContext,
+ OidFederationEventIds.MISSING_MANDATORY_PROVIDED_TRUST_CHAIN);
+ return false;
+ }
+ log.debug("{} Optional provided trust chain was not found, nothing to do", getLogPrefix());
return false;
}
return true;
@@ -116,7 +178,7 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {
if (!providedTrustChainValidationStrategy.test(profileRequestContext, trustChain)) {
log.warn("{} The trust chain validation failed", getLogPrefix());
- ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_MSG_CTX);
+ ActionSupport.buildEvent(profileRequestContext, OidFederationEventIds.INVALID_PROVIDED_TRUST_CHAIN);
return;
}
@@ -133,10 +195,22 @@ public class ValidateProvidedTrustChain extends AbstractTrustChainResolutionActi
ActionSupport.buildEvent(profileRequestContext, errorEventId);
return;
}
+ if (policyCompliantChains.size() != 1) {
+ log.error("{} Unexpected result for the policy compliant trust chains: {}", getLogPrefix(),
+ policyCompliantChains.size());
+ ActionSupport.buildEvent(profileRequestContext, EventIds.INVALID_PROFILE_CTX);
+ return;
+ }
log.debug("{} Setting the policy compliant trust chains into the context: {}", getLogPrefix(),
policyCompliantChains);
trustChainContext.setPolicyCompliantTrustChains(policyCompliantChains);
+ trustChainContext.setProvidedTrustChain(policyCompliantChains.get(0).getTrustChain());
+ if (validProvidedTrustChainEventId != null) {
+ ActionSupport.buildEvent(profileRequestContext, validProvidedTrustChainEventId);
+ log.debug("{} Published event {} as provided trust chain was valid", getLogPrefix(),
+ validProvidedTrustChainEventId);
+ }
}
}
\ No newline at end of file
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractRequestObjectProvidedTrustChainResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractRequestObjectProvidedTrustChainResolver.java
new file mode 100644
index 0000000..139b193
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/AbstractRequestObjectProvidedTrustChainResolver.java
@@ -0,0 +1,146 @@
+/*
+ * 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.text.ParseException;
+import java.util.List;
+import java.util.Optional;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.util.EntityStatementHelper;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver;
+import net.shibboleth.shared.annotation.constraint.NonnullAfterInit;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.component.AbstractIdentifiableInitializableComponent;
+import net.shibboleth.shared.component.ComponentInitializationException;
+import net.shibboleth.shared.logic.Constraint;
+import net.shibboleth.shared.resolver.ResolverException;
+
+/**
+ * Abstract resolver for provided trust chains to be used with messages containing request object.
+ */
+public abstract class AbstractRequestObjectProvidedTrustChainResolver
+ extends AbstractIdentifiableInitializableComponent
+ implements ProvidedTrustChainResolver {
+
+ /** JSON object mapper used for decoding entity statement payload. */
+ @NonnullAfterInit protected ObjectMapper objectMapper;
+
+ /**
+ * Set the JSON {@link ObjectMapper} used for decoding entity statement payload.
+ *
+ * @param mapper object mapper
+ */
+ 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("ObjectMapper cannot be null");
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nonnull
+ public Iterable<List<EntityStatement<?>>> resolve(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ final List<EntityStatement<?>> result = resolveSingle(profileRequestContext);
+ return result == null ? CollectionSupport.emptyList() : CollectionSupport.listOf(result);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public List<EntityStatement<?>> resolveSingle(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ checkComponentActive();
+ final SignedJWT requestObject = Optional.ofNullable(profileRequestContext)
+ .filter(prc -> prc.getInboundMessageContext() != null)
+ .map(prc -> prc.ensureInboundMessageContext())
+ .map(msgCtx -> msgCtx.getMessage())
+ .map(message -> fetchRequestObject(message))
+ .orElse(null);
+
+ if (requestObject == null) {
+ return null;
+ }
+
+ final Object rawHeader = requestObject.getHeader().getCustomParam("trust_chain");
+ if (rawHeader != null) {
+ return parseList(rawHeader);
+ }
+ final JWTClaimsSet claimsSet;
+ try {
+ claimsSet = requestObject.getJWTClaimsSet();
+ } catch (final ParseException e) {
+ throw new ResolverException("Could not parse JWT payload");
+ }
+ final Object rawPayload = claimsSet.getClaim("trust_chain");
+ if (rawPayload != null) {
+ return parseList(rawPayload);
+ }
+ return null;
+ }
+
+ /**
+ * Parses request object from the inbound message.
+ *
+ * @param message input containing request object
+ * @return request object or null if it could not be parsed
+ */
+ @Nullable protected abstract SignedJWT fetchRequestObject(@Nullable final Object message);
+
+ /**
+ * Parses the given input into a trust chain.
+ *
+ * @param input raw object
+ * @return trust chain
+ * @throws ResolverException if the non-value input was not valid
+ */
+ @Nonnull private List<EntityStatement<?>> parseList(@Nonnull final Object input)
+ throws ResolverException {
+ if (input instanceof List<?> list) {
+ final List<String> strings = list.stream()
+ .filter(String.class::isInstance)
+ .map(String.class::cast)
+ .toList();
+ if (strings.size() == list.size()) {
+ assert objectMapper != null;
+ final List<EntityStatement<?>> result =
+ EntityStatementHelper.deserializeTrustChain(strings, objectMapper);
+ if (result != null) {
+ return result;
+ }
+ throw new ResolverException("Unexpected contents in the trust_chain: invalid syntax");
+ }
+ }
+ throw new ResolverException("Unexpected contents in the trust_chain: not a list of strings");
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizationRequestObjectProvidedTrustChainResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizationRequestObjectProvidedTrustChainResolver.java
new file mode 100644
index 0000000..e3f3a50
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultAuthorizationRequestObjectProvidedTrustChainResolver.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.AuthorizationRequest;
+
+/**
+ * Default resolver for looking up the previously selected trust chain to be used in the authorization endpoint. The
+ * selected trust chain is fetched primarily via trust_chain header of the signed request object, secondarily via
+ * similarly named payload claim.
+ */
+public class DefaultAuthorizationRequestObjectProvidedTrustChainResolver extends AbstractRequestObjectProvidedTrustChainResolver {
+
+ /** {@inheritDoc} */
+ @Nullable protected SignedJWT fetchRequestObject(@Nullable final Object message) {
+ return Optional.ofNullable(message)
+ .filter(AuthorizationRequest.class::isInstance)
+ .map(AuthorizationRequest.class::cast)
+ .filter(request -> request.specifiesRequestObject())
+ .map(request -> request.getRequestObject())
+ .filter(SignedJWT.class::isInstance)
+ .map(SignedJWT.class::cast)
+ .orElse(null);
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver.java
new file mode 100644
index 0000000..10ec0e6
--- /dev/null
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
+
+import java.util.Optional;
+
+import javax.annotation.Nullable;
+
+import com.nimbusds.jwt.SignedJWT;
+import com.nimbusds.oauth2.sdk.PushedAuthorizationRequest;
+
+/**
+ * Default resolver for looking up the previously selected trust chain to be used in the pushed authorization endpoint.
+ * The selected trust chain is fetched primarily via trust_chain header of the signed request object, secondarily via
+ * similarly named payload claim.
+ */
+public class DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver
+ extends AbstractRequestObjectProvidedTrustChainResolver {
+
+ /** {@inheritDoc} */
+ @Nullable protected SignedJWT fetchRequestObject(@Nullable final Object message) {
+ return Optional.ofNullable(message)
+ .filter(PushedAuthorizationRequest.class::isInstance)
+ .map(PushedAuthorizationRequest.class::cast)
+ .map(request -> request.getAuthorizationRequest())
+ .filter(authzRequest -> authzRequest.specifiesRequestObject())
+ .map(request -> request.getRequestObject())
+ .filter(SignedJWT.class::isInstance)
+ .map(SignedJWT.class::cast)
+ .orElse(null);
+ }
+
+}
diff --git a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainResolver.java
similarity index 55%
rename from idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
rename to idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainResolver.java
index 7747d45..637f83e 100644
--- a/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainLookupFunction.java
+++ b/idp-oidfed-op-impl/src/main/java/net/shibboleth/idp/plugin/oidc/op/oidfed/profile/navigate/ExplicitClientRegistrationRequestTrustChainResolver.java
@@ -17,27 +17,39 @@ package net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate;
import java.util.List;
import java.util.Optional;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
-import org.opensaml.messaging.context.navigate.ContextDataLookupFunction;
import org.opensaml.profile.context.ProfileRequestContext;
import net.shibboleth.idp.plugin.oidc.op.oidfed.messaging.impl.ExplicitClientRegistrationRequest;
import net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.EntityStatement;
+import net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver;
+import net.shibboleth.shared.collection.CollectionSupport;
+import net.shibboleth.shared.resolver.ResolverException;
/**
- * A function that returns pushed trust chain set in the explicit registration request.
+ * A resolver that returns pushed trust chain set in the explicit registration request.
*/
@ThreadSafe
-public class ExplicitClientRegistrationRequestTrustChainLookupFunction
- implements ContextDataLookupFunction<ProfileRequestContext, List<EntityStatement<?>>> {
+public class ExplicitClientRegistrationRequestTrustChainResolver
+ implements ProvidedTrustChainResolver {
/** {@inheritDoc} */
- @Nullable
- public List<EntityStatement<?>> apply(@Nullable final ProfileRequestContext input) {
- return Optional.ofNullable(input)
- .map(profileRequestContext -> profileRequestContext.getInboundMessageContext())
+ @Override @Nonnull
+ public Iterable<List<EntityStatement<?>>> resolve(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ final List<EntityStatement<?>> result = resolveSingle(profileRequestContext);
+ return result == null ? CollectionSupport.emptyList() : CollectionSupport.listOf(result);
+ }
+
+ /** {@inheritDoc} */
+ @Override @Nullable
+ public List<EntityStatement<?>> resolveSingle(@Nullable final ProfileRequestContext profileRequestContext)
+ throws ResolverException {
+ return Optional.ofNullable(profileRequestContext)
+ .map(profileContext -> profileContext.getInboundMessageContext())
.map(messageContext -> messageContext.getMessage())
.filter(ExplicitClientRegistrationRequest.class::isInstance)
.map(ExplicitClientRegistrationRequest.class::cast)
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
index 4d7606f..01e771e 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net.shibboleth.idp/postconfig.xml
@@ -136,7 +136,12 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.entityConfiguration.claimsValidator:shibboleth.oidfed.DefaultEntityConfigurationClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ </bean>
</util:list>
</property>
</bean>
@@ -233,7 +238,12 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.subordinateStatement.claimsValidator:shibboleth.oidfed.DefaultSubordinateStatementClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ </bean>
</util:list>
</property>
</bean>
@@ -405,7 +415,11 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.remoteResolver.claimsValidator:shibboleth.oidfed.DefaultResolveEntityResponseClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ </bean>
</util:list>
</property>
</bean>
@@ -534,7 +548,11 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.trustMarkStatus.claimsValidator:shibboleth.oidfed.DefaultTrustMarkStatusClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ </bean>
</util:list>
</property>
</bean>
@@ -621,7 +639,11 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.trustMark.claimsValidator:shibboleth.oidfed.DefaultTrustMarkClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean parent="shibboleth.BiConditions.Expression" c:expression="true"/>
+ </property>
+ </bean>
</util:list>
</property>
</bean>
@@ -877,17 +899,56 @@
</property>
</bean>
+ <bean id="shibboleth.oidfed.DefaultProvidedTrustChainResolver"
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.DefaultProvidedTrustChainResolver">
+ <property name="profileIdLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.profile.logic.DefaultMetadataLookupExtensionContextProfileIDLookupFunction"/>
+ </property>
+ <property name="profileSpecificResolvers">
+ <util:map value-type="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.ProvidedTrustChainResolver">
+ <entry key="#{T(net.shibboleth.oidc.profile.config.OIDCSSOProfileConfiguration).PROFILE_ID}">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultAuthorizationRequestObjectProvidedTrustChainResolver"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </entry>
+ <entry key="#{T(net.shibboleth.oidc.profile.oauth2.config.OAuth2PushedAuthorizationRequestConfiguration).PROFILE_ID}">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultPushedAuthorizationRequestObjectProvidedTrustChainResolver"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </entry>
+ </util:map>
+ </property>
+ </bean>
+
+ <util:map id="DefaultAutoRegisteredTokenClaimsSetDecorators" value-type="java.util.List">
+ <entry key="#{null}">
+ <util:list>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainIDsClaimsSetDecorator"/>
+ </util:list>
+ </entry>
+ <entry key="store">
+ <util:list>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainIDsClaimsSetDecorator"/>
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredClientInformationStoreClaimsSetDecorator"
+ p:clientInformationManager-ref="#{'%{idp.oidfed.autoreg.clientInformationManager:shibboleth.oidc.ClientInformationManager}'.trim()}" />
+ </util:list>
+ </entry>
+ </util:map>
+
<bean id="AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAuthorizationCodeClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
<bean id="AutoRegisteredTrustChainAccessTokenClaimsSetDecorator"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAccessTokenClaimsSetDecorator" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainAccessTokenClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
<bean id="AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRefreshTokenClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
+
<bean id="AutoRegisteredTrustChainRequestUriClaimsSetDecorator"
- class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRequestUriClaimsSetDecorator" />
+ class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.AutoRegisteredTrustChainRequestUriClaimsSetDecorator"
+ p:claimsSetDecorators-ref="DefaultAutoRegisteredTokenClaimsSetDecorators"/>
<bean id="AutomaticRegistrationCondition"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultAutomaticRegistrationActivationCondition">
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
index eff94cc..1419a94 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-beans.xml
@@ -14,6 +14,75 @@
</property>
</bean>
+ <bean id="ValidateProvidedTrustChain" class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.impl.ValidateProvidedTrustChain"
+ scope="prototype"
+ p:metadataPolicyMergingStrategy-ref="#{'%{idp.oidfed.autoregister.TrustChainMetadataPolicyMergingStrategy:DefaultTrustChainMetadataPolicyMergingStrategy}'.trim()}"
+ p:metadataPolicyEnforcer-ref="#{'%{idp.oidfed.autoregister.MetadataPolicyEnforcer:DefaultMetadataPolicyEnforcer}'.trim()}"
+ p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
+ p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}"
+ p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}"
+ p:providedTrustChainResolver="#{getObject('shibboleth.oidfed.ProvidedTrustChainResolver') ?: getObject('shibboleth.oidfed.DefaultProvidedTrustChainResolver')}"
+ p:mandatoryProvidedTrustChainCondition-ref="shibboleth.Conditions.FALSE"
+ p:ignoreProvidedTrustChainCondition-ref="shibboleth.Conditions.FALSE"
+ p:validProvidedTrustChainEventId="ValidProvidedTrustChain">
+ <property name="metadataLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.DefaultCombinedMetadataFromTrustChainLookupStrategy" />
+ </property>
+ <property name="providedTrustChainValidationStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
+ p:metadataCache-ref="#{'%{idp.oidfed.autoregister.ProvidedTrustChainMetadataCache:DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+ </property>
+ </bean>
+
+ <bean id="DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <constructor-arg>
+ <bean p:cacheId="DefaultAutoRegisterFetchThroughProvidedTrustChainMetadataCache" parent="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"/>
+ </constructor-arg>
+ </bean>
+
+ <bean id="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"
+ class="net.shibboleth.oidc.metadata.cache.impl.FetchThroughMetadataCacheBuilderSpec">
+ <property name="criteriaToIdentifierStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.ProvidedTrustChainContainerCriterion))?.getValue()?.getEntityId()" />
+ </property>
+ <property name="identifierExtractionStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.getEntityId()"/>
+ </property>
+ <property name="fetchStrategy">
+ <bean parent="shibboleth.Functions.Expression" c:expression="#input?.get(T(net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.ProvidedTrustChainContainerCriterion))?.getValue()" />
+ </property>
+ <property name="metadataFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.trustchain.DefaultTrustChainSignatureValidationFilterStrategy"
+ p:customFilterStrategies="#{getObject('%{idp.oidfed.cache.providedTrustChain.customFilterStrategies:}'.trim())}">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadSignatureValidationCredentialResolver" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ <property name="entityStatementSignatureValidationFilterStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.local.DefaultEntityStatementSignatureValidationFilterStrategy">
+ <property name="trustEngine">
+ <bean class="net.shibboleth.oidc.security.impl.ExplicitKeySignedJWTTrustEngine">
+ <constructor-arg index="0">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultLocalTrustAnchorCredentialResolver"
+ c:cache-ref="shibboleth.oidfed.LocalTrustAnchorsMetadataCache" />
+ </constructor-arg>
+ <constructor-arg index="1">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.security.credential.DefaultPayloadJOSEObjectCredentialResolver" />
+ </constructor-arg>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+ </property>
+ </bean>
+
<bean id="TrustChainCandidatesExist" parent="shibboleth.Conditions.Expression"
c:expression="#input.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext)) and #input.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrustChainContext)).getPolicyCompliantTrustChains() != null and #input.ensureInboundMessageContext().ensureSubcontext(T(net.shibboleth.idp.plugin.oidc.op.oidfed.profile.context.RelyingPartyTrust [...]
@@ -135,6 +204,14 @@
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.MandatoryTrustMarksLookupFunction"
p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
</property>
+ <property name="claimsSetDecorationTypeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.ClaimsSetDecorationTypeLookupFunction"
+ p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
+ </property>
+ <property name="providedChainClaimsSetDecorationTypeLookupStrategy">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.navigate.ProvidedChainClaimsSetDecorationTypeLookupFunction"
+ p:relyingPartyContextLookupStrategy-ref="AutomaticRegistrationRelyingPartyCreationStrategy"/>
+ </property>
</bean>
</beans>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
index 44c78c8..60ecb5d 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidc/metadata-lookup-ext/oidfed/metadata-lookup-ext-oidfed-flow.xml
@@ -7,9 +7,18 @@
<set name="conversationScope.automaticallyRegistered" value="false" />
</on-entry>
<if test="AutomaticRegistrationCondition.test(opensamlProfileRequestContext) and !opensamlProfileRequestContext.ensureInboundMessageContext().containsSubcontext(T(net.shibboleth.oidc.metadata.context.OIDCMetadataContext))"
- then="ChooseResolutionMethod" else="proceed" />
+ then="CheckProvidedTrustChain" else="proceed" />
</decision-state>
+ <action-state id="CheckProvidedTrustChain">
+ <evaluate expression="ValidateProvidedTrustChain" />
+ <evaluate expression="'proceed'" />
+ <transition on="proceed" to="ChooseResolutionMethod"/>
+ <transition on="ValidProvidedTrustChain" to="CheckIfDoAutomaticRegistration">
+ <set name="flowScope.transitionForReselectTrustChain" value="'DoAutomaticRegistration'" />
+ </transition>
+ </action-state>
+
<decision-state id="ChooseResolutionMethod">
<if test="UseResolverApiCondition.test(opensamlProfileRequestContext)"
then="CallResolveEntityApi" else="ResolveTrustChains" />
@@ -61,6 +70,7 @@
<end-state id="InvalidMetadataAgainstPolicy"/>
<end-state id="NoTrustChainsResolved" />
<end-state id="InvalidTrustChainAgainstConstraints" />
+ <end-state id="InvalidProvidedTrustChain" />
<end-state id="HandleError"/>
<global-transitions>
@@ -68,6 +78,7 @@
<transition on="InvalidMetadataAgainstPolicy" to="InvalidMetadataAgainstPolicy" />
<transition on="NoTrustChainsResolved" to="NoTrustChainsResolved" />
<transition on="InvalidTrustChainAgainstConstraints" to="InvalidTrustChainAgainstConstraints" />
+ <transition on="InvalidProvidedTrustChain" to="InvalidProvidedTrustChain" />
<transition on="HandleError" to="HandleError" />
</global-transitions>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
index 7878418..79cdd84 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/flows/oidfed/register/register-beans.xml
@@ -44,19 +44,21 @@
p:arraysAsSpaceSeparatedList="%{idp.oidfed.policy.arraysAsSpaceSeparatedList:scope}"
p:metadataLookupStrategy-ref="DefaultCombinedMetadataFromTrustChainLookupStrategy"
p:metadataValidationCondition-ref="#{'%{idp.oidfed.MetadataValidationCondition:DefaultMetadataValidationCondition}'.trim()}"
- p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}">
+ p:federationPolicyConstraints-ref="%{idp.oidfed.FederationPolicyConstraints:shibboleth.oidfed.DefaultFederationPolicyConstraints}"
+ p:mandatoryProvidedTrustChainCondition-ref="shibboleth.Conditions.TRUE"
+ p:ignoreProvidedTrustChainCondition-ref="shibboleth.Conditions.FALSE">
<property name="providedTrustChainValidationStrategy">
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.logic.DefaultProvidedTrustChainValidationStrategy"
- p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedTrustChainMetadataCache:FetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
+ p:metadataCache-ref="#{'%{idp.oidfed.register.ProvidedTrustChainMetadataCache:DefaultFetchThroughProvidedTrustChainMetadataCache}'.trim()}" />
</property>
- <property name="providedTrustChainLookupStrategy">
- <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainLookupFunction" />
+ <property name="providedTrustChainResolver">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.navigate.ExplicitClientRegistrationRequestTrustChainResolver" />
</property>
</bean>
- <bean id="FetchThroughProvidedTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
+ <bean id="DefaultFetchThroughProvidedTrustChainMetadataCache" parent="shibboleth.oidc.CacheBuilder">
<constructor-arg>
- <bean p:cacheId="FetchThroughProvidedTrustChainMetadataCache" parent="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"/>
+ <bean p:cacheId="DefaultFetchThroughProvidedTrustChainMetadataCache" parent="FetchThroughProvidedTrustChainMetadataCacheBuilderSpec"/>
</constructor-arg>
</bean>
@@ -145,7 +147,12 @@
</bean>
<bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultEntityStatementContentValidationCondition"
p:claimsValidator-ref="%{idp.oidfed.cache.explicitRegistrationRequest.claimsValidator:shibboleth.oidfed.DefaultExplicitRegistrationRequestClaimsValidator}"
- p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier"/>
+ p:httpServletRequestSupplier-ref="shibboleth.HttpServletRequestSupplier">
+ <property name="headerValidator">
+ <bean class="net.shibboleth.idp.plugin.oidc.op.oidfed.metadata.cache.DefaultTrustChainHeaderValidationCondition"
+ p:objectMapper-ref="shibboleth.oidfed.JWTPayloadJSONObjectMapper" />
+ </property>
+ </bean>
</util:list>
</property>
</bean>
diff --git a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
index 70a942e..abc34ff 100644
--- a/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
+++ b/idp-oidfed-op-impl/src/main/resources/META-INF/net/shibboleth/idp/service/relying-party/postconfig.xml
@@ -20,7 +20,9 @@
<bean id="OIDFED.AutomaticRegistration" parent="AbstractOIDFederationProfile" lazy-init="true"
class="net.shibboleth.idp.plugin.oidc.op.oidfed.profile.config.impl.DefaultOIDFederationAutomaticRegistrationProfileConfiguration"
- p:mandatoryTrustMarks="%{idp.oidfed.automaticRegistration.mandatoryTrustMarks:}">
+ p:mandatoryTrustMarks="%{idp.oidfed.automaticRegistration.mandatoryTrustMarks:}"
+ p:defaultClaimsSetDecorationType="%{idp.oidfed.automaticRegistration.defaultClaimsSetDecorationType:}"
+ p:providedChainClaimsSetDecorationType="%{idp.oidfed.automaticRegistration.providedClaimsSetDecorationType:store}">
<property name="localMetadataPolicyLookupStrategy">
<bean parent="shibboleth.Functions.Constant">
<constructor-arg name="target">
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
index 14cbafc..518529b 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/oidfed/TrustChainTestUtil.java
@@ -140,12 +140,14 @@ public class TrustChainTestUtil {
public static SignedJWT signedJwt(final JWSAlgorithm algorithm, final JWK jwk, final String type,
final JWTClaimsSet claimsSet) {
- final SignedJWT signedJwt = new SignedJWT(
- new JWSHeader.Builder(algorithm)
- .type(new JOSEObjectType(type))
- .keyID(jwk.getKeyID())
- .build(),
+ return signedJwt(algorithm, jwk,
+ new JWSHeader.Builder(algorithm).type(new JOSEObjectType(type)).keyID(jwk.getKeyID()).build(),
claimsSet);
+ }
+
+ public static SignedJWT signedJwt(final JWSAlgorithm algorithm, final JWK jwk, final JWSHeader header,
+ final JWTClaimsSet claimsSet) {
+ final SignedJWT signedJwt = new SignedJWT(header, claimsSet);
try {
if (JWSAlgorithm.Family.RSA.contains(algorithm)) {
signedJwt.sign(new RSASSASigner(jwk.toRSAKey()));
@@ -158,4 +160,5 @@ public class TrustChainTestUtil {
}
return null;
}
+
}
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
index bfd6a2b..9b26b9f 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AbstractFederationFlowTest.java
@@ -171,14 +171,17 @@ public class AbstractFederationFlowTest extends AbstractOidcFlowTest {
}
protected JWT signedRequestObject(final Map<String,Object> claims, final JWK jwk) {
+ return signedRequestObject(claims, new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(jwk.getKeyID()).build(),
+ jwk);
+ }
+
+ protected JWT signedRequestObject(final Map<String,Object> claims, final JWSHeader header, final JWK jwk) {
final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
for (final String claim : claims.keySet()) {
builder.claim(claim, claims.get(claim));
}
try {
- final SignedJWT jwt =
- new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(jwk.getKeyID()).build(),
- builder.build());
+ final SignedJWT jwt = new SignedJWT(header, builder.build());
final RSASSASigner signer = new RSASSASigner(jwk.toRSAKey());
jwt.sign(signer);
return jwt;
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
index 456e24a..762f251 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/AuthorizeFlowAutomaticRegistrationTest.java
@@ -40,6 +40,9 @@ import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
@@ -459,6 +462,71 @@ public class AuthorizeFlowAutomaticRegistrationTest extends AbstractFederationFl
assertErrorCode(result, "invalid_request_object");
}
+ @Test
+ public void testWithPushedValidTrustChain_signedRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+
+ final List<String> trustChain = List.of(
+ rpEntityConfiguration(clientId),
+ subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject())),
+ trustedAnchorConfiguration());
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(rpKey.getKeyID())
+ .customParam("trust_chain", trustChain)
+ .build();
+
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "client_id", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), header, rpKey));
+ final AuthenticationResponse responseMessage = parseSuccessResponse(result, AuthenticationResponse.class);
+ final AuthenticationSuccessResponse successResponse = responseMessage.toSuccessResponse();
+ Assert.assertEquals(successResponse.getRedirectionURI().toString(), redirectUri);
+ Assert.assertNull(successResponse.getIDToken());
+ Assert.assertNull(successResponse.getAccessToken());
+ Assert.assertNotNull(successResponse.getAuthorizationCode());
+ Assert.assertEquals(unwrapTrustChainFromAuthorizeCode(successResponse), List.of(clientId, anchorId));
+ }
+
+ @Test
+ public void testWithPushedInvalidTrustChain_signedRequestObject()
+ throws IOException, UnsupportedOperationException, URISyntaxException {
+ final String clientId = uniqueClientId();
+
+ final List<String> trustChain = List.of(
+ "invalid" + rpEntityConfiguration(clientId),
+ subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject())),
+ trustedAnchorConfiguration());
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(rpKey.getKeyID())
+ .customParam("trust_chain", trustChain)
+ .build();
+
+ final FlowExecutionResult result =
+ launchAuthenticationRequest(clientId, "openid profile", signedRequestObject(Map.of(
+ "iss", clientId,
+ "client_id", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid profile",
+ "redirect_uri", redirectUri), header, rpKey));
+ Assert.assertEquals(result.getOutcome().getId(), "ErrorView");
+ }
+
+
@Test
public void testWithValidTrustChain_leafKeySignedRequestObject()
throws IOException, UnsupportedOperationException, URISyntaxException {
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
index d9b8b8f..206f314 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/PushedAuthorizeFlowAutomaticRegistrationTest.java
@@ -41,7 +41,9 @@ import org.testng.annotations.Test;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTClaimsSet;
@@ -360,6 +362,79 @@ public class PushedAuthorizeFlowAutomaticRegistrationTest extends AbstractFedera
assertErrorCode(result, OAuth2Error.UNAUTHORIZED_CLIENT_CODE);
}
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_providedTrustChain() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+
+ final List<String> trustChain = List.of(
+ rpEntityConfiguration(clientId, metadata),
+ subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject())),
+ trustedAnchorConfiguration());
+
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(rpKey.getKeyID())
+ .customParam("trust_chain", trustChain)
+ .build();
+
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri), header, rpKey).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertSuccessResponse(result, clientId);
+ final PushedAuthorizationSuccessResponse response =
+ parseSuccessResponse(result, PushedAuthorizationSuccessResponse.class);
+ verifyAuthorizeEndpoint(clientId, response.getRequestURI().toString());
+ }
+
+ @Test
+ public void testWithPublicClientWithSignedRequestObject_providedInvalidTrustChain() throws Exception {
+ final OIDCClientMetadata metadata = new OIDCClientMetadata();
+ metadata.setRedirectionURI(new URI(redirectUri));
+ metadata.setResponseTypes(Set.of(ResponseType.CODE));
+ metadata.setJWKSet(new JWKSet(rpKey.toPublicJWK()));
+ metadata.setTokenEndpointAuthMethod(ClientAuthenticationMethod.NONE);
+ final String clientId = uniqueClientId();
+
+ final List<String> trustChain = List.of(
+ "invalid" + rpEntityConfiguration(clientId, metadata),
+ subordinateStatement(clientId, Map.of("openid_relying_party",
+ new OIDCClientMetadata().toJSONObject())),
+ trustedAnchorConfiguration());
+
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(rpKey.getKeyID())
+ .customParam("trust_chain", trustChain)
+ .build();
+
+ setHttpFormRequest("POST", createRequestParameters(clientId, "openid", "code", signedRequestObject(Map.of(
+ "client_id", clientId,
+ "iss", clientId,
+ "aud", issuer,
+ "exp", Instant.now().plus(Duration.ofMinutes(5)).toEpochMilli(),
+ "jti", UUID.randomUUID(),
+ "response_type", "code",
+ "scope", "openid",
+ "redirect_uri", redirectUri), header, rpKey).serialize()));
+ final FlowExecutionResult result =
+ flowExecutor.launchExecution(PushedAuthorizeFlowTest.FLOW_ID, null, externalContext);
+ assertErrorCode(result, OAuth2Error.INVALID_REQUEST_CODE);
+ }
+
@Test
public void testWithTestVectors() throws Exception {
final List<Map<String, Object>> vectors = loadPolicyTestVectors();
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
index 3aa17fd..43c44e5 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/EntityConfigurationMetadataCacheTest.java
@@ -34,10 +34,12 @@ import org.testng.annotations.Test;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.JWKSet;
@@ -351,6 +353,48 @@ public class EntityConfigurationMetadataCacheTest extends AbstractFederationFlow
assertNoEntityConfiguration(entityId);
}
+ @Test
+ public void testValidClaims_forbiddenHeader_trustChain()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(leafKey.getKeyID())
+ .customParam("trust_chain", "forbidden")
+ .build();
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, header, builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
+ @Test
+ public void testValidClaims_forbiddenHeader_peerTrustChain()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(entityId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(leafKey.getKeyID())
+ .customParam("peer_trust_chain", "forbidden")
+ .build();
+ final String entityConfiguration = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, leafKey, header, builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(entityId), mockResponse(entityConfiguration));
+ assertNoEntityConfiguration(entityId);
+ }
+
@Test
public void testEmptyAuthorityHintsClaim()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
diff --git a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
index 0f5b20a..76a68c1 100644
--- a/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
+++ b/idp-oidfed-op-impl/src/test/java/net/shibboleth/idp/plugin/oidc/op/profile/flow/oidfed/cache/SubordinateStatementMetadataCacheTest.java
@@ -34,10 +34,12 @@ import org.testng.annotations.Test;
import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JOSEException;
+import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWEHeader;
import com.nimbusds.jose.JWEObject;
import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.Payload;
import com.nimbusds.jose.crypto.RSAEncrypter;
import com.nimbusds.jose.jwk.JWKSet;
@@ -277,6 +279,54 @@ public class SubordinateStatementMetadataCacheTest extends AbstractFederationFlo
assertNoSubordinateStatement(entityId);
}
+ @Test
+ public void testValidClaims_forbiddenHeader_trustChain()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(trustedAnchorKey.getKeyID())
+ .customParam("trust_chain", "forbidden")
+ .build();
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, header, builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+
+ assertNoSubordinateStatement(entityId);
+ }
+
+ @Test
+ public void testValidClaims_forbiddenHeader_peerTrustChain()
+ throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
+ final String entityId = uniqueClientId();
+ final JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder().issuer(anchorId).subject(entityId)
+ .issueTime(Date.from(Instant.now()))
+ .expirationTime(Date.from(Instant.now().plusSeconds(300)))
+ .claim("jwks", new JWKSet(leafKey).toJSONObject(true))
+ .claim("metadata", Map.of("federation_entity", Collections.emptyMap()));
+ final JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256)
+ .type(new JOSEObjectType("entity-statement+jwt"))
+ .keyID(trustedAnchorKey.getKeyID())
+ .customParam("peer_trust_chain", "forbidden")
+ .build();
+ final String subordinateStatement = TrustChainTestUtil.signedJwt(
+ JWSAlgorithm.RS256, trustedAnchorKey, header, builder.build()).serialize();
+
+ mapResponse(entityConfigurationUrl(anchorId), mockResponse(trustedAnchorConfiguration()));
+ mapResponse(subordinateStatementUrl(anchorFetchEndpoint, entityId),
+ mockResponse(subordinateStatement));
+
+ assertNoSubordinateStatement(entityId);
+ }
+
@Test
public void testIssuedInFuture()
throws MetadataCacheException, UnsupportedOperationException, IOException, URISyntaxException {
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list