★ wanayoo — archive 1999 http://developer.iplanet.com/viewsource/wilcox_schema.htmlNouvelle recherche | Portail wanayoo
iPlanet

You are here:  Home > Developers > View Source Articles > LDAP View Source Article
LDAP View Source Article
 iPlanet Developers


Developer Program
  Membership
  One-to-One Support
  Newsgroups
  Member Services

Developer Publications
  View Source
  Developer News

Documentation
  Technical Manuals
  White Papers
  TechNotes
  Sample Code
  FAQs
  Books

Technologies
  Application Server   CSS
  DOM
  CORBA
  Directory & LDAP
  Dynamic HTML
  Java
  JavaScript
  Linux
  RDF
  Security
  SSJS
  XML
  XUL

Developer Downloads
  Tools & SDKs
  Patches

iPlanet Products
  Technical Resources










spacer
The LDAP Heavyweight
LDAP Schema Management With Netscape Tools and Java

By Mark Wilcox


Send comments and questions about this article to View Source.
Click here for printer-friendly version

Every LDAP server uses a schema, which is just a fancy word for database design. Every database that you use, whether it's a simple flat file stored in a comma-delimited format or a sophisticated Oracle database with a thousand tables joined together in complex relationships, has a schema.

You might be wondering what databases have to do with LDAP. Well, behind every LDAP server there's a database. LDAP is just a standard protocol for talking to a database over the Internet. The LDAP server stores its information in a database of one flavor or another. Some systems use a standard relational database, while others use a proprietary format (such as Novell or Microsoft Exchange). Netscape Directory Server uses a highly modified version of the Berkeley v2 database format. Netscape's databases are optimized for LDAP operations and provide features like transaction management, which allows you to recover in case something catastrophic should happen to the system. If you'd rather use a different database format than the one Netscape provides, you can do so with the Netscape Server Plug-In API (NSAPI).

When we talk about a database, we're really referring to the fields and tables in the database. In LDAP, fields and tables are known as attributes and object classes, respectively. If you're new to LDAP terminology and concepts, you might want to check out some of the resources listed at the end of this article.

Back to schemas: Again, every LDAP server relies on a schema for its database design. One difference between LDAP and the database formats you might be familiar with (for example, a relational database like Oracle or Access) is that every LDAP server has a common, standard schema that you can access. This means that everyone who writes an LDAP client can expect the LDAP server to have some standard object classes and attributes.

For example, if I'm going to search an LDAP server and I don't know anything about it except that it allows public searching of entries that match the inetOrgPerson object class, I can expect to find certain standard attributes: I'll know that at the very least the database will contain each person's full name (represented by the cn attribute) and surname (the sn attribute). I can also expect to find common (but optional) attributes such as a person's email address (the mail attribute) and first name (the givenname attribute). These attributes apply to all LDAP servers, whether Netscape's or those that comply with the Microsoft Exchange LDAP interface, OpenLDAP, or Sun Directory Service.

A popular misconception of LDAP is that it's inflexible. This may stem from the fact that the X.500 protocol from which LDAP is derived was fairly rigid in its implementation. LDAP - in particular, version 3 of LDAP - is as flexible as any database format. It has a fixed set of attributes and object classes simply to ensure that it remains interoperable between different LDAP-speaking clients.

EXTENDING OR MODIFYING A SCHEMA: WHY AND HOW

LDAP provides such a rich set of object classes and attributes that at first I couldn't think of why I'd want to extend the standard schema. I thought that surely the inetOrgPerson object class had all the attributes I'd ever need for directory services at the University of North Texas (UNT). It turns out that that was very naive and small-minded of me. The designers of LDAP thought of many things, but they couldn't allow for everything that could possibly be needed in a schema. As you build your directory services, you'll surely discover additional attributes that you'll need.

For example, we made such a discovery at UNT when we migrated a database of student and staff information from a Sun NIS infrastructure to LDAP. (For more on the LDAP-NIS gateway, see PADL Software's web site.) We needed to record each person's middle name, but the standard inetOrgPerson object class has no concept of a middle name. In the existing database there was also an other field that could contain a person's former last name (if it had changed after marriage, for instance), a professional title (such as "Dr."), or a name suffix (such as "Jr." or "III"). So we needed two new attributes, middlename and other. We also needed a way to keep track of student and staff ID numbers, including former ID numbers if they'd changed. And we wanted to keep all the relevant information about a person in a single entry.

If you look at any of the LDAP vendors, such as Netscape, Sun, and Microsoft, you'll see that they've added object classes and attributes to provide extra functionality. In fact, the entire Netscape version 4 line of servers (which is expected to ship by the end of June) will store all their configuration information in LDAP directories. Sun uses LDAP for the storage of distributed Java objects, and Microsoft has added all sorts of object classes and attributes for the Active Directory service because they want you to manage your entire network through it.

There are three options for adding to or otherwise modifying a schema:

  • Using the Netscape Directory Server Admin screen, or Netscape Console if you use Netscape Directory Server 4. This is fairly straightforward, so I'm just going to point you to the server documentation for details.
  • Programmatic access, using either command-line utilities or the Netscape Directory SDK for Java. This isn't all that hard, especially if you use Java. The rest of this article describes these approaches.
  • Modifying the user_at.conf and user_oc.conf files (but not any other .conf file). This is recommended only if you're sure of what you're doing, and only as a last resort.

USING THE COMMAND-LINE UTILITIES

Getting at the schema of an LDAP server is easy if the server supports LDAP v3. In LDAP v3 there's a special entry, cn=schema, that contains all the schema information for the server. It has two attributes:

  • objectclasses, which refers to the object class definitions
  • attributetypes, which refers to attribute definitions

To get the schema information for an LDAP server with the ldapsearch command-line utility, you specify a search base of cn=schema (with the command switch -b "cn=schema") and provide any search filter, such as "objectclass=*". You must also specify that your client is capable of communicating via the LDAP v3 protocol. LDAP v3 is the default version for the ldapsearch utility that ships with Netscape Directory Server 3 and later (as well as the version that ships with the Netscape Directory SDK for C), so you don't have to do anything special.

The server will display a long, ugly-looking list of object class definitions that looks something like this:

dn: cn=schema
objectclasses: ( 2.5.6.0 NAME 'top' DESC 'Standard ObjectClass' MUST ( objectclass )  MAY ( aci ) )
objectclasses: ( 2.5.6.1 NAME 'alias' DESC 'Standard ObjectClass'  SUP 'top'  MUST ( objectclass $ aliasedobjectname )  MAY ( aci ) )
objectclasses: ( 2.5.6.7 NAME 'organizationalPerson' DESC 'Standard ObjectClass'  SUP 'person'  MUST ( objectclass $ sn $ cn )  MAY ( aci $ description $ seealso $ telephonenumber $ userpassword $ destinationindicator $ facsimiletelephonenumber $ internationalisdnnumber $ l $ ou $ physicaldeliveryofficename $ postofficebox $ postaladdress $ postalcode $ preferreddeliverymethod $ registeredaddress $ st $ street $ teletexterminalidentifier $ telexnumber $  title $ x121address ) )

Most people panic when they first see this display, but it's easy to parse (say, with Perl). It breaks down like this:

  • The object identifier (OID) of the object class
  • The name of the object class (NAME)
  • The description of the object class (DESC)
  • The object class this class derives from, referred to as the superior object class (SUP)
  • The required attributes (MUST)
  • followed by the allowed attributes (MAY). The individual attributes are separated by $ symbols.
Note: OIDs are used for identifying names in LDAP to make it easier on the computer, not the user. Computers are very good at doing number comparisons, but horrible at name comparisons, because name matching relies a lot on context. For example, "Mark" can refer to my name or it can be a descriptive term, as in "He bears the mark of Mozilla." Also, OIDs are guaranteed to be unique, unlike human names.

Similarly, the display of attribute definitions looks like this:

attributetypes: ( abstract-oid NAME 'abstract' DESC 'Standard Attribute' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )
attributetypes: ( 2.16.840.1.113730.3.1.95 NAME 'accountUnlockTime' DESC 'Standard Attribute' SYNTAX '1.3.6.1.4.1.1466.115.121.1.15' )

This display shows the following for each attribute:

  • The OID of the attribute
  • The name of the attribute (NAME)
  • The description of the attribute (DESC)
  • The OID of the attribute's syntax (SYNTAX)

Notice that some OIDs aren't numbers but are instead names that represent OID numbers. Netscape Directory Server lets you to do this as a convenience.

You may be bothered by the fact that the attribute's syntax is shown as an OID rather than in an easily understandable form - for example, as 1.3.6.1.4.1.1466.115.121.1.15 rather than the description "case-ignore string." To make it easier on you, Table 1 shows the relationship of the syntax OIDs to human-readable attribute syntax.


Table 1. OIDs vs. human-readable syntax
OID Format
1.3.6.1.4.1.1466.115.121.1.15 cis (case-ignore string)
1.3.6.1.4.1.1466.115.121.1.5 bin (binary)
1.3.6.1.4.1.1466.115.121.1.50 tel (telephone number)
1.3.6.1.4.1.1466.115.121.1.26 ces (case-exact string)
1.3.6.1.4.1.1466.115.121.1.27 int (integer)
1.3.6.1.4.1.1466.115.121.1.12 dn (distinguished name)


In case you think I figured this out by reading the LDAP RFCs, you're wrong: instead I downloaded the source code to the Netscape Directory SDK for Java and looked in its schema classes to figure out the relationship of the OIDs to human-readable syntax.

Adding an Attribute and an Object Class

To use the ldapmodify command-line utility to add a new attribute called middleName with a syntax of case-ignore string (meaning that a search or comparison will ignore case), we'd need LDIF code that looks like this:

# OIDs for examples start at 1.2.3.4.5.17. For real use, get your own!
dn: cn=schema
changetype: modify
add: attributetypes

# Attribute is middleName, with a syntax of case-ignore string
attributetypes: (1.2.3.4.5.17.1 NAME 'middleName' DESC 'A middle name'
   SYNTAX '1.3.6.1.4.1.1466.115.121.1.15')
If we save this code to a file named attribute.ldif, we can add it to the server with
ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f attribute.ldif
at the command prompt (for example, the C:\ prompt in Windows NT or the % in Unix).

Next we need to add an object class that has this attribute. Here's the LDIF code:

dn: cn=schema
changetype: modify

# A new object class: testPerson
# This extends inetOrgPerson to allow for middleName.
add: objectclasses
objectclasses: (1.2.3.4.5.17.2 NAME 'testPerson' SUP 'inetOrgPerson'
   MUST (objectclass $ sn $ cn) MAY (middleName))
If we save this code to a file named objectclass.ldif, we can add the object class with the following at the command prompt:
ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f objectclass.ldif
Note that to modify the server schema you must log in as the Directory Manager, since no other user has write permission on the schema.

Adding a User With the New Attribute

Now we can add a user that has a middleName attribute, with this LDIF code:

dn: uid=testuser,ou=People,o=airius.com
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
objectclass: testPerson
uid: testuser
mail: testuser@airius.com
cn: John Test User
givenname: John
givenname: John Test
middlename: Test
sn: User
ou: People
ou: Accounting

If we save this code to a file named user.ldif, we can add the object class with

ldapmodify -h localhost -D "cn=Directory Manager" -w "password" -f user.ldif
You'll notice that we added attributes for the user that aren't specified in the testPerson object class. This is because LDAP is object-oriented and testPerson is derived from inetOrgPerson. Thus testPerson inherited all the attributes that were part of inetOrgPerson, which inherited attributes from organizationalPerson, which inherited attributes from person, which finally inherited attributes from top, the root object class.

To add a user, you can log in as any user who has rights to add an entry to the server, not just as the Directory Manager.

Getting Your Own OID

It's easy to get an OID: You can go to ANSI (American National Standards Institute) and pay $1000 for a number, or to IANA (Internet Assigned Numbers Authority) and get one for free.

Actually, IANA issues "private enterprise numbers" rather than OIDs. If you get one of these numbers from them, you'll need to append it to 1.3.6.1.4.1, which is the official private Internet OID. For example, if you fill out the IANA forms and (typically after about a week) get the private enterprise number 5131, your OID will be rooted at 1.3.6.1.4.1.5131. Your first new attribute or object class could then be 1.3.6.1.4.1.5131.1. There's no true regulation for OID numbers, just as long as they're unique. To help guarantee uniqueness, you can register your OIDs at the Object Identifiers Registry. This site is the semiofficial central registry for OIDs for any protocol, though IANA also has a registry of all assigned numbers.

To quote Mark Smith (one of the creators of LDAP and Netscape's Directory Server Architect), "The bottom line is that an OID is an OID is an OID - as long as you have been assigned one from someone who can trace the lineage back to the root of all OIDs (set up by the ITU), you can use an OID for anything you like."

USING THE NETSCAPE DIRECTORY SDK AND JAVA

Now that we've seen how it's possible to deal with the server schema information with the command-line utilities, you'll probably want an easier way. In my opinion, the easiest way to deal with schema information from a programming standpoint is to use the Netscape Directory SDK for Java, which contains some special classes for this purpose (see Table 2).


Table 2. Schema-related classes in the Directory SDK for Java
Class name Description
LDAPSchema The schema used by an LDAP server
LDAPSchemaElement The base class that represents a generic element in the schema
LDAPObjectClassSchema An object class description in the schema
LDAPAttributeSchema An attribute description
LDAPMatchingRuleSchema How the server will handle matches (for example, whether a match will equal the entire string or just part of it)


You use the LDAPSchema class to get a copy of the server's schema via an LDAPConnection object, and then the rest of the classes to get schema information or set properties of the schema. (Remember that if you want to set properties of the schema, you must log in as the Directory Manager.)

The example program schemaTest.java (Listing 1) obtains the schema object from the server and prints out the inetOrgPerson object class and the mail attribute. It then adds a middleName attribute, along with a testPerson object class that extends inetOrgPerson by adding the middleName attribute, just as in our earlier example. Most of this code has been lifted from the Java SDK documentation.


Listing 1. schemaTest.java, for dealing with LDAP schema information
import netscape.ldap.*;
import java.util.*;

public class schemaTest {
   public static void main(String[] args) {
      LDAPConnection ld = new LDAPConnection();
      String hostname = "localhost";
      int portnumber = LDAPv2.DEFAULT_PORT;
      String bindDN = "cn=Directory Manager";
      String bindPW = "jessica98";
 
      /* Construct a new LDAPSchema object to hold the schema that you want to 
         retrieve. */
      LDAPSchema dirSchema = new LDAPSchema();
      try {
         ld.connect(hostname, portnumber, bindDN, bindPW);
         
         /* Get the schema from the directory. Anonymous Case Exact Strings OK. */
         dirSchema.fetchSchema(ld);
         
         /* Get and print the definition of the inetOrgPerson object class. */
         LDAPObjectClassSchema objClass = dirSchema.getObjectClass("inetOrgPerson");
         if (objClass != null)
            System.out.println("inetOrgPerson := " + objClass.toString());
         
         /* Get and print the definition of the userPassword attribute. */
         LDAPAttributeSchema attrType = dirSchema.getAttribute("mail");
         if (attrType != null)
            System.out.println("mail := " + attrType.toString());
     
         System.out.println("------------------------------------");
         System.out.println("Adding middleName attribute");

         /* Create a new attribute for Java. */
         LDAPAttributeSchema newAttrType = new LDAPAttributeSchema("middleName", 
            "1.2.3.4.5.17.1", "A middle name", LDAPAttributeSchema.cis, false);
         /* Add the new attribute type to the schema. */
         newAttrType.add(ld);

         System.out.println("Now adding new test object class");
      
         /* Add a new object class. */
         /* Note you need Directory Server 4 or later for this part because of bug in
         version 3 with adding an object class that is a subclass of an existing class. */

         String[] requiredAttrs = new String[1];
         String[] optionalAttrs = new String[1];

         requiredAttrs[0] = "";
         optionalAttrs[0] = "middleName";
         LDAPObjectClassSchema newObjClass = new LDAPObjectClassSchema("testPerson", 
            "1.2.3.4.5.17.2", "inetOrgPerson", "A Test Person", requiredAttrs, 
            optionalAttrs);
        
         /* Add the new object class to the schema. */
         newObjClass.add(ld);
      
         /* Fetch the schema again from the server to verify that the changes were made. */
         dirSchema.fetchSchema(ld);

         /* Get and print the new attribute type. */
         newAttrType = dirSchema.getAttribute("middleName");
         if (newAttrType != null)
            System.out.println("middleName = " + newAttrType.toString());
        
         /* Get and print the new object class. */
         newObjClass = dirSchema.getObjectClass("testPerson");
         if (newObjClass != null)
            System.out.println("testPerson = " + newObjClass.toString());
         ld.disconnect();
      } catch (Exception e) {
         System.err.println(e.toString());
         System.exit(1);
      }
      System.exit(0);
   }
}

The add.java program (Listing 2) adds a user of the testPerson object class to the server. In this program, we not only set an attribute called middleName with a value of "Edward", but we also add a value in both the cn and givenName attributes that includes "Edward". This is because local clients will likely know about the middleName attribute, but outside clients that might be searching the directory (such as the clients built into Communicator) will probably not know about it. So to allow outside clients to search for someone by middleName, we also include this information in the standard attributes cn and givenName. Local clients will know to how to get the definitive middle name, while the general public will appreciate being able to fully search the directory.

Note: Due to a bug in Netscape Directory Server 3, this example won't work in that version. You must use version 4 or later.


Listing 2. add.java, for adding a new user of the testPerson object class
import netscape.ldap.*;
import java.io.*;
import java.util.*;
public class add {
   public static void main(String args[]) {
      String host = "localhost";
      int port = 389;
      String base = "o=airius.com";
      int scope = LDAPv2.SCOPE_SUB;
      String dn = "uid=kvaughan, ou=People, o=airius.com";
      String pwd = "bribery";
      String new_dn = "uid=mewilcox,ou=People,o=airius.com";
      String objectclass_values [] = {"top", "person", "organizationalperson", 
         "inetorgperson", "testPerson"};
      String cn_values [] = {"Mark Wilcox", "Mark Edward Wilcox"};
      String sn_values [] = {"Wilcox"};
      String givenname_values [] = {"Mark", "Mark Edward"};
      String ou_values [] = {"People", "Accounting"};
      String uid_values [] = {"mewilcox"};
      String mail_values[] = {"mewilcox@airius.com"};
      String mn_values[] = {"Edward"};
      
      LDAPAttributeSet attrib_set = null;
      LDAPAttribute attribute = null;
      LDAPEntry entry = null;
      LDAPConnection ld = null;
      try {
         ld = new LDAPConnection();
        
         /* Must bind as a user with rights to write to the server */
         ld.connect(host,port,dn,pwd);
         attrib_set = new LDAPAttributeSet();
         attribute = new LDAPAttribute("objectclass", objectclass_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("cn", cn_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("sn", sn_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("givenname",givenname_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("mail", mail_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("ou", ou_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("middleName", mn_values);
         attrib_set.add(attribute);
         attribute = new LDAPAttribute("uid", uid_values);
         attrib_set.add(attribute);
        
         /* Create the entry object. */
         entry = new LDAPEntry(new_dn, attrib_set);
        
         /* Add the object. */
         ld.add(entry);
         if (ld != null)
            ld.disconnect();
      } catch(LDAPException e) {
         e.printStackTrace();
      }
   }
}

CONCLUSION

LDAP server schema management is an important part of integrating LDAP into your organization. It gives you the benefits of traditional database design, such as the flexibility to build tables (object classes) with fields (attributes) as you see fit, yet you'll still enjoy the benefit of LDAP: the standard, core attributes of a standard set of object classes.

A final note: I've compiled a set of LDAP FAQs and posted them on all the Netscape LDAP newsgroups, as well as on my LDAP Root site; I'll be updating the FAQs on a regular basis.

Until next time.


FURTHER RESOURCES


View Source wants your feedback!
Write to us and let us know
what you think of this article.

I'd like to thank Rob Weltman for always answering my Java and Directory SDK questions. He's also the inspiration behind putting the new FAQs together and getting them posted on the newsgroups.

Mark Wilcox is the web administrator at the University of North Texas. He's been involved with LDAP since the alpha-release days of Netscape Directory Server 1.0. When not working with web technologies, he likes to spend time with his wife Jessica.


Related Readings:


Any sample code included above is provided for your use on an "AS IS" basis, under the Netscape License Agreement - Terms of Use

spacer spacer


                                                       
iPlanet International | Year 2000 | Site Map | Feedback
Products | Solutions | Support | Services | Download | About Us | Developer
© 2000 Sun-Netscape Alliance. All Rights Reserved  Privacy Policy