Best Practices for LDAP Client Applications
Table of Contents
- 1. Use secure connections
- 2. Reuse connections
- 3. Use paged searches
- 4. Apply resource limits
- 5. Request only what you need
- 6. Use specific LDAP filters
- 7. Use indexed searches
- 8. Ask the directory server what it supports
- 9. Trust result codes
- 10. Avoid server-side sorting
- 11. Check Result Codes
- 12. Indexed Properties (Appendix)
- 13. Host names and ports
- 14. Certificates
- 15. Attribute Ordering
- 16. Filters involving dates and times
- 17. Filter syntax
- 18. Supported controls in root DSE
- 19. berkeleyEduCalNetIDUpdatedDate format
- 20. Resources
Use secure connections
Always use secure connections when sending credentials for authentication, and when reading or writing any data that is not public.
For LDAP applications, either connect to the directory server's LDAPS port (636), or if possible, begin each session with the StartTLS extended operation on the (cleartext) LDAP port (389).
See https://github.com/ucidentity/ldap-client-examples
Reuse connections
The LDAP protocol is stateful. Typically you bind (connect), search or make an update, and then unbind (disconnect). The server maintains a context and enforces authorization decisions concerning your requests. Your application should reuse connections.
You can make multiple requests without having to set up a new connection and authenticate for every request. Many client libraries for LDAP allow your application to share connections in a pool, avoiding the overhead of setting up and tearing down connections if you use them often. This can significantly improve your application’s performance and reduce load on the directory server.
See https://github.com/ucidentity/ldap-client-examples
Use paged searches
If you suspect that the result set from a given query may be very large, you should be able to retrieve a result set in small pieces. The Simple Paged Result extended control allows this type of retrieval by allowing a one-way traversal of the result set. Options on this control allow the client to set the initial page size and reset the page size with each subsequent request to the server.
The Simple Paged Result control is also used to access all of a large result set when there is a server-side administrative limit to the number of items returned from a query. For example, CalNet LDAP Directory servers have a default server-side limit of 1000 entries as the maximum number of results that are returned in a single request. If the results of a query exceed this limit, the Paged Results control is used with a page size equal to or less than the server-side limit in order to retrieve all of the results of the query.
See https://github.com/ucidentity/ldap-client-examples
Apply resource limits
LDAP client applications can set time limits and size limits on search requests to avoid overuse of server resources. Setting limits is appropriate when the searches your application performs are not fixed, but instead partially or fully determined by user input.
The ldapsearch command has --sizeLimit and --timeLimit options.
Request only what you need
Request the attributes you need explicitly and request all the attributes in the same search. This reduces the number of trips to the server and improves the speed of your application.
Good Search (asks for specific attributes):
ldapsearch -H ldaps://ldap.berkeley.edu -D "$LDAP_USER" -w $LDAP_PWD -b "dc=berkeley,dc=edu" -s sub "(&(objectClass=person)(uid=2))" uid, givenName
Bad Search (asks for all attributes):
ldapsearch -H ldaps://ldap.berkeley.edu -D "$LDAP_USER" -w $LDAP_PWD -b "dc=berkeley,dc=edu" -s sub "(&(objectClass=person)(uid=2))" "*"
Use specific LDAP filters
The difference between a general filter like (berkeleyEduAlternateID=*@berkeley.edu) and a good, specific filter like (berkeleyEduAlternateID=agent.cooper@berkeley.edu) is significant processing time and a very large number of entries, both for the directory server and for your application. In general, try to use short, specific filters. As a rule, prefer equality filters over substring filters.
Furthermore, always use & with ! to restrict the potential result set before returning all entries that do not match part of the filter. For example:(&(berkeleyEduDept=FBI)(!(berkeleyAlternateID=agent.cooper@berkeley.edu)))
Use indexed searches
Some directory servers reject unindexed searches by default because unindexed searches are generally far more resource-intensive. The CalNet directory currently allows unindexed searches but that may change in the future. If your application needs to use a filter that results in an unindexed search, then work with us to find a solution, such as having the directory maintain the indexes required by your application.
See Currently Indexed Properties
Ask the directory server what it supports
Directory servers expose their capabilities, suffixes they support, and other information as attribute values on the root DSE. This allows your application to discover a variety of information at run time, rather than storing configuration separately. Querying the directory about its configuration and the features it supports can make your application easier to deploy and maintain.
For example, rather than hard-coding dc=berkeley,dc=edu as a suffix DN in your configuration, you can search the root DSE on DS servers for namingContexts, and then search under the naming context DNs to locate the desired entries to initialize your configuration.
The directory servers also advertise supported security mechanisms such as the root DSE attributes supportedTLSCiphers and supportedTLSProtocols. The root DSE attribute subschemaSubentry shows the DN of the entry holding LDAP schema definitions.
Trust result codes
The LDAP result codes that your application gets from the directory server are reliable and consistent. For example, if you request a modified operation and you get ResultCode.SUCCESS, then consider the operation a success rather than immediately issuing a search to get the modified entry.
The LDAP replication model is loosely convergent. The directory server will send you ResultCode.SUCCESS before replicating your change to every directory server instance. If you issue a read immediately after a write, and your request is sent to a different directory server instance, you could get an inconsistent result.
Avoid server-side sorting
Directory servers also support a resource-intensive operation called server-side sorting. When your application requests a server-side sort, the directory server retrieves all the entries matching your search, and then returns the whole set of entries in sorted order. For result sets of any size server-side sorting therefore ties up server resources that could be used elsewhere. Alternatives include both sorting the results after your application receives them, and working with our team to enable appropriate browsing (virtual list view) indexes on the directory server for applications that must regularly page through long lists of search results.
Check Result Codes
LDAP result codes are standard and clearly defined, and listed in "LDAP Result Codes". When your application receives a result, it must use the result code value to determine what action to take. When the result is not what you expect, read or at least log the additional message information.
Indexed Properties
The following attributes are indexed and should be preferred for search filters. If your application requires another index please let us know.
| Property | Index Type |
|---|---|
| aci | equality, presence |
| berkeleyEduAffID | equality, presence |
| berkeleyEduAffiliations | equality, presence |
| berkeleyEduAffType | equality, presence |
| berkeleyeduafftypes | equality, presence |
| berkeleyEduAlternateId | equality, presence |
| berkeleyEduAppToken | equality, presence |
| berkeleyEduCADSAffID | equality, presence |
| berkeleyEduCalMailAccountOwner | equality, presence |
| berkeleyEduCalNetAffID | equality, presence |
| berkeleyeducalnetidupdateddate | equality, presence |
| berkeleyEduCalNetIDUpdatedFlag | equality, presence |
| berkeleyeducalnetuidconsolidationdate | equality, presence |
| berkeleyEduCode | equality, presence |
| berkeleyEduConfidentialFlag | equality, presence |
| berkeleyEduCSID | equality, presence |
| berkeleyEduEmailRelFlag | equality, presence |
| berkeleyEduEmpTitleCode | equality, presence |
| berkeleyeduexpdate | equality, presence |
| berkeleyEduGuestSponsorUid | equality, presence |
| berkeleyEduHCMID | equality, presence |
| berkeleyEduIsMemberOf | equality, presence |
| berkeleyEduKerberosPrincipalString | equality, presence |
| berkeleyEduOrgUnitParent | equality, presence |
| berkeleyEduOrgUnitProcessUnitFlag | equality, presence |
| berkeleyEduPersonAddressPrimaryFlag | equality, presence |
| berkeleyEduPrimaryDeptUnit | equality, presence |
| berkeleyEduSPAUsersGroup | equality, presence |
| berkeleyEduStuID | equality, presence |
| berkeleyEduTestIDFlag | equality, presence |
| berkeleyEduUCPathID | equality, presence |
| cn | equality, substring |
| createtimestamp | equality, ordering |
| departmentNumber | equality, presence |
| displayName | equality, presence, substring |
| employeeNumber | equality, presence |
| entryUUID | equality |
| givenName | equality, substring |
| equality, substring | |
| modifytimestamp | equality, ordering |
| objectClass | equality |
| ou | equality |
| sn | equality, substring |
| telephoneNumber | equality, substring |
| UCnetID | equality, presence |
| uid | equality |
LDAP Developer Notes
Host names and ports
The OpenDJ test environments may be reached at ldap-test.berkeley.edu, on TCP port 389 (cleartext and StartTLS) and port 636 (SSL/TLS). The OpenDJ production environment may be reached at ldap.berkeley.edu, on TCP port 389 (StartTLS) and port 636 (SSL/TLS). Note that in the DNS, CNAME alias records for ldap-test.b.e and ldap.b.e point to the canonical names for the corresponding VIP addresses (169.229.54.238 and 169.229.54.228, respectively).
Certificates
Information about the certificate and certificate chain can be found at this page: InCommon Certificate Chain.
Attribute Ordering
It is very unsafe to rely on the attributes being in any specific order when you parse the output from the ldapsearch tool. If you ask for attr1, attr2 and attr3, in that order, your results may not follow the same order.
When used against Oracle DSEE, the ldapsearch tool always returns the attributes you requested in the order you specified, but this is not mandated by either the LDAP standard or the ldapsearch specifications. In fact, OpenDJ (and other LDAP servers) will return the attributes in the most efficient way they can, i.e. by following the order in which the attributes are stored in the backend database.
Therefore, please don't rely on any specific order. The accepted and official way to parse the output of ldapsearch (which is in LDIF format) is by identifying the attributes by their names.
Filters involving dates and times
Dates and times in LDAP must follow the 1.3.6.1.4.1.1466.115.121.1.24 syntax notation, i.e. they must be represented as a string, in the format YYYYMMDDhhmmssZ.
The "hhmmssZ" part is not optional in the standard, yet Oracle DSEE was somewhat lenient and accepted dates in the YYYYMMDD format, by assuming that time was 00:00:00 GMT (Z). OpenDJ is not as lenient, and requires a full time specification, with trailing "Z".
Filter syntax
The LDAP filter specification says that the space character is invalid between an attribute name and the filter assertion. OpenDJ is more strict in this check than Oracle DSEE. Thus (uid =123456) may have worked with DSEE, but will fail with OpenDJ.
Supported controls in root DSE
If you want to read operational attributes of the root DSE (such as supportedcontrol) you need to explicitly use the attribute names in your query, or use the "+" wildcard. Oracle DSEE extended the standard practice to allow the use of the "*" wildcard for operational attributes, but this is not the case with OpenDJ.
berkeleyEduCalNetIDUpdatedDate format
As a result of the migration from Oracle Directory Server to OpenDJ, some applications have encountered issues when searching for entries based on the berkeleyEduCalNetIDUpdatedDate attribute.
We found out that the Manage Your Identity application, that allows people to choose and update their own CalNet ID, populates that attribute without a time zone indication. This was done by design (in order to make things easier for application developers, see Detecting Entry Changes, and it worked just fine with Oracle DSEE, but unfortunately OpenDJ is more strictly compliant to the LDAP specification from RFC 4517, and refuses to allow certain search operations on entries that do not follow the standard.
To restore full search functionality, we need to update the Manage Your Identity application and fix all existing values of the berkeleyEduCalNetIDUpdatedDate to include a time zone indication. The following are some examples of how the content of that attribute will change after the update:
| Existing value | Value after fix |
|---|---|
| 20120918091000 | 20120918091000-0700 (PDT, equivalent to 20130918161000Z) |
| 20121120091000 | 20121120091000-0800 (PST, equivalent to 20121120171000Z) |
| 20120918171000 | 20120918171000-0700 (PDT, equivalent to 20120919001000Z) |
This will require all code that does comparisons or searches with date-time literals to include a time zone as well. According to RFC 4517, acceptable time zone indications include “Z” for UTC (but watch for day rollover, as in the third example), or time differences in the [+|-]hhmm format. Three-letter abbreviations (PDT, PST, EST and so on) are not accepted. A date-time without a time zone indication will also be rejected by OpenDJ.
Since the fix requires to update all entries having a CalNet ID, applications that check for the modifyTimestamp attribute to detect updates will see hundreds of thousands of changes happening in just half an hour or so. If this happens to be your case, please contact the CalNet team as described in the Resources section below.
Resources
Please send any questions and comments to CalNet Support at calnet-admin@berkeley.edu.