10 min read

This is the third article in the article mini-series on Python LDAP applications by Matt Butcher. The first part deals with the installation and configuration of Python-LDAP library, and the binding-unbinding operations, and changing of the LDAP password. The second article takes a look at some of LDAP operations.

In this article we will see some more LDAP operations such as add operation, delete operation etc. Then we will take a look at LDAP URL Library.

The ModRDN Operation

Another simple write operation that can be done through the Python-LDAP API is the ModRDN operation. This operation is used to change the relative DN (RDN) of a record.

We can change an RDN using the modrdn() or modrdn_s() method. These two methods take three parameters:

  • The full DN
  • The new RDN
  • An optional flag indicating whether the attribute corresponding to the RDN should be deleted from the record

For example, if we want to change the UID attribute for uid=manny,ou=users,dc=example,dc=com, we will need to use a ModRDN operation, since this attribute is used in the DN. Here’s an example for changing the UID from manny to immanuel.

>>> l.modrdn_s('uid=manny,ou=users,dc=example,dc=com',
... 'uid=immanuel', False)
(109, [])
>>> l.compare_s('uid=immanuel,ou=users,dc=example,dc=com','uid',
... 'immanuel')
1
>>>

In this example, we first use modrdn_s() to change the DN of a record from uid=manny,ou=users,dc=example,dc=com to uid=immanuel,ou=users,dc=example,dc=com. The False flag at the end of the modrdn_s() method indicates that the old UID (uid=manny) should be left in the record. The LDIF for uid=immanuel‘s record now, after the ModRDN operation, looks something like this:

dn: uid=immanuel,ou=Users,dc=example,dc=com
cn: Manny Kant
givenName: Manny
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
ou: Users
sn: Kant
uid: immanuel
uid: manny

If we had set the last flag to True instead of False, the manny attribute value of uid would have been deleted.

More sophisticated DN modifications can be made with the rename() and rename_s() methods. But your OpenLDAP server will need to be running the HDB backend for all of the renaming features to work.

The Add Operation

The LDAP add operation is used to add new (complete) records to the directory information tree.

Here, we will look at adding records through the add() and add_s() methods of the LDAPObject class. Both of these methods take only two parameters:

  • The string DN of the new record
  • A list of attribute tuples

While the first parameter is straightforward, we’ve looked at dozens of DNs already; the second attribute is a little trickier.

The addition list looks something like this:

add_record = [
('objectclass', ['person','organizationalperson','inetorgperson']),
('uid', ['francis']),
('cn', ['Francis Bacon'] ),
('sn', ['Bacon'] ),
('userpassword', ['secret']),
('ou', ['users'])
]

If there is only one value in the attribute value list, the value can be just a string – it need not be a list. Example: (‘ou’, ‘user’) is an acceptable alternative to (‘ou’, [‘user’]).

The list of attributes is made up of two-value tuples, where the first item of each tuple is the attribute name, and the second value is a list of attribute values. All of the values are expected to be strings.

If you have values in a dictionary, where the attribute name is the key and the attribute values are stored in a list in the dictionary value, you can use the ldap.modlist module’s addModList() function to create an attributes list in the form specified above.

Once you have a list in the correct format, writing it to the directory is just a matter of executing the add() or add_s() method.

>>> l.add_s('uid=francis,ou=users,dc=example,dc=com', add_record)
(105, [])
>>>

This line performs an LDAP add operation, sending this new data to the server. The server ensures that the new record adheres to the appropriate schemas (e.g. the schemas for the person, organizationalPerson, and inetOrgPerson object classes), and then writes the entry to the directory.

As might be expected, the add() method functions the same way that the add_s() method does, except that it returns an ID number. The result must be retrieved using the result() method.

We can dump the new entry from the server (using the dump_record.py program developed earlier in the series) to verify that the record is as we expect it to be:

$ ./dump_record.py 'uid=matt,ou=users,dc=example,dc=com' 'uid=francis,
ou=users,dc=example,dc=com'
Password for uid=matt,ou=users,dc=example,dc=com:

dn: uid=francis,ou=users,dc=example,dc=com
cn: Francis Bacon
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
ou: users
sn: Bacon
uid: francis
userPassword: secret

We can tell by comparing this record with the add_record list above that the record is correct.

The main error encountered when adding is violating the schema, either by adding attributes that are not supported, or by failing to add required attributes. When one of these conditions is met, an exception will be raised.

For example, if no structural object class is specified in the attributes, an OTHER exception will be raised. If a record does not contain the attributes used in the UID, a NAMING_VIOLATION will be raised. If a record is missing an attribute required by a structural object class, an OBJECT_CLASS_VIOLATION will be raised, and so on.

Of course, since all of these are subclasses of LDAPError, these numerous exceptions can all be caught in a try/except clause like this:

>>> try:
... l.add_s('uid=william,ou=users,dc=example,dc=com', attrs )
... except ldap.LDAPError, e:
... print e.message['info']...

This will catch any of the LDAP exceptions, and display some of the error text, rather than showing the stack trace.

Now we are ready to move on to the most complicated of writing operations: the LDAP modify operation.

The Modify Operation

Here we will look at the LDAP modify operation, which is used for modifying attributes – adding, replacing, or removing them from already-existing records.

The OpenLDAP command line tool ldapmodify provides one way of performing this operation. In the Python-LDAP library, the modify() and modify_s() methods provide asynchronous and synchronous methods for performing modifications to the directory information tree.

The signature of these methods is same as that of the add methods. There are two parameters: the DN and a list of modification tuples. The main difference is that the form of the tuples in this modification list is different than those in the add methods.

A tuple in a modification list has three items:

  • The modification type
  • The attribute name
  • A list of attribute values

Modification type is one of three different constants defined in the ldap module:

  • MOD_ADD: This is used to add an attribute value. If the attribute already exists (and the schema permits multiple values), the new value will be added, and the old value will remain.
  • MOD_DELETE: The attribute value will be removed, if it exists.
  • MOD_REPLACE: The given attribute values will replace all other values for that attribute name. In other words, all old values for the attribute will be deleted, and then this value will be added.

For example, a simple list for adding a new givenName to an existing entry might look like this:

mod_attrs = [( ldap.MOD_ADD, 'givenName', 'Francis' )]

This list contains only one attribute to be modified. It will (if successful) add a new givenName attribute to the specified record. The modification can then be done with code like this:

>>> mod_attrs = [( ldap.MOD_ADD, 'givenName', 'Francis' )]>>> l.modify_s('uid=francis,ou=users,dc=example,dc=com', mod_attrs)
(103, [])
>>>

This will add the specified attribute value to the uid=francis record that we created above. As a result, dumping the LDIF record will show the newly added attribute:

dn: uid=francis,ou=users,dc=example,dc=com
cn: Francis Bacon
givenName: Francis
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
ou: users
sn: Bacon
uid: francis
userPassword: secret

The highlighted line above shows the newly added attribute value.

The modifyModList() function in the ldap.modlist module can help convert modification lists stored in dictionaries to the appropriate tuple-based format.

What if Francis decided that he preferred to go by Frank? We could perform a slightly more sophisticated modification, changing his givenName to Frank, and adding a second CN value:

>>> mod_attrs = [
... ( ldap.MOD_REPLACE, 'givenName', 'Frank' ),
... ( ldap.MOD_ADD, 'cn', 'Frank Bacon' )
... ]>>> l.modify_s('uid=francis,ou=users,dc=example,dc=com', mod_attrs)
(103, [])
>>>

Notice that our modification list now has two different modifications. First, it will replace givenName. Second, it will add a new cn attribute value. The result will be something like this:

dn: uid=francis,ou=users,dc=example,dc=com
cn: Francis Bacon
cn: Frank Bacon
givenName: Frank
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
ou: users
sn: Bacon
uid: francis
userPassword: secret

If we wanted to change the UID attribute, we would have to use the modrdn() or modrdn_s() method, since uid is used in the DN. If we try to change it with modify_s() or modify(), we will get a NAMING_VIOLATION exception.

Finally, we can use the modify methods to remove attribute values:

>>> mod_attrs = [ (ldap.MOD_DELETE, 'cn','Francis Bacon') ]>>> l.modify_s('uid=francis,ou=users,dc=example,dc=com', mod_attrs)
(103, [])
>>>

This will remove only the attribute value Francis Bacon from the cn attribute. If no such value exists, a NO_SUCH_ATTRIBUTE exception will be raised. Otherwise, the value will be discarded.

Note that some attributes are required by the record’s object classes to be present in an entry. Attempting to delete the last value for such an attribute will result in an OBJECT_CLASS_EXCEPTION being raised.

Removing All Attribute Values

Sometimes it is necessary to remove all of the values for an attribute in a record, instead of just one specific value, as we did above. Let’s look at an example.

First, we add a few attribute values – two descriptions:

>>> mod_attrs = [ 
... (ldap.MOD_ADD, 'description', 'Author of New Organon'),
... (ldap.MOD_ADD, 'description', 'British empiricist')
... ]>>> l.modify_s('uid=francis,ou=users,dc=example,dc=com', mod_attrs)
(103, [])

Now we have a record with two new descriptions. We can perform a very specific search to verify this.

>>> l.search_s('uid=francis,ou=users,dc=example,dc=com', 
... ldap.SCOPE_BASE, '(uid=francis)',['description'])
[('uid=francis,ou=users,dc=example,dc=com', {'description': ['Author of
New Organon', 'British empiricist']})]

This search looks at just the uid=francis record, and shows just the description attributes. Now, how can we delete both of these attribute values without having to supply the exact attribute values for each?

We can do this removal by creating a modification entry that uses None instead of a string for the final item in the attribute tuple:

>>> mod_attrs = [( ldap.MOD_DELETE, 'description', None )]>>> l.modify_s('uid=francis,ou=users,dc=example,dc=com', mod_attrs)
(103, [])
>>>

A simple search will verify that both description attribute values have been deleted:

>>> l.search_s('uid=francis,ou=users,dc=example,dc=com', 
... ldap.SCOPE_BASE, '(uid=francis)',['description'])
[('uid=francis,ou=users,dc=example,dc=com', {})]>>>

The server returned one entry – one with the DN for uid=francis – but since there were no description attribute values, the dictionary is empty.


Subscribe to the weekly Packt Hub newsletter. We'll send you the results of our AI Now Survey, featuring data and insights from across the tech landscape.

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here