[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