You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by ha...@apache.org on 2026/08/14 06:12:52 UTC
(knox) 02/02: KNOX-3417: KnoxLdapRealm builds the LDAP search filter from the client username without RFC-4515 escaping (#1348)
This is an automated email from the ASF dual-hosted git repository.
hanicz pushed a commit to branch v3.0.0
in repository https://gitbox.apache.org/repos/asf/knox.git
commit cc6b674e33f47d90b7600adab3bb2f181b4e7691
Author: hanicz <ha...@users.noreply.github.com>
AuthorDate: Fri Aug 14 08:10:30 2026 +0200
KNOX-3417: KnoxLdapRealm builds the LDAP search filter from the client username without RFC-4515 escaping (#1348)
(cherry picked from commit 3cb8ef745c44b39377d855d835198de183a4cef3)
---
.../knox/gateway/shirorealm/KnoxLdapRealm.java | 48 +++++++++++++--
.../knox/gateway/shirorealm/KnoxLdapRealmTest.java | 70 ++++++++++++++++++++++
2 files changed, 114 insertions(+), 4 deletions(-)
diff --git a/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealm.java b/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealm.java
index 8b9f78d1a..2306230ba 100644
--- a/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealm.java
+++ b/gateway-provider-security-shiro/src/main/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealm.java
@@ -700,10 +700,10 @@ public class KnoxLdapRealm extends DefaultLdapRealm {
"(&(objectclass=%1$s)(%2$s=%3$s))",
getUserObjectClass(),
userSearchAttributeName,
- expandTemplate( getUserSearchAttributeTemplate(), matchedPrincipal ) );
+ expandTemplate(getUserSearchAttributeTemplate(), matchedPrincipal, true));
}
} else {
- searchFilter = expandTemplate( userSearchFilter, matchedPrincipal );
+ searchFilter = expandTemplate(userSearchFilter, matchedPrincipal, true);
}
SearchControls searchControls = getUserSearchControls();
@@ -749,17 +749,57 @@ public class KnoxLdapRealm extends DefaultLdapRealm {
return new SimpleAuthenticationInfo(token.getPrincipal(), credentialsHash.toHex(), credentialsHash.getSalt(), getName());
}
- private static String expandTemplate( final String template, final Matcher input ) {
+ private static String expandTemplate(final String template, final Matcher input) {
+ return expandTemplate(template, input, false);
+ }
+
+ private static String expandTemplate( final String template, final Matcher input, final boolean escapeForLdapFilter ) {
String output = template;
Matcher matcher = TEMPLATE_PATTERN.matcher( output );
while( matcher.find() ) {
String lookupStr = matcher.group( 1 );
int lookupIndex = Integer.parseInt( lookupStr );
String lookupValue = input.group( lookupIndex );
- output = matcher.replaceFirst( lookupValue == null ? "" : lookupValue );
+ if (lookupValue == null) {
+ lookupValue = "";
+ } else if (escapeForLdapFilter) {
+ lookupValue = escapeLdapSearchFilterValue(lookupValue);
+ }
+ // quoteReplacement is required: replaceFirst treats '\' and '$' in the replacement specially
+ output = matcher.replaceFirst(Matcher.quoteReplacement(lookupValue));
matcher = TEMPLATE_PATTERN.matcher( output );
}
return output;
}
+ private static String escapeLdapSearchFilterValue(final String value) {
+ if (value == null) {
+ return null;
+ }
+ final StringBuilder sb = new StringBuilder(value.length());
+ for (int i = 0; i < value.length(); i++) {
+ final char c = value.charAt(i);
+ switch (c) {
+ case '\\':
+ sb.append("\\5c");
+ break;
+ case '*':
+ sb.append("\\2a");
+ break;
+ case '(':
+ sb.append("\\28");
+ break;
+ case ')':
+ sb.append("\\29");
+ break;
+ case '\0':
+ sb.append("\\00");
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
}
diff --git a/gateway-provider-security-shiro/src/test/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealmTest.java b/gateway-provider-security-shiro/src/test/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealmTest.java
index d26bdbbe0..63261602f 100644
--- a/gateway-provider-security-shiro/src/test/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealmTest.java
+++ b/gateway-provider-security-shiro/src/test/java/org/apache/knox/gateway/shirorealm/KnoxLdapRealmTest.java
@@ -19,13 +19,83 @@
package org.apache.knox.gateway.shirorealm;
+import org.apache.shiro.realm.ldap.LdapContextFactory;
+import org.easymock.Capture;
+import org.easymock.EasyMock;
import org.junit.Test;
+import javax.naming.NamingEnumeration;
+import javax.naming.directory.SearchControls;
+import javax.naming.directory.SearchResult;
+import javax.naming.ldap.LdapContext;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
public class KnoxLdapRealmTest {
+ private static String captureSearchFilter(KnoxLdapRealm realm, String principal) throws Exception {
+ LdapContextFactory factory = EasyMock.createNiceMock(LdapContextFactory.class);
+ LdapContext ctx = EasyMock.createNiceMock(LdapContext.class);
+ NamingEnumeration<SearchResult> results = EasyMock.createNiceMock(NamingEnumeration.class);
+
+ EasyMock.expect(factory.getSystemLdapContext()).andReturn(ctx).anyTimes();
+ Capture<String> filter = EasyMock.newCapture();
+ EasyMock.expect(ctx.search(EasyMock.anyString(), EasyMock.capture(filter),
+ EasyMock.anyObject(SearchControls.class))).andReturn(results);
+ EasyMock.expect(results.hasMore()).andReturn(false).anyTimes();
+ EasyMock.replay(factory, ctx, results);
+
+ realm.setContextFactory(factory);
+ try {
+ realm.getUserDn(principal);
+ } catch (IllegalArgumentException expected) {
+ // mock returns no entry, so getUserDn throws after the search; we only need the filter
+ }
+ return filter.getValue();
+ }
+
+ private static KnoxLdapRealm searchModeRealm() {
+ KnoxLdapRealm realm = new KnoxLdapRealm();
+ realm.setSearchBase("dc=hadoop,dc=apache,dc=org");
+ realm.setUserSearchBase("ou=people,dc=hadoop,dc=apache,dc=org");
+ realm.setUserSearchAttributeName("uid");
+ realm.setUserObjectClass("person");
+ return realm;
+ }
+
+ @Test
+ public void getUserDnEscapesLdapFilterMetacharacters() throws Exception {
+ String filter = captureSearchFilter(searchModeRealm(), "*)(uid=admin");
+ assertEquals("(&(objectclass=person)(uid=\\2a\\29\\28uid=admin))", filter);
+ }
+
+ @Test
+ public void getUserDnEscapesWildcard() throws Exception {
+ String filter = captureSearchFilter(searchModeRealm(), "*");
+ assertEquals("(&(objectclass=person)(uid=\\2a))", filter);
+ }
+
+ @Test
+ public void getUserDnEscapesBackslash() throws Exception {
+ String filter = captureSearchFilter(searchModeRealm(), "a\\b");
+ assertEquals("(&(objectclass=person)(uid=a\\5cb))", filter);
+ }
+
+ @Test
+ public void getUserDnLeavesLegitimateUsernameUnchanged() throws Exception {
+ String filter = captureSearchFilter(searchModeRealm(), "sam");
+ assertEquals("(&(objectclass=person)(uid=sam))", filter);
+ }
+
+ @Test
+ public void getUserDnEscapesValueButPreservesOperatorFilterStructure() throws Exception {
+ KnoxLdapRealm realm = searchModeRealm();
+ realm.setUserSearchFilter("(uid={0})");
+ String filter = captureSearchFilter(realm, "a)(b");
+ assertEquals("(uid=a\\29\\28b)", filter);
+ }
+
@Test
public void setGetSearchBase() {
KnoxLdapRealm realm = new KnoxLdapRealm();