Due to a recent reorg, I have the opportunity to replace our defect tracking system, which has quite a bit of really wasteful process baked into the tool, with a new one. I’ve been looking at defect tracking software for a while, and chose Eventum, an open source project by MySQL AB for a number of reasons, some of them including:
- Its open source
- Its written in PHP, so I don’t have to worry about messing with fastcgi, mod_perl, or mod_python
- It is extensible (you can add custom fields, etc)
- It uses MySQL, rather than SQLLite or something like that, so we can integrate it into the rest of our home-grown build software
- It supports email integration. While we won’t be using this right away, we’ll be implementing it in a later iteration
- Its simple to use, with a very simple interface, once you get use to it. Everything is essentially on one screen.
- It has time tracking, along with some basic reporting built in
One thing it doesn’t have built in is LDAP authentication. I wrote a previous article about all of the work we’ve done to integrate both our home grown applications and a few open source applications in with our LDAP store, to minimize the management of multiple passwords across systems, so this was very important to me. I started with many, many Google searches to see if someone else has done this, only to hit one dead end after another. At first I was being lazy and decided to just forget about it. One system not tied to the LDAP tree isn’t that big of a deal, but then my perfectionism set in. Why would I settle for that when LDAP authentication should be really easy to integrate into an Open Source package?
So I decided to spend a few hours to get it working. Since I had no success finding an implementation, I figured I could do my part and post what I have. There are a couple of caveats that I want to throw out before we actually get to the code though:
- It isn’t done “right”. This is all extra work for me, so I got enough done so that it would work. The right way to do this would to refactor the auth stuff out into a workflow like hierarchy that could be pluggable (see this post in the eventum mailing list). I’ll get to that someday, but right now this solution hacks the auth module to get authentication working.
- LDAP Settings are not configurable through the interface. I don’t have time for that, so a set of defines at the top of the LDAPAuthenticator class contains all of the configuration information for the LDAP server. Bummer, but like I said, I’m on a schedule.
- Users still have to be added to the Eventum database – they are not added automagically when they log in. I want control of who is in the system, so I’ve elected to leave this functionality out and just do authentication.
With these three caveats in place though, given my experience looking around for this stuff, at least this code works and will be able to be used by others. Its a starting point – which is more than is out there today. Anyone is free to use this and take the time to do it right. With that said, I’d love to receive updates if someone actually takes this up. For now though, this works for me.
So, now to the code. I wrote a small PHP class called “class.LDAPAuthenticator.php. There are two functions in it. Because Eventum uses email address as the login, we need a way to get the full user DN from the email address. This is what the email_to_dn function does. Given an email address, it returns the full distinguished name of the user. This is called by the main class function, ldap_authenticate. The ldap_authenticate function takes the same arguments as the class.auth.php function isCorrectPassword, which consist of the email address and the password. It binds to the LDAP authentication tree using the full DN of the user and the password supplied. If authentication is successful, it returns TRUE, otherwise it returns FALSE, just like the isCorrectPassword function used to validate the password from the Eventum database.
The code looks like this:
# Change these values to access another LDAP server. define("LDAP_PORT", 636); define("LDAP_HOST", 'ldaps://ldapserver.example.com:' . LDAP_PORT); define("LDAP_BIND_DN", 'PUT THE BIND DN HERE'); define("LDAP_BIND_PASSWORD", 'PUT THE BIND PASSWORD HERE'); define("LDAP_SEARCH_DN", "PATH OF THE TREE TO SEARCH FOR USERS"); class LDAPAuthenticator { # Look up a users full distinguised name from # their email address, since Eventum uses # email address as the login name. function email_to_dn($emailAddress) { $returnDN = ""; $server = ldap_connect(LDAP_HOST); if ($server == FALSE) { return($returnDN); } ldap_set_option($server, LDAP_OPT_PROTOCOL_VERSION, 3) ; $ldapbind = ldap_bind($server, LDAP_BIND_DN, LDAP_BIND_PASSWORD); # verify binding if ($ldapbind) { # find the user based on the entered email address. $result = ldap_search($server, LDAP_SEARCH_DN, "(&(mail=$emailAddress))", array("dn")); $info = ldap_get_entries($server, $result); # if we actually got a value back, return the users DN if ($info["count"] > 0) { $returnDN = $info[0]["dn"]; } ldap_unbind($server); } return($returnDN); } # Authenticate with the LDAP server. Function returns true # if authentication was successful, false otherwise. function ldap_authenticate($email, $password) { $returnValue = FALSE; $userDN = LDAPAuthenticator::email_to_dn($email); if ($userDN == "") { return($returnValue); } $server = ldap_connect(LDAP_HOST); if ($server == FALSE) { return($returnValue); } ldap_set_option($server, LDAP_OPT_PROTOCOL_VERSION, 3) ; $ldapbind = ldap_bind($server, LDAPAuthenticator::email_to_dn($email), $password); if ($ldapbind) { $returnValue = TRUE; ldap_unbind($server); } return($returnValue); } }
Save this file as class.LDAPAuthenticator.php and put it in your Eventum includes directory. Modify the define statements at the top to contain your LDAP server information.
Now, to use it. Go to your Eventum includes directory and add the following line to the top of the class.auth.php file:
require_once(APP_INC_PATH . "class.LDAPAuthenticator.php");
I have this at the end of all of the rest of the require statements.
Now, replace the isCorrectPassword function in class.auth.php with the following function:
/** * Checks whether the provided password match against the email * address provided. * * @access public * @param string $email The email address to check for * @param string $password The password of the user to check for * @return boolean */ function isCorrectPassword($email, $password) { return(LDAPAuthenticator::ldap_authenticate($email, $password)); }
… and VOILA. You can now authenticate off of your LDAP tree.
Now, I know it isn’t pretty, hacking the code directly – but it works, and its more of a starting point than I can find anywhere else. I hope its useful to others. Again, if anyone takes this further and does it “right”, I would be really happy to get a copy of the modifications.
One more thing – don’t forget to require SSL on the URL to your Eventum installation by using the SSLRequireSSL directive in your Apache server. You don’t want these passwords floating around in the clear across the network.
Download the Eventum LDAP hack here and happy authenticating.