Reputation: 1298
An ejb service takes a ldap filter as string and returns a result from ActiveDirectory.
The problem is that sometimes attribute values contain special characters that need to be escaped for the entire filter as specified here:
https://msdn.microsoft.com/en-us/library/aa746475(v=vs.85).aspx
and for distinguished name attibute values as specified here:
https://msdn.microsoft.com/en-us/library/aa366101(v=vs.85).aspx
In order to accomplish this the service must do the following:
Java native javax.naming.ldap.Rdn
escapes dn values all right but is not idempotent. As for the other tasks, so far I have been unable to find a library that would allow me to accomplish them.
Right now I am inclined to think that the job of escaping the ldap filter should be done by the user of the service rather than by the service itself as it is very hard for the service to tell escapes from actual values. Also, parsing a complex string such as a ldap filter without a well tested library seems to me error prone.
Any ideas on how to solve this? Can this task be automated at all?
Upvotes: 7
Views: 18711
Reputation: 531
The answer by Pluto is great and concise, but non-ASCII UTF-8 characters (e.g. é, á, ö, etc.) need special handling too. Here's my verbose solution.
/**
* Filter components need to escape special chars.
* Note that each piece of the filter needs to be escaped,
* not the whole filter expression, for example:
*
* "(&(cn="+ esc("Admins") +")(member="+ esc("CN=Doe\\, Jöhn,OU=ImPeople,DC=ds,DC=augur,DC=com") +"))"
*
* @see Oracle Directory Server Enterprise Edition 11g Reference doc
* @see http://docs.oracle.com/cd/E29127_01/doc.111170/e28969/ds-ldif-search-filters.htm#gdxoy
* @param s A String field within the search expression
* @return The escaped string, safe for use in the search expression.
*/
public static String esc(String s)
{
if(s == null) return "";
StringBuilder sb = new StringBuilder(s.length());
for (byte c : s.getBytes(StandardCharsets.UTF_8))
{
if (c=='\\') { sb.append("\\5c"); }
else if (c=='*') { sb.append("\\2a"); }
else if (c=='(') { sb.append("\\28"); }
else if (c==')') { sb.append("\\29"); }
else if (c==0) { sb.append("\\00"); }
else if ((c&0xff)>127) { sb.append("\\").append(to2CharHexString((c))); } // UTF-8's non-7-bit characters, e.g. é, á, etc...
else { sb.append((char)c); }
}
return sb.toString();
}
/**
* @return The least significant 16 bits as a two-character hex string,
* padded by a leading '0' if necessary.
*/
public static String to2CharHexString(int i)
{
String s = Integer.toHexString(i & 0xff);
if (s.length()==1) return "0"+s;
else return s;
}
Upvotes: 5
Reputation: 3026
If using maven repository apache-ldap-api as LDAP client library, we can use Util class FilterEncoder to handle encoding of special characters in LDAP search filter values as below
filter = "(attributeKey=" + FilterEncoder.encodeFilterValue(attributeValue) + ")";
Upvotes: 0
Reputation: 61378
Here's mine:
static public string LDAPEscape(string s)
{
StringBuilder sb = new StringBuilder(s.Length);
int i;
for (i = 0; i < s.Length; i++)
{
char c = s[i];
if ("/*)(\\\0".IndexOf(c) >= 0)
sb.Append('\\').Append(((uint)c).ToString("x2"));
else
sb.Append(c);
}
return sb.ToString();
}
Upvotes: 0
Reputation: 2817
You can't reliably escape values in an LDAP filter provided by the caller if that filter represents the final filter to be used for the query. Consider the following filter assembled by the caller:
String value = "*)(objectClass=*";
String filter = "(|(attr1=constvalue)(attr2=" + value + "))";
search(filter);
The resulting filter matches all objects because the code does not escape value
:
(|(attr1=constvalue)(attr2=*)(objectClass=*))
There is no way to escape value
based on the final filter because the start and end position cannot be identified anymore.
To resolve this ambiguity and filter injection issue, the input values must be escaped while building the filter, not afterwards. However, this does not mean that the caller has to deal with escaping details directly - that's error-prone, so I don't recommend that.
All it takes for the EJB to handle escaping is knowing the desired filter including placeholders instead of actual values and a list of values. Java's DirContext
already provides a mechanism you can take advantage of. DirContext.search
has a filterExpr
parameter that represents a filter template that can contain placeholders and a filterArgs
parameter representing a list of corresponding values.
With that, the example above would turn into:
String value = "*)(objectClass=*";
String filter = "(|(attr1=constvalue)(attr2={0}))";
search(filter, new String[] { value });
Implementation of search
:
dircontext.search(basedn, filter, valuearray, null);
That is the best compromise I can think of. It fully addresses LDAP filter injection issues, yet it does not require the caller to handle escaping, the caller only has to provide a filter template and a list of values.
Generally, using library facilities for escaping should be preferred over custom code to ensure that all cases are handled correctly. The other two answers illustrate that.
Upvotes: 0
Reputation: 3026
For escaping LDAP filters, I relied on this page to write the code below: http://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Special_Characters
String LdapEscape(String ldap)
{
if(ldap == null) return "";
return ldap.replace("\\", "\\5C").replace("*", "\\2A").replace("(", "\\28").replace(")", "\\29").replace("\000", "\\00");
}
The most important thing to keep in mind here is that replacing \
with \5C
must happen first so that you don't double escape any characters. Otherwise it's very straightforward; there aren't any special tricks to watch out for.
I'd like to point out that this is meant to escape individual values placed in LDAP filters, not the entire LDAP filter. However if you wanted, you could use that function to escape something like this so it can be searched for:
LdapEscape("(!(sn=m*))"); // \28!\28sn=m\2A\29
Upvotes: 8