Chapter 12. What Are Directory Services?

  • Some Background

  • Introducing Java Naming Directory Interface

  • Using the JNDI to Access LDAP-Based Data

Directory services are the services provided by special network databases that are used, much as paper phone books are (i.e., to map names to addresses, phone numbers, and services). The directory is really a distributed hierarchically arranged database made up of keys and associated attribute name/value pairs.

Whether or not you realize it, you use directory services whenever you use the Internet. The Domain Name System is a form of directory, although not as general purpose as the directories we will be discussing in this chapter. DNS provides a UDP-based naming lookup service, namely that of mapping IP addresses to host names and vice versa. Whenever we use our Web browser to retrieve a Web page, IP must use DNS to look up and retrieve the IP address of the host computer that has the page we wish to view. The same is true for FTP: whenever we want to retrieve a file from an FTP server, IP uses DNS to look up the IP address of the host running the FTP server.

Whenever we use our e-mail client, whether it be Netscape Messenger, Outlook, or any number of other e-mail clients (SMTP/POP3/MAPI), the e-mail address books provided are usually based on Directory Services and the Lightweight Directory Access Protocol (LDAP). If you are a Netscape Messenger user and check out directories under your browser preferences (for newer browser users Directory definition is right with the Address Book), you will be able to see the hostnames of a number of commercial directories that allow general access to the public. If you then select your personal address book, export (under the file drop-down menu) it to a file, and then use Notepad or Wordpad to view it, you will notice that each entry is keyed by a set of tags. You should see the dn: tag followed by a set of name/value attributes. Files in this form are in a format known as LDAP Data Interchange Format (LDIF). This file format is used for the batch process loading (and backing up) of Directory Servers. The LDIF file itself is usually populated with information from other data sources (possibly your company's Human Resources Database, the Userid/password database for your Local Area Network, or data extracted from a relational database that keeps track of the hardware configuration of all the workstations in your site and is used via Directory Services by your local helpdesk).

Currently the two most popular uses of directory servers are for user authentication (by userid and password) for accessing limited access Web pages/sites and as e-mail address books for intranet-based address books (for a corporation) and by the large ISPs as address books for their users. In the near future, a big user of directory servers will be e-Commerce applications using the Enterprise Java Beans framework because directory servers are extremely fast. As a special-purpose database, they provide an ideal mechanism for giving persistence to Java objects.

Some Background

The concept and architecture of modern directory services comes to us from the ISO X.500 specification for directory services.

A directory is intended to be a hierarchically arranged database; arrangement of the database is left up to the designer of the Directory Information Tree (DIT). The DIT of a multinational corporation could be arranged a number of ways. It could be arranged hierarchically by geographic location as in Figure 12-1 or by organizational function as in Figure 12-2.

Distinguished name structure by geographic location.

Figure 12-1. Distinguished name structure by geographic location.

Distinguished name structure by organizational function.

Figure 12-2. Distinguished name structure by organizational function.

Every directory entry is uniquely identified by an ordered sequence of name/value attributes. The ordering of the attributes is such that it reflects the hierarchical relationship that exists between the attributes. Assume the following naming attributes:

Attr. Name

Meaning

c

Country

o

Organization

ou

Organizational Unit

cn

Common Name

If we use the concatenation of these attributes and their values to identify an entry in the directory, we have defined the entry's distinguished name. Distinguished names are unique, much like the primary key in a relational database table. My distinguished name using the preceding schema would be

{C=US, O=SUNY, OU=Binghamton, CN=Dick Steflik}
				

Figure 12-1 shows this pictorially.

Each node in the tree structure has a distinguished name made up of the list of attributes up to and including that node. The distinguished name for Binghamton University would be

{ dn: ou=binghamton, o=suny, c=us}

X.500 defines the directory service as an object (see Figure 12-2), accessed through a set of service ports. Each port is intended to provide access to a specific set of services. Three of the primary service ports defined early in the X.500 development are:

  1. Read Port provides the ability to read information from a directory.

  2. Search Port provides the ability to search and list directory information.

  3. Modify Port provides the ability to add, modify, and delete directory entries.

To support these service ports, the DAP has a very comprehensive set of protocol-based operations that address all the facilities needed to create and maintain a large distributed directory.

For applications that are directory-oriented but of a scale smaller than what X.500 and DAP were designed for, applications like address books for Web browsers, authentication for Web pages, a lighterweight version of the DAP has been developed. This slimmed down version of DAP is named Lightweight Directory Access Protocol or LDAP for short. LDAP is described in RFC 2251 as an access mechanism to X.500 directories. It is a language-independent description of the protocol operations required to interact with an X.500 directory.

Introducing Java Naming Directory Interface

The architecture of Java Naming and Directory Interface (JNDI) is a Java-specific architecture for accessing a number of directory-based data repositories including LDAP. The Java Interface to LDAP is only one of a number of services provided through the JNDI architectural model shown in Figure 12-3.

JNDI architecture.

Figure 12-3. JNDI architecture.

If we re-examine the architectural picture of JDBC in Chapter 4, we can see some very real similarities. In Figure 12-3, if we replace "JNDI" with "JDBC," "Service Provider Interface" with "Driver Manager," and "CORBA, NDS, NIS, LDAP, DNS, RMI" with words like "DB2, Oracle, Access, Sybase," we essentially have the same picture. The architecture is essentially the same after all; directories are really just special-purpose databases, and for each database (datasource) there is a driver or service provider.

The main difference between directories and relational databases is that the directory information model is hierarchical, whereas, the model for relational databases is a set of tables. Relational databases are much more general purpose than directories; because directories are special-purpose, their data model can be tailored to their special-purpose uses. This can make them extremely fast for the types of queries done against them, much faster than the equivalent query using a relational database.

Using the JNDI to Access LDAP-Based Data

The Netscape Directory server comes with a sample LDIF file for the Airius Corporation that can be imported to set a reasonably sized and typically set up database. We'll use this directory to demonstrate the major LDAP features.

Setting up the Airius Directory

To start this exercise, go to the Netscape download site for server software and download a trial copy of the Directory Server. This will get you a 30–60 day copy of the world's best Directory Server (yes, I am a little biased). This won't run on W95/W98 so make sure to download a copy for the appropriate platform you want to run the service on.

Using the Netscape Admin Interface, turn off the instance of the Directory Server that you wish to install Airius on. (In Netscape Suite Spot there is a separate Admin server through which you do all Suite Spot server administration.) Click on the button with the name of the instance you want to administer. When the page for the Netscape Directory Server administration is displayed, click the Database Management button and then click on the Import choice in the left-hand frame. On the Import panel, select the radio button for Airius.ldif and then click the OK button. Once imported, remember to turn the Directory Server back on before exiting the Admin Server.

To test your installation, enter the directory setup screen for your browser (and add the server by, assign it a name, enter the IP Address/Hostname or Localhost (if you are using it locally), enter port 389 as the LDAP port, and enter "o=airius.com" as the search root. Save this and try to query *; you should get an address book filled with the people of the Airius Corp.

The Airius Schema

The following is the first part of the Airius LDIF file. If we examine it a little bit, we can determine the schema of the Airius directory and will start to see the power of the LDIF import/export file format. We will also see that some of the information in the LDIF file can be added/updated through the Administration Interface to the Directory Server and that some of the data is best put in via the LDIF file, even though some of the data is directly put in by the people in the directory.

Note

When examining the LDIF file, keep in the back of your mind the fact that lines that are indented by a single space are continuation lines for the preceding line.

dn: o=airius.com
objectclass: top
objectclass: organization
o: airius.com
aci: (target ="ldap:///o=airius.com")
 (targetattr !="userPassword")
 (version 3.0;acl "Anonymous read-search access";allow
 (read, search, compare)(userdn = "ldap:///anyone");)
aci: (target="ldap:///o=airius.com")
 (targetattr = "*")
 (version   3.0; acl "allow all Admin group"; allow(all)
 groupdn = "ldap:///cn=Directory Administrator
 s, ou=Groups, o=airius.com";)

dn: ou=Groups, o=airius.com
objectclass: top
objectclass: organizationalunit
ou: Groups
dn: cn=Directory Administrators, ou=Groups, o=airius.com
cn: Directory Administrators
objectclass: top
objectclass: groupofuniquenames
ou: Groups
uniquemember: uid=kvaughan, ou=People, o=airius.com
uniquemember: uid=rdaugherty, ou=People, o=airius.com
uniquemember: uid=hmiller, ou=People, o=airius.com

The line dn: o=Arius.com identifies this as the distinguished name for the root of the directory tree and is also a member of the "top" and "organization" object classes; the only information stored in this node is "o: airius.com," which identifies the organization as airius.com.

dn: o=airius.com
objectclass: top
objectclass: organization
o: airius.com

The next group of lines is really a single line (notice the indention) that identifies the aci (access control information) for the current node (root)

aci: (target ="ldap:///o=airius.com")
 (targetattr !="userPassword")
 (version 3.0;acl "Anonymous read-search access";allow
 (read, search, compare)(userdn = "ldap:///anyone");)

Letting our imagination run a little bit wild, we can surmise that anyone in the directory has authority to read, search, and compare the userPassword. The second aci:

aci: (target="ldap:///o=airius.com")
 (targetattr = "*")
 (version   3.0; acl "allow all Admin group"; allow(all)
 groupdn = "ldap:///cn=Directory Administrator
 s, ou=Groups, o=airius.com";)

authorizes anyone in the group with the common name "Directory Administrators" full authority for the tree rooted at "o=airius.com" (target attribute). The next distinguished name:

dn: ou=Groups, o=airius.com
objectclass: top
objectclass: organizationalunit
ou: Groups

identifies at the next level in the tree an organizational unit called Groups. The addition of the next dn: ou=Directory Administrators structures our tree as shown in Figure 12-4. This dn: has additional information in it in the form of the "uniquemenbers," which stores each dn: of the unique members as part of the data for that node.

Airius.com.

Figure 12-4. Airius.com.

Examining the node

dn: cn=Directory Administrators, ou=Groups, o=airius.com
cn: Directory Administrators
objectclass: top
objectclass: groupofuniquenames
ou: Groups
uniquemember: uid=kvaughan, ou=People, o=airius.com
uniquemember: uid=rdaugherty, ou=People, o=airius.com
uniquemember: uid=hmiller, ou=People, o=airius.com

we notice that the uniquemembers tag identifies a dn: that is in another branch of the tree. The unique members are in the ou=People branch of the tree—which implies that the tree actually looks like Figure 12-5.

Airius.com.

Figure 12-5. Airius.com.

Notice that to reference information in another branch of the tree all that needs to be done is to identify its dn:.

If we examine more of the LDIF, we quickly come to the realization that the majority of the file is taken up with the definitions of the individual people in the company. Let's look more closely at a single entry because it is the meat of the schema and identifies the attributes that we can query against:

dn: uid=kvaughan, ou=People, o=airius.com
cn: KirstenVaughan
sn: Vaughan
givenname: Kirsten
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
ou: Human Resources
ou: People
l: Sunnyvale
uid: kvaughan
mail: 
telephonenumber: +1 408 555 5625
facsimiletelephonenumber: +1 408 555 3372
roomnumber: 2871
userpassword: bribery

Most of the attribute entries are self-explanatory. Now that we understand the schema, let's get on to the business of using the JNDI to access information in our directory.

Connecting

Recall from JDBC that, before we can query, add to, update, or delete anything from a database we must make a connection to it. To do this we need to create a reference to an object that implements the DirContext interface. This is usually done by creating an InitialDirContext object and assigning it to a DirContext variable. To make the connection, we need to pass some environmental information to the InitDirContext object; this is done by loading a Hashtable with a minimum of two pieces of information:

  1. The fully qualified name of the service provider to be used

  2. The URL (including port number) of the directory server we want to access.

We do this using predefined keys set up in the Context interface.

// create a hash table for passing environment info
Hashtable  environment = new Hashtable( );
// identify the service provider
environment.put(Context.INITIAL_CONTEXT_FACTORY,
                "com.sun.jndi.ldap.LdapCtxFactory");
// identify the directory to be accessed
environment.put(Context.PROVIDER_URL,
                "ldap://mydirectory.com:389");
// get a reference to the directory context
DirContext context = new InitialDirContext(environment);

Searching

This gets us a connection to the directory we wish to use, but to do a search we must supply some additional information. A useful search would be to search the directory for uid=kvaughan and display her attribute information.

When searching, we must set up a SearchControls object to tell the search engine the scope of our search. The SearchControls class has three scopes, identified as constants, that we can use depending on what it is we want to search for:

  1. OBJECT_SCOPE—Limits the search to the names object.

  2. ONE_LEVEL_SCOPE—Limits the search to all of the objects at the same level in the named context.

  3. SUBTREE_SCOPE—Limits the search to the named subtree.

    //set the search scope
    SearchControls scope = new SearchControl;
    Scope.setSearchScope(SearchControls.SUBTREE_SCOPE)
    

To do the actual search, we invoke the Search method on the DirContext object we created a little while ago. The Search method has a number of overloads; make sure that you read the API carefully, or you may not get the results you are after. For our example, we will need to identify the base of our search, a search filter (similar in purpose to the "where" clause of an SQL select statement), and any constraints we set (SUBTREE_SCOPE). The results from a directory search come back in a data structure called a NamingEnumeration. NamingEnumeration is a JNDI-specific extension of the Enumeration class that allows exceptions to be thrown during enumeration (this implies that it needs to be in a try/catch statement).

public static String BASE = "0=airius.com";
public static String FILTER = "uid=kvaughan";
NamingEnumeration result = context.search(BASE, FILTER, scope);

If we have done everything right to this point, the search results are waiting for us in the NamingEnumeration. Each entry in NamingEnumeration is a SearchResults object; remember that NamingEnumeration holds objects and that as we take objects out of it we must cast them to the appropriate type.

SearchResult srchresult = (SearchResult) result.next();

At this point, we can retrieve the distinguished name from the SearchResult object by using its getName method;

String dn = srchresult.getName();

This will give us the distinguished name relative to where we rooted our search. To get the entire distinguished name, we must concatenate the variable we used to base our search on

String temp = "dn= " + dn + BASE;

The attributes are still in the SearchResults object and can be retrieved into an Attributes object using the SearchResults getAttributes method

Attributes attrs = srchresults.getAttributes();

Recall from our brief introduction to LDIF that any attribute may have multiple values (e.g., a person may have multiple e-mail addresses). What we need to do now is iterate through the returned attributes and, for each attribute found, iterate through the list of returned values for that attribute.

NamingEnumeration ne = attrs.getAll();
While (ne.hasMoreElements)
{
   Attribute attr = (Attribute) ne.next();
   System.out.println(attr.getID());
   Enumeration values = attr.getAll();
   while ( values.hasMoreElements())
      System.out.println("    " + values.nextElement());
}

The whole example follows:

import java.util.Hashtable;
import java.util.Enumeration;
import javax.naming.*;
import javax.naming.directory.*;

public class DirectorySearch
{
   public static String BASE = "o=airius.com";
   public static String FILTER = "uid=kvaughan";

   public static void main (String args[])
   {
      try
      {
         // create a hash table for passing environment info
         Hashtable  environment = new Hashtable( );
         // identify the service provider
         environment.put(Context.INITIAL_CONTEXT_FACTORY,
                         "com.sun.jndi.ldap.LdapCtxFactory");
         // identify the directory to be accessed
         environment.put(Context.PROVIDER_URL,
                         "ldap://mydirectory.com:389");
         // get a reference to the directory context
         DirContext context = new InitialDirContext(environment);
         //set the search scope
         SearchControls scope = new SearchControls();
         scope.setSearchScope(SearchControls.SUBTREE_SCOPE);
         NamingEnumeration result = context.search(BASE, FILTER,
                                    scope);
         SearchResult  srchresult = (SearchResult) result.next();
         String dn = srchresult.getName();
         String temp = "dn= " + dn + BASE;
         Attributes attrs = srchresult.getAttributes();
         NamingEnumeration ne = attrs.getAll();
         while (ne.hasMoreElements())
         {
            Attribute attr = (Attribute) ne.next();
            String attrname = attr.getID() + ": ";
            Enumeration values = attr.getAll();
            while ( values.hasMoreElements())
               System.out.println(attrname + values.nextElement());
          }
       }
       catch (Exceptione)
       {
          System.out.println("Exception: " + e.toString());
       }
    }
}

After compiling and running the program, we get Figure 12-6.

Output from the DirectorySearch program.

Figure 12-6. Output from the DirectorySearch program.

This just happens to be what we were looking for. Neat stuff.

So far we've covered most of the basics that we need to be able to start thinking about adding, modifying, and deleting entries to/from the directory. We'll address these topics using code snippets and some associated commentary.

Adding Persons to the Directory

The hardest part about adding a person to the directory, besides creating the data input panel, is creating a class that defines all the attributes that identify the person. This is necessary because, to add the person to the directory, we must bind the person object to their distinguished name.

String dn = "uid=mtoad, ou=People, o=airius.com";
Person newperson = new Person("mtoad","Mark","Toad",
                              "ou=Engineering",
                              "[email protected]");
context.bind(dn, newperson);

Modifying Information Already in the Directory

To modify attributes of an existing entry, we first set up our environment and get a reference to a directory context as we have in the previous examples. After we have the reference, we create a ModificationItem array to hold the modifications we wish performed on the item. Finally, we use the modifyAttributes method of the DirContext object to update the data.

ModificationItem[] updates = new ModificationItem[3];
Attribute update0 = new BasicAttribute("roomnumber", "1234");
Attribute update1 = new BasicAttribute("l", "Chicago");
Updates[0] = new
        ModificationItem(DirContext.REPLACE_ATTRIBUTE,update0);

Updates[1] = new
        ModificationItem(DirContext.REPLACE_ATTRIBUTE,update1);
context.modifyAttributes(dn,updates);

The DirContext interface also provides the tagged constants ADD_ATTRIBUTE and DELETE_ATTRIBUTE for effecting the adding and deleting of attribute information.

Removing Entries from the Directory

To delete an item from the directory, we start out as we have previously, by obtaining a reference to a directory context; then all we do is use the destroySubcontext method of the DirContext interface.

context.destroySubcontext(dn);

Authentication

One additional thing we must remember for any of the directory modification operations is that, according to our directory ACI (refer to the section on LDIF), only people belonging to the Directory Administrator group had all authority and would be allowed to write into the directory. To do this, the application must authenticate with the uid and password of one of the Directory Administrators. This is done by adding three additional tagged values to the context environment Hashtable.

public static String ADMIN = "uid=kvaughan, ou=People,
                              o=airius.com";
public static String ADMIN_PW = "bribery";
.
.
.
env.put(Context.SECURITY_AUTHENTICATION,"simple");
env.put(Context.SECURITY_PRINCIPAL,ADMIN);
env.put(Context.SECURITY_CREDENTIALS,ADMIN_PW);

Summary

There you have the five-penny tour of Directory Services, LDAP, and JNDI. As the industry progresses in its quest for ultimately scalable applications that are robust and secure, we will see the Directory Server, LDAP, and JNDI take on larger roles. One of the most exciting of which is the storage of Java objects. This is exciting because Directory Servers are so darned fast that they make an ideal place to store serialized objects produced as part of an application's shutdown process; as part of the application's startup process, it retrieves the object from the server and picks up where it left off. The best part is that this is just the normal way that Directory Servers do business (i.e., binding names to objects).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset