PHP
Article

Essentials of LDAP with PHP

By Matthew Setter

Ever wanted a simple way to store address book style information and network information actually next to any kind of ordered information?

If so, then there’s a technology which has been around since 1993, one which despite not having the cool factor of such technologies as Node.js and Go, allows you to do exactly this. It’s called LDAP!

What is LDAP?

LDAP is short for Lightweight Directory Access Protocol and was developed at the University of Michigan around 1993, by Tim Howes, Steve Kille, Colin Robbins, and Wengyik Yeong.

In short, LDAP is an internet-ready version of an earlier protocol called X.500, which was developed back in the 80’s by the International Telecommunications Union (ITU) for managing telephone directories and directory services.

Whilst LDAP technically refers to the protocol, the name is often applied to the client and server as well. If it helps, you can think of it like SQL is for database servers; it’s the language used to interact with LDAP-enabled servers.

There are a number of LDAP-enabled servers around, the most common of which is Microsoft’s ActiveDirectory; which has been pervasive throughout their product lineup since the release of Windows 2000.

There’s an open source choice as well, one which we’ll be using throughout this article series, called OpenLDAP. It makes no assumptions as to the schema you’re using or the information you’re storing.

Here in part one of the series, I’m going to:

  1. Take you through the basics of setting up an OpenLDAP
  2. Show you how to load up a set of records
  3. Show you how to connect to it and perform some basic operations

Terminology

Before we do that, we need to look at a bit of the terminology. Continuing with the SQL analogy, there are a couple of terms which you’ll need to know, and which I’ll be using throughout the series, which you can find in the table below.

LDAP Term Description
dn A dn, or Distinguished Name, is a record’s unique identifier. This is much like a primary key in a relational database.
Directory Schema (or just Schema) In an LDAP directory, the entry values are governed by a directory schema. A directory schema is a set of definitions and constraints concerning the structure of the directory information.
entry An entry is much like a record in a database, and contains attributes which store the data for the entry.
attribute An attribute is much like an element in an associative array or column in a database. It specifies the type of information which can be stored for that attribute, along with other criteria, such as sorting and searching rules, case-sensitivity and so on.
cn cn is short for common name. An example would be “John Smith”
sn sn is short for surname.

The terminology is quite sophisticated, so I’m unable to cover it all here. But hopefully these basics are enough to get you started.

For more detailed information, check out this guide from O’Reilly, or the LDAP entry on Wikipedia.

Installing An LDAP Server

I’ve never found installing and configuring OpenLDAP particularly straight-forward and a lot of the information available on the net can be misleading or be for a mixture of versions of the server. Here is my best, most concise set of steps, based on using a Debian-based server.

Firstly, install the core server and utils by running the following commands:

sudo apt-get install slapd ldap-utils

These commands will ask you a set of questions, install the server, along with setting it to start at boot time. After it’s done, run the following command, which will help us better configure the installation:

dpkg-reconfigure slapd

This will ask a series of questions, and here’s a guide to answering them:

  • Omit OpenLDAP server configuration? No
  • DNS domain name: homestead.localdomain
  • Name of your organization: … Whatever & Co
  • Admin Password:
  • Confirm Password:
  • OK
  • BDB
  • Do you want your database to be removed when slapd is purged? No
  • Move old database? Yes
  • Allow LDAPv2 Protocol? No

Verify the Installation

With that done, let’s quickly verify that everything’s working, by running the following command:

ldapsearch -x -b dc=homestead,dc=localdomain

You shouldn’t receive an error, but if so, make sure that OpenLDAP’s running; you can do this by running the following command:

sudo netstat -tlnp | grep slapd

You should see output such as the following (formatted for readability):

tcp    0   0 0.0.0.0:389  0.0.0.0:*    LISTEN    6556/slapd      
tcp6   0   0 :::389       :::*         LISTEN    6556/slapd

Populating The Database

Now that the server is set up, we need to load it up with data. Create a new file called users.ldif and in it, add the following records:

dn: cn=Sheldon Cooper,ou=People,dc=homestead,dc=localdomain
cn: Sheldon Cooper
objectClass: person
objectClass: inetOrgPerson
sn: Cooper

dn: cn=Leonard Hofstadter,ou=People,dc=homestead,dc=localdomain
cn: Leonard Hofstadter
objectClass: person
objectClass: inetOrgPerson
sn: Hofstadter

dn: cn=Howard Wolowitz,ou=People,dc=homestead,dc=localdomain
cn: Howard Wolowitz
objectClass: person
objectClass: inetOrgPerson
sn: Wolowitz

dn: cn=Rajesh Koothrappali,ou=People,dc=homestead,dc=localdomain
cn: Rasjesh Koothrappali
objectClass: person
objectClass: inetOrgPerson
sn: Koothrappali

With the file saved, run the following command to load the information into the database:

ldapadd -x -W -D "cn=admin,dc=homestead,dc=localdomain" -f users.ldif

This will prompt you for the password you set earlier. Enter it and you should see output like the following:

adding new entry "cn=Sheldon Cooper,ou=People,dc=homestead,dc=localdomain"

adding new entry "cn=Leonard Hofstadter,ou=People,dc=homestead,dc=localdomain"

adding new entry "cn=Howard Wolowitz,ou=People,dc=homestead,dc=localdomain"

adding new entry "cn=Rajesh Koothrappali,ou=People,dc=homestead,dc=localdomain"

Verify the Records are Present

Now let’s do a quick check that the records are available. From the command line, run:

ldapsearch -x -b "dc=homestead,dc=localdomain" -s sub "objectclass=*"

This should display output similar to the following, which I’ve truncated for the purposes of readability:

# extended LDIF
#
# LDAPv3
# base <dc=homestead,dc=localdomain> with scope subtree
# filter: objectclass=*
# requesting: ALL
#

# homestead.localdomain
dn: dc=homestead,dc=localdomain
objectClass: top
objectClass: dcObject
objectClass: organization
o: homestead
dc: homestead

Interacting with PHP

With all these steps completed, we’re now ready to use PHP to query the server. To keep things simple, we’re using the Zend-Ldap component from Zend Framework 2.

There are a number of PHP LDAP libraries available, but this was the one that I found most effective and straight-forward to use. Granted, I am a bit of a Zend Framework evangelist, but this honestly is the simplest library I’ve found.

To handle the dependency, as in almost all PHP projects these days, we’ll use Composer to make it available. In your project directory, create a composer.json file, as below.

{
    "require": {
        "php": ">=5.3.0",
        "zendframework/zend-ldap": "2.3.*@dev"
    }
}

After that, run composer install to create the vendor directory and bring in the dependency.

Connecting to the LDAP server

With that done, create a new file called index.php in the root of your project directory; in there add the code below:

<?php
require 'vendor/autoload.php';

$baseDn = 'dc=homestead,dc=localdomain';
$options = array(
    'host' => '192.168.10.10',
    'password' => 'homestead',
    'bindRequiresDn' => true,
    'baseDn' => 'dc=homestead,dc=localdomain',
    'username' => "cn=admin,$baseDn"
);
$ldap = new Zend\Ldap\Ldap($options);
$ldap->bind();

This will first make the Zend\Ldap available via the composer generated autoload file. I’ve next defined a variable, $baseDn, to keep the code that much more readable here in the article.

Next I’ve created an array called $options, which stores the configuration settings we’ll use for initializing the Zend\Ldap\Ldap object.

In it I’ve specified the hostname, password, basedn and username. We could have skipped the username and password, but as we’ll be performing authenticated operations, it’s simplest to add it all now.

With the new Zend\Ldap\Ldap object initialized, I then called the bind method to make the connection to the server.

Searching The Database

Now let’s perform the first and simplest operation on the LDAP server: searching. The code below will search for every record in ou=People,dc=homestead,dc=localdomain, which will return the four we loaded earlier.

The first argument, (objectclass=*) specifies an SQL-like filter. This equates to running a SELECT *. The final argument performs a search which will look for all records.

There are a number of different search types, which we’ll cover in the next article. For now, this type is sufficient for our needs.

$result = $ldap->search(
   '(objectclass=*)',
   "ou=People,$baseDn",
   Zend\Ldap\Ldap::SEARCH_SCOPE_SUB
);

Next, we’ll iterate over the records in two ways. Firstly, we’ll call the toArray() method on the $result object, which we pass to json_encode. I’ve done this as a simple way of displaying all the information available.

print json_encode($result->toArray());

Alternatively, we could iterate over the dataset using a foreach as below. In this example, we’ve displayed the dn and cn elements of each record.

foreach ($result as $item) {
    echo $item["dn"] . ': ' . $item['cn'][0] . '<br />';
}

Updating an Entry

Now that we’ve looked at basic record searching and iteration, let’s look at updating a record. This requires three steps:

  1. Fetching the record
  2. Updating an existing property or setting a new one
  3. Persisting the record back to the LDAP server
$hm = $ldap->getEntry(
    "cn=Rajesh Koothrappali,ou=People,$baseDn"
);
Zend\Ldap\Attribute::setAttribute(
    $hm, 'mail', 'koothrappalir@homestead.localdomain'
);
$ldap->update(
    "cn=Rajesh Koothrappali,ou=People,$baseDn", $hm
);

In the code above, we’ve retrieved an entry by calling the getEntry method, passing in the record dn. We next called the setAttribute() method, specifying the record object, the property we want to set, then the value of the property.

Finally, we called the update() method, passion in the record dn and the record object. All being well, the record will be updated.

Deleting an Entry

Now that we can search and update records, let’s finish up by stepping through deleting a record. To do this, we call the delete method, passing in the dn of the record we want to delete.

As the operation could fail, in this example I’ve wrapped the call in a try/catch block, which catches an LdapException if thrown and prints out the reason for the failure.

There could be a variety of reasons for the exception to be thrown, such as the record not existing and the user we’ve authenticated as not having sufficient permission.

try {
    $ldap->delete("cn=Hans Meier,ou=People,$baseDn");
} catch (\Zend\Ldap\Exception\LdapException $e) {
    print $e->getMessage();
}

Wrapping Up

And that’s how to set up and interact with an LDAP server – specifically OpenLDAP – in PHP. I hope you’ve enjoyed this quick run through of how to do it. In the next article, we’ll be exploring LDAP in further depth by:

  • Performing more complicated searches
  • Inserting records
  • Moving records
  • Making secure connections

If you’d like more information on what we’ve covered today, there’s a host of links in the further reading section which should satisfy your curiosity.

Further Reading

  • Taylor Ren

    An off topic question: say this is setup on a machine with IP 10.0.0.8, how to setup a Thunderbird client that is running on 10.0.0.2 to use this LDAP service?

    • http://www.matthewsetter.com/ Matthew Setter

      Taylor, I’m not quite sure about how to answer you with Thunderbird. But, as time allows, I’ll try and find out for you. With respect to the ldapadd error, I’ll go through the steps again and see what I might have missed. Thanks for letting me know.

    • jerrac

      Make sure the “ou=People,dc=homestead,dc=localdomain” ou is created before you try to import the users from the example.

  • https://www.phpcontext.com/wordpress Mike Mx

    Just be sure to use updated LDAP extension for PHP (CVE-2014-6387)

  • http://www.matthewsetter.com/ Matthew Setter

    Thanks for the update Mike

  • http://www.demogar.com demogar

    I have a small question on regards of using OpenLDAP for development and AD for production. Will I need to make any change on the base code to make it work on both environments (other than the connection string)?

    My question is, is ActiveDirector and OpenLDAP equal on their structure or will I need to make some adjustments?

    • jerrac

      Active Directory and OpenLDAP have significant differences. For development, use the same LDAP as you will use in production.

      This is from experience, I’ve been working with Novell eDirectories, Active Directories, OpenLDAP directories, and Mac Open Directories here at work. There are major differences between all of them.

  • Chuck Burgess

    And if you run up against a query resultset that’s too large for the LDAP server’s size limit, give this a shot — http://thenazg.blogspot.com/2012/08/overcoming-ldap-size-limit-exceeded.html

  • Namjith Aravind

    Zf2 ldap have any function for change password of the authenticated user?

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.