[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