interceptors = new ArrayList<>(directoryService.getInterceptors());
+ int authIdx = -1;
+ for (int i = 0; i < interceptors.size(); i++) {
+ if (interceptors.get(i).getName().equalsIgnoreCase("authenticationInterceptor")) {
+ authIdx = i;
+ break;
+ }
+ }
+
+ final GroupLookupInterceptor interceptor = new GroupLookupInterceptor(directoryService, backend);
+ if (authIdx != -1) {
+ interceptors.add(authIdx, interceptor);
+ } else {
+ interceptors.add(interceptor);
+ }
+ directoryService.setInterceptors(interceptors);
+ }
+
/**
* Stop the LDAP server
*/
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
index a0b09c2449..4845cda79e 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapMessages.java
@@ -96,4 +96,10 @@ public interface LdapMessages {
@Message(level = MessageLevel.ERROR,
text = "Failed to copy attribute: {0}")
void ldapAttributeCopyError(@StackTrace(level = MessageLevel.DEBUG) Exception e);
+
+ @Message(level = MessageLevel.DEBUG, text = "LDAP authentication succeeded for user: {0}")
+ void ldapAuthSucceeded(String user);
+
+ @Message(level = MessageLevel.WARN, text = "LDAP authentication failed for user: {0}")
+ void ldapAuthFailed(String user, @StackTrace(level = MessageLevel.INFO) Throwable cause);
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java
new file mode 100644
index 0000000000..117c778ea2
--- /dev/null
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/LdapUtils.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you 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.apache.knox.gateway.services.ldap;
+
+import org.apache.directory.api.ldap.model.name.Dn;
+
+public class LdapUtils {
+
+ public static String extractUsernameFromDn(Dn dn) {
+ if (dn == null || dn.isEmpty()) {
+ return null;
+ }
+
+ try {
+ return "uid".equalsIgnoreCase(dn.getRdn().getType())
+ ? dn.getRdn().getValue()
+ : null;
+ } catch (Exception ignored) {
+ return null;
+ }
+ }
+}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
index b0cdcd8607..9c3d96aed4 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java
@@ -20,9 +20,11 @@
import com.google.gson.Gson;
import org.apache.directory.api.ldap.model.entry.DefaultEntry;
import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.ldap.LdapMessages;
+import org.apache.knox.gateway.services.ldap.LdapUtils;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -45,6 +47,7 @@ public class FileBackend implements LdapBackend {
static class UserData {
String username;
+ String password;
String cn;
String sn;
List groups;
@@ -141,4 +144,17 @@ public List searchUsers(String filter, SchemaManager schemaManager) throw
return results;
}
+
+ @Override
+ public boolean authenticate(Dn userDn, String password) {
+ // Extract username from DN (e.g., uid=admin, ou=people,dc=hadoop,dc=apache,dc=org)
+ final String username = LdapUtils.extractUsernameFromDn(userDn);
+
+ if (username != null) {
+ UserData userData = users.get(username);
+ return userData != null && password != null && password.equals(userData.password);
+ }
+
+ return false;
+ }
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
index 6530c13b37..4c459ff265 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapBackend.java
@@ -18,6 +18,7 @@
package org.apache.knox.gateway.services.ldap.backend;
import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import java.util.List;
@@ -65,4 +66,13 @@ public interface LdapBackend {
* @return List of matching entries
*/
List searchUsers(String filter, SchemaManager schemaManager) throws Exception;
+
+ /**
+ * Authenticate a user with password
+ *
+ * @param userDn The user's Distinguished Name
+ * @param password The user's password
+ * @return true if authentication is successful, false otherwise
+ */
+ boolean authenticate(Dn userDn, String password);
}
diff --git a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
index 38328b5713..053a487bb2 100644
--- a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
+++ b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java
@@ -24,11 +24,13 @@
import org.apache.directory.api.ldap.model.entry.Entry;
import org.apache.directory.api.ldap.model.exception.LdapException;
import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.name.Dn;
import org.apache.directory.api.ldap.model.schema.SchemaManager;
import org.apache.directory.ldap.client.api.DefaultLdapConnectionFactory;
import org.apache.directory.ldap.client.api.LdapConnection;
import org.apache.directory.ldap.client.api.LdapConnectionConfig;
import org.apache.directory.ldap.client.api.LdapConnectionPool;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
import org.apache.directory.ldap.client.api.ValidatingPoolableLdapConnectionFactory;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.services.ldap.LdapMessages;
@@ -253,6 +255,25 @@ public void close() {
}
}
+ @Override
+ public boolean authenticate(Dn userDn, String password) {
+ final String userDnText = userDn.toString(); //at this point we are sure it's not NULL
+ // Create a temporary connection for authentication (bind)
+ final LdapConnectionConfig authConfig = new LdapConnectionConfig();
+ authConfig.setLdapHost(host);
+ authConfig.setLdapPort(port);
+ authConfig.setName(userDnText);
+ authConfig.setCredentials(password);
+ try (LdapConnection connection = new LdapNetworkConnection(authConfig)){
+ connection.bind();
+ LOG.ldapAuthSucceeded(userDnText);
+ return true;
+ } catch (Exception e) {
+ LOG.ldapAuthFailed(userDnText, e);
+ return false;
+ }
+ }
+
@Override
public Entry getUser(String username, SchemaManager schemaManager) throws Exception {
LdapConnection connection = null;
diff --git a/pom.xml b/pom.xml
index 4e3cf3d351..055f5e955a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -800,6 +800,7 @@
com.nimbusds:nimbus-jose-jwt
org.apache.httpcomponents:httpcore
org.apache.knox:gateway-server
+ org.apache.mina:mina-core
org.glassfish.hk2.external:jakarta.inject