Tuesday, July 14, 2009

Beginning PHP and Oracle From Novice to Professional by W. Jason Gilmore and Bob Bryla Chapter 17

As corporate hardware and software infrastructures expanded throughout the last decade, IT
professionals found themselves overwhelmed with the administrative overhead required to manage
the rapidly growing number of resources being added to the enterprise. Printers, workstations, servers, switches, and other miscellaneous network devices all required continuous monitoring and manage- ment, as did user resource access and network privileges.
Quite often the system administrators cobbled together their own internal modus operandi for
maintaining order, systems that all too often were poorly designed, insecure, and nonscalable. An alternative but equally inefficient solution involved the deployment of numerous disparate systems, each doing its own part to manage some of the enterprise, yet coming at a cost of considerable over- head because of the lack of integration. The result was that both users and administrators suffered from the absence of a comprehensive management solution, at least until directory services came along.
Directory services offer system administrators, developers, and end users alike a consistent,
efficient, and secure means for viewing and managing resources such as people, files, printers, and applications. The structure of these read-optimized data repositories often closely models the phys- ical corporate structure, an example of which is depicted in Figure 17-1.

Figure 17-1. A model of the typical corporate structure

305

Numerous leading software vendors have built flagship directory services products and indeed centered their entire operations around such offerings. The following are just a few of the more popular products:

• Fedora Directory Server: http://directory.fedora.redhat.com/

• Microsoft Active Directory: http://www.microsoft.com/activedirectory/

• Novell eDirectory: http://www.novell.com/products/edirectory/

• Oracle Collaboration Suite: http://www.oracle.com/collabsuite/

All widely used directory services products depend heavily upon an open specification known as the Lightweight Directory Access Protocol, or LDAP. In this chapter, you will learn how easy it is to talk to LDAP via PHP’s LDAP extension. In the end, you’ll possess the knowledge necessary to begin talking to directory services via your PHP applications.
Because an introductory section on LDAP wouldn’t be nearly enough to do the topic justice, it’s assumed you’re reading this chapter because you’re already a knowledgeable LDAP user and are seeking more information about how to communicate with your LDAP server using the PHP language. If you are, however, new to the topic, consider taking some time to review the following online resources before continuing:

LDAP v3 specification (http://www.ietf.org/rfc/rfc3377.txt): The official specification of
Lightweight Directory Access Protocol Version 3

The Official OpenLDAP Web site (http://www.openldap.org/): The official Web site of LDAP’s widely used open source implementation

IBM LDAP Redbooks (http://www.redbooks.ibm.com/): IBM’s free 700+ page introduction to LDAP

Using LDAP from PHP

PHP’s LDAP extension seems to be one that has never received the degree of attention it deserves. Yet it offers a great deal of flexibility, power, and ease of use, three traits developers yearn for when creating the often complex LDAP-driven applications. This section is devoted to a thorough exami- nation of these capabilities, introducing the bulk of PHP’s LDAP functions and weaving in numerous hints and tips on how to make the most of PHP/LDAP integration.

■Note The examples found throughout this chapter use an LDAP server made available for testing purposes by
the OpenLDAP project. However, because the data found on this server is likely to change over time, the sample results are contrived. Further, read-only access is available, meaning you will not be able to insert, modify, or delete data as demonstrated later in this chapter. Therefore, to truly understand the examples, you’ll need to set up your own LDAP server or be granted administrator access to an existing server. For Linux, consider using OpenLDAP (http://www.openldap.org/). For Windows, numerous free and commercial solutions are available, although Lucas Bergman’s OpenLDAP binaries for Windows seem to be particularly popular. See http://www.bergmans.us/ for more information.

Connecting to an LDAP Server

The ldap_connect() function establishes a connection to an LDAP server identified by a specific host name and optionally a port number. Its prototype follows:

resource ldap_connect([string hostname [, int port]])

If the optional port parameter is not specified, and the ldap:// URL scheme prefaces the server or the URL scheme is omitted entirely, LDAP’s standard port 389 is assumed. If the ldaps:// scheme is used, port 636 is assumed. If the connection is successful, a link identifier is returned; on error, FALSE is returned. A simple usage example follows:

<?php
$host = "ldap.openldap.org";
$port = "389";
$connection = ldap_connect($host, $port)
or die("Can't establish LDAP connection");
?>

Although Secure LDAP (LDAPS) is widely deployed, it is not an official specification. OpenLDAP 2.0
does support LDAPS, but it’s actually been deprecated in favor of another mechanism for ensuring secure LDAP communication known as Start TLS.

Securely Connecting Using the Transport Layer Security Protocol

Although not a connection-specific function per se, ldap_start_tls() is introduced in this section nonetheless because it is typically executed immediately after a call to ldap_connect() if the developer wants to connect to an LDAP server securely using the Transport Layer Security (TLS) protocol. Its prototype follows:

boolean ldap_start_tls(resource link_id)

There are a few points worth noting regarding this function:

• TLS connections for LDAP can take place only when using LDAPv3. Because PHP uses LDAPv2 by default, you need to declare use of version 3 specifically, by using ldap_set_option() before making a call to ldap_start_tls().
• You can call the function ldap_start_tls() before or after binding to the directory, although calling it before makes much more sense if you’re interested in protecting bind credentials.

An example follows:

<?php
$connection = ldap_connect("ldap.openldap.org"); ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_start_tls($connection);
?>

Because ldap_start_tls() is used for secure connections, new users commonly mistakenly
attempt to execute the connection using ldaps:// instead of ldap://. Note from the preceding example that using ldaps:// is incorrect, and ldap:// should always be used.

Binding to the LDAP Server

Once a successful connection has been made to the LDAP server (see the earlier section “Connecting to an LDAP Server”), you need to pass a set of credentials under the guise of which all subsequent LDAP queries will be executed. These credentials include a username of sorts, better known as an RDN, or Relative Distinguished Name, and a password. To do so, you use the ldap_bind() function. Its prototype follows:

boolean ldap_bind(resource link_id [, string rdn [, string pswd]])

Although anybody could feasibly connect to the LDAP server, proper credentials are often required before data can be retrieved or manipulated. This feat is accomplished using ldap_bind(). This function requires at minimum the link_id returned from ldap_connect() and likely a username and password denoted by rdn and pswd, respectively. An example follows:
<?php
$host = "ldap.openldap.org";
$port = "389";

$connection = ldap_connect($host, $port)
or die("Can't establish LDAP connection");

ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);

ldap_bind($connection, $username, $pswd)
or die("Can't bind to the server.");
?>

Note that the credentials supplied to ldap_bind() are created and managed within the LDAP
server and have nothing to do with any accounts residing on the server or the workstation from which you are connecting. Therefore, if you are unable to connect anonymously to the LDAP server, you need to talk to the system administrator to arrange for an appropriate account.
Also, demonstrated in the previous example, to connect to the test ldap.openldap.org server
you’ll need to execute ldap_set_option() because only the version 3 protocol is accepted.

Closing the LDAP Server Connection

After you have completed all of your interaction with the LDAP server, you should clean up after yourself and properly close the connection. One function, ldap_unbind(), is available for doing just this. Its prototype follows:

boolean ldap_unbind(resource link_id)

The ldap_unbind() function terminates the LDAP server connection associated with link_id. A usage example follows:
<?php

// Connect to the server
$connection = ldap_connect("ldap.openldap.org")
or die("Can't establish LDAP connection");

// Bind to the server
ldap_bind($connection) or die("Can't bind to LDAP.");

// Execute various LDAP-related commands...

// Close the connection ldap_unbind($connection)
or die("Could not unbind from LDAP server.");
?>

■Note The PHP function ldap_close() is operationally identical to ldap_unbind(), but because the LDAP API refers to this function using the latter terminology, it is recommended over the former for reasons of readability.

Retrieving LDAP Data

Because LDAP is a read-optimized protocol, it makes sense that a bevy of useful data search and retrieval functions would be offered within any implementation. Indeed, PHP offers numerous functions for retrieving directory information. Those functions are examined in this section.

Searching for One or More Records

The ldap_search() function is one you’ll almost certainly use on a regular basis when creating LDAP-enabled PHP applications because it is the primary means for searching a directory based on a specified filter. Its prototype follows:

resource ldap_search(resource link_id, string base_dn, string filter
[, array attributes [, int attributes_only [, int size_limit
[, int time_limit [int deref]]]]])

A successful search returns a result set, which can then be parsed by other functions, which are introduced later in this section; a failed search returns FALSE. Consider the following example in which ldap_search() is used to retrieve all users with a first name beginning with the letter A:

$results = ldap_search($connection, "dc=OpenLDAP,dc=Org", "givenName=A*");

Several optional attributes tweak the search behavior. The first, attributes, allows you to specify exactly which attributes should be returned for each entry in the result set. For example, if you want to obtain each user’s last name and e-mail address, you could include these in the attributes list:

$results = ldap_search($connection, "dc=OpenLDAP,dc=Org", "givenName=A*", "surname,mail");
Note that if the attributes parameter is not explicitly assigned, all attributes will be returned for each entry, which is inefficient if you’re not going to use all of them.
If the optional attributes_only parameter is enabled (set to 1), only the attribute types are
retrieved. You might use this parameter if you’re only interested in knowing whether a particular attribute is available in a given entry and you’re not interested in the actual values. If this parameter is disabled (set to 0) or omitted, both the attribute types and their corresponding values are retrieved.
The next optional parameter, size_limit, can limit the number of entries retrieved. If this param- eter is disabled (set to 0) or omitted, no limit is set on the retrieval count. The following example retrieves both the attribute types and corresponding values of the first five users with first names beginning with A:

$results = ldap_search($connection, "dc=OpenLDAP,dc=Org", "givenName=A*", 0, 5);

Enabling the next optional parameter, time_limit, places a limit on the time, in seconds, devoted to a search. Omitting or disabling this parameter (setting it to 0) results in no set time limit, although such a limit can be (and often is) set within the LDAP server configuration. The next example performs the same search as the previous example, but limits the search to 30 seconds:

$results = ldap_search($connection, "dc=OpenLDAP,dc=Org", "givenName=A*", 0, 5, 30);

The eighth and final optional parameter, deref, determines how aliases are handled. Aliases are out of the scope of this chapter, although you’ll find plenty of information about the topic online.

Doing Something with Returned Records

Once one or several records have been returned from the search operation, you’ll probably want to do something with the data, either output it to the browser or perform other actions. One of the easiest ways to do this is through the ldap_get_entries() function, which offers an easy way to place all members of the result set into a multidimensional array. Its prototype follows:

array ldap_get_entries(resource link_id, resource result_id)

The following list offers the numerous items of information that can be derived from this array:

return_value["count"]: The total number of retrieved entries

return_value[n]["dn"]: The Distinguished Name (DN) of the nth entry in the result set

return_value[n]["count"]: The total number of attributes available in the nth entry of the result set

return_value[n]["attribute"]["count"]: The number of items associated with the nth entry of attribute

return_value[n]["attribute"][m]: The mth value of the nth entry attribute return_value[n][m]: The attribute located in the nth entry’s mth position Consider an example:
<?php

$host = "ldap.openldap.org";
$port = "389";

$dn = "dc=OpenLDAP,dc=Org";

$connection = ldap_connect($host)
or die("Can't establish LDAP connection");

ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);

ldap_bind($connection)
or die("Can't bind to the server.");

// Retrieve all records of individuals having first name
// beginning with letter K
$results = ldap_search($connection, $dn, "givenName=K*");

// Dump records into array
$entries = ldap_get_entries($connection, $results);

// Determine how many records were returned
$count = $entries["count"];

// Cycle through array and output name and e-mail address for($x=0; $x < $count; $x++) {
printf("%s ", $entries[$x]["cn"][0]);
printf("(%s) <br />", $entries[$x]["mail"][0]);
}

?>

Executing this script produces output similar to this:

Kyle Billingsley (billingsley@example.com) Kurt Kramer (kramer@example.edu)
Kate Beckingham (beckingham.2@example.edu)

Retrieving a Specific Entry

You should use the ldap_read() function when you’re searching for a specific entry and can identify that entry by a particular DN. Its prototype follows:

resource ldap_read(resource link_id, string base_dn, string filter
[, array attributes [, int attributes_only [, int size_limit
[, int time_limit [int deref]]]]])

For example, to retrieve the first and last name of a user identified only by his user ID, you might execute the following:

<?php

$host = "ldap.openldap.org";

// Who are we looking for?
$dn = "uid=wjgilmore,ou=People,dc=OpenLDAP,dc=Org";

// Connect to the LDAP server
$connection = ldap_connect($host)
or die("Can't establish LDAP connection");

ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);

// Bind to the LDAP server
ldap_bind($connection) or die("Can't bind to the server.");

// Retrieve the desired information
$results = ldap_read($connection, $dn, '(objectclass=person)', array("givenName", "sn"));

// Retrieve an array of returned records
$entry = ldap_get_entries($connection, $results);

// Output the first and last names
printf("First name: %s <br />", $entry[0]["givenname"][0]);
printf("Last name: %s <br />", $entry[0]["sn"][0]);

// Close the connection ldap_unbind($connection);

?>

This returns the following:

First Name: William
Last Name: Gilmore

Counting Retrieved Entries

It’s often useful to know how many entries are retrieved from a search. PHP offers one explicit function for accomplishing this, ldap_count_entries(). Its prototype follows:

int ldap_count_entries(resource link_id, resource result_id)

The following example returns the total number of LDAP records representing individuals having a last name beginning with the letter G:
$results = ldap_search($connection, $dn, "sn=G*");
$count = ldap_count_entries($connection, $results);
echo "<p>Total entries retrieved: $count</p>";

This returns the following:

Total entries retrieved: 45

Sorting LDAP Records

The ldap_sort() function can sort a result set based on any of the returned result attributes. Sorting is carried out by simply comparing the string values of each entry, rearranging them in ascending order. Its prototype follows:

boolean ldap_sort(resource link_id, resource result, string sort_filter)

An example follows:

<?php

// Connect and bind...
$results = ldap_search($connection, $dn, "sn=G*", array("givenName", "sn"));

// Sort the records by the user's first name ldap_sort($connection, $results, "givenName");

$entries = ldap_get_entries($connection,$results);

$count = $entries["count"];

for($i=0;$i<$count;$i++) {
printf("%s %s <br />",
$entries[$i]["givenName"][0], $entries[$i]["sn"][0]);
}

ldap_unbind($connection);
?>

This returns the following:

Jason Gilmore John Gilmore Robert Gilmore

Inserting LDAP Data

Inserting data into the directory is as easy as retrieving it. In this section, two of PHP’s LDAP insertion functions are introduced.

Adding a New Entry

You can add new entries to the LDAP directory with the ldap_add() function. Its prototype follows:

boolean ldap_add(resource link_id, string dn, array entry)

An example follows; although keep in mind this won’t execute properly because you don’t possess adequate privileges to add users to the OpenLDAP directory:

<?php
/* Connect and bind to the LDAP server...*/

$dn = "ou=People,dc=OpenLDAP,dc=org";
$entry["displayName"] = "John Wayne";
$entry["company"] = "Cowboys, Inc.";
$entry["mail"] = "pilgrim@example.com";
ldap_add($connection, $dn, $entry) or die("Could not add new entry!");
ldap_unbind($connection);
?>

Pretty simple, huh? But how would you add an attribute with multiple values? Logically, you
would use an indexed array:

$entry["displayName"] = "John Wayne";
$entry["company"] = "Cowboys, Inc.";
$entry["mail"][0] = "pilgrim@example.com";
$entry["mail"][1] = "wayne.2@example.edu";
ldap_add($connection, $dn, $entry) or die("Could not add new entry!");

Adding to Existing Entries

The ldap_mod_add() function is used to add additional values to existing entries, returning TRUE on success and FALSE on failure. Its prototype follows:

boolean ldap_mod_add(resource link_id, string dn, array entry)

Revisiting the previous example, suppose that the user John Wayne requested that another e-mail address be added. Because the mail attribute is multivalued, you can just extend the value array using PHP’s built-in array expansion capability. An example follows, although keep in mind this won’t execute properly because you don’t possess adequate privileges to modify users residing in the OpenLDAP directory:

$dn = "ou=People,dc=OpenLDAP,dc=org";
$entry["mail"][] = "pilgrim@example.com";
ldap_mod_add($connection, $dn, $entry)
or die("Can't add entry attribute value!");

Note that the $dn has changed here because you need to make specific reference to John Wayne’s directory entry.
Suppose that John now wants to add his title to the directory. Because the title attribute is single-valued it can be added like so:
$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org";
$entry["title"] = "Ranch Hand";
ldap_mod_add($connection, $dn, $entry) or die("Can't add new value!");

Updating LDAP Data

Although LDAP data is intended to be largely static, changes are sometimes necessary. PHP offers two functions for carrying out such modifications: ldap_modify(), for making changes on the attribute level, and ldap_rename(), for making changes on the object level. Both are introduced in this section.

Modifying Entries

The ldap_modify() function is used to modify existing directory entry attributes, returning TRUE on success and FALSE on failure. Its prototype follows:

boolean ldap_modify(resource link_id, string dn, array entry)

With this function, you can modify one or several attributes simultaneously. Consider an example:
$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org";
$attrs = array("Company" => "Boots 'R Us", "Title" => "CEO");
ldap_modify($connection, $dn, $attrs);

■Note The ldap_mod_replace() function is an alias to ldap_modify().

Renaming Entries

The ldap_rename() function is used to rename an existing entry. Its prototype follows:

boolean ldap_rename(resource link_id, string dn, string new_rdn, string new_parent, boolean delete_old_rdn)

The new_parent parameter specifies the newly renamed entry’s parent object. If the parameter delete_old_rdn is set to TRUE, the old entry is deleted; otherwise, it will remain in the directory as a nondistinguished value of the renamed entry.

Deleting LDAP Data

Although it is rare, data is occasionally removed from the directory. Deletion can take place on two levels—removal of an entire object, or removal of attributes associated with an object. Two functions are available for performing these tasks, ldap_delete() and ldap_mod_del(), respectively. Both are introduced in this section.

Deleting Entries

The ldap_delete() function removes an entire entry from the LDAP directory, returning TRUE on success and FALSE on failure. Its prototype follows:

boolean ldap_delete(resource link_id, string dn)

An example follows:

$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org";
ldap_delete($connection, $dn) or die("Could not delete entry!");

Completely removing a directory object is rare; you’ll probably want to remove object attributes rather than an entire object. This feat is accomplished with the function ldap_mod_del(), introduced next.

Deleting Entry Attributes

The ldap_mod_del() function removes the value of an entity instead of an entire object. Its prototype follows:

boolean ldap_mod_del(resource link_id, string dn, array entry)

This limitation means it is used more often than ldap_delete() because it is much more likely that attributes will require removal rather than entire objects. In the following example, user John Wayne’s company attribute is deleted:

$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org";
ldap_mod_delete($connection, $dn, array("company"));

In the following example, all entries of the multivalued attribute mail are removed:

$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org ";
$attrs["mail"] = array();
ldap_mod_delete($connection, $dn, $attrs);

To remove just a single value from a multivalued attribute, you must specifically designate that value, like so:
$dn = "cn=John Wayne,ou=People,dc=OpenLDAP,dc=org ";
$attrs["mail"] = "pilgrim@example.com";
ldap_mod_delete($connection, $dn, $attrs);

Working with the Distinguished Name

It’s sometimes useful to learn more about the DN of the object you’re working with. Several functions are available for doing just this, each of which is introduced in this section.

Converting the DN to a Readable Format

The ldap_dn2ufn() function converts a DN to a more readable format. Its prototype follows:

string ldap_dn2ufn(string dn)

This is best illustrated with an example:

<?php
// Define the dn
$dn = "OU=People,OU=staff,DC=ad,DC=example,DC=com";

// Convert the DN to a user-friendly format echo ldap_dn2ufn($dn);
?>

This returns the following:

People, staff, ad.example.com

Loading the DN into an Array

The ldap_explode_dn() function operates much like ldap_dn2ufn(), except that each component of the DN is returned in an array rather than in a string, with the first array element containing the array size. Its prototype follows:

array ldap_explode_dn(string dn, int only_values)

If the only_values parameter is set to 0, both the attributes and corresponding values are included in the array elements; if it is set to 1, just the values are returned. Consider this example:

<?php

$dn = "OU=People,OU=staff,DC=ad,DC=example,DC=com";
$dnComponents = ldap_explode_dn($dn, 0);

foreach($dnComponents as $component)
printf("%s <br />", $component);

?>

This returns the following:

5
OU=People OU=staff DC=ad DC=example DC=com

Error Handling

Although we’d all like to think of our programming logic and code as foolproof, it rarely turns out that way. That said, you should use the functions introduced in this section because they not only aid you in determining causes of error, but also provide your end users with the pertinent informa- tion they need if an error occurs that is due not to programming faults but to inappropriate or incorrect user actions.

Converting LDAP Error Numbers to Messages

The ldap_err2str() function translates one of LDAP’s standard error numbers to its corresponding string representation. Its prototype follows:

string ldap_err2str(int errno)

For example, error integer 3 represents the time limit exceeded error. Therefore, executing the following function yields an appropriate message:

echo ldap_err2str (3);

This returns the following:

Time limit exceeded

Keep in mind that these error strings might vary slightly, so if you’re interested in offering some- what more user-friendly messages, always base your conversions on the error number rather than on an error string.

Retrieving the Most Recent Error Number

The LDAP specification offers a standardized list of error codes that might be generated during inter- action with a directory server. If you want to customize the otherwise terse messages offered by ldap_error() and ldap_err2str(), or if you would like to log the codes, say, within a database, you can use ldap_errno() to retrieve this code. Its prototype follows:

int ldap_errno(resource link_id)

Retrieving the Most Recent Error Message

The ldap_error() function retrieves the last error message generated during the LDAP connection specified by a link identifier. Its prototype follows:

string ldap_error(resource link_id)

Although the list of all possible error codes is far too long to include in this chapter, a few are presented here just so you can get an idea of what is available:

LDAP_TIMELIMIT_EXCEEDED: The predefined LDAP execution time limit was exceeded. LDAP_INVALID_CREDENTIALS: The supplied binding credentials were invalid. LDAP_INSUFFICIENT_ACCESS: The user has insufficient access to perform the requested operation.
Not exactly user friendly, are they? If you’d like to offer a somewhat more detailed response to
the user, you’ll need to set up the appropriate translation logic. However, because the string-based error messages are likely to be modified or localized, for portability it’s always best to base such translations on the error number rather than on the error string.

Summary

The ability to interact with powerful third-party technologies such as LDAP through PHP is one of the main reasons programmers love working with the language. PHP’s LDAP support makes it so easy to create Web-based applications that work in conjunction with directory servers and has the potential to offer a number of great value-added benefits to your user community.
The next chapter introduces what is perhaps one of PHP’s most compelling features: session handling. You’ll learn how to play “Big Brother,” tracking users’ preferences, actions, and thoughts as they navigate through your application. Okay, maybe not their thoughts, but perhaps we can request that feature for a forthcoming version.

0 comments: