[java-oidc-common] branch main updated: Add a map based cache and associated metadata resolver

Phil Smart philip.smart at jisc.ac.uk
Thu Oct 21 14:07:30 UTC 2021


This is an automated email from the git hooks/post-receive script.

philsmart pushed a commit to branch main
in repository java-oidc-common.

View the commit online:
http://git.shibboleth.net/view/?p=java-oidc-common.git;a=commit;h=94daf4a872142fada7d1c71d41d08f782e539683

The following commit(s) were added to refs/heads/main by this push:
     new 94daf4a  Add a map based cache and associated metadata resolver
94daf4a is described below

commit 94daf4a872142fada7d1c71d41d08f782e539683
Author: Phil Smart <philip.smart at jisc.ac.uk>
AuthorDate: Thu Oct 21 15:07:27 2021 +0100

    Add a map based cache and associated metadata resolver
---
 .../oidc/metadata/MapBasedMetadataResolver.java    |  37 +++++
 ....java => MapBasedMetadataCacheFactoryBean.java} |  35 ++--
 .../metadata/cache/impl/MetadataCacheBuilder.java  |  14 ++
 .../impl/OIDCProviderMetadataCacheFactoryBean.java |  13 --
 .../impl/OIDCMapBasedMetadataResolver.java         |  48 ++++++
 .../metadata/impl/OIDCMapMetadataResolverTest.java | 177 +++++++++++++++++++++
 6 files changed, 285 insertions(+), 39 deletions(-)

diff --git a/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/MapBasedMetadataResolver.java b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/MapBasedMetadataResolver.java
new file mode 100644
index 0000000..9ff8baa
--- /dev/null
+++ b/oidc-common-metadata-api/src/main/java/net/shibboleth/oidc/metadata/MapBasedMetadataResolver.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.metadata;
+
+import java.util.Map;
+
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.utilities.java.support.component.IdentifiedComponent;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.Resolver;
+
+/**
+ * A resolver that is capable of resolving Maps which meet certain supplied criteria.
+ * <p>
+ * At a minimum, a {@link MapBasedMetadataResolver} implementation MUST support the following criteria:</p>
+ * <ul>
+ * <li>{@link IssuerIDCriterion}</li>
+ * </ul>
+ */
+public interface MapBasedMetadataResolver extends Resolver<Map<String,Object>, CriteriaSet>, IdentifiedComponent {
+
+}
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java
similarity index 70%
copy from oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
copy to oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java
index eeea722..111d3bd 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MapBasedMetadataCacheFactoryBean.java
@@ -1,27 +1,23 @@
 package net.shibboleth.oidc.metadata.cache.impl;
 
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.springframework.beans.factory.FactoryBeanNotInitializedException;
-
-import com.nimbusds.oauth2.sdk.id.Issuer;
-import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;
-
 import net.shibboleth.oidc.metadata.cache.MetadataCache;
 import net.shibboleth.oidc.metadata.impl.DefaultBatchBackingStore;
 import net.shibboleth.oidc.metadata.impl.DefaultDynamicBackingStore;
 
 /**
- * Factory bean to create an OIDC specific metadata cache. Either a {@link DynamicMetadataCache} 
+ * Factory bean to create a map based metadata cache. Either a {@link DynamicMetadataCache} 
  * or {@link BatchMetadataCache} is created depending on the cache properties configured.
  */
-public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<Issuer, OIDCProviderMetadata> {
+public class MapBasedMetadataCacheFactoryBean extends MetadataCacheBuilder<String, Map<String, Object>> {
     
     
     private final AtomicInteger cacheID;
     
     /** Constructor.*/
-    public OIDCProviderMetadataCacheFactoryBean() {
+    public MapBasedMetadataCacheFactoryBean() {
         cacheID = new AtomicInteger(1);
     }
 
@@ -31,24 +27,11 @@ public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<I
         return MetadataCache.class;
     }
     
-    /**
-     * {@inheritDoc}
-     * <p>Only supports prototype metadata instances, each resolver must have its own cache.</p>
-     */
-    @Override
-    public void afterPropertiesSet() throws Exception {        
-        if (isSingleton()) {
-            throw new 
-                FactoryBeanNotInitializedException("Only prototype metadata cache instances are supported");
-        }
-        super.afterPropertiesSet();
-    }
-
     @Override
-    protected MetadataCache<OIDCProviderMetadata> createInstance() throws Exception {
+    protected MetadataCache<Map<String, Object>> createInstance() throws Exception {
         
         if (getCacheOperationMode() == CacheOperationMode.DYNAMIC) {
-            final DynamicMetadataCache<Issuer, OIDCProviderMetadata> cache = new DynamicMetadataCache<>(
+            final DynamicMetadataCache<String, Map<String, Object>> cache = new DynamicMetadataCache<>(
                     new DefaultDynamicBackingStore<>(getMaxCacheDuration()), getFetchStrategy());
             cache.setMinCacheDuration(getMinCacheDuration());
             //FIXME what did we do with this.
@@ -63,12 +46,12 @@ public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<I
             cache.setInitialCleanupTaskDelay(getInitialCleanupTaskDelay());
             cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
             cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("OIDCProviderDynamicMetadataCache"+cacheID.getAndIncrement());
+            cache.setId("MapBasedDynamicMetadataCache"+cacheID.getAndIncrement());
             cache.initialize();
             return cache;
         } else if (getCacheOperationMode() == CacheOperationMode.BATCH) {
             //TODO why have the backing stores as interfaces if you are setting the concrete type here? inject somehow?
-            final BatchMetadataCache<Issuer, OIDCProviderMetadata> cache = 
+            final BatchMetadataCache<String, Map<String, Object>> cache = 
                     new BatchMetadataCache<>(
                     new DefaultBatchBackingStore<>(), getLoadingStrategy(), getParsingStrategy());
             cache.setMinCacheDuration(getMinCacheDuration());
@@ -81,7 +64,7 @@ public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<I
             cache.setCriteriaToIdentifierStrategy(getCriteriaToIdentifierStrategy());
             cache.setMetadataFilterStrategy(getMetadataFilterStrategy());
             cache.setMetadataBeforeRemovalHook(getMetadataBeforeRemovalHook());
-            cache.setId("OIDCProviderBatchMetadataCache"+cacheID.getAndIncrement());
+            cache.setId("MapBasedBatchMetadataCache"+cacheID.getAndIncrement());
             cache.initialize();
             return cache;
         } else {
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java
index a609f44..f0d44d7 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/MetadataCacheBuilder.java
@@ -27,6 +27,7 @@ import java.util.function.Function;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
+import org.springframework.beans.factory.FactoryBeanNotInitializedException;
 import org.springframework.beans.factory.config.AbstractFactoryBean;
 
 import net.shibboleth.oidc.metadata.cache.CacheLoadingContext;
@@ -148,6 +149,19 @@ public abstract class MetadataCacheBuilder<T,U> extends AbstractFactoryBean<Meta
         cacheOperationMode = CacheOperationMode.BATCH;
     }
     
+    /**
+     * {@inheritDoc}
+     * <p>Only supports prototype metadata instances, each resolver must have its own cache.</p>
+     */
+    @Override
+    public void afterPropertiesSet() throws Exception {        
+        if (isSingleton()) {
+            throw new 
+                FactoryBeanNotInitializedException("Only prototype metadata cache instances are supported");
+        }
+        super.afterPropertiesSet();
+    }
+    
     /**
      * Set the cache operation mode.
      * 
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
index eeea722..dedfdc6 100644
--- a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/cache/impl/OIDCProviderMetadataCacheFactoryBean.java
@@ -31,19 +31,6 @@ public class OIDCProviderMetadataCacheFactoryBean extends MetadataCacheBuilder<I
         return MetadataCache.class;
     }
     
-    /**
-     * {@inheritDoc}
-     * <p>Only supports prototype metadata instances, each resolver must have its own cache.</p>
-     */
-    @Override
-    public void afterPropertiesSet() throws Exception {        
-        if (isSingleton()) {
-            throw new 
-                FactoryBeanNotInitializedException("Only prototype metadata cache instances are supported");
-        }
-        super.afterPropertiesSet();
-    }
-
     @Override
     protected MetadataCache<OIDCProviderMetadata> createInstance() throws Exception {
         
diff --git a/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCMapBasedMetadataResolver.java b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCMapBasedMetadataResolver.java
new file mode 100644
index 0000000..af9deae
--- /dev/null
+++ b/oidc-common-metadata-impl/src/main/java/net/shibboleth/oidc/metadata/impl/OIDCMapBasedMetadataResolver.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the University Corporation for Advanced Internet Development,
+ * Inc. (UCAID) under one or more contributor license agreements.  See the
+ * NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The UCAID licenses this file to You 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.metadata.impl;
+
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.oidc.metadata.MapBasedMetadataResolver;
+import net.shibboleth.oidc.metadata.cache.MetadataCache;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+
+/** Concrete metadata resolver for OIDC Provider configuration metadata resolution.*/
+public class OIDCMapBasedMetadataResolver extends AbstractOIDCMetadataResolver<String, Map<String,Object>> 
+        implements MapBasedMetadataResolver {
+
+    /**
+     * Constructor.
+     *
+     * @param metadataCache the cache to hold metadata.
+     */
+    protected OIDCMapBasedMetadataResolver(
+            @Nonnull final MetadataCache<Map<String,Object>> metadataCache) {
+        super(metadataCache);
+        
+    }
+
+    @Override
+    protected void initMetadataResolver() throws ComponentInitializationException {
+        // Do nothing       
+    }
+
+}
diff --git a/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/impl/OIDCMapMetadataResolverTest.java b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/impl/OIDCMapMetadataResolverTest.java
new file mode 100644
index 0000000..96f877e
--- /dev/null
+++ b/oidc-common-metadata-impl/src/test/java/net/shibboleth/oidc/metadata/impl/OIDCMapMetadataResolverTest.java
@@ -0,0 +1,177 @@
+package net.shibboleth.oidc.metadata.impl;
+
+import static org.testng.Assert.assertNotNull;
+import static org.testng.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Function;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.nimbusds.oauth2.sdk.id.Issuer;
+
+import net.shibboleth.oidc.metadata.BatchBackingStore;
+import net.shibboleth.oidc.metadata.cache.CacheLoadingContext;
+import net.shibboleth.oidc.metadata.cache.impl.BatchMetadataCache;
+import net.shibboleth.oidc.metadata.cache.impl.ManuallyTriggeredScheduledExecutorService;
+import net.shibboleth.oidc.metadata.criterion.IssuerIDCriterion;
+import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
+import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
+import net.shibboleth.utilities.java.support.resolver.ResolverException;
+
+//TODO complete tests if we keep this.
+public class OIDCMapMetadataResolverTest {
+    
+    private final String GOOD_PROVIDER_CONFIGURATION_INFO = "{\n"
+            + "\"issuer\": \"https://example.oidc.op.org\",\n"
+            + "\"authorization_endpoint\": \"https://example.oidc.op.org/o/oauth2/v2/auth\",\n"
+            + "\"device_authorization_endpoint\": \"https://oauth2.googleapis.com/device/code\",\n"
+            + "\"token_endpoint\": \"https://oauth2.googleapis.com/token\",\n"
+            + "\"userinfo_endpoint\": \"https://openidconnect.googleapis.com/v1/userinfo\",\n"
+            + "\"revocation_endpoint\": \"https://oauth2.googleapis.com/revoke\",\n"
+            + "\"jwks_uri\": \"https://www.googleapis.com/oauth2/v3/certs\",\n"
+            + "\"response_types_supported\": [\n"
+            + "\"code\",\n"
+            + "\"token\",\n"
+            + "\"id_token\",\n"
+            + "\"code token\",\n"
+            + "\"code id_token\",\n"
+            + "\"token id_token\",\n"
+            + "\"code token id_token\",\n"
+            + "\"none\"\n"
+            + "],\n"
+            + "\"subject_types_supported\": [\n"
+            + "\"public\"\n"
+            + "],\n"
+            + "\"id_token_signing_alg_values_supported\": [\n"
+            + "\"RS256\"\n"
+            + "],\n"
+            + "\"scopes_supported\": [\n"
+            + "\"openid\",\n"
+            + "\"email\",\n"
+            + "\"profile\"\n"
+            + "],\n"
+            + "\"token_endpoint_auth_methods_supported\": [\n"
+            + "\"client_secret_post\",\n"
+            + "\"client_secret_basic\"\n"
+            + "],\n"
+            + "\"claims_supported\": [\n"
+            + "\"aud\",\n"
+            + "\"email\",\n"
+            + "\"email_verified\",\n"
+            + "\"exp\",\n"
+            + "\"family_name\",\n"
+            + "\"given_name\",\n"
+            + "\"iat\",\n"
+            + "\"iss\",\n"
+            + "\"locale\",\n"
+            + "\"name\",\n"
+            + "\"picture\",\n"
+            + "\"sub\"\n"
+            + "],\n"
+            + "\"code_challenge_methods_supported\": [\n"
+            + "\"plain\",\n"
+            + "\"S256\"\n"
+            + "],\n"
+            + "\"grant_types_supported\": [\n"
+            + "\"authorization_code\",\n"
+            + "\"refresh_token\",\n"
+            + "\"urn:ietf:params:oauth:grant-type:device_code\",\n"
+            + "\"urn:ietf:params:oauth:grant-type:jwt-bearer\"\n"
+            + "]\n"
+            + "}";
+    
+    
+    /** 
+     * Batch metadata cache. Cast to the testable metadata cache and not the interface 
+     * to allow access to backing store.
+     */
+    private TestableBatchMetadataCache<String, Map<String,Object>> batchCache;
+    
+    private OIDCMapBasedMetadataResolver batchResolver;
+    
+    @BeforeMethod
+    public void setup() throws Exception {
+        
+        final Function<CacheLoadingContext, byte[]> metadataLoadingStrat = 
+                c -> GOOD_PROVIDER_CONFIGURATION_INFO.getBytes();
+        
+        final Function<byte[], List<Map<String,Object>>> parsingStrat = 
+                in -> {
+                    try {
+                        ObjectMapper mapper = new ObjectMapper();
+                        return List.of(
+                                mapper.readValue(new String(in, "UTF-8"), new TypeReference<Map<String,Object>>(){}));
+                    } catch (UnsupportedEncodingException | JsonProcessingException e) {
+                        return null;
+                    }
+                };
+        
+        ManuallyTriggeredScheduledExecutorService scheduler = new ManuallyTriggeredScheduledExecutorService();
+        batchCache = new TestableBatchMetadataCache<String, Map<String,Object>>(new DefaultBatchBackingStore<>(), 
+                metadataLoadingStrat, parsingStrat, scheduler);
+        
+        batchCache.setId("MockBatchCache");
+        batchCache.setIdentifierExtractionStrategy(m -> m.get("issuer").toString());
+        batchCache.setCriteriaToIdentifierStrategy(crit -> {
+            final IssuerIDCriterion issuerId = crit.get(IssuerIDCriterion.class);
+            if (issuerId != null) {
+                return issuerId.getIssuerID().getValue();
+            }
+            return null;});
+        batchCache.setMetadataFilterStrategy((metadata, context) -> metadata);
+        batchCache.setRefreshDelayFactor(0.75f);
+        batchCache.setMinRefreshDelay(Duration.ofMillis(1000));
+        batchCache.setMaxRefreshDelay(Duration.ofMillis(1000));
+        batchCache.setMinCacheDuration(Duration.ofMinutes(10));
+        batchCache.setRefreshDelayFactor(0.75f);
+        //This needs thinking about
+        batchCache.setMetadataExpirationTimeStrategy((m, time) -> time.plus(Duration.ofMinutes(5)));
+        batchCache.initialize();
+        
+        batchResolver = new OIDCMapBasedMetadataResolver(batchCache);
+        batchResolver.setId("MockMapBatchResolver");
+        batchResolver.initialize();
+        
+    }
+    
+    @Test
+    void testBatchResolve_Success() throws ResolverException, IOException, ComponentInitializationException {
+        batchResolver.initialize();
+        Iterable<Map<String,Object>> found = 
+                batchResolver.resolve(new CriteriaSet(new IssuerIDCriterion(new Issuer("https://example.oidc.op.org"))));
+        assertNotNull(found);
+        assertTrue(found.iterator().hasNext());
+    }
+    
+    /** Extension of the {@link BatchMetadataCache} to expose certain internals.*/
+    class TestableBatchMetadataCache<IdentifierType, MetadataType> 
+                            extends BatchMetadataCache<IdentifierType, MetadataType> {
+
+        TestableBatchMetadataCache(@Nonnull final BatchBackingStore<IdentifierType, MetadataType> store,
+                @Nonnull final Function<CacheLoadingContext, byte[]> metadataLoadingStrategy,
+                @Nonnull final Function<byte[], List<MetadataType>> parseStrategy,
+                @Nullable final ScheduledExecutorService executor) {
+            super(store, metadataLoadingStrategy, parseStrategy, executor);
+        }
+        
+        /* Expose the backing store with a public method.*/
+        public BatchBackingStore<IdentifierType, MetadataType> getBackingStore(){
+            return super.getBackingStore();
+        }
+        
+    }
+
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.


More information about the commits mailing list