This is an automated email from the ASF dual-hosted git repository.

jbonofre pushed a commit to branch karaf-4.4.x
in repository https://gitbox.apache.org/repos/asf/karaf.git


The following commit(s) were added to refs/heads/karaf-4.4.x by this push:
     new d2983139fd [KARAF-7671] Fix LDAP cache handling so it does not reset 
on every login attempt (#2071)
d2983139fd is described below

commit d2983139fd9868e25fb210a18a2bfb81c6370b39
Author: JB Onofré <[email protected]>
AuthorDate: Sun Oct 5 08:54:44 2025 +0200

    [KARAF-7671] Fix LDAP cache handling so it does not reset on every login 
attempt (#2071)
    
    - Add metrics to the LDAPCache object
     - Fix unit test to _not_ disable cache
     - Update unit test to verify metrics counts and caching are working
    
    Co-authored-by: Matt Pavlovich <[email protected]>
---
 .../apache/karaf/jaas/modules/impl/Activator.java  |  1 +
 .../karaf/jaas/modules/ldap/LDAPBackingEngine.java |  2 -
 .../modules/ldap/LDAPBackingEngineFactory.java     |  1 -
 .../apache/karaf/jaas/modules/ldap/LDAPCache.java  | 86 ++++++++++++++++++++++
 .../karaf/jaas/modules/ldap/LDAPLoginModule.java   |  4 +-
 .../jaas/modules/ldap/LDAPPubkeyLoginModule.java   |  1 -
 .../karaf/jaas/modules/ldap/LdapCacheTest.java     | 62 ++++++++++++----
 .../apache/karaf/jaas/modules/ldap/ldap.properties |  1 +
 .../karaf/jaas/modules/ldap/ldap_badref.properties |  1 +
 9 files changed, 139 insertions(+), 20 deletions(-)

diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/impl/Activator.java 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/impl/Activator.java
index b91f5f6fc0..9abc16dcd4 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/impl/Activator.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/impl/Activator.java
@@ -56,6 +56,7 @@ public class Activator extends BaseActivator implements 
ManagedService {
     @Override
     protected void doOpen() throws Exception {
         super.doOpen();
+        LDAPCache.clear();
         register(BackingEngineFactory.class, new 
PropertiesBackingEngineFactory());
         register(BackingEngineFactory.class, new 
PublickeyBackingEngineFactory());
 
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngine.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngine.java
index 90daf5dc2c..1039a60ee8 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngine.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngine.java
@@ -18,8 +18,6 @@ import org.apache.karaf.jaas.boot.principal.GroupPrincipal;
 import org.apache.karaf.jaas.boot.principal.RolePrincipal;
 import org.apache.karaf.jaas.boot.principal.UserPrincipal;
 import org.apache.karaf.jaas.modules.BackingEngine;
-import org.apache.karaf.jaas.modules.ldap.LDAPCache;
-import org.apache.karaf.jaas.modules.ldap.LDAPOptions;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngineFactory.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngineFactory.java
index 16d1a78025..35768c263c 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngineFactory.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPBackingEngineFactory.java
@@ -16,7 +16,6 @@ package org.apache.karaf.jaas.modules.ldap;
 
 import org.apache.karaf.jaas.modules.BackingEngine;
 import org.apache.karaf.jaas.modules.BackingEngineFactory;
-import org.apache.karaf.jaas.modules.ldap.LDAPLoginModule;
 import java.util.Map;
 
 /**
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
index 07efe25587..20759e8ee5 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPCache.java
@@ -30,6 +30,7 @@ import javax.naming.event.NamingExceptionEvent;
 import javax.naming.event.ObjectChangeListener;
 import java.io.Closeable;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -37,6 +38,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -45,6 +47,7 @@ import org.slf4j.LoggerFactory;
 
 public class LDAPCache implements Closeable, NamespaceChangeListener, 
ObjectChangeListener {
 
+       private static final AtomicLong idGenerator = new AtomicLong(0l);
     private static final ConcurrentMap<LDAPOptions, LDAPCache> CACHES = new 
ConcurrentHashMap<>();
 
     private static Logger LOGGER = 
LoggerFactory.getLogger(LDAPLoginModule.class);
@@ -68,14 +71,43 @@ public class LDAPCache implements Closeable, 
NamespaceChangeListener, ObjectChan
         return cache;
     }
 
+    public static LDAPCache removeCache(long id) {
+
+        LDAPOptions match = null;
+        for(Map.Entry<LDAPOptions, LDAPCache> entry : CACHES.entrySet()) {
+            if(entry.getValue().getId() == id) {
+                match = entry.getKey();
+            }
+        }
+
+        if(match != null) {
+            return CACHES.remove(match);
+        }
+
+        return null;
+    }
+
+    public static Map<LDAPOptions, LDAPCache> getCacheCopy() {
+        return Collections.unmodifiableMap(CACHES);
+    }
+
+    private final long id;
     private final Map<String, String[]> userDnAndNamespace;
     private final Map<String, String[]> userRoles;
     private final Map<String, String[]> userPubkeys;
     private final LDAPOptions options;
     private DirContext context;
+    private final AtomicLong userDNCacheHitCount = new AtomicLong(0l);
+    private final AtomicLong userDNCacheMissCount = new AtomicLong(0l);
+    private final AtomicLong userRolesCacheHitCount = new AtomicLong(0l);
+    private final AtomicLong userRolesCacheMissCount = new AtomicLong(0l);
+    private final AtomicLong userPubkeysCacheHitCount = new AtomicLong(0l);
+    private final AtomicLong userPubkeysCacheMissCount = new AtomicLong(0l);
+    private final AtomicLong clearCacheCount = new AtomicLong(0l);
 
     public LDAPCache(LDAPOptions options) {
         this.options = options;
+        this.id = idGenerator.getAndIncrement();
         userDnAndNamespace = new HashMap<>();
         userRoles = new HashMap<>();
         userPubkeys = new HashMap<>();
@@ -142,10 +174,13 @@ public class LDAPCache implements Closeable, 
NamespaceChangeListener, ObjectChan
     public synchronized String[] getUserDnAndNamespace(String user) throws 
Exception {
         String[] result = userDnAndNamespace.get(user);
         if (result == null) {
+            this.userDNCacheMissCount.incrementAndGet();
             result = doGetUserDnAndNamespace(user);
             if (result != null && !options.getDisableCache()) {
                 userDnAndNamespace.put(user, result);
             }
+        } else {
+            this.userDNCacheHitCount.incrementAndGet();
         }
         return result;
     }
@@ -207,10 +242,13 @@ public class LDAPCache implements Closeable, 
NamespaceChangeListener, ObjectChan
     public synchronized String[] getUserRoles(String user, String userDn, 
String userDnNamespace) throws Exception {
         String[] result = userRoles.get(userDn);
         if (result == null) {
+            this.userRolesCacheMissCount.incrementAndGet();
             result = doGetUserRoles(user, userDn, userDnNamespace);
             if (!options.getDisableCache()) {
                 userRoles.put(userDn, result);
             }
+        } else {
+            this.userRolesCacheHitCount.incrementAndGet();
         }
         return result;
     }
@@ -218,10 +256,13 @@ public class LDAPCache implements Closeable, 
NamespaceChangeListener, ObjectChan
     public synchronized String[] getUserPubkeys(String userDn) throws 
NamingException {
         String[] result = userPubkeys.get(userDn);
         if (result == null) {
+            this.userPubkeysCacheMissCount.incrementAndGet();
             result = doGetUserPubkeys(userDn);
             if (!options.getDisableCache()) {
                 userPubkeys.put(userDn, result);
             }
+        } else {
+            this.userPubkeysCacheHitCount.incrementAndGet();
         }
         return result;
     }
@@ -366,8 +407,53 @@ public class LDAPCache implements Closeable, 
NamespaceChangeListener, ObjectChan
     }
 
     protected synchronized void clearCache() {
+        this.clearCacheCount.incrementAndGet();
         userDnAndNamespace.clear();
         userRoles.clear();
         userPubkeys.clear();
     }
+
+    public Map<String, String[]> listCachedUserDNAndNamespace() {
+        return Collections.unmodifiableMap(userDnAndNamespace);
+    }
+
+    public Map<String, String[]> listCachedUserPubkeys() {
+        return Collections.unmodifiableMap(userPubkeys);
+    }
+
+    public Map<String, String[]> listCachedUserRoles() {
+        return Collections.unmodifiableMap(userRoles);
+    }
+
+    public long getUserDNCacheHitCount() {
+        return this.userDNCacheHitCount.get();
+    }
+
+    public long getUserDNCacheMissCount() {
+        return this.userDNCacheMissCount.get();
+    }
+
+    public long getUserRolesCacheHitCount() {
+        return this.userRolesCacheHitCount.get();
+    }
+
+    public long getUserRolesCacheMissCount() {
+        return this.userRolesCacheMissCount.get();
+    }
+
+    public long getUserPubkeysCacheHitCount() {
+        return this.userPubkeysCacheHitCount.get();
+    }
+
+    public long getUserPubkeysCacheMissCount() {
+        return this.userPubkeysCacheMissCount.get();
+    }
+
+    public long getClearCacheCount() {
+        return this.clearCacheCount.get();
+    }
+
+    public long getId() {
+        return this.id;
+    }
 }
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
index 770e15e1a5..e64e36e3f9 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPLoginModule.java
@@ -42,11 +42,9 @@ import org.slf4j.LoggerFactory;
 public class LDAPLoginModule extends AbstractKarafLoginModule {
 
     private static Logger logger = 
LoggerFactory.getLogger(LDAPLoginModule.class);
-    
-        
+
     public void initialize(Subject subject, CallbackHandler callbackHandler, 
Map<String, ?> sharedState, Map<String, ?> options) {
         super.initialize(subject, callbackHandler, options);
-        LDAPCache.clear();
     }
 
     public boolean login() throws LoginException {
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
index b3772bfbd7..308763d8bc 100644
--- 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/ldap/LDAPPubkeyLoginModule.java
@@ -44,7 +44,6 @@ public class LDAPPubkeyLoginModule extends 
AbstractKarafLoginModule {
 
     public void initialize(Subject subject, CallbackHandler callbackHandler, 
Map<String, ?> sharedState, Map<String, ?> options) {
         super.initialize(subject, callbackHandler, options);
-        LDAPCache.clear();
     }
 
     public boolean login() throws LoginException {
diff --git 
a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapCacheTest.java
 
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapCacheTest.java
index b0185456d4..1568ac532d 100644
--- 
a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapCacheTest.java
+++ 
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/ldap/LdapCacheTest.java
@@ -73,6 +73,8 @@ public class LdapCacheTest extends AbstractLdapTestUnit {
     @Test
     public void testAdminLogin() throws Exception {
         Properties options = ldapLoginModuleOptions();
+        validateMetrics(LDAPCache.getCache(new LDAPOptions(options)), 0, 0, 0, 
0, 0, 0, 0);
+
         LDAPLoginModule module = new LDAPLoginModule();
         CallbackHandler cb = new NamePasswordCallbackHandler("admin", 
"admin123");
         Subject subject = new Subject();
@@ -90,20 +92,45 @@ public class LdapCacheTest extends AbstractLdapTestUnit {
         assertTrue(module.logout());
         assertEquals("Principals should be gone as the user has logged out", 
0, subject.getPrincipals().size());
 
-        LDAPCache ldapCache = new LDAPCache(new LDAPOptions(options));
-        DirContext context = ldapCache.open();
-        addUserToGroup(context, "cn=admin,ou=people,dc=example,dc=com", 
"another");
-        ldapCache.close();
-
-        Thread.sleep(100);
+        try(LDAPCache ldapCache = LDAPCache.getCache(new 
LDAPOptions(options))) {
+            // Cache clear is called on dirContext open (correctly)
+            validateMetrics(ldapCache, 0, 1, 0, 1, 0, 0, 1);
+
+            DirContext context = ldapCache.open();
+            addUserToGroup(context, "cn=admin,ou=people,dc=example,dc=com", 
"another");
+            Thread.sleep(100);
+
+            for(int i=0; i < 10; i++) {
+                module = new LDAPLoginModule();
+                subject = new Subject();
+                module.initialize(subject, cb, null, options);
+                assertEquals("Precondition", 0, 
subject.getPrincipals().size());
+                assertTrue(module.login());
+                assertTrue(module.commit());
+                assertEquals("Postcondition", 3, 
subject.getPrincipals().size());
+
+                if(i == 0) {
+                    // Cache is cleared when naming event detects change to 
the tree
+                    // due to group add above
+                    validateMetrics(ldapCache, 0, 2, 0, 2, 0, 0, 2);
+                } else {
+                    validateMetrics(ldapCache, i, 2, i, 2, 0, 0, 2);
+                }
+            }
+        }
+    }
 
-        module = new LDAPLoginModule();
-        subject = new Subject();
-        module.initialize(subject, cb, null, options);
-        assertEquals("Precondition", 0, subject.getPrincipals().size());
-        assertTrue(module.login());
-        assertTrue(module.commit());
-        assertEquals("Postcondition", 3, subject.getPrincipals().size());
+    @Test
+    public void testLDAPCacheHashCode() throws Exception {
+        Properties options = ldapLoginModuleOptions();
+        LDAPCache cache1 = LDAPCache.getCache(new LDAPOptions(options));
+        LDAPCache cache2 = LDAPCache.getCache(new LDAPOptions(options));
+        assertEquals(cache1, cache2);
+        assertEquals(Integer.valueOf(cache1.hashCode()), 
Integer.valueOf(cache2.hashCode()));
+
+        // Validate instance handling is correct
+        cache1.clearCache();
+        assertEquals(Long.valueOf(1), 
Long.valueOf(cache2.getClearCacheCount()));
     }
 
     private void addUserToGroup(DirContext context, String userCn, String 
group) throws NamingException {
@@ -128,4 +155,13 @@ public class LdapCacheTest extends AbstractLdapTestUnit {
         return new Properties(file);
     }
 
+    protected void validateMetrics(LDAPCache ldapCache, long userDNCacheHit, 
long userDNCacheMiss, long userRoleCacheHit, long userRoleCacheMiss, long 
userPubkeyCacheHit, long userPubkeyCacheMiss, long clearCache) {
+           assertEquals(Long.valueOf(userDNCacheHit), 
Long.valueOf(ldapCache.getUserDNCacheHitCount()));
+           assertEquals(Long.valueOf(userDNCacheMiss), 
Long.valueOf(ldapCache.getUserDNCacheMissCount()));
+           assertEquals(Long.valueOf(userRoleCacheHit), 
Long.valueOf(ldapCache.getUserRolesCacheHitCount()));
+           assertEquals(Long.valueOf(userRoleCacheMiss), 
Long.valueOf(ldapCache.getUserRolesCacheMissCount()));
+           assertEquals(Long.valueOf(userPubkeyCacheHit), 
Long.valueOf(ldapCache.getUserPubkeysCacheHitCount()));
+           assertEquals(Long.valueOf(userPubkeyCacheMiss), 
Long.valueOf(ldapCache.getUserPubkeysCacheMissCount()));
+           assertEquals(Long.valueOf(clearCache), 
Long.valueOf(ldapCache.getClearCacheCount()));
+    }
 }
diff --git 
a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap.properties
 
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap.properties
index 7916b1ea44..8c9f7b920e 100644
--- 
a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap.properties
+++ 
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap.properties
@@ -18,6 +18,7 @@
 
################################################################################
 
 debug=true
+disableCache=false
 connection.url=ldap://127.0.0.1:portno
 connection.username=uid=admin,ou=system
 connection.password=secret
diff --git 
a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_badref.properties
 
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_badref.properties
index 07221d8bcc..f3e9afd767 100644
--- 
a/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_badref.properties
+++ 
b/jaas/modules/src/test/resources/org/apache/karaf/jaas/modules/ldap/ldap_badref.properties
@@ -18,6 +18,7 @@
 
################################################################################
 
 debug=true
+disableCache=false
 connection.url=ldap://127.0.0.1:portno
 connection.username=uid=admin,ou=system
 connection.password=secret

Reply via email to