This is an automated email from the ASF dual-hosted git repository.
smolnar82 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 76fa85b5c KNOX-3389: Embedded LDAP getUserGroups drops roles-lookup
roles that have no backing group (#1318)
76fa85b5c is described below
commit 76fa85b5c08c1869e9db2e021536a2696841d748
Author: Sandor Molnar <[email protected]>
AuthorDate: Wed Jul 22 20:15:09 2026 +0200
KNOX-3389: Embedded LDAP getUserGroups drops roles-lookup roles that have
no backing group (#1318)
---
.../services/ldap/KnoxLDAPServerManager.java | 19 +++---
.../services/ldap/KnoxLDAPServerManagerTest.java | 70 ++++++++++++++++++++++
2 files changed, 82 insertions(+), 7 deletions(-)
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
index 30b6d0d0b..3e42c2117 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManager.java
@@ -27,6 +27,7 @@ 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.exception.LdapInvalidDnException;
import org.apache.directory.api.ldap.model.message.SearchRequest;
import org.apache.directory.api.ldap.model.message.SearchRequestImpl;
import org.apache.directory.api.ldap.model.message.SearchScope;
@@ -56,7 +57,6 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
@@ -465,14 +465,19 @@ public class KnoxLDAPServerManager {
Attribute memberOf = entry.get("memberOf");
if (memberOf != null) {
for (Value value : memberOf) {
- String groupDn = value.getString();
- if
(groupDn.toLowerCase(Locale.ROOT).startsWith("cn=")) {
- int commaIdx = groupDn.indexOf(',');
- if (commaIdx > 0) {
- groups.add(groupDn.substring(3, commaIdx));
+ try {
+ Dn groupDn = new Dn(value.getString());
+ String groupName =
LdapUtils.extractGroupName(groupDn);
+ // A full group DN (cn=role,ou=groups,...) is
always resolved. A bare
+ // RDN (cn=role) is only produced by the roles
lookup interceptor when
+ // no template group DN was available (see
LDAPRolesLookupInterceptor#addRoleAttribute),
+ // so only accept it when that interceptor is
active.
+ if (groupName != null && (groupDn.size() > 1 ||
hasRolesLookupInterceptor)) {
+ groups.add(groupName);
}
+ } catch (LdapInvalidDnException e) {
+ // Skip memberOf values that are not valid DNs
}
-
}
}
}
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
index bb7dde208..327d701c3 100644
---
a/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/ldap/KnoxLDAPServerManagerTest.java
@@ -33,7 +33,10 @@ import
org.apache.knox.gateway.services.security.AliasService;
import
org.apache.knox.gateway.services.ldap.control.RolesLookupBypassControlFactory;
import org.apache.knox.gateway.services.ldap.model.constants.SchemaConstants;
import org.easymock.EasyMock;
+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.services.ldap.interceptor.UserSearchInterceptor;
import org.junit.Test;
import org.junit.Before;
@@ -46,6 +49,7 @@ import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.io.OutputStream;
+import java.lang.reflect.Field;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.security.KeyPair;
@@ -427,6 +431,40 @@ public class KnoxLDAPServerManagerTest {
assertTrue(controlFactoryMap.get(SchemaConstants.ROLES_LOOKUP_BYPASS_CONTROL_OID)
instanceof RolesLookupBypassControlFactory);
}
+ @Test
+ public void testGetUserGroupsResolvesBareRdnRolesWhenRolesLookupActive()
throws Exception {
+ serverManager.initialize(createNoInterceptorConfig());
+ serverManager.start();
+
+ // The roles lookup interceptor stores roles that have no template
group DN as a bare
+ // RDN (cn=role) with no trailing DN components - exactly the "no
groups at all" case.
+ addUserWithMemberOf("sam", "cn=platform:admin-sam",
"cn=ml-workspace-abc:viewer-sam");
+ setRolesLookupInterceptorFlag(true);
+
+ List<String> groups = serverManager.getUserGroups("sam");
+
+ assertEquals("Both bare-RDN roles should be resolved as groups", 2,
groups.size());
+ assertTrue("Expected platform:admin-sam among " + groups,
groups.contains("platform:admin-sam"));
+ assertTrue("Expected ml-workspace-abc:viewer-sam among " + groups,
groups.contains("ml-workspace-abc:viewer-sam"));
+ }
+
+ @Test
+ public void testGetUserGroupsIgnoresBareRdnWhenRolesLookupInactive()
throws Exception {
+ serverManager.initialize(createNoInterceptorConfig());
+ serverManager.start();
+
+ // A real group DN (with a comma) alongside a bare cn= RDN. Without
the roles lookup
+ // interceptor a bare RDN is unexpected data and must be skipped,
while the full DN
+ // group is still resolved.
+ addUserWithMemberOf("sam", "cn=analysts,ou=groups,dc=test,dc=com",
"cn=platform:admin-sam");
+ setRolesLookupInterceptorFlag(false);
+
+ List<String> groups = serverManager.getUserGroups("sam");
+
+ assertEquals("Only the full-DN group should be resolved when roles
lookup is inactive",
+ List.of("analysts"), groups);
+ }
+
@Test(expected = LdapException.class)
public void testBindRequiredRejectsAnonymous() throws Exception {
useBindPassword(BIND_PASSWORD);
@@ -626,6 +664,38 @@ public class KnoxLDAPServerManagerTest {
return aliasService;
}
+ /** A minimal config that starts the embedded server with base partitions
and no interceptors. */
+ private GatewayConfig createNoInterceptorConfig() {
+ GatewayConfig mockConfig =
EasyMock.createNiceMock(GatewayConfig.class);
+
expect(mockConfig.getGatewayDataDir()).andReturn(tempWorkDir.getAbsolutePath()).anyTimes();
+ expect(mockConfig.getLDAPPort()).andReturn(port).anyTimes();
+
expect(mockConfig.getLDAPBaseDN()).andReturn("dc=test,dc=com").anyTimes();
+
expect(mockConfig.getLDAPInterceptorNames()).andReturn(List.of()).anyTimes();
+ replay(mockConfig);
+ return mockConfig;
+ }
+
+ /** Add a user under ou=people,dc=test,dc=com carrying the given memberOf
values. */
+ private void addUserWithMemberOf(String uid, String... memberOf) throws
Exception {
+ SchemaManager schemaManager =
serverManager.directoryService.getSchemaManager();
+ Entry entry = new DefaultEntry(schemaManager, new Dn(schemaManager,
"uid=" + uid + ",ou=people,dc=test,dc=com"));
+ entry.add("objectClass", "top", "person", "organizationalPerson",
"inetOrgPerson");
+ entry.add("cn", uid);
+ entry.add("sn", uid);
+ entry.add("uid", uid);
+ for (String value : memberOf) {
+ entry.add("memberOf", value);
+ }
+ serverManager.directoryService.getAdminSession().add(entry);
+ }
+
+ /** Toggle the private flag that gates lenient bare-RDN parsing in
getUserGroups. */
+ private void setRolesLookupInterceptorFlag(boolean value) throws Exception
{
+ Field field =
KnoxLDAPServerManager.class.getDeclaredField("hasRolesLookupInterceptor");
+ field.setAccessible(true);
+ field.setBoolean(serverManager, value);
+ }
+
private Map<String, String> createFileBackendInterceptorConfig() {
Map<String, String> config = new HashMap<>();
config.put("interceptorType", "backend");