I develop a lot of internal applications at work that run via the web for simplicity. I’ve found that using an SSO or Single Sign-On is the only way to go. Having that SSO be a users’ Windows password is perfect solution. I set out to use LDAP lookups via PHP and found a sweet class called adLDAP on SourceForge.net. After working with the class for a bit, it became obvious it was in need of some minor modifications. Here is my improved version of this class:

 /*
LDAP FUNCTIONS FOR MANIPULATING ACTIVE DIRECTORY
Version 1.4
 
Maintained by Scott Barnett
email: scott@wiggumworld.com
http://adldap.sourceforge.net/
 
Works with both PHP 4 and PHP 5
 
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
 
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
 
********************************************************************
Something to keep in mind is that Active Directory is a permissions
based directory. If you bind as a domain user, you can't fetch as
much information on other users as you could as a domain admin.
********************************************************************
 
FUNCTIONS:
 
authenticate($username,$password)
Authenticate to the directory with a specific username and password
 
user_info($user,$fields=NULL)
Returns an array of information for a specific user
 
user_groups($user)
Returns an array of groups that a user is a member off
 
user_ingroup($user,$group)
Returns true if the user is a member of the group
 
group_info($group)
Returns an array of information for a specific group
 
all_users($include_desc = false, $search = "*", $sorted = true)
Returns all AD users (expensive on resources)
 
all_groups($include_desc = false, $search = "*", $sorted = true)
Returns all AD groups (expensive on resources)
 
group_hasuser($user, $group)
Returns true if the user is a member of the group
 
group_members($group)
Returns an array of users that a group has
 
*/
 
// Different type of accounts in AD
define ('ADLDAP_NORMAL_ACCOUNT', 805306368);
define ('ADLDAP_WORKSTATION_TRUST', 805306369);
define ('ADLDAP_INTERDOMAIN_TRUST', 805306370);
define ('ADLDAP_SECURITY_GLOBAL_GROUP', 268435456);
define ('ADLDAP_DISTRIBUTION_GROUP', 268435457);
define ('ADLDAP_SECURITY_LOCAL_GROUP', 536870912);
define ('ADLDAP_DISTRIBUTION_LOCAL_GROUP', 536870913);
 
class adLDAP {
// BEFORE YOU ASK A QUESTION, PLEASE READ THE FAQ
// http://adldap.sourceforge.net/faq.php
 
// These vars will be set in your inital call to create a new adLDAP
var $_account_suffix;
var $_base_dn;
var $_domain_controllers; // An array of domain controllers. Specify multiple controllers if you would like the class to balance the LDAP queries amongst multiple servers
// Examples:
// $_account_suffix="@yourdomain.local";
// $_base_dn = "DC=yourdomain,DC=local";
// $_domain_controllers = array ("dc.mydomain.local", "dc2.mydomain.local");
 
// optional account for searching
var $_ad_username=NULL;
var $_ad_password=NULL;
 
// AD does not return the primary group. http://support.microsoft.com/?kbid=321360
// This tweak will resolve the real primary group, but may be resource intensive.
// Setting to false will fudge "Domain Users" and is much faster.
var $_real_primarygroup=true;
 
//other variables
var $_user_dn;
var $_user_pass;
var $_conn;
var $_bind;
 
// default constructor
function adLDAP($_account_suffix, $_base_dn, $_domain_controllers){
// Set Config Vars
$this->_account_suffix = $_account_suffix;
$this->_base_dn = $_base_dn;
$this->_domain_controllers = $_domain_controllers;
//connect to the LDAP server as the username/password
$this->_conn = ldap_connect($this->random_controller());
ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 0); //disable plain text passwords
ldap_set_option($this->_conn, LDAP_OPT_DEBUG_LEVEL, 7);
return true;
}
 
// default destructor
function __destruct(){ ldap_close ($this->_conn); }
 
function random_controller(){
//select a random domain controller
mt_srand(doubleval(microtime()) * 100000000);
return ($this->_domain_controllers[array_rand($this->_domain_controllers)]);
}
 
// authenticate($username,$password)
//    Authenticate to the directory with a specific username and password
//    Extremely useful for validating login credentials
function authenticate($username,$password){
//validate a users login credentials
$returnval=false;
 
if ($username!=NULL && $password!=NULL){ //prevent null bind
$this->_user_dn=$username.$this->_account_suffix;
$this->_user_pass=$password;
 
$this->_bind = @ldap_bind($this->_conn,$this->_user_dn,$this->_user_pass);
if ($this->_bind){ $returnval=true; }
else {$returnval=false;}
}
return ($returnval);
}
 
// rebind()
//    Binds to the directory with the default search username and password
//    specified above.
function rebind(){
//connect with another account to search with if necessary
$ad_dn=$this->_ad_username.$this->_account_suffix;
$this->_bind = @ldap_bind($this->_conn,$ad_dn,$this->_ad_password);
if ($this->_bind){ return (true); }
return (false);
}
 
// user_info($user,$fields)
//    Returns an array of information for a specific user
function user_info($user,$fields=NULL){
if ($user!=NULL){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
if ($this->_bind){ //perform the search and grab all their details
$filter="samaccountname=".$user;
if ($fields==NULL){ $fields=array("samaccountname","mail","memberof","department","displayname","telephonenumber","primarygroupid"); }
$sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
$entries = ldap_get_entries($this->_conn, $sr);
 
// AD does not return the primary group in the ldap query, we may need to fudge it
if ($this->_real_primarygroup){
$entries[0]["memberof"][]=$this->group_cn($entries[0]["primarygroupid"][0]);
} else {
$entries[0]["memberof"][]="CN=Domain Users,CN=Users,".$this->_base_dn;
}
$entries[0]["memberof"]["count"]++;
 
return ($entries);
}
}
 
return (false);
}
 
// user_groups($user)
//    Returns an array of groups that a user is a member off
function user_groups($user){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
if ($this->_bind){
//search the directory for their information
$info=@$this->user_info($user,array("memberof","primarygroupid"));
$groups=$info[0]["memberof"]; //presuming the entry returned is our guy (unique usernames)
 
$group_array=array();
 
for ($i=0; $i< $groups["count"]; $i++){ //for each group
$line=$groups[$i];
 
$group_name="";
$line_length=strlen($line);
//more presumptions, they're all prefixed with CN=
//so we ditch the first three characters and the group
//name goes up to the first comma
for ($j=3; $j<$line_length; $j++){
if ($line[$j]==","){
$j=$line_length;
} else {
$group_name.=$line[$j];
}
}
$group_array[$i] = $group_name;
}
return ($group_array);
}
return (false);
}
 
// group_members($group)
//    Returns an array of users that a group has
function group_members($group){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
if ($this->_bind){
//search the directory for their information
$info=@$this->group_info($group,array("member","cn"));
$users=$info[0]["member"]; //presuming the entry returned is our guy (unique usernames)
 
$user_array=array();
 
for ($i=0; $i< $users["count"]; $i++){ //for each group
$line=$users[$i];
 
$user_name="";
$line_length=strlen($line);
//more presumptions, they're all prefixed with CN=
//so we ditch the first three characters and the group
//name goes up to the first comma
for ($j=3; $j<$line_length; $j++){
if ($line[$j]==","){
$j=$line_length;
} else {
$user_name.=$line[$j];
}
}
$user_array[$i] = $user_name;
}
return ($user_array);
}
return (false);
}
 
// user_ingroup($user,$group)
//    Returns true if the user is a member of the group
function user_ingroup($user,$group){
if (($user!=NULL) &amp;&amp; ($group!=NULL)){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
if ($this->_bind){
$groups=$this->user_groups($user);
if (in_array($group,$groups)){ return (true); }
}
}
return (false);
}
 
// group_hasuser($user,$group)
//    Returns true if the user is a member of the group
function group_hasuser($user,$group){
if (($user!=NULL) &amp;&amp; ($group!=NULL)){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
if ($this->_bind){
$user_info=$this->user_info($user,array("dn"));
$user_array=explode(",",$user_info[0]["dn"]); // Explode User DN
$user_name=explode("=",$user_array[0]);
$group_members=$this->group_members($group);
if (in_array($user_name[1],$group_members)){ return (true); }
}
}
return (false);
}
 
function group_cn($gid){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
// coping with AD not returning the primary group
// http://support.microsoft.com/?kbid=321360
// for some reason it's not possible to search on primarygrouptoken=XXX
// if someone can show otherwise, I'd like to know about it :)
// this way is resource intensive and generally a pain in the @#%^
 
$r=false;
 
if ($this->_bind){
$filter="(&amp;(objectCategory=group)(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP ."))";
$fields=array("primarygrouptoken","samaccountname","distinguishedname");
$sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
$entries = ldap_get_entries($this->_conn, $sr);
 
for ($i=0; $i< $entries["count"]; $i++){
if ($entries[$i]["primarygrouptoken"][0]==$gid){
$r=$entries[$i]["distinguishedname"][0];
$i=$entries["count"];
}
}
}
return ($r);
}
 
// group_info($group_name,$fields=NULL)
// Returns an array of information for a specified group
function group_info($group_name,$fields=NULL){
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
if ($this->_bind){
$filter="(&amp;(objectCategory=group)(name=".$group_name."))";
if ($fields==NULL){ $fields=array("member","cn","description","distinguishedname","objectcategory","samaccountname"); }
$sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
$entries = ldap_get_entries($this->_conn, $sr);
return ($entries);
}
return (false);
}
 
function all_users($include_desc = false, $search = "*", $sorted = true){
// Returns all AD users
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
if ($this->_bind){
$users_array = array();
 
//perform the search and grab all their details
$filter = "(&amp;(objectClass=user)(samaccounttype=". ADLDAP_NORMAL_ACCOUNT .")(objectCategory=person)(cn=$search))";
$fields=array("samaccountname","displayname");
$sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
$entries = ldap_get_entries($this->_conn, $sr);
 
for ($i=0; $i< $entries["count"]; $i++){
if( $include_desc &amp;&amp; strlen($entries[$i]["displayname"][0]) > 0 )
$users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["displayname"][0];
else if( $include_desc )
$users_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0];
else
array_push($users_array, $entries[$i]["samaccountname"][0]);
}
if( $sorted ){ asort($users_array); }
return ($users_array);
}
return (false);
}
 
function all_groups($include_desc = false, $search = "*", $sorted = true){
// Returns all AD groups
if ($this->_ad_username!=NULL){ $this->rebind(); } //bind as a another account if necessary
 
if ($this->_bind){
$groups_array = array();
 
//perform the search and grab all their details
$filter = "(&amp;(objectCategory=group)(samaccounttype=". ADLDAP_SECURITY_GLOBAL_GROUP .")(cn=$search))";
$fields=array("samaccountname","description");
$sr=ldap_search($this->_conn,$this->_base_dn,$filter,$fields);
$entries = ldap_get_entries($this->_conn, $sr);
 
for ($i=0; $i< $entries["count"]; $i++){
if( $include_desc &amp;&amp; strlen($entries[$i]["description"][0]) > 0 )
$groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["description"][0];
else if( $include_desc )
$groups_array[ $entries[$i]["samaccountname"][0] ] = $entries[$i]["samaccountname"][0];
else
array_push($groups_array, $entries[$i]["samaccountname"][0]);
}
if( $sorted ){ asort($groups_array); }
return ($groups_array);
}
return (false);
}
} // End class
 
?>

Here is a list of the modifications that I have made:

  • Added group_hasuser() function
  • Added group_members() function
  • Changed base_dn and dc list from hardcoded to being set when creating a new instance of this class. It’s more modular this way.
  • Fixed examples.php

I’m simply including the class file here as this is definitely not my original code or project. I am simply providing a copy of the class file that I am personally using. Enjoy.