From a369ab9a1eeb8a343d230e7435f72fb33436e988 Mon Sep 17 00:00:00 2001 From: "M. Allan Neilson" Date: Wed, 15 Jul 2026 10:58:04 -0700 Subject: [PATCH] Initial cache setup for auth information. --- cda-gui/package-lock.json | 39 ----- .../main/java/cwms/cda/data/dao/AuthDao.java | 34 ++-- .../main/java/cwms/cda/util/LruTtlMap.java | 150 ++++++++++++++++++ 3 files changed, 171 insertions(+), 52 deletions(-) create mode 100644 cwms-data-api/src/main/java/cwms/cda/util/LruTtlMap.java diff --git a/cda-gui/package-lock.json b/cda-gui/package-lock.json index cc71620c0..d33ee305e 100644 --- a/cda-gui/package-lock.json +++ b/cda-gui/package-lock.json @@ -1200,9 +1200,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1217,9 +1214,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1234,9 +1228,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1251,9 +1242,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1268,9 +1256,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1285,9 +1270,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1302,9 +1284,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1319,9 +1298,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1336,9 +1312,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1353,9 +1326,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -1370,9 +1340,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1387,9 +1354,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -1404,9 +1368,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/AuthDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/AuthDao.java index 0d3111a59..3c0bbf8c1 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/AuthDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/AuthDao.java @@ -17,6 +17,7 @@ import cwms.cda.security.DataApiPrincipal; import cwms.cda.security.MissingRolesException; import cwms.cda.security.Role; +import cwms.cda.util.LruTtlMap; import io.javalin.core.security.RouteRole; import io.javalin.http.Context; import io.javalin.http.HttpCode; @@ -112,6 +113,9 @@ public class AuthDao extends Dao { */ private static ThreadLocal instance = new ThreadLocal<>(); + // start with ten second cache + private static final LruTtlMap authCache = new LruTtlMap<>(150, 10_000); + private AuthDao(DSLContext dsl, String defaultOffice) { super(dsl); if (getDbVersion(dsl) < Dao.CWMS_23_03_16) { @@ -180,9 +184,11 @@ public Optional getByUniqueName(String uniqueName, String limi * @throws CwmsAuthException throw for any issue with verification of Key or user information. */ public DataApiPrincipal getByApiKey(String apikey) throws CwmsAuthException { - String userName = checkKey(apikey); - Set roles = getRolesForUser(userName); - return new DataApiPrincipal(userName,roles); + return authCache.computeIfAbsent(apikey, providedKey -> { + String userName = checkKey(providedKey); + Set roles = getRolesForUser(userName); + return new DataApiPrincipal(userName,roles); + }); } /** @@ -637,16 +643,18 @@ public void resetContext(DSLContext dslContext) { * @throws CwmsAuthException if anything goes wrong with the database query. */ public Optional getPrincipalFromPrincipal(String principal) throws CwmsAuthException { - String user = userForPrincipal(principal); - if (user != null) { - Set roles = this.getRolesForUser(user); - // In this case "cac_auth" just means the user is an actually user verify by some sort of - // identify management system. E.g. "not an apikey" - roles.add(new Role("cac_auth")); - return Optional.of(new DataApiPrincipal(user, roles)); - } else { - return Optional.empty(); - } + return Optional.ofNullable(authCache.computeIfAbsent(principal, newPrincipal -> { + String user = userForPrincipal(principal); + if (user != null) { + Set roles = this.getRolesForUser(user); + // In this case "cac_auth" just means the user is an actually user verify by some sort of + // identify management system. E.g. "not an apikey" + roles.add(new Role("cac_auth")); + return new DataApiPrincipal(user, roles); + } else { + return null; + } + })); } diff --git a/cwms-data-api/src/main/java/cwms/cda/util/LruTtlMap.java b/cwms-data-api/src/main/java/cwms/cda/util/LruTtlMap.java new file mode 100644 index 000000000..1502dd777 --- /dev/null +++ b/cwms-data-api/src/main/java/cwms/cda/util/LruTtlMap.java @@ -0,0 +1,150 @@ +package cwms.cda.util; + +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class LruTtlMap implements Map { + + private final long ttl; + private final int maxSize; + private final Map> map; + + public LruTtlMap(int maxSize, int ttl) + { + this.ttl = ttl; + this.maxSize = maxSize; + map = Collections.synchronizedMap( + new LinkedHashMap>(maxSize, 0.75f, true) + { + @Override + protected boolean removeEldestEntry(Map.Entry> eldest) { + return this.size() > LruTtlMap.this.maxSize; + }; + } + ); + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return false; // todo? + } + + @Override + public V get(Object key) { + var entry = map.get(key); + V ret = null; + + if (entry != null && (System.currentTimeMillis() - entry.insertTime) >= ttl) { + map.remove(key); + ret = null; + } else if (entry != null) { + ret = entry.value; + } + return ret; + } + + @Override + public V put(K key, V value) { + return value != null ? map.put(key, new TtlEntry<>(value)).value : null; + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return map.computeIfAbsent(key, newKey -> new TtlEntry(mappingFunction.apply(newKey))).value; + } + + @Override + public V remove(Object key) { + var entry = map.remove(key); + if (entry != null) { + return entry.value; + } else { + return null; + } + } + + @Override + public void putAll(Map m) { + m.forEach((k,v) -> put(k,v)); + } + + @Override + public void clear() { + map.clear();; + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection values() { + return map.values() + .stream() + .map(e -> e != null ? e.value : null) + .collect(Collectors.toSet()); + } + + @Override + public Set> entrySet() { + return map.entrySet() + .stream() + .map(es -> { + var v = es.getValue(); + return new Entry() { + + @Override + public K getKey() { + return es.getKey(); + } + + @Override + public V getValue() { + return v != null ? v.value : null; + } + + @Override + public V setValue(V value) { + throw new UnsupportedOperationException( + "method 'setValue' is not supported." + ); + } + }; + }) + .collect(Collectors.toSet()); + } + + private static class TtlEntry + { + private final long insertTime; + private final V value; + + public TtlEntry(V value) + { + this.value = value; + insertTime = System.currentTimeMillis(); + } + } + +} \ No newline at end of file