Welcome!

PowerBuilder Authors: Dan Joe Barry, Carmen Gonzalez, Ian Thain, Yakov Werde, Paul Slater

Related Topics: PowerBuilder

PowerBuilder: Article

LDAP with EAServer and PB 8.0

LDAP with EAServer and PB 8.0

In Part 1 (PBDJ, Vol. 9, issue 6) I discussed directory services and how they provide authentication, access control, and finder services for our application. In Part 2, I explain how the JNDI API lets us easily use LDAP.

JNDI Overview
The Java Naming and Directory Interface (JNDI) is an application programming interface (API) that provides naming and directory functionality to Java applications. It's independent of any specific directory service implementation, thus a variety of directories - new, emerging, and already deployed - can be accessed the same way.

JNDI Architecture
The JNDI architecture consists of an API and a service provider interface (SPI). Java applications use the JNDI API to access a variety of naming and directory services. The SPI enables these services to be plugged in transparently, allowing the Java application using the JNDI API to access their services (see Figure 1).

The JNDI class libraries are already included in the Java 2 SDK v1.3, which comes with service providers for LDAP, COS naming, and the RMI registry, and in the rt.jar. If you're using an earlier version of the SDK (with EAServer), you can use the JNDI software, available on the JNDI Web site (at the time of writing, the most current file was ldap-1_2_4.zip; put the file lib/ldapbp.jar into your %Jaguar%/java/lib directory). The JNDI API is a generic API for accessing any naming or directory service. Actual access is enabled by plugging in a service provider under the JNDI. A service provider is software that maps the JNDI API to actual calls to the naming or directory server.

Typically, the roles of the service provider and the naming/directory server differ. In the terminology of client/server software, the JNDI and the service provider are the client (called the JNDI client) and the naming/directory server is the server.

Clients and servers can interact in many ways, e.g., using a network protocol so the client and server can exist autonomously in a networked environment. The server typically supports many different clients, not only JNDI clients, provided the clients conform to the specified protocol. The JNDI does not dictate any particular style of interaction between JNDI clients and servers. For example, at one extreme the client and server could be the same entity.

We need to obtain the classes for the service providers we'll be using. In our example, we plan on using the JNDI to access an LDAP directory server, so we need the software for an LDAP service provider.

Once we have obtained the service provider software, we need to set up or have access to a corresponding naming/directory server. Setting up a naming/directory server is typically the job of a network system administrator. Different vendors have different installation procedures for their servers. Some require special machine privileges before the server can be installed, so consult the naming/directory server software's installation instructions. In this example we're using OpenLDAP and this is (hopefully) still up and running.

The Parameters to Pass
The PowerBuilder client talking to our EAServer component will pass different parameters to the component, depending on the actual function call the client is invoking.

We create the classes ldapParam (see Listing 1) and ldapAttrs (see Listing 2). The first one (ldapParam) holds the values to connect to the LDAP server:

  • LDAPUser: The user that connects to the server
  • LDAPPwd: The password for the user
  • LDAPHost: The LDAP host machine's address
  • LDAPPort: The port the LDAP server listens on (default: 389)
  • LDAPDN: The distinguished name for connecting
  • LDAPAttrNames: Not used at the moment ldapAttrs hold the values for making changes on our LDAP server:
  • attrName: The attribute we will change/add
  • attrValues: The value(s) for the attribute

Then we need a class that holds the return values we want to send back to our client (in this case, PowerBuilder) (see Listing 3).

  • ldapsuccess: True, if the last action was successful
  • errorString: If there was an error, we pass back the error information
  • LDAPAttrib: Attributes we want to send back
  • LDAPValue: Values for attributes

Business Functions
In the Remote Interface (see Listing 4) we implement four functions:

1.   ldapAuthenticate: Connect to the LDAP server by username/password verification
2.   ldapSearch: Search for DN and return the attributes
3.   ldapChange: Change attributes on a DN
4.   ldapAdd: Add a DN with the given attributes

The ldapAuthenticate Function
In the ldapAuthenticate function we'll pass the information that's necessary to connect to the LDAP server (see Listing 5). Before performing any operation on a naming or directory service, we need to acquire an initial context, so this is the starting point into the namespace. This is because all methods on naming and directory services are performed relative to some context. To get an initial context, follow these steps:

1.   Select the service provider of the corresponding service you want to access.

Specify the service provider to use for the initial context by creating a set of environment properties (a Hashtable) and adding the name of the service provider class to it. We are using the LDAP service provider from Sun Microsystems, so the code looks like the following:

Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");

2.   Specify any configuration that the initial context needs.

Clients need a variety of information to contact the directory. For example, we need to specify on which machine the server is running and what information is necessary to identify the user to the directory. Such information is passed to the service provider via environment properties. The LDAP service provider requires that the program specifies the location of the LDAP server (IP address and port), as well as user identity information. The following code provides this information:

env.put(Context.PROVIDER_URL, "ldap://" +
ldapparam.LDAPHost+ ":" + ldapparam.LDAPPort);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, ldapparam.LDAPUser);
env.put(Context.SECURITY_CREDENTIALS, ldapparam.LDAPPwd);

3.   Call the initial context constructor.


We're now ready to create the initial context. To do that, pass the environment properties that were created earlier to the InitialContext constructor:

DirContext ctx = new InitialContext(env);

If all is fine, this statement then returns a reference to a DirContext that we'll use when we do different operations on the LDAP directory, such as searching for, adding, or deleting attributes. If there's a problem (either the server could not be found or the username/password verification failed) we'll catch the exceptions and return a message back to the PowerBuilder client.

The ldapSearch Function
One of the most useful features a directory offers is its yellow pages or search service. You can compose a query consisting of the attributes of the entries that you're seeking and submit that query to the directory. The directory then returns a list of entries that satisfy the query.

In Listing 6 we perform a search with a search base and a search filter (though we don't really need the filter). A search filter is a search query expressed in the form of a logical expression. The syntax of search filters accepted by DirContext.search() is described in RFC 2254 and is basically a logical expression in prefix notation (i.e., the logical operator appears before its arguments).

Note that in most LDAP APIs, we must specify the scope as a parameter in the LDAP search method. This defines exactly how much of the tree we want to search. There are three levels of scope:

1.   OBJECT_SCOPE: Searches the base entry; useful if we want to get the attributes/values of one entry
2.   ONELEVEL_SCOPE: Searches the children of the search base, but not any of its grandchildren
3.   SUBTREE_SCOPE: Starts at the base entry and searches everything below it, including the base entry

Listing 6 uses OBJECT_SCOPE and returns all attributes associated with the entry that satisfies the specified filter (in fact, all our entries have an objectclass).

constraints.setSearchScope(SearchControls.OBJECT_SCOPE);

To select the attributes to return, set the search controls argument. Create an array of attribute identifiers to include in the result and pass it to SearchControls.setReturningAttributes(). For example, to return just the attribute sn write:

String[] attrIDs = {"sn"};
constraints.setReturningAttributes(attrIDs);

To perform the search we call:

NamingEnumeration results =
ctx.search(ldapparam.LDAPDN,
"(&(objectclass=*))", constraints);

Each element in a NamingEnumeration object will contain a SearchResult object that we can retrieve like this:

SearchResult si = (SearchResult)results.next();

We can get the DN of an entry like this:

System.out.println("name: " + si.getName());

To get the attributes of an entry we use the getAttributes() method of the SearchResult class:

Attributes attrs = si.getAttributes();

Now we can step through the attributes, read the values, and store them (first in a vector) in order to return them (using a string array) to the client.

To test the login, use the PowerBuilder client. Enter the values for the OpenLDAP administrator or provide these values (see Listing 1 from Part 1):

uid=berham, ou=developer, o=myorg, c=US ham1
uid=betha, ou=developer, o=myorg, c=US betha1

Click the Test Login button and you should get a MessageBox indicating a successful login or an errorstring that tells you what went wrong.

The ldapChange Function
One way to modify the attributes of an object is to supply a list of modification requests (ModificationItem). Each ModificationItem consists of a numeric constant that indicates the type of modification to make and an Attribute that describes the modification (see Listing 7). (Due to space constraints, Listings 7 and 8 can be downloaded from www.sys-con.com/pbdj/sourcec.cfm.) Following are the three types of modifications:

1.   ADD_ATTRIBUTE
2.   REPLACE_ATTRIBUTE
3.   REMOVE_ATTRIBUTE

Modifications are applied in the order in which they appear in the list. Either all or none of the modifications are executed. Use a for loop to get all attributes and change one value (if you want to have more than one value for an attribute, add a second for loop).

ModificationItem[] mods = new ModificationItem[ldapattrs.length];
for(int valNr = 0; valNr < ldapattrs.length; valNr++) { Attribute mod0 = new
BasicAttribute(ldapattrs[valNr].attrName,
ldapattrs[valNr].attrValues[0]);
}

After creating this list of modifications, we can supply it to modifyAttributes() as follows:

ctx.modifyAttributes(ldapparam.LDAPDN, mods);

To test the new function, run the PowerBuilder client and click the Read button; the output is on the left (see Figure 2). Clicking the change button changes my former girlfriend's status to that of wife (see Figure 3). This means that sn and cn attributes have changed. To see it click the Clear button and again the Read button.

The interesting part behind the Change button is these lines:

String cn_values[] = { "Hamboeck"};
String sn_values[] = { "Bettina Hamboeck" };
lstr_attrs[1].attrname = "cn"
lstr_attrs[1].attrvalues = cn_values
lstr_attrs[2].attrname = "sn"
lstr_attrs[2].attrvalues = sn_values
lstr_return = ldap.ldapChange(lstr_param, lstr_attrs)

We set the attribute's name and a new value and pass this to the ldapChange function in our EJB.

The ldapAdd Function
This is very similar to the ldapAdd function (see Listing 8). I've implemented that an attribute can have more than one value, so we have two for loops here.

The createSubcontext function creates a new subcontext with the given name, binds it in the target context (named by all but the terminal atomic component of the name), and associates the supplied attributes with the newly created object.

ctx.createSubcontext(ldapparam.LDAPDN, orig);

To test our new function, run the PowerBuilder client and click the Add button. Verify that this new developer is in the LDAP directory with the Read button (by copying the DN to the read singlelinedit) or the LDAP browser.

Summary
Directory services play an important role in the network environment. They provide authentication, access control, and finder services for the applications. The JNDI API allows us to use LDAP easily, and with EAServer we can provide access to different clients and use the same business logic in different client applications.

Resources

More Stories By Berndt Hamboeck

Berndt Hamboeck is a senior consultant for BHITCON (www.bhitcon.net). He's a CSI, SCAPC8, EASAC, SCJP2, and started his Sybase development using PB5. You can reach him under admin@bhitcon.net.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.