|
|
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. See More |
Python LDAP Applications: Part 4 - LDAP Schema
Using Schema InformationAs with most LDAP servers, OpenLDAP provides schema access to LDAP clients. An LDAP schema defines object classes, attributes, matching rules, and other LDAP structures. In this article, we will take a brief look at what might be the most complex module in the Python-LDAP API, the ldap.schema module. This module provides programmatic access to the schema, using the LDAP subschema record, and the subschema's subentries obtained from the LDAP server. The module has two major components. The first is the SubSchema object, which contains the schema definition, and provides numerous functions for navigating through the definitions stored in the schema. The second component is the model, which contains classes that describe structural components (Schema Elements) of the schema. For example, the model contains classes like ObjectClass, AttributeType, MatchingRule, and DITContentRule. Getting the Schema from the LDAP ServerThe ldap.schema module does not automatically retrieve the schema information. It must be fetched from the server with an LDAP search operation. The schema is always stored in a specific entry, almost always accessible with the DN cn=subschema. (If it is elsewhere, and is accessible, the Root DSE will note the location.) We can retrieve the record by doing a search with a base scope: >>> res = l.search_s('cn=subschema',The search configuration above should return only one record – the record for cn=subschema. Because most of the schema attributes are operational attributes, we need to specify, in the list of attributes, both * for all regular attributes and + for all operational attributes. The ldaphelper.get_search_results() function we created early in this series returns a list of LDAPSearchResult objects. Since we know that we want the first one (in a list of one), we can use the [0] notation at the end to return just the first item in the resulting list. Now, schema_entry contains the LDAPSearchResult object for the cn=subschema record. We need the list of attributes – namely, the schema-defining attributes, usually called the subschema subentry. We can use the get_attributes() method to retrieve the dict of attributes. Now we have the information necessary for creating a new SubSchema object. The SubSchema ObjectThe SubSchema object provides access to the details of the schema definitions. The SubSchema() constructor takes one parameter: a dictionary of attributes that contains the subschema subentry information. This is the information we retrieved and stored in the subschema_subentry variable above. Creating a new SubSchema object is done like this: >>> subschema = ldap.schema.SubSchema( subschema_subentry ) Now we can access the schema information. We can, for instance, get the schema information for the cn attribute: >>> cn_attr = subschema.get_obj( ldap.schema.AttributeType, 'cn' ) The first line employs the get_obj() method to retrieve an AttributeType object. The call to get_obj() above uses two parameters. The first is the class (a subclass of SchemaElement) that represents an attribute. This is ldap.schema.AttributeType. If we were getting an object class instead of an attribute, we would use the same method, but pass an ldap.schema.ObjectClass as the first parameter. The second parameter is a string name (or OID) of the attribute. We could have used 'commonName' or '2.5.4.3' and attained the same result. The cn_attr object (an instance of an AttributeType class) has a number of properties representing schema statements. For example, in the example above, the names property contains a tuple of the attribute names for that attribute, and the desc property contains the value of the description, as specified in the schema. The oid attribute contains the Object Identifier (OID) for the CN attribute. Let's look at one more method of the SubSchema class before moving on to the final script in this article. Using the attribute_types() method of the SubSchema class, we can find out what attributes are required for an record, and what attributes are allowed. For example, consider a record that has the object classes account and simpleSecurityObject. The uid=authenticate,ou=system,dc=example,dc=com entry in our directory information tree is an example of such a user. We can use the attribute_types() method to get information about what attributes this record can or must have: >>> oc_list = ['account', 'simpleSecurityObject'] The oc_list list has the names of the two object classes in which we are interested: account and simpleSecurityObject. Passing this list to the attribute_types() method, we get a two-item tuple. The first item in the tuple is a dictionary of required attributes. The key in the dictionary is the OID: >>> must_attrs.keys() The value in the dictionary is an AttributeType object corresponding to the attribute defined for the OID key: >>> must_attrs['2.5.4.35'].oid In the code snippet above, we assigned each value in the two-item tuple to a different variable: must_attrs contains the first item in the tuple – the dictionary of must-have attributes. The may_attrs contains a dictionary of the attributes that are allowed, but not required. Iterating through the dictionaries and printing the output, we can see that the required attributes for a record that used both the account and the simpleSecurityObject object classes would be userPassword, objectclass, and uid. Several other attributes are allowed, but not required: o, ou, seeAlso, description, l, and host. We could find out which object class definitions required or allowed which of these attributes using the get_obj() method we looked at above: >>> oc_obj = subschema.get_obj( ldap.schema.ObjectClass, 'account' ) From the above, we can see that most of the required and optional attributes come from the account definition, while only userPassword comes from the simpleSecurityObject definition. The requirement of the objectClass attribute comes from the top object class, the ultimate ancestor of all structural object classes. The schema support offered by the Python-LDAP API makes it possible to program schema-aware clients that can, for instance, perform client-side schema checking, dynamically build forms for creating records, or compare definitions between different LDAP servers on a network. Unfortunately, the ldap.schema module is poorly documented. With most of the module, the best source of information is the __doc__ strings embedded in the code: >>> print ldap.schema.SubSchema.attribute_types.__doc__ In some cases, though, the best source of documentation is the code itself. The last script in this article will provide an example of how the schema information can be used. An Example Script: suggest_attributes.pyThis example script compares the attributes in a user-specified record with the possible attributes, and prints out an annotated list of “suggested” available attributes. This script is longer than the other scripts in this article, but it makes use of similar techniques, and we will be able to move through it quickly. Mastering OpenLDAP: Configuring, Securing and Integrating Directory Services
For more information, please visit: www.PacktPub.com/OpenLDAP-Developers-Server-Open-Source-Linux/book Here is the suggest_attributes.py script in its entirety. #!/usr/bin/env python Before looking at the particulars, let's get a basic idea of how this program works. The program takes one command-line parameter: the DN of a record in the directory. For simplicity's sake, the program binds to ldap://localhost as the anonymous user (a more robust version of this program would allow the user pass credentials). Once a connection with the directory server is established and the bind is complete, the script retrieves two records. The first is the subschema record, which contains all of the schema information for the directory server. The second is the record for the DN supplied by the user. The program then compares the attributes in this second record with the attributes that, according to the schema, such a record is allowed to have. Then, it prints a list of all of the attributes that the record is allowed to have, but does not presently have. Here's an example of the script's output: $ ./suggest_attributes.py 'ou=users,dc=example,dc=com' For each attribute, all of the possible attribute names are printed. Thus, street and streetAddress are both given for the attribute on line six. Also, the OID for each attribute is printed in parentheses after the list of attribute names. On the line beneath each attribute is the attribute description from the schema. Now that we have a basic idea of how the program functions, let's walk through the code. The first section of the code performes basic setup: #!/usr/bin/env python After imports and variable declarations, a simple test is done to make sure that a DN was provided on the command line, and the DN is stored in the variable dn. Next, we define a convenience function: def get_record( ldap_con, dn, operational_attrs = False ): This function performs a search for a specific DN, returning an LDAPSearchResult object. If no such object is found, this function returns None. It takes three parameters, two of which are required.
The workings of this function should be familiar by now, as they do not differ from the techniques introduced in the discussion of the search operation. We will move onto the next section of code. As usual, the main LDAP transactions are nested inside of the appropriate try/finally and try/except blocks. Inside these blocks, a bind is done, and the subschema record is retrieved and used to create a new ldap.schema.SubSchema object: l.bind_s(user_dn, user_pw) Using the get_record() function defined above, the script retrieves the cn=subschema record, and then uses the attribute dictionary of that record to create a new instance of the SubSchema class. A similar process is used for fetching the record with the user-supplied DN: record = get_record( l, dn ) First, the record is retrieved with the get_record() function. If the record is not found, then the script will terminate. Once we have the desired record, we need to get the list of object classes for this record. The object classes specify what attributes the entry can have. The values in the objectclass attribute can be used to retrieve information about allowed attributes from the schema: attr_types = subschema.attribute_types( oc_list ) The attribute_types() method of the SubSchema class takes a list of object classes and returns a two-item tuple containing, first, a dictionary of all mandatory attributes, and second, a dictionary of allowed attributes. Since we can assume that a record already has all of the mandatory attributes, all we are really concerned with are the optional attributes, so we only need the second item in the attr_types tuple. And that dictionary is assigned to the may_attrs variable. At this point, we have the information we need. The may_attrs dictionary contains the necessary schema information, and record, an LDAPSearchResult object, has the necessary attribute information for the desired LDAP entry. Now, all we need to do is compare the allowed fields with those actually present, and print our suggestions: sys.stdout.write("Suggested Attributes:n");First, this snippet of code writes Suggested Attributes: to standard output. Then, it initializes a counter, suggestion_count, which will be used both for display purposes, and to print a message if there are no suggested attributes. There are a pair of nested for loops in this segment of code. The outer one loops through all of the attributes retrieved from the schema. The may_attrs dictionary uses the attribute's OID as a key, and an AttributeType object as a value. The AttributeType object contains the information we need, so we retrieve just the values of the dictionary using the itervalues() method. Why use a second for loop? Each attribute may have multiple attribute names, like cn and commonName, so we need to check all attribute names for each attribute. The has_this_attr flag is used to indicate that an attribute is used, regardless of which attribute name is used to refer to the attribute. The inner for loop compares checks the record using each attribute name in the schema, setting has_this_attr to True if one of the attribute names is present in the record's attributes. Based on the has_this_attr flag, we can now tell whether this attribute appears in the target record. If it does not, then we print a helpful suggestion to the system output, using information from attr, the AttributeType schema element object. Finally, if no suggestions were found, then suggestion_count will be 0. If that is the case, the script prints a short message saying that no suggestions were found. This script provides an example of how schema information can be used to enhance a program. Having the schema accessible to the client, and having a library sophisticated enough to make use of the schema, can make developing rich LDAP applications much easier. SummaryWe have quickly examined the Python-LDAP library. While we have covered the major operations, and looked at many of the modules, there are other features of this API that I have, for the sake of brevity, not discussed here. Many of these features are documented in the official online documentation (http://python-ldap.sourceforge.net/doc/python-ldap/), and even more are documented in the source code (using __doc__ strings). In the course of this mini-series, we have covered installing the Python-LDAP library, connecting to and binding to a directory server, running many read and write operations, and even making use of the LDAP URL and LDAP schema modules. Mastering OpenLDAP: Configuring, Securing and Integrating Directory Services
For more information, please visit: www.PacktPub.com/OpenLDAP-Developers-Server-Open-Source-Linux/book About The AuthorMatt Butcher is the principal consultant for Aleph-Null, Inc., a systems integrator that specializes in Free and Open Source solutions.Matt has worked on a wide variety of projects, including embedding Linux in set-top boxes and developing advanced search engines based on artificial intelligence and medical informatics technologies. Matt is involved in several Open Source communities. He is also a member of the Emerging Technologies Lab at Loyola University Chicago, where he is currently finishing a Ph.D. in philosophy. Matt has written two other books for Packt: Managing and Customizing OpenCms 6, and Building Websites with OpenCms. Matt has also contributed articles to Newsforge.com, TheServerSide.com, and LinuxDevices.com. |
BOOK ![]() Mastering OpenLDAP: Configuring, Securing and Integrating Directory Services See More This is the second article in the article mini-series on Python LDAP applications by Matt Butcher. For first part please visit this link. In this article we will see some of the LDAP operations such as compare operation, search operation. We will also see how to change an LDAP password. See More |
| ||||||