[
https://issues.apache.org/jira/browse/KNOX-3341?focusedWorklogId=1026787&page=com.atlassian.jira.plugin.system.issuetabpanels:worklog-tabpanel#worklog-1026787
]
ASF GitHub Bot logged work on KNOX-3341:
----------------------------------------
Author: ASF GitHub Bot
Created on: 25/Jun/26 09:56
Start Date: 25/Jun/26 09:56
Worklog Time Spent: 10m
Work Description: smolnar82 commented on code in PR #1274:
URL: https://github.com/apache/knox/pull/1274#discussion_r3473284050
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -302,10 +326,12 @@ public Entry getUser(String username, SchemaManager
schemaManager) throws Except
connection = getConnection();
// Search for user using configurable attribute
String filter = userSearchFilter.replace("{username}", username);
- try (EntryCursor cursor = connection.search(userSearchBase,
filter, SearchScope.SUBTREE, "*")) {
+ try (EntryCursor cursor = connection.search(remoteUserSearchBase,
filter, SearchScope.SUBTREE, "*")) {
if (cursor.next()) {
Entry sourceEntry = cursor.get();
- return createProxyEntry(sourceEntry, username, connection,
schemaManager, createResolvedParentsCache());
+ addGroupMemberships(sourceEntry, connection,
createEntryCache(), createResolvedParentsCache());
+ return
remoteSchemaConverter.convertRemoteEntryToProxyEntry(sourceEntry,
schemaManager);
Review Comment:
Attribute copying changed from an allowlist to copy-everything.
Assessed by Claude:
```
The old createProxyEntry copied a fixed set (cn, dn, mail, description,
uid, objectClass, identifier, memberOf). The new
RemoteSchemaConverter.convertRemoteEntryToProxyEntry copies all attributes from
the remote entry.
Two concerns:
- Potential exposure of sensitive/operational attributes (e.g.
userPassword) through the proxy that previously weren't surfaced.
Please confirm the remote search ("*") can't return those, or re-introduce
an allowlist/denylist.
- copyAttribute runs convertRemoteDnToProxyDn on every attribute value,
not just DN-valued ones.
A description/mail value that happens to contain the remote base DN
substring would be rewritten. Low probability, but it's blind string rewriting.
```
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/FileBackend.java:
##########
@@ -145,9 +153,13 @@ public List<String> getUserGroups(String username) throws
Exception {
public List<Entry> searchUsers(String filter, SchemaManager schemaManager)
throws Exception {
List<Entry> results = new ArrayList<>();
+ String userFilter = extractUser(filter).toLowerCase(Locale.ROOT);
Review Comment:
Possible NPE here: `extractUser` returns `null` when the filter has no
`uid=/cn=/sAMAccountName=` term.
I'm aware that the file-based backend implementation is for tests only, but
I'd like this to fix.
Analysis by Claude code:
```
extractUser returns null when the filter has no uid=/cn=/sAMAccountName=
term (e.g. (objectclass=inetOrgPerson)). Since the new FileBackend.search()
delegates every filter to searchUsers, and the new UserSearchInterceptor now
forwards all searches to the backend, a general search against a file backend
throws NPE.
It's swallowed by UserSearchInterceptor's catch (Exception e) and logged as
a search failure, so the file backend silently returns nothing for exactly the
"general searches" this PR is about. Should guard the null (return empty or
fall back to wildcard match).
```
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/RemoteSchemaConverter.java:
##########
@@ -0,0 +1,177 @@
+/*
+ * 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.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.entry.Value;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
+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;
+
+public class RemoteSchemaConverter {
+ private static final LdapMessages LOG =
MessagesFactory.get(LdapMessages.class);
+
+ // Proxy configuration
+ private final String proxyBaseDn; // Base DN for proxy entries (e.g.,
dc=proxy,dc=com)
+ private final String proxyUserSearchBase;
+ private final String proxyGroupSearchBase;
+
+ // Backend configuration
+ private final String remoteBaseDn; // Base DN for remote server searches
(e.g., dc=hadoop,dc=apache,dc=org)
+ private final String remoteUserSearchBase;
+ private final String remoteGroupSearchBase;
+ private final String remoteUserIdentifierAttribute;
+ private final String remoteUserObjectClass;
+ private final String remoteGroupObjectClass;
+
+ public RemoteSchemaConverter(String proxyBaseDn,
+ String proxyUserSearchBase,
+ String proxyGroupSearchBase,
+ String remoteBaseDn,
+ String remoteUserSearchBase,
+ String remoteGroupSearchBase,
+ String remoteUserIdentifierAttribute,
+ String remoteUserObjectClass,
+ String remoteGroupObjectClass) {
+ this.proxyBaseDn = proxyBaseDn;
+ this.proxyUserSearchBase = proxyUserSearchBase;
+ this.proxyGroupSearchBase = proxyGroupSearchBase;
+ this.remoteBaseDn = remoteBaseDn;
+ this.remoteUserSearchBase = remoteUserSearchBase;
+ this.remoteGroupSearchBase = remoteGroupSearchBase;
+ this.remoteUserIdentifierAttribute = remoteUserIdentifierAttribute;
+ this.remoteUserObjectClass = remoteUserObjectClass;
+ this.remoteGroupObjectClass = remoteGroupObjectClass;
+ }
+
+ /**
+ * Creates a proxy entry from a backend source entry with all required
attributes.
+ * This method standardizes the conversion of backend LDAP entries to
proxy entries,
+ * preserving the backend DN and copying all standard user attributes.
+ *
+ * @param sourceEntry The entry from the backend LDAP server
+ * @param schemaManager The schemaManager
+ * @return A new Entry with backend DN and all copied attributes
+ * @throws LdapException if entry creation or attribute copying fails
+ */
+ public Entry convertRemoteEntryToProxyEntry(Entry sourceEntry,
SchemaManager schemaManager) throws LdapException {
+ // Standard proxy approach: return entry with backend DN unchanged
+ // This preserves DN integrity for bind operations and DN references
+ Entry entry = new DefaultEntry(schemaManager);
+ entry.setDn(sourceEntry.getDn());
+
+ // Copy all known AttributeTypes as-is from backend response
+ for (Attribute attribute : sourceEntry.getAttributes()) {
+ copyAttribute(sourceEntry, entry, attribute.getId());
+ }
+
+ // Map identifier attribute to uid for consistency if needed
+ if (!"uid".equals(remoteUserIdentifierAttribute)) {
+ Attribute idAttr = sourceEntry.get(remoteUserIdentifierAttribute);
+ if (idAttr != null) {
+ entry.add("uid", idAttr.getString());
+ }
+ }
+
+ // replace userObjectClass and groupObjectClass object classes
+ Attribute objectClassAttribute = sourceEntry.get("objectclass");
+ if (objectClassAttribute.contains(remoteGroupObjectClass)) {
Review Comment:
Possible NPE if `sourceEntry.get("objectclass")` returns `null`.
##########
knox-site/docs/service_ldap_server.md:
##########
@@ -120,23 +128,27 @@ The proxy backend delegates lookups to a remote LDAP or
Active Directory server.
| `gateway.ldap.interceptor.<name>.url` | N/A | Remote LDAP URL (e.g.,
`ldap://remote-host:389`). |
| `gateway.ldap.interceptor.<name>.host` | N/A | Host of remote LDAP (used if
`url` is not provided). |
| `gateway.ldap.interceptor.<name>.port` | N/A | Port of remote LDAP (used if
`url` is not provided). |
+| `gateway.ldap.interceptor.<name>.baseDn` | N/A | **Required**. The base DN
of the LDAP proxy server. |
| `gateway.ldap.interceptor.<name>.remoteBaseDn` | N/A | **Required**. The
base DN of the remote LDAP server. |
| `gateway.ldap.interceptor.<name>.systemUsername` | N/A | Bind DN for the
remote server (alias: `bindDn`). |
| `gateway.ldap.interceptor.<name>.systemPassword` | N/A | Password for the
bind DN (alias: `bindPassword`). |
| `gateway.ldap.interceptor.<name>.userSearchBase` |
`ou=people,{remoteBaseDn}` | Base DN for user searches on the remote server. |
| `gateway.ldap.interceptor.<name>.groupSearchBase` |
`ou=groups,{remoteBaseDn}` | Base DN for group searches on the remote server. |
| `gateway.ldap.interceptor.<name>.userIdentifierAttribute` | `uid` |
Attribute used for user lookup (e.g., `sAMAccountName` for AD). |
+| `gateway.ldap.interceptor.<name>.userObjectClass` | `inetOrgPerson` |
Objectclass for identifying users in remote server (e.g., `person` for AD). |
+| `gateway.ldap.interceptor.<name>.groupObjectClass` | `groupOfNames` |
Objectclass for identifying groups in remote server (e.g., `group` for AD). |
| `gateway.ldap.interceptor.<name>.groupMemberAttribute` | `memberUid` |
Attribute used for group membership (e.g., `member` for AD). |
| `gateway.ldap.interceptor.<name>.useMemberOf` | `false` | If `true`, use the
`memberOf` attribute for efficient group lookups. |
-| `gateway.interceptor.<name>.proxy.poolMaxActive` | `8` | Maximum number of
active connections in the pool. |
+| `gateway.ldap.interceptor.<name>.proxy.poolMaxActive` | `8` | Maximum number
of active connections in the pool. |
+| `gateway.ldap.interceptor.<name>.proxy.poolMaxActive` | `8` | Maximum number
of active connections in the pool. |
Review Comment:
Duplicated row -> has to be eliminated.
##########
gateway-server/src/test/resources/ldap-recursive-test.ldif:
##########
@@ -93,3 +105,77 @@ objectclass:top
objectclass:groupOfNames
cn: cycleGroupB
member: cn=cycleGroupA,ou=recursiveGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf
+sn: User
+uid: memberOfUser
+userPassword:memberOfUser-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf2
+sn: User
+uid: memberOfUser2
+userPassword:memberOfUser2-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 1 Group
+dn: cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel1
+memberOf:
cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 2 Group (Member is Level 1)
+dn: cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel2level2
Review Comment:
Wrong `cn`: it should be `cn: memberOflevel2` (otherwise it'll be a
mismatched RDN).
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -540,27 +683,16 @@ private String extractGroupNameFromDn(String groupDn) {
return null;
}
- private List<Entry> getUserGroupsInternal(LdapConnection connection,
String userDn, String username) throws LdapException, CursorException,
IOException {
+ // tODO reuse this method in recursive calls
Review Comment:
nit: typo
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/RemoteSchemaConverter.java:
##########
@@ -0,0 +1,177 @@
+/*
+ * 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.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.entry.Value;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.api.ldap.model.filter.ExprNode;
+import org.apache.directory.api.ldap.model.filter.FilterParser;
+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;
+
+public class RemoteSchemaConverter {
+ private static final LdapMessages LOG =
MessagesFactory.get(LdapMessages.class);
+
+ // Proxy configuration
+ private final String proxyBaseDn; // Base DN for proxy entries (e.g.,
dc=proxy,dc=com)
+ private final String proxyUserSearchBase;
+ private final String proxyGroupSearchBase;
+
+ // Backend configuration
+ private final String remoteBaseDn; // Base DN for remote server searches
(e.g., dc=hadoop,dc=apache,dc=org)
+ private final String remoteUserSearchBase;
+ private final String remoteGroupSearchBase;
+ private final String remoteUserIdentifierAttribute;
+ private final String remoteUserObjectClass;
+ private final String remoteGroupObjectClass;
+
+ public RemoteSchemaConverter(String proxyBaseDn,
+ String proxyUserSearchBase,
+ String proxyGroupSearchBase,
+ String remoteBaseDn,
+ String remoteUserSearchBase,
+ String remoteGroupSearchBase,
+ String remoteUserIdentifierAttribute,
+ String remoteUserObjectClass,
+ String remoteGroupObjectClass) {
+ this.proxyBaseDn = proxyBaseDn;
+ this.proxyUserSearchBase = proxyUserSearchBase;
+ this.proxyGroupSearchBase = proxyGroupSearchBase;
+ this.remoteBaseDn = remoteBaseDn;
+ this.remoteUserSearchBase = remoteUserSearchBase;
+ this.remoteGroupSearchBase = remoteGroupSearchBase;
+ this.remoteUserIdentifierAttribute = remoteUserIdentifierAttribute;
+ this.remoteUserObjectClass = remoteUserObjectClass;
+ this.remoteGroupObjectClass = remoteGroupObjectClass;
+ }
+
+ /**
+ * Creates a proxy entry from a backend source entry with all required
attributes.
+ * This method standardizes the conversion of backend LDAP entries to
proxy entries,
+ * preserving the backend DN and copying all standard user attributes.
+ *
+ * @param sourceEntry The entry from the backend LDAP server
+ * @param schemaManager The schemaManager
+ * @return A new Entry with backend DN and all copied attributes
+ * @throws LdapException if entry creation or attribute copying fails
+ */
+ public Entry convertRemoteEntryToProxyEntry(Entry sourceEntry,
SchemaManager schemaManager) throws LdapException {
+ // Standard proxy approach: return entry with backend DN unchanged
+ // This preserves DN integrity for bind operations and DN references
+ Entry entry = new DefaultEntry(schemaManager);
+ entry.setDn(sourceEntry.getDn());
+
+ // Copy all known AttributeTypes as-is from backend response
+ for (Attribute attribute : sourceEntry.getAttributes()) {
+ copyAttribute(sourceEntry, entry, attribute.getId());
+ }
+
+ // Map identifier attribute to uid for consistency if needed
+ if (!"uid".equals(remoteUserIdentifierAttribute)) {
+ Attribute idAttr = sourceEntry.get(remoteUserIdentifierAttribute);
+ if (idAttr != null) {
+ entry.add("uid", idAttr.getString());
+ }
+ }
+
+ // replace userObjectClass and groupObjectClass object classes
+ Attribute objectClassAttribute = sourceEntry.get("objectclass");
+ if (objectClassAttribute.contains(remoteGroupObjectClass)) {
+ entry.remove("objectclass", remoteGroupObjectClass);
+ entry.add("objectclass", "groupofnames");
+ }
+ if (objectClassAttribute.contains(remoteUserObjectClass)) {
+ entry.remove("objectclass", remoteUserObjectClass);
+ entry.add("objectclass", "inetOrgPerson");
+ }
+
+ return entry;
+ }
+
+ /**
+ * Converts an LDAP search filter from proxy attributes to remote
attributes.
+ * @param filter the filter
+ * @param schemaManager the schema manager
+ * @return the converted filter
+ * @throws Exception if the filter cannot be parsed
+ */
+ public String convertProxyFilterToRemoteFilter(String filter,
SchemaManager schemaManager) throws Exception {
+ FilterMappingVisitor filterMappingVisitor = new
FilterMappingVisitor(remoteUserIdentifierAttribute, remoteUserObjectClass,
remoteGroupObjectClass, schemaManager);
+
+ // Filter likely has already been annotated by other interceptors.
+ // Clean the filter by removing any modifications or annotations
+ String rawFilter = filter.replaceAll(":\\[.*?\\]",
"").replaceAll("=\\s+", "=");
+ ExprNode cleanNode = FilterParser.parse(schemaManager, rawFilter);
+
+ return cleanNode.accept(filterMappingVisitor).toString();
+ }
+
+ /**
+ * Copy attributes from a remote entry to a proxy entry. DN values are
converted from
+ * the remote base to the proxy base
+ * @param source The remote entry
+ * @param target The proxy entry
+ * @param attributeName The name of the attribute to copy
+ */
+ public void copyAttribute(Entry source, Entry target, String
attributeName) {
+ final Attribute attribute = source.get(attributeName);
+ if (attribute != null) {
+ // Copy all values of the attribute (important for multi-valued
attributes like objectClass)
+ for (Value value : attribute) {
+ String valueString =
convertRemoteDnToProxyDn(value.toString());
+ if (!target.contains(attributeName, valueString)) {
+ try {
+ target.add(attributeName, valueString);
+ } catch (LdapException e) {
+ LOG.ldapAttributeCopyError(e);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Converts a remote dn string to a proxy dn string. This also works for
search base strings.
+ * @param string the remote dn or search base
+ * @return the proxy search base
+ */
+ public String convertRemoteDnToProxyDn(String string) {
Review Comment:
DN/filter conversion via String.replaceAll is regex-fragile.
Assessed by Claude:
```
convertRemoteDnToProxyDn/convertProxyDnToRemoteDn use the base DNs as regex
patterns and the targets as replacement strings (where $/\ are special).
Standard DNs are safe, but this will misbehave if a DN contains regex
meta-characters.
Pattern.quote + Matcher.quoteReplacement (or proper DN parsing) would be
more robust. Same fragility in convertProxyFilterToRemoteFilter's
filter.replaceAll(":\\[.*?\\]", "")... cleanup, which is a bit of a hack
(acknowledged in the comment).
```
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/backend/LdapProxyBackend.java:
##########
@@ -315,72 +341,182 @@ public Entry getUser(String username, SchemaManager
schemaManager) throws Except
}
@Override
- public List<String> getUserGroups(String username) throws Exception {
+ public List<String> getUserGroups(String username, SchemaManager
schemaManager) throws Exception {
Review Comment:
Claude's assessment:
```
`getUserGroups()` with `useMemberOf=true` + `recursiveGroupResolution=true`
likely returns an incomplete CN list.
In the recursive memberOf path, populateParentCacheViaMemberOf fetches group
entries via connection.lookup(groupDn, "memberOf") — requesting only memberOf,
so the cached entries have no cn. Those entries flow back through
getUserGroupsEntries
→ getCnsFromEntries, which does entry.get("cn") and drops anything without a
cn. So the recursively-discovered (and even the directly-resolved) groups get
dropped from the returned name list.
This path has no test — all the new recursive-memberOf tests go through
search() and assert on the memberOf attribute (DN strings via
addGroupMemberships), not on getUserGroups() CNs. Fix: request "cn", "memberOf"
in the lookup, or derive the CN from the DN.
```
##########
gateway-server/src/test/resources/ldap-recursive-test.ldif:
##########
@@ -93,3 +105,77 @@ objectclass:top
objectclass:groupOfNames
cn: cycleGroupB
member: cn=cycleGroupA,ou=recursiveGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf
+sn: User
+uid: memberOfUser
+userPassword:memberOfUser-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf2
+sn: User
+uid: memberOfUser2
+userPassword:memberOfUser2-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 1 Group
+dn: cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel1
+memberOf:
cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 2 Group (Member is Level 1)
+dn: cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel2level2
+memberOf:
cn=memberOflevel3,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+memberOf:
cn=memberOfCycleA,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+member: cn=memberOflevel1,ou=recursiveGroups,dc=hadoop,dc=apache,dc=org
Review Comment:
points to `ou=recursiveGroups` instead of `ou=recursiveMemberOfGroups`.
##########
gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/interceptor/DisabledUserInterceptor.java:
##########
@@ -0,0 +1,124 @@
+/*
+ * 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.interceptor;
+
+import com.google.common.annotations.VisibleForTesting;
+import org.apache.directory.api.ldap.model.cursor.CursorException;
+import org.apache.directory.api.ldap.model.cursor.ListCursor;
+import org.apache.directory.api.ldap.model.entry.Attribute;
+import org.apache.directory.api.ldap.model.entry.Entry;
+import org.apache.directory.api.ldap.model.exception.LdapException;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursor;
+import org.apache.directory.server.core.api.filtering.EntryFilteringCursorImpl;
+import org.apache.directory.server.core.api.interceptor.BaseInterceptor;
+import
org.apache.directory.server.core.api.interceptor.context.SearchOperationContext;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class DisabledUserInterceptor extends BaseInterceptor {
+
+ private static final String UAC_ATTRIBUTENAME = "useraccountcontrol";
+ // Active Directory UserAccountControl Bitmasks
+ private static final int UAC_ACCOUNTDISABLE = 0x0002;
+
+ private static final String NSACCOUNTLOCK_ATTRIBUTE = "nsaccountlock";
+ private static final String NSACCOUNTLOCK_DISABLED_VALUE = "true";
+
+ private boolean removeDisabledUsers;
+
+ public DisabledUserInterceptor(String name, Map<String, String> config) {
+ this(name,
Boolean.parseBoolean(config.getOrDefault("removeDisabledUsers", "false")));
+ }
+
+ protected DisabledUserInterceptor(String name, boolean
removeDisabledUsers) {
+ super(name);
+ this.removeDisabledUsers = removeDisabledUsers;
+ }
+
+ @Override
+ public EntryFilteringCursor search(SearchOperationContext ctx) throws
LdapException {
+ // First execute the interceptor chain to get the results
+ List<Entry> filteredEntries = List.of();
+ try (EntryFilteringCursor originalResults = next(ctx)) {
+ List<Entry> originalEntries = new ArrayList<>();
+ try {
+ while (originalResults.next()) {
+ originalEntries.add(originalResults.get());
+ }
+ } catch (CursorException e) {
+ // rethrow exception on incomplete iteration
+ throw new LdapException(e);
+ }
+ filteredEntries = filterDisabledUsers(originalEntries);
+ } catch (IOException e) {
+ // IOException would only occur after finishing iterating over
results
+ // we can ignore this exception and return the filtered entries
+ } catch (Exception e) {
+ throw e;
+ }
Review Comment:
This catch does nothing, we can remove it.
##########
gateway-server/src/test/resources/ldap-recursive-test.ldif:
##########
@@ -93,3 +105,77 @@ objectclass:top
objectclass:groupOfNames
cn: cycleGroupB
member: cn=cycleGroupA,ou=recursiveGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf
+sn: User
+uid: memberOfUser
+userPassword:memberOfUser-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# entry for sample user memberOfUser using memberOf
+dn: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass:person
+objectclass:organizationalPerson
+objectclass:inetOrgPerson
+cn: MemberOf2
+sn: User
+uid: memberOfUser2
+userPassword:memberOfUser2-password
+memberOf:
cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 1 Group
+dn: cn=memberOflevel1,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel1
+memberOf:
cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+member: uid=memberOfUser2,ou=recursiveMemberOfPeople,dc=hadoop,dc=apache,dc=org
+
+# memberOf Level 2 Group (Member is Level 1)
+dn: cn=memberOflevel2,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+objectclass:top
+objectclass: groupofnames
+cn: memberOflevel2level2
+memberOf:
cn=memberOflevel3,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+memberOf:
cn=memberOfCycleA,ou=recursiveMemberOfGroups,dc=hadoop,dc=apache,dc=org
+member: cn=memberOflevel1,ou=recursiveGroups,dc=hadoop,dc=apache,dc=org
+
Review Comment:
These 2 issues above don't fail the tests (the `memberOf` recursion reads
the `memberOf` attribute, not `cn/member`), which is precisely why issue with
`getUserGroups()` above slipped through untested. I'd fix the data and add a
`getUserGroups()` assertion for the `useMemberOf+recursive` combo.
Issue Time Tracking
-------------------
Worklog Id: (was: 1026787)
Time Spent: 40m (was: 0.5h)
> Enhance LDAP Proxy to support more general search parameters
> ------------------------------------------------------------
>
> Key: KNOX-3341
> URL: https://issues.apache.org/jira/browse/KNOX-3341
> Project: Apache Knox
> Issue Type: Improvement
> Components: Server
> Reporter: David Han
> Assignee: David Han
> Priority: Major
> Fix For: 3.0.0
>
> Time Spent: 40m
> Remaining Estimate: 0h
>
> LDAP search with the LDAP Proxy is focused around user retrieval based on
> uid. This should be enhanced to better support the search filter and returned
> attribute parameters commonly used in LDAP search.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)