LDAP: Allow specification of multiple servers as fallbacks
Resolves #933
Change-Id: I30f16778d0031620a07f1e35601d22251e5c6883
diff --git a/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java b/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
index 95e3071..51a0cb1 100644
--- a/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
+++ b/src/main/java/de/ids_mannheim/korap/authentication/LdapAuth3.java
@@ -207,19 +207,45 @@
lc = new LDAPConnection();
}
- try {
- // timeout - 18.06.25/FB
- lc.connect(ldapConfig.host, ldapConfig.port, ldapConfig.ldapTimeout);
-
- jlog.debug("{}: connect: successfull.", ldapConfig.useSSL ? "LDAPS" : "LDAP");
+ boolean connected = false;
+ LDAPException lastException = null;
+
+ String[] hosts = ldapConfig.host.split("[,\\s]+");
+ for (String hostString : hosts) {
+ if (hostString.isEmpty()) continue;
+
+ String host = hostString;
+ int port = ldapConfig.port;
+
+ if (hostString.contains(":")) {
+ String[] parts = hostString.split(":");
+ host = parts[0].trim();
+ try {
+ port = Integer.parseInt(parts[1].trim());
+ } catch (NumberFormatException e) {
+ jlog.warn("Invalid parsing port for LDAP server {}: {}", hostString, e.getMessage());
+ }
}
- catch (LDAPException e) {
- String fullStackTrace = org.apache.commons.lang3.exception.ExceptionUtils
- .getStackTrace(e);
+
+ try {
+ // timeout - 18.06.25/FB
+ lc.connect(host, port, ldapConfig.ldapTimeout);
+ jlog.debug("{}: connect to {}:{} successful.", ldapConfig.useSSL ? "LDAPS" : "LDAP", host, port);
+ connected = true;
+ break; // Successfully connected
+ } catch (LDAPException e) {
+ lastException = e;
+ jlog.warn("Connecting to LDAP Server {}:{} failed: {}", host, port, e.getMessage());
+ }
+ }
+
+ if (!connected) {
+ String fullStackTrace = lastException != null ? org.apache.commons.lang3.exception.ExceptionUtils
+ .getStackTrace(lastException) : "No valid hosts specified";
jlog.error("Connecting to LDAP Server: failed: '{}'!\n", fullStackTrace);
-
+
ldapTerminate(lc);
- return new LdapAuth3Result(null, isTimeout(e) ? LDAP_AUTH_RTIMEOUT : LDAP_AUTH_RCONNECT);
+ return new LdapAuth3Result(null, lastException != null && isTimeout(lastException) ? LDAP_AUTH_RTIMEOUT : LDAP_AUTH_RCONNECT);
}
jlog.debug("isConnected={}.\n", lc.isConnected() ? "yes" : "no");
diff --git a/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java b/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
index d6b81f5..f45bce9 100644
--- a/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
+++ b/src/test/java/de/ids_mannheim/korap/authentication/LdapAuth3Test.java
@@ -30,6 +30,10 @@
public static final String TEST_LDAPS_TS_CONF = "src/test/resources/test-ldaps-with-truststore.conf";
+ public static final String TEST_LDAP_FALLBACK_CONF = "src/test/resources/test-ldap-fallback.conf";
+ public static final String TEST_LDAP_FALLBACK_FAIL_CONF = "src/test/resources/test-ldap-fallback-fail.conf";
+ public static final String TEST_LDAP_FALLBACK_PORT_CONF = "src/test/resources/test-ldap-fallback-port.conf";
+
public static final String TEST_LDAP_USERS_LDIF = "src/test/resources/test-ldap-users.ldif";
private static final String keyStorePath = "src/test/resources/keystore.p12";
@@ -245,4 +249,30 @@
assertEquals(3269, ldapConfig.port);
assertEquals("localhost", ldapConfig.host);
}
+
+ @Test
+ public void loginWithFallbackWorks () throws LDAPException {
+ // `test-ldap-fallback.conf` defines `host = invalid.local, localhost`
+ // Should connect to localhost successfully after failing on invalid.local
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAP_FALLBACK_CONF));
+ }
+
+ @Test
+ public void loginWithFallbackFails () throws LDAPException {
+ // `test-ldap-fallback-fail.conf` defines `host = invalid1.local, invalid2.local`
+ // Should return LDAP_AUTH_RCONNECT or LDAP_AUTH_RTIMEOUT depending on network
+ int rc = LdapAuth3.login("testuser", "password", TEST_LDAP_FALLBACK_FAIL_CONF);
+ assertTrue(rc == LDAP_AUTH_RCONNECT || rc == LDAP_AUTH_RTIMEOUT,
+ "Expected connection failure, but got code=" + rc);
+ }
+
+ @Test
+ public void loginWithFallbackAndPortWorks () throws LDAPException {
+ // `test-ldap-fallback-port.conf` defines `host = invalid.local, localhost:3268`
+ // and a default port of 1111 which is wrong
+ // Should connect to localhost on port 3268 specifically
+ assertEquals(LDAP_AUTH_ROK,
+ LdapAuth3.login("testuser", "password", TEST_LDAP_FALLBACK_PORT_CONF));
+ }
}
diff --git a/src/test/resources/test-ldap-fallback-fail.conf b/src/test/resources/test-ldap-fallback-fail.conf
new file mode 100644
index 0000000..87eb005
--- /dev/null
+++ b/src/test/resources/test-ldap-fallback-fail.conf
@@ -0,0 +1,6 @@
+host = invalid1.local, invalid2.local
+port = 3268
+searchBase = dc=example,dc=com
+sLoginDN = cn=admin,dc=example,dc=com
+pwd = adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))
diff --git a/src/test/resources/test-ldap-fallback-port.conf b/src/test/resources/test-ldap-fallback-port.conf
new file mode 100644
index 0000000..5f88fc7
--- /dev/null
+++ b/src/test/resources/test-ldap-fallback-port.conf
@@ -0,0 +1,6 @@
+host = invalid.local, localhost:3268
+port = 1111
+searchBase = dc=example,dc=com
+sLoginDN = cn=admin,dc=example,dc=com
+pwd = adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))
diff --git a/src/test/resources/test-ldap-fallback.conf b/src/test/resources/test-ldap-fallback.conf
new file mode 100644
index 0000000..51ab913
--- /dev/null
+++ b/src/test/resources/test-ldap-fallback.conf
@@ -0,0 +1,6 @@
+host = invalid.local, localhost
+port = 3268
+searchBase = dc=example,dc=com
+sLoginDN = cn=admin,dc=example,dc=com
+pwd = adminpassword
+searchFilter=(&(|(uid=${login})(mail=${login})(extraProfile=${login}))(|(userPassword=${password})(extraPassword=${password})))