[java-oidc-common] branch main updated: JCOMOIDC-161 - Facilitate extending the credential resolvers for RP/OP metadata
Codeberg
noreply at shibboleth.net
Tue Mar 31 11:20:53 UTC 2026
This is an automated email from the git hooks/post-receive script.
codeberg pushed a commit to branch main
in repository java-oidc-common.
View the commit online:
https://codeberg.org/Shibboleth/java-oidc-common/commit/a314f0f30a32488797a81e3e2697324107b2a8fc
The following commit(s) were added to refs/heads/main by this push:
new a314f0f3 JCOMOIDC-161 - Facilitate extending the credential resolvers for RP/OP metadata
a314f0f3 is described below
commit a314f0f30a32488797a81e3e2697324107b2a8fc
Author: Henri Mikkonen <henri.mikkonen at iki.fi>
AuthorDate: Tue Mar 31 14:19:54 2026 +0300
JCOMOIDC-161 - Facilitate extending the credential resolvers for RP/OP metadata
https://shibboleth.atlassian.net/browse/JCOMOIDC-161
- New JWKSetDecorator interface extends BiConsumer<List<JWK>, ProfileRequestContext> interface
- Meant for decorating keysets (first parameter) and/via profile request context (second parameter)
- Initial extensions: ClientInformationJWKSetDecorator and ProviderInformationJWKSetDecorators signalling decoration of RP/OP metadata
- All free-standing JWKSetDecorator implementation beans are auto-wired to JWKSetDecoratorManager
- JWKSetDecoratorManager.byClass(..) provides way to fetch the desired extensions
- JWKSetDecorator may be optionally linked to ClientInformationCredentialResolver and ProviderMetadataCredentialResolver
- If linked, they exploit any resolved decorators for fulfilling the keyset resolved via "standard" means
- BaseJWTSignatureSecurityHandler now includes ProfileRequestContextCriterion to the criteria set given to the resolvers
---
.../ClientInformationJWKSetDecorator.java | 27 +++++++
.../oidc/security/credential/JWKSetDecorator.java | 36 +++++++++
.../credential/JWKSetDecoratorManager.java | 74 ++++++++++++++++++
.../ProviderInformationJWKSetDecorator.java | 27 +++++++
.../impl/ClientInformationCredentialResolver.java | 50 +++++++++++-
.../impl/ProviderMetadataCredentialResolver.java | 53 ++++++++++++-
.../impl/BaseJWTSignatureSecurityHandler.java | 11 ++-
.../impl/BaseMetadataCredentialResolverTest.java | 43 ++++++++++-
.../ClientInformationCredentialResolverTest.java | 89 ++++++++++++++++++++--
.../ProviderMetadataCredentialResolverTest.java | 43 ++++++++++-
10 files changed, 433 insertions(+), 20 deletions(-)
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ClientInformationJWKSetDecorator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ClientInformationJWKSetDecorator.java
new file mode 100644
index 00000000..a9c78add
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ClientInformationJWKSetDecorator.java
@@ -0,0 +1,27 @@
+/*
+ * 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.oidc.security.credential;
+
+/**
+ * An interface for decorating key sets related to client information.
+ *
+ * The interface extends the {@code BiConsumer} by specifying the input types explicitly and is expected to operate via
+ * side-effects.
+ *
+ * @since 3.4.0
+ */
+public interface ClientInformationJWKSetDecorator extends JWKSetDecorator {
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecorator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecorator.java
new file mode 100644
index 00000000..85348573
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecorator.java
@@ -0,0 +1,36 @@
+/*
+ * 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.oidc.security.credential;
+
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import org.opensaml.profile.context.ProfileRequestContext;
+
+import com.nimbusds.jose.jwk.JWK;
+
+import net.shibboleth.shared.component.IdentifiedComponent;
+
+/**
+ * An interface for decorating key sets.
+ *
+ * The interface extends the {@code BiConsumer} by specifying the input types explicitly and is expected to operate via
+ * side-effects.
+ *
+ * @since 3.4.0
+ */
+public interface JWKSetDecorator extends BiConsumer<List<JWK>, ProfileRequestContext>, IdentifiedComponent {
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecoratorManager.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecoratorManager.java
new file mode 100644
index 00000000..9758e414
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/JWKSetDecoratorManager.java
@@ -0,0 +1,74 @@
+/*
+ * 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.oidc.security.credential;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.springframework.beans.factory.annotation.Autowired;
+
+import net.shibboleth.shared.annotation.constraint.NotLive;
+import net.shibboleth.shared.annotation.constraint.Unmodifiable;
+import net.shibboleth.shared.collection.CollectionSupport;
+
+/**
+ * Manages and exposes instanced of the {@link JWKSetDecorator} interface.
+ *
+ * @since 3.4.0
+ */
+public class JWKSetDecoratorManager {
+
+ /** Collection of decorators. */
+ @Nonnull private final Collection<JWKSetDecorator> jwkSetDecorators;
+
+ /**
+ * Constructor.
+ *
+ * @param decorators instances to manage
+ */
+ @Autowired
+ public JWKSetDecoratorManager(@Nullable final Collection<JWKSetDecorator> decorators) {
+ jwkSetDecorators =
+ decorators == null ? CollectionSupport.emptyList() : CollectionSupport.copyToList(decorators);
+ }
+
+ /**
+ * Get all of the registered decorators.
+ *
+ * @return all registered decorators
+ */
+ @Nonnull @NotLive @Unmodifiable public Collection<JWKSetDecorator> all() {
+ return CollectionSupport.copyToList(jwkSetDecorators);
+ }
+
+ /**
+ * Get a {@link JWKSetDecorator} by type.
+ *
+ * @param <T> class type
+ * @param claz class type
+ *
+ * @return decorator for the type, or null
+ */
+ @Nullable public <T extends JWKSetDecorator> List<T> byClass(@Nonnull final Class<T> claz) {
+ return jwkSetDecorators.stream()
+ .filter(claz::isInstance)
+ .map(claz::cast)
+ .toList();
+ }
+
+}
diff --git a/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ProviderInformationJWKSetDecorator.java b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ProviderInformationJWKSetDecorator.java
new file mode 100644
index 00000000..191fb475
--- /dev/null
+++ b/oidc-common-crypto-api/src/main/java/net/shibboleth/oidc/security/credential/ProviderInformationJWKSetDecorator.java
@@ -0,0 +1,27 @@
+/*
+ * 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.oidc.security.credential;
+
+/**
+ * An interface for decorating key sets related to provider information.
+ *
+ * The interface extends the {@code BiConsumer} by specifying the input types explicitly and is expected to operate via
+ * side-effects.
+ *
+ * @since 3.4.0
+ */
+public interface ProviderInformationJWKSetDecorator extends JWKSetDecorator {
+
+}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
index 8172fb25..a547ef60 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolver.java
@@ -17,23 +17,31 @@ package net.shibboleth.oidc.security.credential.impl;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
import org.slf4j.Logger;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
import net.shibboleth.oidc.security.credential.BasicJWKCredential;
+import net.shibboleth.oidc.security.credential.ClientInformationJWKSetDecorator;
import net.shibboleth.oidc.security.credential.JOSEObjectCredentialResolver;
+import net.shibboleth.oidc.security.credential.JWKSetDecoratorManager;
import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.annotation.constraint.Positive;
@@ -65,6 +73,10 @@ public class ClientInformationCredentialResolver extends AbstractClientInformati
@Nonnull @Positive
private final Duration keyFetchInterval;
+ /** The manager for JWKSet decorators. */
+ @Nullable
+ private final JWKSetDecoratorManager jwkSetDecoratorManager;
+
/**
* Constructor.
*
@@ -84,12 +96,27 @@ public class ClientInformationCredentialResolver extends AbstractClientInformati
public ClientInformationCredentialResolver(
@Nonnull @ParameterName(name="remoteJwkSetCache") final RemoteJwkSetCache jwkSetCache,
@Nonnull @Positive @ParameterName(name="keyFetchInterval") final Duration interval) {
+ this(jwkSetCache, interval, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param jwkSetCache The cache for remote JWK key sets.
+ * @param interval The remote key refresh interval
+ * @param manager The manager for JWKSet decorators
+ */
+ public ClientInformationCredentialResolver(
+ @Nonnull @ParameterName(name="remoteJwkSetCache") final RemoteJwkSetCache jwkSetCache,
+ @Nonnull @Positive @ParameterName(name="keyFetchInterval") final Duration interval,
+ @Nullable @ParameterName(name="jwkSetDecoratorManager") final JWKSetDecoratorManager manager) {
remoteJwkSetCache = Constraint.isNotNull(jwkSetCache, "The remote JWK set cache cannot be null");
Constraint.isFalse(interval == null || interval.isNegative(), "Remote key refresh must be greater than 0");
keyFetchInterval = interval;
+ jwkSetDecoratorManager = manager;
}
-
+
/** {@inheritDoc} */
@Override
@Nonnull protected Iterable<Credential> resolveFromSource(@Nullable final CriteriaSet criteriaSet)
@@ -150,10 +177,27 @@ public class ClientInformationCredentialResolver extends AbstractClientInformati
} else if (metadata.getJWKSet() != null) {
keySet = metadata.getJWKSet();
} else {
- return credentials;
+ keySet = new JWKSet();
}
assert keySet != null;
- populateCredentialsFromKeySet(keySet, credentials);
+ final List<JWK> keys = new ArrayList<>(keySet.getKeys());
+ final List<ClientInformationJWKSetDecorator> keySetDecorators = Optional.ofNullable(jwkSetDecoratorManager)
+ .map(manager -> manager.byClass(ClientInformationJWKSetDecorator.class))
+ .orElse(null);
+ if (keySetDecorators != null && !keySetDecorators.isEmpty()) {
+ final ProfileRequestContext profileRequestContext =
+ Optional.ofNullable(criteriaSet.get(ProfileRequestContextCriterion.class))
+ .map(criterion -> criterion.getProfileRequestContext())
+ .orElse(null);
+ for (final ClientInformationJWKSetDecorator decorator : keySetDecorators) {
+ log.trace("Calling {} to decorate keyset {}", decorator.getId(), keys);
+ decorator.accept(keys, profileRequestContext);
+ log.trace("Keyset after decorating with {}: {}", decorator.getId(), keys);
+ }
+ } else {
+ log.trace("No keyset decorators resolved");
+ }
+ populateCredentialsFromKeySet(new JWKSet(keys), credentials);
return credentials;
}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolver.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolver.java
index 5c38b430..26cffd42 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolver.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolver.java
@@ -17,20 +17,28 @@ package net.shibboleth.oidc.security.credential.impl;
import java.net.URI;
import java.time.Duration;
import java.time.Instant;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import org.opensaml.profile.context.ProfileRequestContext;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.impl.AbstractCriteriaFilteringCredentialResolver;
import org.slf4j.Logger;
+import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.JWKSetDecoratorManager;
+import net.shibboleth.oidc.security.credential.ProviderInformationJWKSetDecorator;
import net.shibboleth.oidc.security.jose.criterion.ProviderMetadataCriterion;
import net.shibboleth.shared.annotation.ParameterName;
import net.shibboleth.shared.annotation.constraint.Positive;
@@ -66,6 +74,10 @@ public class ProviderMetadataCredentialResolver extends BasicJOSEObjectCredentia
/** The remote key refresh interval. Default value: 30 minutes. */
@Positive private final Duration keyFetchInterval;
+ /** The manager for JWKSet decorators. */
+ @Nullable
+ private final JWKSetDecoratorManager jwkSetDecoratorManager;
+
/**
* Constructor.
*
@@ -85,10 +97,24 @@ public class ProviderMetadataCredentialResolver extends BasicJOSEObjectCredentia
public ProviderMetadataCredentialResolver(
@Nonnull @ParameterName(name="remoteJwkSetCache") final RemoteJwkSetCache jwkSetCache,
@Nonnull @Positive @ParameterName(name="keyFetchInterval") final Duration interval) {
+ this(jwkSetCache, interval, null);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param jwkSetCache The cache for remote JWK key sets.
+ * @param interval The remote key refresh interval
+ */
+ public ProviderMetadataCredentialResolver(
+ @Nonnull @ParameterName(name="remoteJwkSetCache") final RemoteJwkSetCache jwkSetCache,
+ @Nonnull @Positive @ParameterName(name="keyFetchInterval") final Duration interval,
+ @Nullable @ParameterName(name="jwkSetDecoratorManager") final JWKSetDecoratorManager manager) {
remoteJwkSetCache = Constraint.isNotNull(jwkSetCache, "The remote JWK set cache cannot be null");
Constraint.isFalse(interval == null || interval.isNegative(), "Remote key refresh must be greater than 0");
keyFetchInterval = interval;
+ jwkSetDecoratorManager = manager;
}
/** {@inheritDoc} */
@@ -138,6 +164,7 @@ public class ProviderMetadataCredentialResolver extends BasicJOSEObjectCredentia
final LinkedHashSet<Credential> credentials = new LinkedHashSet<>(1);
final URI jwkSetUri = metadata.getJWKSetURI();
+ final JWKSet keySet;
if (jwkSetUri != null) {
final var now = Instant.now();
@@ -147,7 +174,6 @@ public class ProviderMetadataCredentialResolver extends BasicJOSEObjectCredentia
final String keyIdFromCriteria = extractKeyIdFromCriteria(criteriaSet);
- JWKSet keySet = null;
if (StringSupport.trimOrNull(keyIdFromCriteria) != null) {
assert keyIdFromCriteria != null;
keySet = remoteJwkSetCache.fetch(jwkSetUri, keyIdFromCriteria, nowPlusInterval);
@@ -159,11 +185,30 @@ public class ProviderMetadataCredentialResolver extends BasicJOSEObjectCredentia
log.debug("Remote keys could not be fetched, unable to resolve credentials");
return CollectionSupport.emptyList();
}
-
- populateCredentialsFromKeySet(keySet, credentials);
} else {
- log.trace("No JWK Set available, no credentials returned");
+ keySet = new JWKSet();
+ log.trace("No JWK Set available, no credentials resolved before decorators");
+ }
+
+ final List<JWK> keys = new ArrayList<>(keySet.getKeys());
+ final List<ProviderInformationJWKSetDecorator> keySetDecorators = Optional.ofNullable(jwkSetDecoratorManager)
+ .map(manager -> manager.byClass(ProviderInformationJWKSetDecorator.class))
+ .orElse(null);
+ if (keySetDecorators != null && !keySetDecorators.isEmpty()) {
+ final ProfileRequestContext profileRequestContext =
+ Optional.ofNullable(criteriaSet.get(ProfileRequestContextCriterion.class))
+ .map(criterion -> criterion.getProfileRequestContext())
+ .orElse(null);
+ for (final ProviderInformationJWKSetDecorator decorator : keySetDecorators) {
+ log.trace("Calling {} to decorate keyset {}", decorator.getId(), keys);
+ decorator.accept(keys, profileRequestContext);
+ log.trace("Keyset after decorating with {}: {}", decorator.getId(), keys);
+ }
+ } else {
+ log.trace("No keyset decorators resolved");
}
+ populateCredentialsFromKeySet(new JWKSet(keys), credentials);
+
return credentials;
}
diff --git a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java
index 33b60b67..ea63e5c2 100644
--- a/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java
+++ b/oidc-common-crypto-impl/src/main/java/net/shibboleth/oidc/security/impl/BaseJWTSignatureSecurityHandler.java
@@ -24,6 +24,7 @@ import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.messaging.handler.MessageHandlerException;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.ParentProfileRequestContextLookup;
+import org.opensaml.profile.criterion.ProfileRequestContextCriterion;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.UsageCriterion;
import org.opensaml.security.messaging.impl.BaseTrustEngineSecurityHandler;
@@ -62,6 +63,7 @@ import net.shibboleth.shared.resolver.CriteriaSet;
* <li>(Optional) Client information.</li>
* <li>(Optional) Client secret credentials.</li>
* <li>(Required) The SecurityParametersContext.</li>
+ * <li>(Optional) Profile Request Context (since 3.4.0).</li>
* </ol>
*
* @since 2.2.0
@@ -238,7 +240,7 @@ public abstract class BaseJWTSignatureSecurityHandler extends BaseTrustEngineSec
log.trace("{} No client_secret credential found from the profile configuration", getLogPrefix());
}
}
-
+
criteriaSet.add( new UsageCriterion(UsageType.SIGNING));
final SecurityParametersContext secParams = securityParametersContextLookupStrategy.apply(messageContext);
@@ -250,7 +252,12 @@ public abstract class BaseJWTSignatureSecurityHandler extends BaseTrustEngineSec
if (valParams != null) {
criteriaSet.add(new SignatureValidationParametersCriterion(valParams));
}
-
+
+ final ProfileRequestContext profileRequestContext = PRC_LOOKUP.apply(messageContext);
+ if (profileRequestContext != null) {
+ criteriaSet.add(new ProfileRequestContextCriterion(profileRequestContext));
+ }
+
return criteriaSet;
}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/BaseMetadataCredentialResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/BaseMetadataCredentialResolverTest.java
index cca1c0b0..dd306746 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/BaseMetadataCredentialResolverTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/BaseMetadataCredentialResolverTest.java
@@ -14,6 +14,7 @@
package net.shibboleth.oidc.security.credential.impl;
+import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;
@@ -50,6 +51,7 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.JWKSetDecoratorManager;
import net.shibboleth.oidc.security.jose.criterion.KeyIdCriterion;
import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.component.InitializableComponent;
@@ -87,7 +89,9 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
/** The cache being used by the resolver.*/
protected RemoteJwkSetCache cache;
-
+
+ protected JWKSetDecoratorManager decoratorManager = mock(JWKSetDecoratorManager.class);
+
protected HttpClient createMockHttpClient(final String output) throws ClientProtocolException, IOException {
final HttpClient httpClient = Mockito.mock(HttpClient.class);
final ClassicHttpResponse httpResponse = Mockito.mock(ClassicHttpResponse.class);
@@ -121,6 +125,13 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
*/
protected abstract T constructResolver(final RemoteJwkSetCache cache);
+ /**
+ * Construct the resolver to be tested.
+ * @param manager the decorator manager being used by the resolver
+ * @return the resolver to be tested with the given cache configured
+ */
+ protected abstract T decoratingResolver(final JWKSetDecoratorManager manager);
+
/**
* Build the criteria to be used by default in the test cases.
* @return the criteria set
@@ -170,7 +181,13 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
creds.forEach(credsList::add);
// No keys
assertEquals(credsList.size(), 0);
- }
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 0);
+}
@Test
public void testSuccess_NoKeys() throws Exception {
@@ -186,6 +203,12 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
creds.forEach(credsList::add);
// No keys
assertEquals(credsList.size(), 0);
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 0);
}
/* Test refetching the keyset if the keyset is cached but the key ID did not exist.*/
@@ -225,7 +248,13 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
creds.forEach(credsList::add);
// There is one 'enc' RSA-OAEP in the keyset.
assertEquals(credsList.size(), 1);
- }
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 1);
+}
@Test
public void testSuccess_ForSigning() throws Exception {
@@ -240,7 +269,13 @@ public abstract class BaseMetadataCredentialResolverTest<T extends BasicJOSEObje
creds.forEach(credsList::add);
// There are 5 'sig' keys in the keyset, which should match the usage criterion
assertEquals(credsList.size(), 5);
- }
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 6);
+}
/**
* Read a file into a string.
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolverTest.java
index 52993adb..cbf357ae 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolverTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ClientInformationCredentialResolverTest.java
@@ -14,15 +14,21 @@
package net.shibboleth.oidc.security.credential.impl;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+import java.text.ParseException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import org.mockito.Mockito;
+import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.UsageType;
import org.opensaml.security.criteria.KeyAlgorithmCriterion;
@@ -30,13 +36,18 @@ import org.opensaml.security.criteria.UsageCriterion;
import org.springframework.core.io.ClassPathResource;
import org.testng.annotations.Test;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.ClientInformationJWKSetDecorator;
+import net.shibboleth.oidc.security.credential.JWKSetDecoratorManager;
import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
import net.shibboleth.oidc.security.jose.criterion.ProviderMetadataCriterion;
+import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.component.InitializableComponent;
import net.shibboleth.shared.resolver.CriteriaSet;
@@ -61,6 +72,34 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
return resolver;
}
+ protected ClientInformationCredentialResolver decoratingResolver(final JWKSetDecoratorManager decoratorManager) {
+ when(decoratorManager.byClass(eq(ClientInformationJWKSetDecorator.class))).thenReturn(List.of(
+ new ClientInformationJWKSetDecorator() {
+
+ @Override
+ public void accept(final List<JWK> jwks, final ProfileRequestContext prc) {
+ try {
+ jwks.add(JWKSet.parse(readJsonFromFile(REMOTE_JWKSET_ROLLOVER)).getKeys().get(0));
+ } catch (ParseException e) {
+ fail(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getId() {
+ return "decoratedResolver";
+ }
+
+ }));
+ resolver = new ClientInformationCredentialResolver(cache, Duration.ofMinutes(30), decoratorManager);
+ try {
+ resolver.initialize();
+ } catch (ComponentInitializationException e) {
+ fail(e.getMessage(), e);
+ }
+ return resolver;
+ }
+
@Override
protected CriteriaSet buildInitialCriteriaSet() throws Exception {
criteria = new CriteriaSet();
@@ -103,6 +142,11 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
// No keys
assertEquals(credsList.size(), 1);
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 2);
}
@Test
@@ -125,6 +169,12 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
// No keys nor secret
assertEquals(credsList.size(), 0);
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ // still 0 as decorated key usage is 'sig'
+ assertEquals(decoratedCredsList.size(), 0);
}
@Test
@@ -139,8 +189,13 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
assertNotNull(creds);
final List<Credential> credsList = new ArrayList<>();
creds.forEach(credsList::add);
- // No keys
assertEquals(credsList.size(), 2);
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 3);
}
@Test
@@ -156,8 +211,13 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
assertNotNull(creds);
final List<Credential> credsList = new ArrayList<>();
creds.forEach(credsList::add);
- // No keys
assertEquals(credsList.size(), 2);
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 3);
}
@Test
@@ -173,9 +233,15 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
assertNotNull(creds);
final List<Credential> credsList = new ArrayList<>();
creds.forEach(credsList::add);
- // No keys
assertEquals(credsList.size(), 0);
- }
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ // still 0 as decorated key is RSA
+ assertEquals(decoratedCredsList.size(), 0);
+}
@Test
public void testSuccess_InlineJwks_ForEncryptionAndKeyAlg() throws Exception {
@@ -193,7 +259,14 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
final List<Credential> credsList = new ArrayList<>();
creds.forEach(credsList::add);
assertEquals(credsList.size(), 1);
- }
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ // still 1 as decorated key is for signing
+ assertEquals(decoratedCredsList.size(), 1);
+}
@Test
public void testSuccess_InlineJwks_ForSigning() throws Exception {
@@ -210,6 +283,12 @@ public class ClientInformationCredentialResolverTest extends BaseMetadataCredent
final List<Credential> credsList = new ArrayList<>();
creds.forEach(credsList::add);
assertEquals(credsList.size(), 1);
+
+ final Iterable<Credential> decoratedCreds = decoratingResolver(decoratorManager).resolve(criteria);
+ assertNotNull(decoratedCreds);
+ final List<Credential> decoratedCredsList = new ArrayList<>();
+ decoratedCreds.forEach(decoratedCredsList::add);
+ assertEquals(decoratedCredsList.size(), 2);
}
}
diff --git a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolverTest.java b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolverTest.java
index cfac8083..ad15b047 100644
--- a/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolverTest.java
+++ b/oidc-common-crypto-impl/src/test/java/net/shibboleth/oidc/security/credential/impl/ProviderMetadataCredentialResolverTest.java
@@ -14,28 +14,41 @@
package net.shibboleth.oidc.security.credential.impl;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.fail;
+import java.text.ParseException;
+import java.time.Duration;
+import java.util.List;
+
+import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.security.credential.Credential;
import org.springframework.core.io.ClassPathResource;
import org.testng.annotations.Test;
+import com.nimbusds.jose.jwk.JWK;
+import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.oauth2.sdk.id.ClientID;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientInformation;
import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata;
import net.shibboleth.oidc.jwk.RemoteJwkSetCache;
+import net.shibboleth.oidc.security.credential.JWKSetDecoratorManager;
+import net.shibboleth.oidc.security.credential.ProviderInformationJWKSetDecorator;
import net.shibboleth.oidc.security.jose.criterion.ClientInformationCriterion;
import net.shibboleth.oidc.security.jose.criterion.ProviderMetadataCriterion;
+import net.shibboleth.shared.component.ComponentInitializationException;
import net.shibboleth.shared.component.InitializableComponent;
import net.shibboleth.shared.resolver.CriteriaSet;
/**
* Unit tests for {@link ProviderMetadataCredentialResolver}.
*/
- at SuppressWarnings("javadoc")
+ at SuppressWarnings(value={"javadoc","null"})
public class ProviderMetadataCredentialResolverTest extends BaseMetadataCredentialResolverTest<ProviderMetadataCredentialResolver> {
/**
@@ -50,7 +63,33 @@ public class ProviderMetadataCredentialResolverTest extends BaseMetadataCredenti
return resolver;
}
- @SuppressWarnings("null")
+ protected ProviderMetadataCredentialResolver decoratingResolver(final JWKSetDecoratorManager decoratorManager) {
+ when(decoratorManager.byClass(eq(ProviderInformationJWKSetDecorator.class))).thenReturn(List.of(
+ new ProviderInformationJWKSetDecorator() {
+
+ @Override
+ public void accept(final List<JWK> jwks, final ProfileRequestContext prc) {
+ try {
+ jwks.add(JWKSet.parse(readJsonFromFile(REMOTE_JWKSET_ROLLOVER)).getKeys().get(0));
+ } catch (ParseException e) {
+ fail(e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getId() {
+ return "decoratedResolver";
+ }
+ }));
+ resolver = new ProviderMetadataCredentialResolver(cache, Duration.ofMinutes(30), decoratorManager);
+ try {
+ resolver.initialize();
+ } catch (ComponentInitializationException e) {
+ fail(e.getMessage(), e);
+ }
+ return resolver;
+ }
+
@Override
protected CriteriaSet buildInitialCriteriaSet() throws Exception {
criteria = new CriteriaSet();
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.
More information about the commits
mailing list