Creating a Schema

Up to this point we have taken an in-depth look at schema definitions and then implemented a few overlays that made use of custom schemas. By now you should be comfortable working with and reading schemas. Here we are going to create our own schema.

Our goal in this section is to create a small schema for adding blog information to our directory. We want to be able to store a record in the directory to represent a blog, and also link existing entries to these blogs, indicating, for example, that a particular user maintains a particular blog.

To do this we are going to add two object classes—one structural and one auxiliary—and a handful of new attributes. The structural object class, blog, will describe an individual blog. It will contain the necessary attributes to describe a blog.

The auxiliary class blogOwner, will be used to add blog ownership information to a particular entry. Since the information about the blog will be stored in a blog entry, the blogOwner object class will only need one attribute that can be used to point to the appropriate blog entry.

The first thing we will do is walk through the process of obtaining an OID. Then we will create our object classes. After the object classes are created we will define our new attributes. Finally, we will try out our new schema.

As we have seen so far the OID (Object Identifier) plays an important role in defining a schema.

An OID is a sequence of integers separated by dots (.). But OIDs are not arbitrary combinations of digits. They are structured to represent the pedigree of an object. As we will use them here, for creating a new schema, we will treat the OID as being composed of three parts:

  • The base OID
  • The type number
  • The item number

The base part of an OID number is assigned by a naming authority. We will get ours from the Internet Assigned Numbers Authority (IANA).

IANA maintains a registry of OIDs for private enterprises. It allocates numbers free of charge and all that is necessary is a one-time registration. However, IANA only gives one number to each enterprise so, if your organization has one already, you should use the existing one. You can view the registry at http://www.iana.org/assignments/enterprise-numbers.

To obtain a number go to http://iana.org/cgi-bin/enterprise.pl and complete the form there. You will then be assigned an OID looking something like this: 1.3.6.1.4.1.?, where the question mark is replaced with an integer. This OID serves as the basis for the OIDs we use when creating schemas. By appending your own series of digits and dots to this string you can create your own OID numbers, and as long as you take care to keep your OIDs unique within your own domain, you can assume that these OIDs are also globally unique (for you are the only one with the exact base OID).

While this series of digits has some semantic meaning (it means, roughly, that the owner is a private enterprise operating within IANA's namespace), there are no constraints on how you decide to structure your OIDs. You could, for example, just append a new set of random digits to the base OID each time you needed to create a new OID:

But it is often more manageable to come up with some semantic scheme for organization. A version derived from the OpenLDAP foundation's scheme is recommended. From the base OID, create a segment to be used just for LDAP OIDs:

Now we have just one portion of the namespace that will be used only for LDAP OIDs. From here we will use a simple subcategory identifier. Starting with the OID arc 1.3.6.1.4.1.8254.1021, we will create OIDs of the form:

Where x indicates the type of object and y indicates the specific object we are identifying. The OpenLDAP Foundation uses the following types:

We are only going to create object classes and attributes, so the value of x for our classes will be 3 for OIDs attached to attributes and 4 for OIDs attached to object classes.

For the y value, we will just start with the digit 1 and increment each time we define a new object of that type. For example, our first object class will have the OID:

And for our second object class we will just increment the last value from 1 to 2:

Again, this is just one convention and different organizations use different conventions. While I advocate this convention you are free to choose another if you find that it is better for your needs.

There are two things to keep in mind though. First, you need to ensure that the OIDs are unique across your arc. That means you should maintain a registry of them in a place accessible to all people in your organization who work with the OIDs. Second, adding meaning to the numbers can provide tremendous utility, as it can help you recall or derive what an otherwise arbitrary string of numbers represents.

Now we are ready to begin creating our schema.

Our schema definitions are all going in a file called blog.schema, which we will later reference in an include statement in slapd.conf.

Most usually once the base OID for LDAP objects is defined, it is convenient to use the objectidentifier directive in slapd.conf to make the OIDs more readable, and make the process of creating schema definitions less error prone.

We can do this in the first few lines of our schema file:

objectidentifier blogSchema 1.3.6.1.4.1.8254.1021
objectidentifier blogAttrs blogSchema:3
objectidentifier blogOCs blogSchema:4

The first line maps the name blogSchema onto the OID 1.3.6.1.4.1.8254.1021. Now we can refer to that long OID as blogSchema, which is much easier to remember.

The second and third objectidentifier directives add a few more aliases. The second one sets the name blogAttrs refer to the OID blogSchema:3 (which is 1.3.6.1.4.1.8254.1021.3). Thus, when we define attributes we can use the shortcut blogAttrs:1 instead of typing the whole thing out as 1.3.6.1.4.1.8254.1021.3.1.

Similarly, blogOCs alias (short for "blog object classes") can be used to refer to the 1.3.6.1.4.1.8254.1021.4 arc.

With this mechanism in place we have implemented the organizational strategy explained in the previous section, and our OID naming from here on should be a simple matter of incrementing the last integer of an OID.

We will be starting with our object classes, and then use these defined object classes to guide the creation of our attributes. This is typically the way creation of schemas is done, but it does have one counter-intuitive result: object classes must be defined after the attributes that they contain. In effect then, we are jumping to the end of our schema file to add object classes, and will later add attribute definitions between the object identifiers and the object classes.

The first object class to describe is the blog class. This object class will define the attributes necessary to define a blog. For our purposes we are going to create a very simple object class, though there are many more attributes that could be attached.

We want the class to have the following attributes:

  • blogTitle: The title of the blog
  • blogUrl: The URL (Uniform Resource Locater) of the main page for the blog
  • blogFeedUrl: The URL for the RSS or Atom feed of the URL
  • description: A brief text description of the blog

Of these, the blogUrl and blogTitle attributes should be required. blogUrl is an essential component of a blog. Without this, an entry describing a blog would be of little value. And the blogTitle attribute is necessary to give us a naming component to use in DNs.

For the sake of clarity of meaning, here we have prepended the blog string to any new attributes so that they can be immediately distinguished from other similar attributes.

Fortunately for us, description is already defined. While we could use the title attribute, as defined in core.schema, this could introduce confusion, as that attribute is used to refer to the title of a person in an organization. To avoid any confusion then, we will avoid reusing that attribute.

Already we have said that this object class is going to be structural, and we have a scheme for determining an OID number. There are no similar object classes so we will create a class whose superior is top. We now have all the information we need to create our schema definition:

In the OID field we used the object identifier we assigned in the last section. And we started with 1, our first object class.

The blogOwner object class is to be marked auxiliary so that we can attach it to a variety of different entries, regardless of the structural object class. For example, regardless of whether the blog is a corporate blog, or is maintained by an organizational unit, or is simply an individual's, we can add this object class to the desired entry.

We want to use the blogOwner object class to insert a pointer from an entry to the appropriate blog entry in the directory information tree. Since that is all we need, a single attribute will suffice for these purposes:

This object class then, turns out to be even simpler than the previous one:

This OID number differs from the first only in that the last value has been incremented. This follows the scheme we defined in the previous section.

Since this is an auxiliary object class, there is no need for a superior. And since we want this class to be used to point to a blog entry elsewhere in the directory, the blogDN attribute is required.

Now we have our two object classes. In creating them we have referred to four attributes that currently do not exist. It is time to create them.

As we created the blog and blogOwner object classes, we tentatively defined (in our text) four attributes: blogTitle, blogUrl, blogFeedUrl, and blogDN. Now we will define each of these, beginning with blogTitle.

In order to define our attribute we want to decide on the syntax of the attribute and also the matching rules that SLAPD will use for this attribute. The blogTitle will contain values that are strings of text data. So the syntax we want is one that supports this. The Directory String syntax, defined in RFC 4517, is intended for just such a purpose. And it supports internationalization, storing characters in UTF-8.

When performing searches, we do not want the case of the text (upper or lower) to make a difference. In other words, we want "My Blog" and "my blog" to be treated as matches. So we need to find the matching rule that will best support this. There are over three dozen matching rules supported in OpenLDAP (you can see a list by searching the cn=Subschema entry). We want to implement string-based equality and substring matching on our blogTitle attribute, so the pair of matching rules we will want to use are caseIgnoreMatch and caseIgnoreSubstringsMatch.

Now, we have all of the information necessary for creating a new attribute type:

The OID field is blogAttrs:1, indicating that this is our first attribute.

The LDAP syntax OID is the OID for a Directory String. At the end of the OID, the {256} suggests that the maximum length of the title be constrained to 256 characters.

The next two attributes, blogUrl and blogFeedUrl, are similar and we can take advantage of that as we define them.

The first thing to examine is the LDAP syntax of these attributes. Unlike blogTitle, we do not want the values of blogUrl and blogFeedUrl to be in the Directory String syntax, because (according to RFC 3986 and the previous URL standards) URLs are to use a subset of the ASCII character set.

Instead of using Directory String syntax, we should use the IA5 String syntax which describes an extended ASCII character set. The OID for this syntax is 1.3.6.1.4.1.1466.115.121.1.26.

Similarly, when we specify matching rules, we want to use the IA5 matching rules. And since URLs are case-sensitive, we want exact matches. We do not want the case to be ignored. So for matching rules we want caseExactIA5Match and caseExactIA5SubstringsMatch.

Now we can define both attributes:

Since the blogUrl field contains the matching rules and syntax that blogFeedUrl uses, and since there is an obvious similarity in usage between the two, it makes sense to treat blogUrl as the supertype of blogFeedUrl. So, blogFeedUrl inherits the LDAP syntax and matching rules from blogUrl.

Finally, we need to define our blogDN field, which will hold a DN. There is syntax and specific matching rules for DNs, and we will use those. The Distinguished Name syntax, defined with the OID 1.3.6.1.4.1.1466.115.121.1.12, is used for values that are DNs. And the distinguishedNameMatch matching rule is used for performing exact matches on DNs. There are no substring or ordering matches for DNs.

Our last attribute then, looks like this:

Now we have our entire schema defined. We are ready to test it.

As with all other schemas, in order to load this schema, we must include it in slapd.conf.

include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/ppolicy.schema
include /etc/ldap/schema/blog.schema

It is assumed here that blog.schema is located in the /etc/ldap/schema directory (which is a good place to put the schema). If you choose to locate the schema elsewhere, adjust the path accordingly.

The highlighted line in the code is the only addition necessary (the rest should be there already). Note that our schema is only dependent on core.schema. The other three are not necessary to make our schema work.

Restarting SLAPD will load the schema.

Now we can use ldapadd to add a new blog entry to our directory information tree. We will add information about the official corporate blog of Example.Com:

The highlighted portion above is the new entry we are adding. The last line, returned by SLAPD, indicates that the entry has been added successfully.

Our user uid=barbara is responsible for maintaining this blog so we can indicate this relationship by adding the blogOwner object class and blogDN attribute to her record with ldapmodify:

The record for uid=barbara now looks like this:

We have just successfully created and implemented a new schema including new attributes and object classes.