[java-opensaml] branch main updated: IDP-2047 - Get list of locked accounts

Scott Cantor cantor.2 at osu.edu
Thu Aug 3 18:59:58 UTC 2023


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

scantor pushed a commit to branch main
in repository java-opensaml.

View the commit online:
http://git.shibboleth.net/view/?p=java-opensaml.git;a=commit;h=262540d6d5164a42d8a9812c65ee04316fed4271

The following commit(s) were added to refs/heads/main by this push:
     new 262540d6d IDP-2047 - Get list of locked accounts
262540d6d is described below

commit 262540d6d5164a42d8a9812c65ee04316fed4271
Author: Scott Cantor <cantor.2 at osu.edu>
AuthorDate: Thu Aug 3 14:59:55 2023 -0400

    IDP-2047 - Get list of locked accounts
    
    https://shibboleth.atlassian.net/browse/IDP-2047
    
    Add EnumeratableStorageService extension to allow for context iteration.
---
 .../storage/AbstractMapBackedStorageService.java   | 64 ++++++++++++++--------
 .../storage/EnumeratableStorageService.java        | 46 ++++++++++++++++
 .../java/org/opensaml/storage/StorageRecord.java   | 31 +++++++++++
 .../storage/impl/MemoryStorageServiceTest.java     |  4 +-
 .../storage/testing/StorageServiceTest.java        | 31 ++++++++---
 5 files changed, 144 insertions(+), 32 deletions(-)

diff --git a/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractMapBackedStorageService.java b/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractMapBackedStorageService.java
index cecdae6f7..6fb304da4 100644
--- a/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractMapBackedStorageService.java
+++ b/opensaml-storage-api/src/main/java/org/opensaml/storage/AbstractMapBackedStorageService.java
@@ -17,10 +17,9 @@ package org.opensaml.storage;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
-import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -28,6 +27,7 @@ import javax.annotation.Nullable;
 import net.shibboleth.shared.annotation.constraint.Live;
 import net.shibboleth.shared.annotation.constraint.NotEmpty;
 import net.shibboleth.shared.annotation.constraint.Positive;
+import net.shibboleth.shared.collection.CollectionSupport;
 import net.shibboleth.shared.collection.Pair;
 import net.shibboleth.shared.primitive.LoggerFactory;
 
@@ -40,7 +40,8 @@ import org.slf4j.Logger;
  * <p>Abstract methods supply the map of data to manipulate and the lock to use, which allows
  * optimizations in cases where locking isn't required or data isn't shared.</p> 
  */
-public abstract class AbstractMapBackedStorageService extends AbstractStorageService {
+public abstract class AbstractMapBackedStorageService extends AbstractStorageService
+        implements EnumeratableStorageService {
 
     /** Class logger. */
     @Nonnull private final Logger log = LoggerFactory.getLogger(AbstractMapBackedStorageService.class);
@@ -79,8 +80,7 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
             final StorageRecord<?> record = dataMap.get(key);
             if (record != null) {
                 // Not yet expired?
-                final Long exp = record.getExpiration();
-                if (exp == null || System.currentTimeMillis() < exp) {
+                if (record.isValid(System.currentTimeMillis())) {
                     return false;
                 }
                 
@@ -180,10 +180,9 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
             final Map<String, MutableStorageRecord<?>> dataMap = contextMap.get(context);
             if (dataMap != null) {    
                 setDirty();
-                final Long now = System.currentTimeMillis();
+                final long now = System.currentTimeMillis();
                 for (final MutableStorageRecord<?> record : dataMap.values()) {
-                    final Long exp = record.getExpiration();
-                    if (exp == null || now < exp) {
+                    if (record.isValid(now)) {
                         record.setExpiration(expiration);
                     }
                 }
@@ -246,6 +245,38 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
             writeLock.unlock();
         }
     }
+    
+    /** {@inheritDoc} */
+    @Nonnull public Iterable<String> getContextKeys(@Nonnull @NotEmpty final String context) throws IOException {
+        final Lock readLock = getLock().readLock();
+        
+        try {
+            readLock.lock();
+            
+            final Map<String,Map<String,MutableStorageRecord<?>>> contextMap;
+            
+            try {
+                contextMap = getContextMap();
+            } catch (final Exception e) {
+                throw new IOException(e);
+            }
+            
+            final Map<String, MutableStorageRecord<?>> dataMap = contextMap.get(context);
+            if (dataMap == null) {
+                log.debug("Read failed, context '{}' not found", context);
+                return CollectionSupport.emptyList();
+            }
+            
+            final long now = System.currentTimeMillis();            
+            return dataMap.entrySet().stream()
+                    .filter(e -> e.getValue().isValid(now))
+                    .map(Map.Entry::getKey)
+                    .collect(CollectionSupport.nonnullCollector(Collectors.toUnmodifiableList())).get();
+            
+        } finally {
+            readLock.unlock();
+        }
+    }
 
     /**
      * Get the shared lock to synchronize access.
@@ -316,8 +347,7 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
                 return new Pair<>();
             }
             
-            final Long exp = record.getExpiration();
-            if (exp != null && System.currentTimeMillis() >= exp) {
+            if (record.isExpired(System.currentTimeMillis())) {
                 log.debug("Read failed, key '{}' expired in context '{}'", key, context);
                 return new Pair<>();
             }
@@ -375,8 +405,7 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
                 return null;
             }
             
-            final Long exp = record.getExpiration();
-            if (exp != null && System.currentTimeMillis() >= exp) {
+            if (record.isExpired(System.currentTimeMillis())) {
                 log.debug("Update failed, key '{}' expired in context '{}'", key, context);
                 return null;
             }
@@ -468,16 +497,7 @@ public abstract class AbstractMapBackedStorageService extends AbstractStorageSer
      * @return  true iff anything was purged
      */
     protected boolean reapWithLock(@Nonnull final Map<String, MutableStorageRecord<?>> dataMap, final long expiration) {
-        return dataMap.entrySet().removeIf(new Predicate<Entry<String, MutableStorageRecord<?>>>() {
-                public boolean test(@Nullable final Entry<String, MutableStorageRecord<?>> entry) {
-                    if (entry != null) {
-                        final Long exp = entry.getValue().getExpiration();
-                        return exp != null && exp <= expiration;
-                    }
-                    return false;
-                }
-            }
-        );
+        return dataMap.entrySet().removeIf(e -> e.getValue().isExpired(expiration));
     }
     
 }
\ No newline at end of file
diff --git a/opensaml-storage-api/src/main/java/org/opensaml/storage/EnumeratableStorageService.java b/opensaml-storage-api/src/main/java/org/opensaml/storage/EnumeratableStorageService.java
new file mode 100644
index 000000000..62fe558ed
--- /dev/null
+++ b/opensaml-storage-api/src/main/java/org/opensaml/storage/EnumeratableStorageService.java
@@ -0,0 +1,46 @@
+/*
+ * 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 org.opensaml.storage;
+
+import java.io.IOException;
+
+import javax.annotation.Nonnull;
+
+import net.shibboleth.shared.annotation.constraint.NotEmpty;
+
+/**
+ * An extended {@link StorageService} able to enumerate the keys in a context.
+ * 
+ * <p>In principle, it is theoretically possible to implement this extension in a
+ * stateful manner, thus the use of {@link Iterable}. In practice, this is exceedingly
+ * unlikely to work given the locking requirements and risks of contention this would
+ * create.</p>
+ * 
+ * @since 5.0.0
+ */
+public interface EnumeratableStorageService extends StorageService {
+
+    /**
+     * Return an iterable collection of the keys stored in a context.
+     * 
+     * @param context the context to enumerate
+     * 
+     * @return keys stored in a context
+     * 
+     * @throws IOException on error 
+     */
+    @Nonnull Iterable<String> getContextKeys(@Nonnull @NotEmpty final String context) throws IOException;
+    
+}
\ No newline at end of file
diff --git a/opensaml-storage-api/src/main/java/org/opensaml/storage/StorageRecord.java b/opensaml-storage-api/src/main/java/org/opensaml/storage/StorageRecord.java
index 35cdf2a58..2e5b6422e 100644
--- a/opensaml-storage-api/src/main/java/org/opensaml/storage/StorageRecord.java
+++ b/opensaml-storage-api/src/main/java/org/opensaml/storage/StorageRecord.java
@@ -92,6 +92,37 @@ public class StorageRecord<Type> {
     @Nullable public Long getExpiration() {
         return expiration;
     }
+    
+    /**
+     * Get whether the record is valid with respect to the supplied time.
+     * 
+     * @param evalTime time to evaluate expiration against
+     * 
+     * @return true iff the record is non-expired
+     * 
+     * @since 5.0.0
+     */
+    public boolean isValid(final long evalTime) {
+        final Long exp = expiration;
+        if (exp != null) {
+            return evalTime < exp;
+        }
+        
+        return true;
+    }
+
+    /**
+     * Get whether the record has expired with respect to the supplied time.
+     * 
+     * @param evalTime time to evaluate expiration against
+     * 
+     * @return true iff the record is expired
+     * 
+     * @since 5.0.0
+     */
+    public boolean isExpired(final long evalTime) {
+        return !isValid(evalTime);
+    }
 
     /**
      * Set the record version.
diff --git a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
index 5800e4701..1b050a5dd 100644
--- a/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
+++ b/opensaml-storage-impl/src/test/java/org/opensaml/storage/impl/MemoryStorageServiceTest.java
@@ -18,7 +18,7 @@ import java.time.Duration;
 
 import javax.annotation.Nonnull;
 
-import org.opensaml.storage.StorageService;
+import org.opensaml.storage.EnumeratableStorageService;
 import org.opensaml.storage.testing.StorageServiceTest;
 import org.testng.annotations.Test;
 
@@ -31,7 +31,7 @@ public class MemoryStorageServiceTest extends StorageServiceTest {
 
     /** {@inheritDoc} */
     @Override
-    @Nonnull protected StorageService getStorageService() {
+    @Nonnull protected EnumeratableStorageService getStorageService() {
         MemoryStorageService ss = new MemoryStorageService();
         ss.setId("test");
         ss.setCleanupInterval(Duration.ofSeconds(1));
diff --git a/opensaml-testing/src/main/java/org/opensaml/storage/testing/StorageServiceTest.java b/opensaml-testing/src/main/java/org/opensaml/storage/testing/StorageServiceTest.java
index acfe04dbc..8d5a56b16 100644
--- a/opensaml-testing/src/main/java/org/opensaml/storage/testing/StorageServiceTest.java
+++ b/opensaml-testing/src/main/java/org/opensaml/storage/testing/StorageServiceTest.java
@@ -19,9 +19,12 @@ import static org.testng.Assert.assertNotEquals;
 
 import java.io.IOException;
 import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
 
 import javax.annotation.Nonnull;
 
+import org.opensaml.storage.EnumeratableStorageService;
 import org.opensaml.storage.StorageRecord;
 import org.opensaml.storage.StorageService;
 import org.opensaml.storage.VersionMismatchException;
@@ -46,14 +49,14 @@ public abstract class StorageServiceTest {
     
     protected SecureRandom random;
     
-    protected StorageService shared;
+    protected EnumeratableStorageService shared;
 
     /**
      * Returns a fresh service instance to test.
      * 
      * @return  a new instance
      */
-    @Nonnull protected abstract StorageService getStorageService();
+    @Nonnull protected abstract EnumeratableStorageService getStorageService();
     
     /** Called to init a thread in preparation to run a test. */
     protected void threadInit() {
@@ -80,13 +83,18 @@ public abstract class StorageServiceTest {
     public void strings() throws IOException {
         threadInit();
         
-        String context = Long.toString(random.nextLong());
+        final String context = Long.toString(random.nextLong());
         
         for (int i = 1; i <= 100; i++) {
             boolean result = shared.create(context, Integer.toString(i), Integer.toString(i + 1), System.currentTimeMillis() + 300000);
             Assert.assertTrue(result);
         }
         
+        final List<String> keylist = new ArrayList<>();
+        Iterable<String> keys = shared.getContextKeys(context);
+        keys.forEach(keylist::add);
+        Assert.assertEquals(keylist.size(), 100);
+        
         for (int i = 1; i <= 100; i++) {
             StorageRecord<?> rec = shared.read(context, Integer.toString(i));
             assert rec!=null;
@@ -114,13 +122,18 @@ public abstract class StorageServiceTest {
             StorageRecord<?> rec = shared.read(context, Integer.toString(i));
             Assert.assertNull(rec);
         }
+        
+        keylist.clear();
+        keys = shared.getContextKeys(context);
+        keys.forEach(keylist::add);
+        Assert.assertEquals(keylist.size(), 0);
     }
 
     @Test
     public void expiration() throws IOException, InterruptedException {
         threadInit();
         
-        String context = Long.toString(random.nextLong());
+        final String context = Long.toString(random.nextLong());
         
         for (int i = 1; i <= 100; i++) {
             shared.create(context, Integer.toString(i), Integer.toString(i + 1), System.currentTimeMillis() + 5000);
@@ -132,14 +145,16 @@ public abstract class StorageServiceTest {
             StorageRecord<?> rec = shared.read(context, Integer.toString(i));
             Assert.assertNull(rec);
         }
+        
+        Assert.assertFalse(shared.getContextKeys(context).iterator().hasNext());
     }
     
     @Test
     public void updates() throws IOException, VersionMismatchException {
         threadInit();
         
-        String key = "key";
-        String context = Long.toString(random.nextLong());
+        final String key = "key";
+        final String context = Long.toString(random.nextLong());
         
         shared.create(context, key, "foo", null);
         
@@ -187,8 +202,8 @@ public abstract class StorageServiceTest {
     public void objects() throws IOException, InterruptedException {
         threadInit();
         
-        AnnotatedObject o1 = new AnnotatedObject();
-        AnnotatedObject o2 = new AnnotatedObject();
+        final AnnotatedObject o1 = new AnnotatedObject();
+        final AnnotatedObject o2 = new AnnotatedObject();
         
         o1.generate();
         shared.create(o1);

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


More information about the commits mailing list