Integrating Active Directory with PHP
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 Directory and 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.
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'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 the—with-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 "firstname.lastname@example.org." The password will be the highly cryptic word "secret."
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.
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.
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.
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,"email@example.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.
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,"firstname.lastname@example.org","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.