2 - Groovy LDAP : User Guide

Note: This guide is work in progress and is written at the same time as Groovy LDAP integration developes. The API may change. Feedback highly welcome.

Prerequisites

  • Java SE 5 (JDK 1.5) or above
  • Groovy 1.0 or above

Installation

You only have to add the jar file groovy-ldap.jar with the Groovy LDAP classes to the class path. If you plan to write Groovy scripts whoich use the functionality, an easy option is to copy the library into the lib folder of your Groovy installation.

That's it.

Connection to an LDAP server

Main entry point is the class LDAP from the org.apache.directory.groovyldap package (contained in groovy-ldap.jar). You obtain an instance via a call to a static method newInstance, like this:

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance('ldap://zanzibar:10389/')
...

The argument used here is the LDAP URL of your directory server and contains hostname and port. This method as shown above is for anonymous binds. A variant of it expects the bind name of a user and his/her credentials:

...
ldap = LDAP.newInstance('ldap://zanzibar:10389/', 'uid=admin,ou=system', '******')
...

Please note that Groovy LDAP uses JNDI and its configuration facilities. This means that if you provide a jndi.properties file in your class path, Groovy LDAP will take these parameters into account. This enables you to keep your user name and password outside of the script code, for instance.

Like with JNDI with its InitialContext, calling newInstance will not connect to the LDAP server immediately. This means that an authentication will likely occur later (with the first operation).

Basic LDAP operations

Groovy LDAP basically supports the operations provided by the native LDAP protocol, with some exceptions:

  • It is not necessary to connect explicitly to the server or to bind/unbind. Groovy LDAP performs these operations automatically.
  • LDAP controls are not supported yet
  • No asynchronous calls are supported yet. Consequently, no abandon operation is available in the API

LDAP add operation

The add operation is used to create a new entry within the directory.

One option in Groovy LDAP is to define the attribute values of the new entry as a Map. One add operation takes the distinguished name (DN) of the new entry (as a String) and the attributes map as parameters. Here is an example:

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance('ldap://zanzibar:10389', 'uid=admin,ou=system' ,'******')

assert ! ldap.exists('cn=Heather Nova,dc=example,dc=com')

// define the attributes as a map
attrs = [
  objectclass: ['top', 'person'],              
  sn: 'Nova',
  cn: 'Heather Nova'
]
ldap.add('cn=Heather Nova,dc=example,dc=com', attrs)

assert ldap.exists('cn=Heather Nova,dc=example,dc=com')

In the assert expressions, the exists method of Groovy LDAP is used. It checks whether an entry with this DN already exists (see below).

If no error occurs, the script adds the following entry to the directory:

dn: cn=Heather Nova,dc=example,dc=com
cn: Heather Nova
sn: Nova
objectClass: person
objectClass: top

LDAP delete operation

The delete operation is used to delete an existing entry within the directory. As in the original LDAP protocol, only the deletion of leaf entries is allowed. Deletion of a subtree of entries (for instance with the Tree Delete Control) is not supported yet.

Here is an example script which uses the delete method from Groovy LDAP.

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance('ldap://zanzibar:10389', 'uid=admin,ou=system' ,'******')

assert ldap.exists('cn=Heather Nova,dc=example,dc=com')

// delete the entry with the given DN
ldap.delete('cn=Heather Nova,dc=example,dc=com')

assert !ldap.exists('cn=Heather Nova,dc=example,dc=com')

Note that if the given entry does not exist, calling delete throws a javax.naming.NameNotFoundException. This is different to JNDI, where no error will occur in this case. The behavior here imitates the original LDAP delete operation. In the example script, this will not occur due to the assert statements.

LDAP search operation

import org.apache.directory.groovyldap.*

ldap = LDAP.newInstance('ldap://zanzibar:10389/')

// search op
results = ldap.search('(objectClass=*)', 'dc=example,dc=com', SearchScope.ONE )
println "${results.size} entries found:"
for (entry in results) {
  println entry.dn
}

The output looks like:

3 entries found:
cn=Tori Amos,dc=example,dc=com
cn=Kate Bush,dc=example,dc=com
cn=Heather Nova,dc=example,dc=com

Learn more about options on how to search within a directory in the [Reference|5. Groovy LDAP Reference]

LDAP compare operation

The compare operation is used to check attribute value assertions.

The following script adds an entry and uses compare several times to demonstrate the different matching rules for attribute type cn (case insensitive) and userPassword (case sensitive).

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance('ldap://zanzibar:10389', 'uid=admin,ou=system' ,'******')

assert ! ldap.exists('cn=Heather Nova,dc=example,dc=com')

// define an entry for compare ops
attrs = [
  objectclass: ['top', 'person'],              
  sn: 'Nova',
  cn: 'Heather Nova',
  userPassword: 'secret'
]
ldap.add('cn=Heather Nova,dc=example,dc=com', attrs)

assert ldap.exists('cn=Heather Nova,dc=example,dc=com')

assert ldap.compare('cn=Heather Nova,dc=example,dc=com', [cn: 'Heather Nova'] )
assert ldap.compare('cn=Heather Nova,dc=example,dc=com', [cn: 'HEATHER NOVA'] )

assert ldap.compare('cn=Heather Nova,dc=example,dc=com', [userPassword: 'secret'] )
assert ! ldap.compare('cn=Heather Nova,dc=example,dc=com', [userPassword: 'SECRET'] )

ldap.delete('cn=Heather Nova,dc=example,dc=com')

LDAP modify operation

I am not perfectly happy with the syntax for modify yet. Here are two calls that perform modifications against the entry added above:

import org.apache.directory.groovyldap.*

ldap = LDAP.newInstance('ldap://zanzibar:10389', 'uid=admin,ou=system' ,'secret')

dn = 'cn=Heather Nova,dc=example,dc=com'

// Adding a single attribute
descr = [ description: 'a singer-songwriter' ]
ldap.modify(dn, 'ADD', descr)

// performing two operations atomically
mods = [ 
    [ 'REPLACE', [description: 'a singer-songwriter, born in Bermuda'] ],
    [ 'ADD', [userPassword: 'secret'] ]
]
ldap.modify(dn, mods)

The second call uses a list mods of modifications as an argument; each modification consists of a list of two elements (a pair): the modification type and a map with the attribute(s).

Afterwards, the entry looks like this in the directory

dn: cn=Heather Nova,dc=example,dc=com
cn: Heather Nova
sn: Nova
objectClass: person
objectClass: top
userpassword: secret
description: a singer-songwriter, born in Bermuda

Additions to the original LDAP API

LDAP.exists(...): Checking whether an entry exist

implemented, but not documented yet

LDAP.read(...): Reading an entry by its distinguished name

I have included a read operation as in the Netscape API -- note that there is nothing like that in LDAP (you have to perform a search for it). Nevertheless, it is quite handy to have it. In this example I have use an anonymous LDAP connection. My LDAP server allows me to read entries without user and password (which is quite common).

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance("ldap://zanzibar:10389")

// Simple entry lookup via dn
heather = ldap.read('cn=Heather Nova,dc=example,dc=com')

print """
DN: ${heather.dn}
Common name: ${heather.cn}
Object classes: ${heather.objectclass}
"""

The output (if this entry exists) is:

DN: cn=Heather Nova,dc=example,dc=com
Common name: Heather Nova
Object classes: ["person", "top"]

LDAP the Groovy way

Performing searches with closures

There is a search variant in the LDAP class comparable to eachRow in GSQL. It takes a closure as parameter. I have added the search base to the ldap URL. There is also a variant which takes the base and a scope (defaults to subtree).

import org.apache.directory.groovyldap.LDAP

ldap = LDAP.newInstance('ldap://zanzibar:10389/dc=example,dc=com')

ldap.eachEntry ('(objectClass=person)') { person ->  
    println person.cn
}

The output looks like:

Tori Amos
Kate Bush
Heather Nova