[ 
https://issues.apache.org/jira/browse/KNOX-3247?focusedWorklogId=1004484&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1004484
 ]

ASF GitHub Bot logged work on KNOX-3247:
----------------------------------------

                Author: ASF GitHub Bot
            Created on: 11/Feb/26 00:00
            Start Date: 11/Feb/26 00:00
    Worklog Time Spent: 10m 
      Work Description: lmccay commented on code in PR #1144:
URL: https://github.com/apache/knox/pull/1144#discussion_r2790838539


##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -0,0 +1,377 @@
+/*
+ * 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.backend;
+
+import org.apache.directory.api.ldap.model.cursor.CursorException;
+import org.apache.directory.api.ldap.model.cursor.EntryCursor;
+import org.apache.directory.api.ldap.model.entry.Attribute;
+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.exception.LdapException;
+import org.apache.directory.api.ldap.model.message.SearchScope;
+import org.apache.directory.api.ldap.model.schema.SchemaManager;
+import org.apache.directory.ldap.client.api.LdapConnection;
+import org.apache.directory.ldap.client.api.LdapNetworkConnection;
+import org.apache.knox.gateway.i18n.messages.MessagesFactory;
+import org.apache.knox.gateway.services.ldap.LdapMessages;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * LDAP backend that proxies to an external LDAP server.
+ * Can use central LDAP configuration or backend-specific configuration.
+ */
+public class LdapProxyBackend implements LdapBackend {
+    private static final LdapMessages LOG = 
MessagesFactory.get(LdapMessages.class);
+
+    private String ldapUrl;
+    private String bindDn;
+    private String bindPassword;
+    private String userSearchBase;
+    private String groupSearchBase;
+    private String proxyBaseDn;  // Base DN for proxy entries (e.g., 
dc=proxy,dc=com)
+    private String remoteBaseDn;  // Base DN for remote server searches (e.g., 
dc=hadoop,dc=apache,dc=org)
+    private int port;
+    private String host;
+
+    // Configurable attributes for AD/LDAP compatibility
+    private String userIdentifierAttribute = "uid"; // uid for LDAP, 
sAMAccountName for AD
+    private String userSearchFilter = "({userIdAttr}={username})"; // Will be 
populated with userIdentifierAttribute
+    private String groupMemberAttribute = "memberUid"; // member for AD, 
memberUid for POSIX
+    private boolean useMemberOf; // Use memberOf attribute for group lookup 
(efficient for AD)
+
+    @Override
+    public String getName() {
+        return "ldap";
+    }
+
+    @Override
+    public void initialize(Map<String, String> config) throws Exception {
+        // Proxy base DN is for entries created in the proxy LDAP server
+        proxyBaseDn = config.get("baseDn");
+        if (proxyBaseDn == null || proxyBaseDn.isEmpty()) {
+            throw new IllegalArgumentException("baseDn is required for LDAP 
proxy backend");
+        }
+
+        // Remote base DN is for searching the remote LDAP server
+        remoteBaseDn = config.get("remoteBaseDn");
+        if (remoteBaseDn == null || remoteBaseDn.isEmpty()) {
+            throw new IllegalArgumentException("remoteBaseDn is required for 
LDAP proxy backend - this is the base DN of the remote LDAP server");
+        }
+
+        // Support both url and host/port configuration
+        ldapUrl = config.get("url");
+        if (ldapUrl != null && !ldapUrl.isEmpty()) {
+            // Parse URL to extract host and port
+            parseLdapUrl(ldapUrl);
+        } else {
+            host = config.get("host");
+            if (host == null || host.isEmpty()) {
+                throw new IllegalArgumentException("Either 'url' or 'host' is 
required for LDAP proxy backend");
+            }
+            String portStr = config.get("port");
+            if (portStr == null || portStr.isEmpty()) {
+                throw new IllegalArgumentException("'port' is required when 
using 'host' configuration");
+            }
+            port = Integer.parseInt(portStr);
+            ldapUrl = "ldap://"; + host + ":" + port;
+        }
+
+        // Support both naming conventions: bindDn/bindPassword and 
systemUsername/systemPassword
+        bindDn = config.get("bindDn");
+        if (bindDn == null || bindDn.isEmpty()) {
+            bindDn = config.get("systemUsername");
+        }
+
+        bindPassword = config.get("bindPassword");
+        if (bindPassword == null || bindPassword.isEmpty()) {
+            bindPassword = config.get("systemPassword");
+        }
+
+        // Search bases use the remote server's base DN
+        userSearchBase = config.getOrDefault("userSearchBase", "ou=people," + 
remoteBaseDn);
+        groupSearchBase = config.getOrDefault("groupSearchBase", "ou=groups," 
+ remoteBaseDn);
+
+        // Configure attribute mappings for AD/LDAP compatibility
+        userIdentifierAttribute = 
config.getOrDefault("userIdentifierAttribute", "uid");
+        config.getOrDefault("userDnTemplate", 
"uid={username},ou=Users,{baseDn}");
+        groupMemberAttribute = config.getOrDefault("groupMemberAttribute", 
"memberUid");
+        useMemberOf = Boolean.parseBoolean(config.getOrDefault("useMemberOf", 
"false"));
+
+        // Build search filter template
+        userSearchFilter = "(" + userIdentifierAttribute + "={username})";
+
+        LOG.ldapBackendLoading(getName(), "Proxying " + proxyBaseDn + " to " + 
ldapUrl + " (" + remoteBaseDn + ") with " +
+                              userIdentifierAttribute + " attribute" +
+                              (useMemberOf ? " using memberOf lookups" : " 
using group searches"));
+    }
+
+    private void parseLdapUrl(String url) {
+        // Simple URL parsing for ldap://host:port
+        if (url.startsWith("ldap://";)) {
+            String hostPort = url.substring(7);
+            int colonIdx = hostPort.indexOf(':');
+            if (colonIdx > 0) {
+                host = hostPort.substring(0, colonIdx);
+                try {
+                    port = Integer.parseInt(hostPort.substring(colonIdx + 1));
+                } catch (NumberFormatException e) {
+                    port = 389;
+                }
+            } else {
+                host = hostPort;
+                port = 389;
+            }
+        } else if (url.startsWith("ldaps://")) {
+            String hostPort = url.substring(8);
+            int colonIdx = hostPort.indexOf(':');
+            if (colonIdx > 0) {
+                host = hostPort.substring(0, colonIdx);
+                try {
+                    port = Integer.parseInt(hostPort.substring(colonIdx + 1));
+                } catch (NumberFormatException e) {
+                    port = 636;
+                }
+            } else {
+                host = hostPort;
+                port = 636;
+            }
+        }
+    }
+
+    private LdapConnection connect() throws LdapException, IOException {
+        LdapConnection connection = new LdapNetworkConnection(host, port);

Review Comment:
   Done - good call.





Issue Time Tracking
-------------------

    Worklog Id:     (was: 1004484)
    Time Spent: 1h  (was: 50m)

> Knox LDAP Server with Pluggable Backend
> ---------------------------------------
>
>                 Key: KNOX-3247
>                 URL: https://issues.apache.org/jira/browse/KNOX-3247
>             Project: Apache Knox
>          Issue Type: New Feature
>          Components: Server
>            Reporter: Larry McCay
>            Assignee: Larry McCay
>            Priority: Major
>          Time Spent: 1h
>  Remaining Estimate: 0h
>
> By exposing an LDAP interface from Knox, we can provide a rich set of backend 
> implementations that can:
> * Provide easy demo and test environments with a file based backend
> * Provide enterprise integrations by proxying actual LDAP backends
> * Provide novel implementations based on the KNOX-AUTH-SERVICE in other 
> topologies
> * All while simplifying the configuration of consumers by normalizing the 
> exposed schema - resulting in the same LDAP config for all deployments rather 
> than chasing the deployment specific details across the platform.
> Knox can be the central LDAP Server for the platform while integrating with 
> all of the possible combinations that we already support.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to