Sie sind auf Seite 1von 9

Integrating Active Directory with PHP

October 29, 2003

Software engineering is not unlike other industrial craft; the actors in each respective trade are engaged in the practice
of creating something out of nothing. Often their products are the result of skills within several disciplines. For
example, a furniture designer might require knowledge working within wood, metal, and glass, while often a software
product's quality is often the function of the software designer's knowledge and experience working with programming
languages, networking, databases, and user interface design.
An Internet application developer at the Fisher College of Business, I can certainly relate to this idea. We've spent the
last several months developing and deploying Active Directory and Exchange to our 1,000+ faculty, staff and graduate
students. Like many organizations, we run a heterogeneous environment consisting of Windows, Solaris, and Linux
servers, and dominated by Windows workstations. While we've decided that a Microsoft-based e-mail/calendaring
system (Active Directoryand Exchange) would best suit our user's needs (and I dare say that the majority of our users
would agree with this choice), we're particularly fond of continuing to use Open Source solutions for our Web and
database services, namely Linux, the Apache Web server, and MySQL. Despite this mixed environment, we're still
adamant about providing services like single sign-on, online contact directories, and content management tools. In our
situation, such services can only be implemented through cross-platform communication. In many cases, this requires
the integration of PHP, Perl, Apache, and Microsoft's Active Directory. This topic is the focus of this article series.
Within this and the coming articles, I'll highlight key concepts pertinent to implementing services requiring Active
Directory/PHP/Perl/Apache integration. I'll assume that you're already familiar with basic Active Directory data
schemas, in addition to general LDAP syntax. Furthermore, I'll take for granted that you're familiar with PHP and Perl
language syntax, and with Apache's configuration syntax. Finally, because administration-level changes could be
necessary, you'll need privileged control, or at least the administrator's willingness to made changes for you.
On an aside, although all directory server products differ in general implementation, those which are based on the
LDAP standard generally allow for examples based on one product to be easily converted to another. Therefore, if you
happen to be working with a product other than Active Directory, OpenLDAP for instance, all examples provided in this
and the following articles, unless otherwise noted, will work as-is.
In this first installment, we'll take a look at PHP's LDAP support, basing our examples on Microsoft's Active Directory
implementation. After an introduction of the prerequisite configuration tasks and core PHP functions, we'll provide a
highly applicable example demonstrating the creation of a Web-based Active Directory search interface.

Prerequisites
Prior to executing any of the examples offered in this article, you'll need to make sure that a few prerequisite tasks are
accomplished. Each of these tasks are outlined in this section.

PHP Configuration
PHP's LDAP support is not enabled by default. If you haven't already done so, you'll have to rebuild PHP, this time
including the LDAP extension. This is done by passing thewith-ldap flag during the configuration phase of the build
process.
%>./configure --with-ldap [OTHER OPTIONS]
If you're building PHP as a shared module, remember that you won't need to rebuild Apache after making these
changes! If you don't know what I'm talking about, and are wondering how such a convenience could be afforded to
you, see the PHP manual for complete installation and configuration instructions.

A Privileged Account
One's ability to manage institutional resources is directly dependent upon furnishable credentials. The directory
service security model is no different; credentials must be furnished before resources can be accessed. This process,
known as binding, involves tying a particular set of credentials (username and password) to the connection. These
credentials are established within your respective directory server product. In Active Directory, this simply involves
assigning the necessary privileges to a user. If you haven't done so already, I'd like to recommend creating a new user
which will be used specifically for PHP-initiated connections. For the purposes of this article, I'll call that user "ad-web."

Because the user domain must also be declared, this user will be referenced as "ad-web@ad.wjgilmore.com." The
password will be the highly cryptic word "secret."

Firewall Adjustments
If you intend to work with a directory server residing outside of your local network, some firewall adjustments may be
required. By default, LDAP connections take place over port 389; if your directory server supports secure connections
(LDAPS), you'll need to open port 636. Therefore, you'll need to adjust your firewall to allow for access via at least
these two ports, if not already enabled.

Key PHP Functions


The first four functions introduced in this section are practically omnipresent whenever communicating with LDAP via
PHP. These functions include ldap_connect(), ldap_set_option(), ldap_bind(), and ldap_unbind(), and they're
responsible for connecting, setting configuration parameters, binding to, and closing the directory server connection.
Each is introduced in this section.

ldap_connect()
resource ldap_connect([string hostname [, integer port]])
Prior to doing anything with an LDAP server, a connection must be established. You can do so with ldap_connect(),
passing in an optional hostname and port if the connection isn't local. An example follows:
<?php
ldap_connect("ad.wjgilmore.com") or
die("Couldn't connect to AD!");
?>
Once connected to the server, many implementations require that you specify which LDAP protocol version you'll be
using. This is accomplished by using ldap_set_option(), introduced next.

ldap_set_option()
boolean ldap_set_option(resource link_id, int option, mixed newval)
Unlike Perl, PHP's LDAP connection function does not offer a means for declaring the intended protocol version within
the connection function. Therefore, after successfully establishing a connection, you'll likely need to declare this
version by setting PHP's constant LDAP_OPT_PROTOCOL_VERSION. This constant, one of twelve capable of being
modified via this function, determines which protocol version should be used. An example follows:
<?php
$ad = ldap_connect("ldap://ad.wjgilmore.com") or
die("Couldn't connect to AD!");
ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);
?>
Keep in mind that in my experience this function is a requirement for executing certain directory server tasks via an
API. However, this may not be the case with all APIs.

ldap_bind()
boolean ldap_bind(resource link_id [, string bind_rdn [, string bind_pswd]])
Entering a restricted area doesn't imply that you have free reign over its secrets. Exactly what material you can
access, and whether you can add, modify, or destroy the contents is dependent upon your furnishable credentials.
Directory Services' security model is based on the same concept. Although anybody can establish a connection with a
visible directory server, credentials must be supplied before anything can be done with that connection. The PHP
function used to supply this credentials is ldap_bind().
<?php

$ad = ldap_connect("ldap://ad.wjgilmore.com")
or die("Couldn't connect to AD!");
$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret")
or die("Couldn't bind to AD!");
?>
Once a set of credentials have been successfully bound to the server, you can begin carrying out queries against it.

ldap_unbind()
boolean ldap_unbind(resource link_id)
Once all directory server-specific tasks have been completed, the connection should be closed. This is accomplished
by using the ldap_unbind() function. An example follows:
<?php
// Connect to the directory server.
$ad = ldap_connect("ldap://ad.wjgilmore.com")
or die("Couldn't connect to AD!");

// Bind to the directory server.


$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret") or
die("Couldn't bind to AD!");

// Carry out directory server-specific tasks.

// Close the connection


ldap_unbind($ad);
?>
Although necessary, the aforementioned functions aren't very exciting. Not to worry, as we're not finished yet; the
remainder of this article is devoted to actually retrieving and managing the data located in your directory server.

Searching and Manipulating Active Directory Data


In this section, you'll learn how to search and retrieve data from the directory server, as well as add, modify, and
delete entries.

ldap_search()
resource ldap_search ( resource link_identifier, string base_dn, string filter [, array attributes [, int attrsonly [, int
sizelimit [, int timelimit [, int deref]]]]])
The ldap_search() function offers a powerful means for searching the directory server pointed to by link_identifier. It
will search to a depth of LDAP_SCOPE_SUBTREE, a value that can be set via the previously introduced function
ldap_set_option(). By default, this value is set to search to an infinite depth, or through the entire scope of the tree as
defined by the base_dn. The search filter, equivalent to a relational database query, is passed in via the filter
parameter. Finally, you can specify exactly which attributes should be returned within the search results via the
attributes parameter. The remaining four parameters are optional, and therefore in the interests of space, I'll leave it as
an exercise to you to learn more about them. Let's consider an example:
<?php

$dn = "OU=People,OU=staff,DN=ad,DN=wjgilmore,DN=com";

$attributes = array("displayname", "l");

$filter = "(cn=*)";

$ad = ldap_connect("ldap://ad.wjgilmore.com")
or die("Couldn't connect to AD!");

ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);

$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret")
or die("Couldn't bind to AD!");

$result = ldap_search($ad, $dn, $filter, $attributes);

$entries = ldap_get_entries($ad, $result);

for ($i=0; $i<$entries["count"]; $i++)


{
echo $entries[$i]["displayname"]
[0]."(".$entries[$i]["l"][0].")<br />";
}

ldap_unbind($ad);

?>
Most of this is likely straightforward, save for the potentially odd way in which attribute values are referenced. All
attribute rows are ultimately multi-dimensional arrays, with each attribute value referenced by a combination of row
number, attribute name, and attribute array index. So, for example, even attributes such as "sn", the attribute name for
the user's last name, is an indexed array.

ldap_mod_add()
boolean ldap_mod_add(resource link_id, string dn, array entry)
Adding entries to the directory server is accomplished via the ldap_mod_add() function. A new entry is added simply
by creating an array consisting of the attribute/value mappings intended to comprise the new row. This process is
perhaps best explained with an example:
<?php
$dn = "OU=People,OU=staff,DN=ad,DN=wjgilmore,DN=com";

$ad = ldap_connect("ldap://ad.wjgilmore.com")

or die("Couldn't connect to AD!");

ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);

$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret")
or die("Couldn't bind to AD!");

$user["givenname"] = "Jason";
$user["sn"] = "Gilmore";
$user["mail"][] = "jason@wjgilmore.com";

$result = ldap_mod_add($ad, $dn, $user);

if ($result) echo "User added!" else


echo "There was a problem!";

ldap_unbind($ad);

?>
As is the case with all directory server tasks, be sure that the binding user has proper permissions to add the target
data; otherwise, errors will occur.

ldap_mod_replace()
boolean ldap_mod_replace(resource link_id, string dn, array entry)
Modifying entry attributes is accomplished via the ldap_mod_replace() function. It operates exactly like ldap_add(),
save for the added step of identifying the entry you'd like to modify. This is done by pointing to a very specific dn. Like
ldap_add(), both a valid link identifier and an array consisting of the entries you'd like to update must be provided. An
example follows, demonstrating how a user's telephone number would be modified. In particular, take note of the very
specific DN (pointing to my particular entry).
<?php
$dn = "CN=Jason Gilmore,OU=People,OU=staff,DN=ad,
DN=wjgilmore,DN=com";

$ad = ldap_connect("ldap://ad.wjgilmore.com")
or die("Couldn't connect to AD!");

ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);

$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret")
or die("Couldn't bind to AD!");

$user["telephonenumber"] = "614-999-9999";

$result = ldap_mod_replace($ad, $dn, $user);


if ($result) echo "User modified!" else
echo "There was a problem!";

ldap_unbind($ad);

?>
As is the case with all directory server tasks, be sure that the binding user has proper permissions to modify the target
data; otherwise, unexpected errors will occur.

ldap_delete()
boolean ldap_delete(resource link_id, string dn)
Rounding out our survey of key PHP LDAP functions is ldap_delete(). This function is used to delete an existing entry.
Like ldap_mod_replace(), a very specific DN must be provided to effect the deletion. The following example
demonstrates how to remove the "Jason Gilmore" user entry from Active Directory:
<?php
$dn = "CN=Jason Gilmore,OU=People,OU=staff,DN=ad,
DN=wjgilmore,DN=com";

$ad = ldap_connect("ldap://ad.wjgilmore.com")
or die("Couldn't connect to AD!");

ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3);

$bd = ldap_bind($ad,"ad-web@ad.wjgilmore.com","secret")
or die("Couldn't bind to AD!");

$result = ldap_delete($ad, $dn);


if ($result) echo "User deleted!" else
echo "There was a problem!";

ldap_unbind($ad);

?>
As is the case with all directory server tasks, be sure that the binding user has proper permissions to delete the target
data; otherwise, unexpected errors will occur.

Searching Active Directory via the Web


I always like to round out a tutorial with an applicable example that readers can immediately adapt to their own needs.
In this tutorial, I'll show you how to create a search interface capable of searching by name, location, or phone
number. All you'll need to do is modify the connection variables and base DN. To begin, let's create the search
interface, which will be saved as "search.html":

<p>
<form action="search.html" method="post">
Search criteria:<br />
<input type="text" name="keyword" size="20"
maxlength="20" value="" /><br />
Filter:<br />
<select name="filter">
<option value="">Choose One:</option>
<option value="sn">Last Name</option>
<option value="telephonenumber">Phone</option>
<option value="l">City</option>
</select><br />
<input type="submit" value="Search!" />
</form>
</p>
Figure 1 offers an example of what this search form would look like in the browser.

Figure 1. The Active Directory Search Form


Next, we'll need to create the logic that effects the search. This short bit of code is shown here:
<?php

// Designate a few variables


$host = "ldap://ad.gilmore.com";
$user = "ad-web@ad.wjgilmore.com";
$pswd = "secret";

$ad = ldap_connect($host)
or die( "Could not connect!" );

// Set version number


ldap_set_option($ad, LDAP_OPT_PROTOCOL_VERSION, 3)

or die ("Could not set ldap protocol");

// Binding to ldap server


$bd = ldap_bind($ad, $user, $pswd)
or die ("Could not bind");

// Create the DN
$dn = "OU=People,OU=staff,DN=ad,DN=wjgilmore,DN=com";

// Specify only those parameters we're interested in displaying


$attrs = array("displayname","mail","telephonenumber");

// Create the filter from the search parameters


$filter = $_POST['filter']."=".$_POST['keyword']."*";

$search = ldap_search($ad, $dn, $filter, $attrs)


or die ("ldap search failed");

$entries = ldap_get_entries($ad, $search);

if ($entries["count"] > 0) {

for ($i=0; $i<$entries["count"]; $i++) {


echo "<p>Name: ".$entries[$i]["displayname"][0]."<br />";
echo "Phone: ".$entries[$i]["telephonenumber"][0]."<br />";
echo "Email: ".$entries[$i]["mail"][0]."</p>";
}

} else {
echo "<p>No results found!</p>";
}

ldap_unbind($ad);

?>
You can either change the action destination specified in the search interface, pointing it to a file consisting of the
above script, or you can bundle it into the same file as the search interface, and use isset() and an if conditional to
trigger execution in the case that the search submit button is depressed. Of course, you'll want to add some additional
data validation criteria prior to deploying such a script. Figure 2 offers a sampling of the search results.

Figure 2. Search Results

Conclusion
Although PHP has long been my primary language for developing Web applications, I've found Perl to be an integral
part of my programmer's toolkit. When working with directory servers, this sentiment is no different. Therefore, the
next article is devoted to Perl/LDAP basics. As was the case with this article, all examples are specific to Microsoft's
Active Directory, although you should be able to easily apply them to any directory server implementation. We'll round
out that article with an example demonstrating how to create statically cached Web-based user directories using a
Perl script and CRON (or Windows Task Scheduler).
I welcome questions and comments! E-mail me at jason@wjgilmore.com. I'd also like to hear more about your
experiences integrating Microsoft and Open Source technologies!

Das könnte Ihnen auch gefallen