Building LDAP Filters
The filter in an LDAP query restricts the objects that the search will return. It is the equivalent of the WHERE clause in the ubiquitous SQL SELECT statement in the RDBMS world. For example, we could use a filter to limit a search of an entire Active Directory domain naming context to find a specific user's object by their login name.
We rarely want to search for every object in the directory or within a certain scope, so it is critical to learn to use LDAP filters effectively.
The grammar and rules for LDAP filters are part of the LDAP version 3 specification and they are defined in RFC 2254. The syntax is actually quite simple compared to that of many other query languages. We can be productive creating LDAP filters in just a few minutes.
We specify the DirectorySearcher's filter using its Filter property. If no value is specified, the default (objectClass=*) filter is used. This filter matches any object and is similar to using SELECT * in the SQL world.
Basic Syntax
At their most basic level, filters are composed of what are called filter comparisons or components. Here is the basic format for any individual comparison:
()
For example:
(displayName=John Doe)
In this example, the attribute name is displayName, the filter type is =, and the attribute value is John Doe. The surrounding parentheses, (), are required to delimit an individual filter component.
To create a complex filter, individual components are composed into a filter list using logical AND, OR, and NOT operations (specified as &, |, and !). For a filter with two components that must both be true, the filter list might look like this:
(&(displayName=John Doe)(telephoneNumber=5551212))
Here the two comparisons are nested inside an & operation, so both must be true to satisfy the filter. This nesting can be done to arbitrary depths to create some very complex expressions. Here is a more complicated example.
(|(&(displayName=John Doe)(telephoneNumber=5551212)) (&(displayName=Mike Smith)(telephoneNumber=5551000)))
This filter requires that the object have either a displayName of John Doe and a phone number of 5551212, or a displayName of Mike Smith and a phone number of 5551000.
Filter Types
The comparison itself is often called the filter type, and there are seven valid filter types, as listed in Table 4.1.
Filter Type Symbol |
Filter Type Description |
---|---|
= |
Equal |
~= |
Approximately equal |
>= |
Greater than or equal |
<= |
Less than or equal |
attrib:matchingrule |
Extensible |
=* |
Presence |
= [initial] any [final] |
Substring |
Equal
This filter type is the most straightforward. It simply checks for equality and can be used with all attribute types if the value part can be interpreted correctly. Here are some examples:
(sn=dunn) Last Name equals "dunn" (isDeleted=TRUE) Boolean equality
Approximately Equal
This filter type is intended to indicate that a value is approximately equal to the actual value. In practice, this filter type does not seem to produce predictable results and it is not used frequently.
Greater Than or Equal
This filter type compares the value to see if it is greater than or equal to the attribute value. For instance:
(lockoutTime>=1)
Note that not all attribute syntaxes may use this filter type, although it is generally available to strings, dates, and numbers.
Less Than or Equal
This filter type is similar to >= and has the same restrictions on usage. Note that LDAP filters do not support the simple > and < semantics. The = logic is always included. As such, we must sometimes remember to add or subtract one from our comparisons.
Extensible
Extensible filters also allow provider-specific matching rules to be used. The rule is defined by an Object Identifier (OID) and it uses syntax like this:
(userAccountControl:1.2.840.113556.1.4.803:=2)
Active Directory and ADAM define two extensible filter types for doing bitwise AND and OR comparisons on numeric attributes. In this example, we are searching for objects that have bit 2 set, indicating in this case that the account is disabled.
Presence
This filter type allows us simply to check whether an object has a specific attribute. The presence filter type does not specify a value in the comparison. The value is not *. Instead, the whole filter type is =*. This may seem like a minor point, but a presence filter type is different from a substring filter type, even though they both use *. Please do not confuse them.
As an example, the following filter will find objects with a displayName attribute:
(displayName=*)
All attribute syntaxes may use presence filter types.
Substring
The substring filter type allows us to match part of a string using the familiar * character as a wildcard placeholder. For example, the following filter would find anyone named Frank or Frances:
(givenName=fran*)
The wildcard character may be placed anywhere in the string, including at the beginning or end, and multiple wildcard characters may be used. As such, all of these are valid as well:
(givenName=*rank) (givenName=Fr*nk) (givenName=F*an*)
The value must have at least one character other than the wildcard placeholder in order to differentiate a substring filter type from a presence filter type.
Not all attribute syntaxes support substring searches, but all of the standard string syntaxes do.
Reserved Characters in Values
Much as we would in any other language, we need to escape reserved characters if we wish to search for the intrinsic value itself. To escape reserved characters, we need to replace the character with its ASCII hex value equivalent. Table 4.2 lists the reserved characters and their escape sequences. Notice that the escape sequence comprises just the character (indicating binary data) and the hex of the ASCII character equivalent.
Character |
Escape Sequence |
---|---|
* |
2A |
( |
28 |
) |
29 |
|
5C |
NUL |
|