2.1 Connect and Search in the LDAP Server - rukichen/GrailsGroovyLDAP GitHub Wiki

Topics:
2.1.1 Writing a Groovy Program
2.1.2 Open Connection
2.1.3 Close Connection
2.1.4 Keep Connection open
2.1.5 Binding
2.1.6 Searching
2.1.7 Print Search
2.1.8 Complex Searches

2.1.1 Writing a Groovy Program

As you should know groovy is a language based on java. Due to that fact we can use the Apache API to communicate with an LDAP Server. But because the Apache API is intended for Java use, we have to make some compromises to our groovy programming. Still, most of the simplicity of groovy is kept as far as possible.

To start. we need to add a main method into our class. In this method we can start working with our API.

class FirstGroovy { 
    static void main(def args) { 
        ...
	}
}  

2.1.2 Open Connection

Starting of with the connection to the LDAP Server. Add this line into our main method.

def connection = new LdapNetworkConnection( "localhost", 10389)  

The editor will ask you to import the following lib : org.apache.directory.ldap.client.api.LdapNetworkConnection

The first argument of LdapNetworkConnection is the name or address of your host. In our case it is localhost. The second argument is the port number. Usually the default port for LDAP Server is 389 and 636 for the secure connection. Always check if the ports are open and listening( on linux by default all ports are closed).

For secure connection:

def connection = new LdapNetworkConnection( "localhost", 10636, true )

2.1.3 Close Connection

If you don't need the connection any more close it by:

connection.close()

2.1.4 Keep Connection open

By default the connection will be kept open for 30 seconds. If you need a longer connection you can set a timer like this:

connection.setTimeOut( 0 )   

Setting a value equal or below 0 will keep the connection open forever. So don't forget to close it after you finished.

2.1.5 Binding

In LDAP, if you want to access the data, the common way to do it is to bind to the server. However, it's important to understand that binding is a different from connecting.
A connection to an LDAP server opens a socket between the client and the server. You must provide the address and the port. The bind operation creates a Session which will hold user information for the time of the session. This information is limited, but includes the user's credentials.
But it's possible to bind anonymously, which doesn't require a user or password, and still be able to send requests to the server (but server can forbid anonymous binds).

Anonymous bind:

connection.bind()

user/password bind:

connection.bind( "uid=admin,ou=system", "secret" ) 

In our local server, you will need to bind via user/password bind.

When you finish, unbind.t's important to know that when you issue an Unbind, the connection is dropped. It's done like this:

connection.unBind()

Unimportant Fact:
you can also bind like this:

connection.bind( "uid=admin,ou=system" )

It is like an anonymous bind, with telling the server your name. So not anonymous anymore

2.1.6 Searching

Now we can connect to a server and opened a session by bind. We are ready for some data.

Simple Searching

def cursor = connection.search( "ou=system", "(objectClass=*)", SearchScope.ONELEVEL)
...
cursor.close()  

The simple search is done by the method search(). It needs three arguments. The first one is the starting point, where to search , we used here ou=system along with its children, which have an ObjectClass attribute.

The second argument is filter for searching. This gets more important the more explicit your search is.
The insides of the filter are called connectors. These will help you to define your target. The construction is like this: (& (node1) (node2) ... (nodeN)) You can connect one ore more nodes with AND(&), OR(|) and NOT(!). But don't for the the brackets, if you us more than one connector. It can get quite confusing quite fast.
In addition to this you can use expressions for you nodes. For example objectClass= organizationalUnit.

Expression In Words Explanation
= Equality the entry matches
=* Presence it has the Attribute on the left side
>= Superior superior to the right part
<= Inferior inferior to the right part
=[start][*][middle][*][final] Substring the entry should match a substring

The third argument is setting the SearchScope, this is way it will search through the data. There are three ways of searching: ONELEVEL, OBJECT and SUBTREE. To show you what the difference is I will give you the output of our current DB we have implemented so far.

ONELEVEL
IT will return all elements below the given DN, but not the element associated with the DN.

Entry
    dn: prefNodeName=sysPrefRoot,ou=system
    objectClass: top
    objectClass: organizationalUnit
    objectClass: extensibleObject
    prefNodeName: sysPrefRoot
    
Entry
    dn: ou=configuration,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: configuration
   
Entry
    dn: ou=consumers,ou=system
    objectclass: top
    objectclass: organizationalUnit
    ou: consumers

Entry
    dn: ou=groups,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: groups

Entry
    dn: uid=admin,ou=system
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: inetOrgPerson
    objectClass: tlsKeyInfo
    publicKeyFormat: X.509
    keyAlgorithm: RSA
    uid: admin
    privateKey: 0x30 0x82 0x01 0x54 0x02 0x01 0x00 ...
    privateKeyFormat: PKCS#8
    displayName: Directory Superuser
    publicKey: 0x30 0x5C 0x30 0x0D 0x06 0x09 0x2A ...
    userCertificate: 0x30 0x82 0x01 0x71 0x30 0x82 ...
    sn: administrator
    cn: system administrator
    userPassword: 0x73 0x65 0x63 0x72 0x65 0x74 

Entry
    dn: ou=users,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: users 

OBJECT
It will return the entry for a given DN, if it exists. You could use LdapConnection.lookup if the filter is irrelevant.

Entry
    dn: ou=system
    objectClass: top
    objectClass: organizationalUnit
    objectClass: extensibleObject
    ou: system

SUBTREE
IT will return all the elements starting from the DN, including the element associated with the DN, no matter how deep the tree.

Entry
    dn: prefNodeName=sysPrefRoot,ou=system
    objectClass: top
    objectClass: organizationalUnit
    objectClass: extensibleObject
    prefNodeName: sysPrefRoot

Entry
    dn: cn=Administrators,ou=groups,ou=system
    objectClass: top
    objectClass: groupOfUniqueNames
    uniqueMember: 0.9.2342.19200300.100.1.1=admin,2.5.4.11=system
    cn: Administrators

Entry
    dn: ou=services,ou=configuration,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: services

Entry
    dn: ou=system
    objectClass: top
    objectClass: organizationalUnit
    objectClass: extensibleObject
    ou: system

Entry
    dn: ou=configuration,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: configuration

Entry
    dn: ou=consumers,ou=system
    objectclass: top
    objectclass: organizationalUnit
    ou: consumers

Entry
    dn: ou=interceptors,ou=configuration,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: interceptors

Entry
    dn: ou=groups,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: groups

Entry
    dn: uid=admin,ou=system
    objectClass: top
    objectClass: person
    objectClass: organizationalPerson
    objectClass: inetOrgPerson
    objectClass: tlsKeyInfo
    publicKeyFormat: X.509
    keyAlgorithm: RSA
    uid: admin
    privateKey: 0x30 0x82 0x01 0x54 0x02 0x01 0x00 0x30 ...
    privateKeyFormat: PKCS#8
    displayName: Directory Superuser
    publicKey: 0x30 0x5C 0x30 0x0D 0x06 0x09 0x2A 0x86 ...
    userCertificate: 0x30 0x82 0x01 0x71 0x30 0x82 0x01 ...
    sn: administrator
    cn: system administrator
    userPassword: 0x73 0x65 0x63 0x72 0x65 0x74 

Entry
    dn: ou=users,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: users

Entry
    dn: ou=partitions,ou=configuration,ou=system
    objectClass: top
    objectClass: organizationalUnit
    ou: partitions

2.1.7 Print Search

Now that we found what we are looking for, we want to see it. For printing you will need to get all the entries out of your search object the cursor. In groovy iteration through an object is quite easy. With 'cursor.each' it will to every Object within. In cursors we have the entries, so you tell him for every entry print it in the curly brackets after 'each'.

cursor.each { entry ->  
        println entry
}

We can use the print to define more detailed what we want to see. For example if you want to see only the uid of the users in our db. First we need to change the search to ou=users,dc=example,dc=com to get the user table. We will get than all users with their details if we print only that. If we add now a .get("uid") at the entry in the print, it will print uid: uids. If you only want the uids with out the definition write an extra .get() at the end.

def cursor = connection.search( "ou=users,dc=example,dc=com",
                                       "(objectClass=*)", SearchScope.ONELEVEL)
cursor.each { entry ->
             println entry.get("uid")
}

This works with every entry, but you will have to know the identifier to get the wanted information.

2.1.8 Complex Searches

What we haven seen so far was the simple search. We searched a basic way. Often this might be enough for your own purpose, but sometimes it has to be more detailed. In Apache we can open a Search Request and give it the details we want.

def req = new SearchRequestImpl()
req.setScope(SearchScope.SUBTREE)
req.addAttributes("*")
req.setTimeLimit(0)
req.setBase( new Dn("ou=users,dc=example,dc=com"))
req.setFilter("(cn=jane austen)")

The possible commands are here:

command explaination
setBase( new Dn()) The base DN
setFilter("( )") The filter
setScope() the scope
setSizeLimit( ) The size limit
setTimeLimit( ) The time limit
addAttributes(" ") The list of attributes to return
setDerefAliases( ) The alias dereferencing mode (one of DEREF_ALWAYS,
DEREF_FINDING_BASE_OBJ, DEREF_IN_SEARCHING
or NEVER_DEREF_ALIASES)
setTypesOnly( ) The TypesOnly flag (to get the AttributeTypes but no values)
addControl( ) The controls

We send the request to the server and save the result in searchCursor. We will have to iterate through the it to get all response. In the response we check if it is the a S_earchResultEntry_ and get the ResultEntry out and print it.

def searchCursor = connection.search(req)
while (searchCursor.next())	{
    def response = searchCursor.get()
    if (response instanceof SearchResultEntry) {
        def resultEntry = ((SearchResultEntry)response).getEntry()
        println resultEntry
    }
}
searchCursor.close()

You can see, this is more complicated than the first search but it gives you more options and control over the search.

<<< Back 1.3. Setting up a local LDAP-Server 2.2 Adding entries in to the server Next>>>
⚠️ **GitHub.com Fallback** ⚠️