6.3. Working With Search Filters

When you look someone up in the telephone directory, you use the value of one attribute of a person's entry (last name), to recover the person's directory entry, which has other attributes (phone number, address). LDAP works the same way. In LDAP, search requests identify both the scope of the directory entries to consider (for example, all people or all organizations), and also the entries to retrieve based on some attribute value (for example, surname, mail address, phone number, or something else). The way you express the attribute value(s) to match is by using a search filter.

LDAP search filters define what entries actually match your request. For example, the following simple equality filter says, "Match all entries that have a surname attribute (sn) value equivalent to Jensen."

(sn=Jensen)

When you pass the directory server this filter as part of your search request, the directory server checks the entries in scope for your search to see whether they match.[7] If the directory server finds entries that match, it returns those entries as it finds them.

The example, (sn=Jensen), shows a string representation of the search filter. The OpenDJ LDAP SDK lets you express your filters as strings, or as Filter objects. In both cases, the SDK translates the strings and objects into the binary representation sent to the server over the network.

Equality is just one of the types of comparisons available in LDAP filters. Comparison operators include the following.

Table 6.1. LDAP Filter Operators

OperatorDefinitionExample
=

Equality comparison, as in (sn=Jensen).

This can also be used with substring matches. For example, to match last names starting with Jen, use the filter (sn=Jen*). Substrings are more expensive for the directory server to index. Substring searches therefore might not be permitted for many attributes.

"(cn=My App)" matches entries with common name My App.

"(sn=Jen*)" matches entries with surname starting with Jen.

<=

Less than or equal to comparison, which works alphanumerically.

"(cn<=App)" matches entries with commonName up to those starting with App (case-insensitive) in alphabetical order.

>=

Greater than or equal to comparison, which works alphanumerically.

"(uidNumber>=1151)" matches entries with uidNumber greater than 1151.

=*

Presence comparison. For example, to match all entries having a userPassword, use the filter (userPassword=*).

"(member=*)" matches entries with a member attribute.

~=

Approximate comparison, matching attribute values similar to the value you specify.

"(sn~=jansen)" matches entries with a surname that sounds similar to Jansen (Johnson, Jensen, and so forth).

[:dn][:oid]:=

Extensible match comparison.

At the end of the OID or language subtype, you further specify the matching rule as follows:

  • Add .1 for less than

  • Add .2 for less than or equal to

  • Add .3 for equal to (default)

  • Add .4 for greater than or equal to

  • Add .5 for greater than

  • Add .6 for substring

(uid:dn:=bjensen) matches entries where uid having the value bjensen is a component of the entry DN.

(lastLoginTime: 1.3.6.1.4.1.26027.1.4.5:=-13w) matches entries with a last login time more recent than 13 weeks.

You also use extensible match filters with localized values. Directory servers like OpenDJ support a variety of internationalized locales, each of which has an OID for collation order, such as 1.3.6.1.4.1.42.2.27.9.4.76.1 for French. OpenDJ also lets you use the language subtype, such as fr, instead of the OID.

"(cn:dn:=My App)" matches entries who have My App as the common name and also as the value of a DN component.

!

NOT operator, to find entries that do not match the specified filter component.

Take care to limit your search when using ! to avoid matching so many entries that the server treats your search as unindexed.

'!(objectclass=person)' matches non-person entries.

&

AND operator, to find entries that match all specified filter components.

'(&(l=Cupertino)(!(uid=bjensen)))' matches entries for users in Cupertino other than the user with ID bjensen.

|

OR operator, to find entries that match one of the specified filter components.

"|(sn=Jensen)(sn=Johnson)" matches entries with surname Jensen or surname Johnson.

When taking user input, take care to protect against users providing input that has unintended consequences. OpenDJ SDK offers several Filter methods to help you. First, you can use strongly typed construction methods such as Filter.equality().

String userInput = getUserInput();
Filter filter = Filter.equality("cn", userInput);

// Invoking filter.toString() with input of "*" results in a filter
// string "(cn=\2A)".

You can also let the SDK escape user input by using a template with Filter.format() as in the following example.

String template = "(|(cn=%s)(uid=user.%s))";
String[] userInput = getUserInput();
Filter filter = Filter.format(template, userInput[0], userInput[1]);

Finally, you can explicitly escape user input with Filter.escapeAssertionValue().

String baseDN = "ou=people,dc=example,dc=com";
String userInput = getUserInput();

// Filter.escapeAssertionValue() transforms user input of "*" to "\2A".
SearchRequest request = Requests.newSearchRequest(
        baseDN, SearchScope.WHOLE_SUBTREE,
        "(cn=" + Filter.escapeAssertionValue(userInput) + "*)", "cn", "mail");


[7] In fact, the directory server probably checks an index first, and might not even accept search requests unless it can use indexes to match your filter rather than checking all entries in scope.