An LDAP v3 Client Library for Dart

The Lightweight Directory Access Protocol (LDAP) is a protocol for accessing directories.

An LDAP directory is organised as a hierarchy of entries, where one or more root entries are allowed. Each entry can be identified by a distinguished name, which is an ordered sequence of attribute/value pairs. Each entry contains a set of attributes. Attributes have a name and are associated with a set of one or more values (i.e. attributes can be repeated and are unordered).

This library can be used to query (search for and compare entries) and modify (add, delete and modify) LDAP directories.

This library supports the LDAP v3 protocol, which is defined in IETF RFC 4511.

Using dartdap

Overview

To perform operations on an LDAP directory, the basic process is:

  1. Create an LDAPConnection object.
  2. Connect to the LDAP directory (using the connect method).
  3. Authenticate to the LDAP directory, if needed (using the bind method).
  4. Perform LDAP operations (e.g. search, add, delete methods).
  5. Close the connection (using the close method).

Please reference dartdap in pubspec.yaml using dartdap: "^0.1.0" so Dart versioning will prevent breaking changes from being used. See the bottom of this page for some notes about recent breaking changes.

Basic example

import 'package:dartdap/dartdap.dart';

...

// Step 1: create an LDAP connection object

var host = "localhost";
var port = 10389; // null = use default LDAP/LDAPS port
var ssl = false;
var bindDN = "cn=Manager,dc=example,dc=com"; // null = unauthenticated bind
var password = "[email protected]";

var connection = new LDAPConnection(host, ssl: ssl, port: port);

try {
  // Step 2: connect to the LDAP directory

  await connection.connect();

  // Step 3: authenticate to the LDAP directory

  await connection.bind(bindDN, password);

  // Step 4: perform search operation

  var base = "dc=example,dc=com";
  var filter = Filter.present("objectClass");
  var attrs = ["dc", "objectClass"];

  var count = 0;

  await for (var entry in connection.search(base, filter, attrs).stream) {
    // Processing stream of SearchEntry
    count++;
    print("dn: ${entry.dn}");

    // Getting all attributes returned
    for (var attr in entry.attributes.values) { // entry.attributes is a Map<String,Attribute>
      for (var value in attr.values) { // attr.values is a Set
        print("  ${attr.name}: $value");
      }
    }

    // Getting a particular attribute
    assert(entry.attributes["dc"].values.length == 1); // expecting one value
    var dc = entry.attributes["dc"].values.first;
    print("# dc=$dc");
  }

  print("# Number of entries: ${count}");

} catch (e) {
  print("Exception: $e");

} finally {
  // Step 5: close the connection
  await connection.close();
}

Step 1: create an LDAP connection object

The first step is to instantiate an LDAPConnection object using its constructor. If the port is null, the default port is used based on whether ssl is true or not (port 389 when SSL is not used, port 636 when SSL is used).

The binding parameters (bindDN and password) are optional.

Step 2: connect to the LDAP directory

The connect method is called to establish a network connection to the LDAP directory. It uses the host, port and ssl properties of the object. It returns a future which completes when the connection has been established. If the connection cannot be established an exception will be thrown: either LdapSocketServerNotFoundException or LdapSocketRefusedException.

Step 3: authenticate to the LDAP directory (optional)

Call the bind method to establish an authenticated bind.

The credentials (bindDN and password) can be provided as parameters to the bind method. If they are not provided, the default credentials in the object (e.g. set via its constructor) will be used.

If the bind fails, an exception will be thrown.

Step 4: perform search operation

This example performs a search operation.

The search method returns a SearchResult object, from which a stream of SearchEntry objects can be obtained. The results are obtained by listening to the stream (which in the example is done using the "await for" syntax).

The SearchEntry contains the entry's distinguished name and the attributes returned. The dn is a String. The attributes is a Map from the name of the attribute (a String) to an Attribute.

An Attribute has a values member, which returns a Set of the values of the attribute. It is a Set because LDAP allows attributes to have multiple values. It also has a name member, which is the name of the attribute as a String.

Step 5: close the connection

When finished with the connection, call the close method.

In the above example, the close is performed in the finally section, to ensure it gets closed even if an exception is thrown.

Adding entries

try {
  var attrs = {
    "objectClass": ["organizationalUnit"],
    "description": "Example organizationalUnit entry"
  };

  await ldap.add("ou=Engineering,dc=example,dc=com", attrs);

} on LdapResultEntryAlreadyExistsException catch (_) {
  // cannot add entry because it already exists

} on LdapException catch (e) {
  // some other problem
  
}

Modifying entries

try {
  var mod1 = new Modification.replace("description", ["Engineering department"]);
  await ldap.modify("ou=Engineering,dc=example,dc=com", [mod1]);

} on LdapResultObjectClassViolationException catch (_) {
  // cannot modify entry because it would violate the schema rules

} on LdapException catch (e) {

}

Moving entries

try {
  await ldap.modifyDN(oldDN, newDN);

} on LdapException catch (e) {

}

Comparing entries

try {
  r = await ldap.compare("ou=Engineering,dc=example,dc=com", "description", "ENGINEERING DEPARTMENT");
  if (r.resultCode == ResultCode.COMPARE_FALSE) {
  
  } else if (r.resultCode == ResultCode.COMPARE_TRUE) {
  
  } else {
    assert(false);
  }

} on LdapException catch (e) {

}

Deleting entries

try {
  await ldap.delete("ou=Business Development,dc=example,dc=com");

} on LdapResultNoSuchObjectException catch (_) {
  // entry did not exist to delete

} on LdapException catch (e) {

}

Older example

This is an example of using dartdap without using the new await/async Dart syntax.

import 'package:dartdap/dartdap.dart';

void main() {
  var ldap = new LDAPConnection("ldap.example.com"
                                ssl: false, port: 389);



  ldap.connect()
  .then((LDAPConnection ldap) {
    ldap.bind("cn=admin,dc=example,dc=com", "[email protected]")
  .then(LDAPConnection ldap)

    var base = "dc=example,dc=com";
    var filter = Filter.present("objectClass");
    var attrs = ["dn", "cn", "objectClass"];

    print("LDAP Search: baseDN=\"${base}\", attributes=${attrs}");

    var count = 0;

    ldap.search(base, filter, attrs).stream.listen(
        (SearchEntry entry) => print("${++count}: $entry"),
        onDone: () => print("Found ${count} entries"));
  });
  });
}

Exceptions

Methods in the package throws exceptions which are subclasses of the LdapException abstract class.

See the LdapException class for more details.

Breaking changes

Planned changes for future releases

  • Renaming of other classes and methods to follow the Dart conventions. For example, LDAPConnection and LDAPResult to become LdapConnection and LdapResult, respectively.

  • Considering deprecating the DN since it offers limited value (syntax is not any more readable than using normal String operations)

v0.0.9 to v0.1.0

  • Library is now called "dartdap" instead of "ldap_client". There was a disconnect: package X was imported, but only library Y was imported. That would have been ok if there were multiple libraries in dartdap (or plans to produce a LDAP server library), but it currently only contains one publically visible library.

  • Internal organisation of libraries/imports/exports have been cleaned up. This should not be noticable by existing code, unless it was directly referencing those internal libraries or files.

  • LDAPConnection deprecated. Programs should use whatever configuration mechanism they normally use (e.g. databases or configuration files) rather than having to use a special configuration mechanism only for dartdap (and still having to use the other configuration mechanism for the rest of the program). It is also unsafe due to a race condition that could occur.

  • LDAPException renamed to LdapException to follow the Dart conventions.

  • New exceptions for all the LDAP result error conditions have been created and LDAP operations now throw them. Instead of checking the LDAPResult resultCode returned by the LDAP operations, catch the new exceptions.

  • SocketException exceptions are now being internally caught and thrown in LdapSocketException objects. This make it easier to detect common failure conditions. Instead of catching SocketException, catch the new LdapSocketException (or its subclasses).

Libraries

dartdap

LDAP v3 client library.