Below is some code that we use for another PHP product to authenticate our users active a MS Active Directory. it is a plugin to a calendering system that authenticates our users to use the calendar.
The problem with AD is that it does not follow the LDAP specification that expects a unique user ID number for each user, instead AD uses a unique "name" for each user. This causes problems when trying to use it in traditional mySQL database based authentication. This code creates a unique user ID number based on the samAccountName used in AD to uniquely identify a user.
I am not a ’coder’ (more of a hacker, coder wannabe) so I don’t understand the code fully and I it won’t work out of the box for MODx, but I think it is a good starting point for someone to build an authentication plugin/module for MODx using an LDAP server as the user source. It works for us in another scenario. PM me if you want to know what Calendar it is.
<?php
#################################
#
### LDAP AUTH MODULE VERSION 1.1
#
#################################
global $ldap_opts;
#################################
#
### LDAP OPTIONS
#
#################################
# LDAP Host //Set your LDAP Server address here
$ldap_opts['host'] = 'ldap://xxx.xxx.xxx.xxx.xxx.xx';
# LDAP Protocol version, 3 is most common //3 works for MS Active Directory
$ldap_opts['version'] = 3;
# LDAP anonymous dn and password. This should
# have access to connect and list users //In AD you need put domain then username ie mydomain\\username
$ldap_opts['anon_dn'] = 'xxx\\xx';
$ldap_opts['anon_pw'] = 'xxxx';
$ldap_opts['debug'] = 0;
# DN Suffix //Can use multiple OU's to get users seperate each with a comma
$ldap_opts['suffix'] = array(
'OU=Teachers, OU=Staff, DC=internal, DC=xxx, DC=xxx, DC=xxx, DC=xx',
'OU=Network_Managers, DC=internal, DC=xxx, DC=xxx, DC=xxx, DC=xx'
);
# Default admin/superuser userid // This is the name of the user who will be the super admin (in AD it is samAcccountName) ie ssmith
$ldap_opts['admin'] = "xxxx";
# attributes translations
# 'name' = user's full name
# 'uid' = unique id number of user.
# Set to __autogen creates uids. Some
# LDAP dbs do not have uids. MS AD et. al. Getting
# this from your LDAP db is faster if it exists.
# 'userid' = login user id
# 'email' = email address
$ldap_opts['attribs'] = array(
'name' => 'givenname',
'uid' => '__autogen',
'userid' => 'name',
'email' => 'mail',
'dn' => 'dn'
);
#############################
#
### END OPTIONS
#
#############################
#########################################################
#
# FUNCTIONS REQUIRERD BY ALL AUTH MODULES
#
# The following naming conventions are used:
#
# userid - the login userid.
# id - the unique id of a user (uid)
# user name - the common name of the user
#
# E.g. A user named (user name) "John Smith" may have
# a userid of "jsmith" and an id of 234
#
#
# -----------------------------------------------------
#
# _ex_auth_login(&$user_object)
#
# rerturn true or false based on authentication.
#
# Where $user_object is a reference to a user. Auth
# against $user_object->userid and $user_object->pass
#
#
# ----------------------------------------------------
#
# _ex_auth_get_user(&$user_object)
#
# set values in $user_object, return value is ignored
#
# $user_object as passed will have either, userid, or
# id set. This function MUST set:
#
# $user_object->id - the unique id for this user
# - must be a number > 0
#
# $user_object->userid - the userid of this user,
# - e.g. jsmith
#
# this function may optionally and is encurated to set:
#
# $user_object->name - the common name of ther user
# - e.g. Joe Smith
#
# $user_object->email - the primary e-mail address of
# - this user
#
# if no user is found, set all of the user's attributes
# to null.
#
# --------------------------------------------------
#
# _ex_auth_get_users_by_id($ids, $associative = false)
#
# return an array of users that have ids in $ids
#
# This should return an array of 'key' => values for id,
# userid, name, and email.
#
# $.g. $users = array();
#
# for each user in ($ids)
#
# $user[] = array('name' => $uname, 'userid' => $userid, 'id' => $uid, 'email' => $email);
#
# return $users;
#
# if $associative is true, the array returned must
# have the users' ids as it's key. E.g.
#
# for each user in ($ids)
#
# $user[$uid] = array('name' => $uname, 'userid' => $userid, 'id' => $uid, 'email' => $email);
#
# return $users;
#
#
# ------------------------------------------------
#
# _ex_auth_search_users($key = false, $search_str, $anchor_start = false, $associative = false)
#
# search and return an array of users and their attributes.
# the attributes that must be, or rmay be set follow the same
# rules as _ex_auth_get_user
#
# where $key is the field to search:
#
# "name" - search users' names
# "userid" - search userids
# false (default) - search both userids and users' names
#
# $search_str is the string to search, this contains
# no wildcard characters. If your authentication
# method requires wildcards, this function is
# expected to add them
#
# $anchor_start if this is true, a wildcard character
# is only appended to the end of the string. If
# false (default) prepend and append wildcards
#
# $associative indicates that the function must return
# an array of associative arrays with the user's
# id as the key. E.g. a searrch that returned 2
# users may set by:
#
# -- for each user returned from search..
# $users[$user['id']] = array('userid' => $user['unique_id'],'name' => $user['name']);
#
# return $users;
#
# -------------------------------------------------
#
# _ex_auth_list_chars($key)
#
# search and return a list characters that there are
# user entrires for. If your installation consists of 2
# users; bob and joe, this function would return an array
# containing 'b' and 'j'. The characters retruned MUST be
# lower case.
#
# $key is the field to search:
#
# "name" - search users' names
# "userid" - search userid
#
#
# If your installation is full of users that range from
# a - z, you may just return an array containing a-z
# using:
#
# return array_map("strtolower", $GLOBALS['_cal_alphabet']);
#
#########################################################
if($ldap_opts['debug']) {
echo("LDAP fields:<br>");
foreach(array_keys($ldap_opts['attribs']) as $atr)
echo("{$atr}: {$ldap_opts['attribs'][$atr]}<br>");
echo("<br>");
}
######################################
#
### AUTHENTICATE A USER
#
#####################################
function _ex_auth_login(&$u)
{
global $ldap_opts;
$auth = ldap_connect($ldap_opts['host']);
# SET VERSION
#################
if($ldap_opts['version'] == 3) ldap_set_option($auth, LDAP_OPT_PROTOCOL_VERSION, 3);
$login = ldap_bind($auth, $u->dn, $u->pass);
if(!$login) {
return 0;
}
return 1;
}
#######################
#
### SET A SINGLE USER
#
########################
function _ex_auth_get_user(&$u)
{
global $ldap_opts, $_cal_sql, $_cal_dbpref;;
$ldap_opts['conn_id'] or _ex_auth_ldap_connect();
# GET ALL VALUES FOR USER
###########################
if($u->id) $filter = $ldap_opts['attribs']['uid'] ."=". $u->id;
else if($u->userid) $filter = $ldap_opts['attribs']['userid'] ."=". $u->userid;
else return 1;
# AUTOGEN ID?
##################
if($ldap_opts['attribs']['uid'] == '__autogen' && $u->id) {
# GET USERID FROM THYME
########################
list($uinf) = $_cal_sql->query("select id, userid from {$_cal_dbpref}Users where
id = {$u->id}");
# alter filter
###############
$filter = $ldap_opts['attribs']['userid'] ."=". $uinf['userid'];
}
#$filter = "({$filter}) (OU=Network_Managers | OU=Teachers)";
if($ldap_opts['debug']) {
echo("Using filter: {$filter}<br>");
}
if(is_array($ldap_opts['suffix'])) {
for($i = 0; $i < count($ldap_opts['suffix']); $i++) {
if($ldap_opts['debug']) {
echo("Suffix: {$ldap_opts['suffix'][$i]}<br>");
}
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'][$i], $filter, array_values($ldap_opts['attribs']), 0, 1);
if($sr && @ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$i = count($ldap_opts['suffix']);
}
}
} else {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'], $filter, array_values($ldap_opts['attribs']), 0, 1);
if(!$sr) return 0;
}
if(!@ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$u->email = $u->name = $u->id = $u->userid = null;
return 0;
}
$info = ldap_get_entries($ldap_opts['conn_id'], $sr);
$u->email = $info[0][$ldap_opts['attribs']['email']][0];
$u->name = $info[0][$ldap_opts['attribs']['name']][0];
$u->userid = $info[0][$ldap_opts['attribs']['userid']][0];
$u->dn = $info[0][$ldap_opts['attribs']['dn']];
# AUTOGEN ID?
##################
if($ldap_opts['attribs']['uid'] == '__autogen') {
if($u->id) $info[0][$ldap_opts['attribs']['uid']][0] = $u->id;
else $info[0][$ldap_opts['attribs']['uid']][0] = _ex_auth_mk_uid($u->userid);
}
$u->id = $info[0][$ldap_opts['attribs']['uid']][0];
if($u->userid == $ldap_opts['admin']) $u->admin = 1;
if($ldap_opts['debug'] && !$u->id) {
echo("Failed to get user info.<br><br>");
echo("<pre>passed:\n");
print_r($u);
echo("\nReturned from LDAP:\n");
print_r($info[0]);
echo("</pre>\n");
exit;
}
return 1;
}
###############################################
#
### GET USERS BY ID
#
###############################################
function _ex_auth_get_users_by_id($ids, $associative = false)
{
global $ldap_opts;
$ldap_opts['conn_id'] or _ex_auth_ldap_connect();
if(!is_array($ids) || count($ids) < 1) return array();
$filter = "(|(". $ldap_opts['attribs']['uid'] ."=". join(")(".$ldap_opts['attribs']['uid'] ."=", $ids) ."))";
# AUTOGEN UIDS
###################
if($ldap_opts['attribs']['uid'] == '__autogen') {
# will need these later
$userids = _ex_auth_get_userids($ids);
$filter = "(|(". $ldap_opts['attribs']['userid'] ."=". join(")(".$ldap_opts['attribs']['userid'] ."=", $userids) ."))";
}
$users = $info = array();
# GET ALL ENTRIES
#####################
if(is_array($ldap_opts['suffix'])) {
for($i = 0; $i < count($ldap_opts['suffix']); $i++) {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'][$i],$filter,
array_values($ldap_opts['attribs']), 0, 0);
if(ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$info = array_merge($info, ldap_get_entries($ldap_opts['conn_id'], $sr));
}
}
} else {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'],$filter,
array_values($ldap_opts['attribs']), 0, 0);
if(!ldap_count_entries($ldap_opts['conn_id'], $sr)) {
return array();
}
$info = ldap_get_entries($ldap_opts['conn_id'], $sr);
if($info['count'] < 1) return array();
}
$info['count'] = count($info) - 1;
if(is_array($userids)) $userids = array_flip($userids);
# GRAB EACH USER
###################
for ($i=0; $i<$info["count"]; $i++) {
# POPULATE UIDS
#################
if($ldap_opts['attribs']['uid'] == '__autogen') {
$info[$i][$ldap_opts['attribs']['uid']][0] = $userids[$info[$i][$ldap_opts['attribs']['userid']][0]];
}
if($associative) {
$users[$info[$i][$ldap_opts['attribs']['uid']][0]] = array(
'name' => $info[$i][$ldap_opts['attribs']['name']][0],
'userid' => $info[$i][$ldap_opts['attribs']['userid']][0],
'email' => $info[$i][$ldap_opts['attribs']['email']][0],
'dn' => $info[0][$ldap_opts['attribs']['dn']]);
} else {
$users[] = array('id' => $info[$i][$ldap_opts['attribs']['uid']][0],
'name' => $info[$i][$ldap_opts['attribs']['name']][0],
'userid' => $info[$i][$ldap_opts['attribs']['userid']][0],
'email' => $info[$i][$ldap_opts['attrirbs']['email']][0],
'dn' => $info[0][$ldap_opts['attribs']['dn']]);
}
}
return $users;
}
################################################
#
### RETURN A LIST OF USERS THAT MATCH A QUERY
#
################################################
function _ex_auth_search_users($key = false, $search_str, $anchor_start = false, $associative = false)
{
global $ldap_opts;
$ldap_opts['conn_id'] or _ex_auth_ldap_connect();
# SEARCH OPTIONS
#######################
$search_str = ($anchor_start ? "" : "*") . $search_str . ($search_str ? "*" : "");
switch($key)
{
case 'name':
$filter = $ldap_opts['attribs']['name'] . "={$search_str}";
break;
case 'userid':
$filter = $ldap_opts['attribs']['userid'] ."={$search_str}";
break;
default:
$filter = "(|({$ldap_opts['attribs']['userid']}={$search_str})({$ldap_opts['attribs']['name']}={$search_str}))";
}
$info = array();
# GET ALL ENTRIES
#####################
if(is_array($ldap_opts['suffix'])) {
for($i = 0; $i < count($ldap_opts['suffix']); $i++) {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'][$i],$filter,
array_values($ldap_opts['attribs']), 0, 0);
if(ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$info = array_merge($info, ldap_get_entries($ldap_opts['conn_id'], $sr));
}
}
} else {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'],$filter,
array_values($ldap_opts['attribs']), 0, 0);
if(!ldap_count_entries($ldap_opts['conn_id'], $sr)) {
return array();
}
$info = ldap_get_entries($ldap_opts['conn_id'], $sr);
if($info['count'] < 1) return array();
}
$info['count'] = count($info) - 1;
# AUTOGEN UIDS
################
if($ldap_opts['attribs']['uid'] == '__autogen') {
$userids = array();
# we have to get each userid
for($i = 0; $i < $info['count']; $i++)
$userids[] = $info[$i][$ldap_opts['attribs']['userid']][0];
# will need these later
$userids = _ex_auth_mk_uids($userids);
}
# GRAB EACH USER
###################
for ($i=0; $i<$info["count"]; $i++) {
if($ldap_opts['attribs']['uid'] == '__autogen') {
$info[$i][$ldap_opts['attribs']['uid']][0] = $userids[$info[$i][$ldap_opts['attribs']['userid']][0]];
}
if($associative) {
$users[$info[$i][$ldap_opts['attribs']['uid']][0]] = array('name' => $info[$i][$ldap_opts['attribs']['name']][0],
'userid' => $info[$i][$ldap_opts['attribs']['userid']][0],
'email' => $info[$i][$ldap_opts['attribs']['email']][0],
'dn' => $info[$i][$ldap_opts['attribs']['dn']]);
} else {
$users[] = array('id' => $info[$i][$ldap_opts['attribs']['uid']][0],
'name' => $info[$i][$ldap_opts['attribs']['name']][0],
'userid' => $info[$i][$ldap_opts['attribs']['userid']][0],
'email' => $info[$i][$ldap_opts['attribs']['email']][0],
'dn' => $info[$i][$ldap_opts['attribs']['dn']]);
}
}
return $users;
}
#############################################################
#
### RETURN A LIST OF CHARACTERS THERE ENTRIES FOR
#
############################################################
# if you have a large LDAP database and this is expensive,
# you may simply return an array consisting of a-z as keys
# return array( 'a' => 1, 'b' => 1, 'c' => 1, 'd' => 1 ... etc..)
function _ex_auth_list_chars($key)
{
#return array_flip(array_map("strtolower", $GLOBALS['_cal_alphabet']));
global $ldap_opts;
$ldap_opts['conn_id'] or _ex_auth_ldap_connect();
$chars = array();
# SELECT USERNAME OR COMMON NAME
$key = ($key == 'name' ? $ldap_opts['attribs']['name'] : $ldap_opts['attribs']['userid']);
# GET ENTRIES FOR EACH CHAR
#############################
foreach($GLOBALS['_cal_alphabet'] as $c) {
$info = array();
if(is_array($ldap_opts['suffix'])) {
for($i = 0; $i < count($ldap_opts['suffix']); $i++) {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'][$i],
"($key=$c*)", array($key), 0, 0);
if(ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$info = ldap_get_entries($ldap_opts['conn_id'], $sr);
# stop when we get 1
$i = count($ldap_opts['suffix']);
}
}
} else {
$sr = ldap_search($ldap_opts['conn_id'], $ldap_opts['suffix'],
"($key=$c*)", array($key), 0, 0);
if(!ldap_count_entries($ldap_opts['conn_id'], $sr)) {
$info = ldap_get_entries($ldap_opts['conn_id'], $sr);
}
}
if(!(count($info) -1)) continue;
$chars[substr(strtolower($info[0][$key][0]),0,1)] = 1;
}
return $chars;
}
#################################
#
### INTERNAL CONNECT FUNCTION
#
################################
function _ex_auth_ldap_connect()
{
global $ldap_opts;
$ldap_opts['conn_id'] = @ldap_connect($ldap_opts['host']);
# SET VERSION
#################
if($ldap_opts['version'] == 3)
@ldap_set_option($ldap_opts['conn_id'], LDAP_OPT_PROTOCOL_VERSION, 3);
$auth = @ldap_bind($ldap_opts['conn_id'], $ldap_opts['anon_dn'], $ldap_opts['anon_pw']);
if(!$auth) {
return 0;
}
}
#####################################
#
### UID GENERATION
#
#####################################
function _ex_auth_mk_uids($usernames) {
global $_cal_sql, $_cal_dbpref;
$uinf = $_cal_sql->query("select id, userid from {$_cal_dbpref}Users
where userid in ('". join("','",
array_map(array('_cal_sql','escape_string'),$usernames)) ."')", true);
# userids in $usernames, but not in $uinf
##########################################
$create = array_diff($usernames, $uinf);
if(!count($create)) return array_flip($uinf);
# there are userids to create
###############################
foreach($create as $u) {
$_cal_sql->query("insert into {$_cal_dbpref}Users (userid) values ('".
$_cal_sql->escape_string($u) ."')");
}
# now get them ...
##################
$new = $_cal_sql->query("select userid, id from {$_cal_dbpref}Users
where userid in ('". join("','",
array_map(array('_cal_sql','escape_string'),$create)) ."')", true);
return array_merge(array_flip($uinf), $new);
}
function _ex_auth_mk_uid($username) {
global $_cal_sql, $_cal_dbpref;
list($uinf) = $_cal_sql->query("select userid, id from {$_cal_dbpref}Users
where userid = '". $_cal_sql->escape_string(strtolower($username)) ."'");
if($uinf['id']) return $uinf['id'];
# does not exist, create it
#############################
$uid = $_cal_sql->query("insert into {$_cal_dbpref}Users (userid) values ('".
$_cal_sql->escape_string(strtolower($username)) ."')");
# DBs that return last insert id
###################################
if(_CAL_SQL_DRIVER_ == 'mysql' || _CAL_SQL_DRIVER_ == 'mssql') return $uid;
list($uinf) = $_cal_sql->query("select userid, id from {$_cal_dbpref}Users
where userid = '". $_cal_sql->escape_string($username) ."'");
if($uinf['id']) return $uinf['id'];
}
function _ex_auth_get_userids($ids) {
global $_cal_sql, $_cal_dbpref;
return $_cal_sql->query("select id, userid from {$_cal_dbpref}Users where id in (".
join(",", $ids) .")", true);
}
?>