Here's what I came up with. If you use this solution please understand that there is absolutely no support or warranty and this might break everything that's ever been connected to your network ever.
Limitations:
Your users and groups must exist inside of an OU. I'm not really sure why, but if you don't include and OU in the search path ldap_search can't seem to find anything. This could probably be fixed by using adLDAP instead of my home brew solution, but I'm too lazy.
You can't have a mix of LDAP users and standard users (except for admin).
Users are created automatically and their permissions are assigned from LDAP... so that makes the user manager pretty useless.
Users are not assigned to any projects when they login the first time.
Your web server must be able to communicate with your LDAP server.
I hope this is a good starting point for other people interested in implementing LDAP authentication. If you do improve this solution please post it back to this thread so that everyone can benefit.
New Helper Class System_Api_LDAPHelper
<?php
// File Location: /system/api/ldaphelper.inc.php
if ( !defined( 'WI_VERSION' ) ) die( -1 );
class System_Api_LDAPHelper
{
private $_params;
private $_ldap_connection = false;
public function __construct( $params = array() )
{
$this->setParams( $params );
}
// set params
public function setParams( $params = array() )
{
// default ldap parameters
$default_params = array(
'ldap_domain_suffix' => "domain.local",
'ldap_server' => "pdc.domain.local",
'ldap_user_ou' => "DC=domain,DC=local",
'ldap_group_ou' => "DC=domain,DC=local",
'ldap_user_filter' => "(&(objectclass=person)(samaccountname=%s))",
'ldap_group_filter' => "(&(objectclass=group)(cn=%s))"
);
// merge provided params into defaults
foreach ( $params as $key => $value )
if ( isset( $default_params[$key] ) )
$default_params[$key] = $value;
// update the object
$this->_params = $default_params;
}
// connect to ldap
public function connect()
{
if ( $this->_ldap_connection ) return true;
$this->_ldap_connection = @ldap_connect( $this->_params['ldap_server'] );
if ( ! $this->_ldap_connection ) throw new Exception("Could not connect to LDAP server.");
}
// bind to ldap server
public function bind( $user, $password )
{
if ( ! $this->_ldap_connection ) $this->connect();
if ( @ldap_bind( $this->_ldap_connection, "{$user}@{$this->_params['ldap_domain_suffix']}", $password ) ) return true;
else return false;
}
// get user info from ldap
public function getUserInfo( $user )
{
// search for the user
if ( ! $results = @ldap_search(
$this->_ldap_connection,
$this->_params['ldap_user_ou'],
sprintf( $this->_params['ldap_user_filter'], str_replace( array( "(", ")", "*" ), array( "\(", "\)", "\*"), $user ) ),
array( 'mail', 'dn', 'sn', 'givenName', 'displayName')
) ) return false; // user is not found!
// get the user details
$user = @ldap_get_entries( $this->_ldap_connection, $results );
// if there are more than one users returned throw and exception
if ($user['count'] > 1) throw new Exception("Too many users returned from LDAP search.");
$return = array(
'dn' => $user[0]['dn']
);
for ( $i = 0; $i < $user[0]['count']; $i++ )
{
$name = $user[0][$i];
$return[$name] = $user[0][$name][0];
}
// otherwise return the user
return $return;
}
// check user memberships
public function isMember( $groupname, $userdn, &$checkedgroups=array() )
{
// check to see if group has already been checked, if it has return false
// otherwise add it to the array of checked groups, this will prevent
// getting stuck in a check loop
if ( in_array( $groupname, $checkedgroups ) ) return false;
$checkedgroups[] = $groupname;
// search for group to get dn and members
if ( ! $results = @ldap_search(
$this->_ldap_connection,
$this->_params['ldap_group_ou'],
sprintf( $this->_params['ldap_group_filter'], str_replace( array( "(", ")", "*" ), array( "\(", "\)", "\*" ), $groupname ) ),
array( 'dn', 'cn', 'member' )
)) return false; // no GROUP with that name found
// get the requested attributes from the query
$group = @ldap_get_entries( $this->_ldap_connection, $results );
// check to make sure we haven't found two groups
if ( $group['count'] > 1 ) throw new exception("Too many groups returned by LDAP.");
// if this is true the user is a direct member of the group
if ( @ldap_compare( $this->_ldap_connection, $group[0]['dn'], 'member', $userdn ) === true ) return true;
// check to see if any other members were returned
if ( ! isset( $group[0]['member'] ) ) return false;
// otherwise we need to search any member groups
for ( $i=0; $i < $group[0]['member']['count']; $i++ )
{
// don't bother checking groups that have already been checked
$groupname = preg_replace( "/CN=([^,]+),.*/i", "$1", $group[0]['member'][$i] );
if ( $this->isMember( $groupname, $userdn, $checkedgroups ) ) return true;
}
// if we never find it return false
return false;
}
}
?>
Then modify /system/api/sessionmanager.inc.php and add the following code after line 63 (the beginning of the login function):
<?php
// LDAP AUTH START ***********************************************************************
// options for System_Api_LDAPHelper
$ldap_params = array(
'ldap_domain_suffix' => 'yourdomain.local',
'ldap_server' => 'yourdomainconroller.yourdomain.local',
'ldap_user_ou' => 'OU=Users,OU=YOURDOMAIN,DC=gsnwgl,DC=local',
'ldap_group_ou' => 'OU=YOURDOMAIN,DC=gsnwgl,DC=local'
);
// user groups in Acitive Directory for auth levels in WebIssues
// if these groups do not exist or users are not assigned to them you won't be able to login
$ldap_user_group = 'WebIssues Users';
$ldap_admin_group = 'WebIssues Administrators';
// **** don't modify below this line ****
$ldap = new System_Api_LDAPHelper( $ldap_params );
$userManager = new System_Api_UserManager;
// user is authenticated to LDAP
if ( $ldap->bind( $login, $password ) )
{
$ldap_user = $ldap->getUserInfo( $login );
// setup or update user password
$query = 'SELECT user_id FROM {users} WHERE user_login = %s OR user_name = %s';
if ( ! ( $userId = $this->connection->queryScalar( $query, $login, $ldap_user['displayname'] ) ) )
{
$userId = $userManager->addUser( $login, $ldap_user['displayname'], $password, 0 );
}
else
{
$passwordHash = new System_Core_PasswordHash();
$newHash = $passwordHash->hashPassword( $password );
$query = 'UPDATE {users} SET user_passwd = %s, passwd_temp = %d WHERE user_id = %d';
$this->connection->execute( $query, $newHash, 0, $userId );
}
// set access level
$accessLevel = System_Const::NoAccess;
if ( $ldap->isMember( $ldap_user_group, $ldap_user['dn'] ) ) $accessLevel = System_Const::NormalAccess;
if ( $ldap->isMember( $ldap_admin_group, $ldap_user['dn'] ) ) $accessLevel = System_Const::AdministratorAccess;
$query = 'UPDATE {users} SET user_access = %d WHERE user_login = %s';
$this->connection->execute( $query, $accessLevel, $login );
// update email address
if ( isset( $ldap_user['mail'] ) && ! empty( $ldap_user['mail']) )
{
$query = 'SELECT COUNT(*) FROM {preferences} WHERE pref_key = %s AND user_id = %d';
if ( ! $this->connection->queryScalar( $query, 'email', $userId) )
{
$query = 'INSERT INTO {preferences} ( user_id, pref_key, pref_value ) VALUES ( %d, %s, %s )';
$this->connection->execute( $query, $userId, 'email', $ldap_user['mail'] );
}
else
{
$query = 'UPDATE {preferences} SET pref_value = %s WHERE user_id = %d AND pref_key = %s';
$this->connection->execute( $query, $ldap_user['mail'], $userId, 'email' );
}
}
}
// user is not authenticated to LDAP and not the admin user
elseif ( $login != 'admin' )
{
$query = 'UPDATE {users} SET user_access = %d WHERE user_login = %s';
$this->connection->execute( $query, System_Const::NoAccess, $login );
}
// LDAP AUTH END *************************************************************************/
?>
Note: don't include the PHP tags into sessionmanager.inc.php
Yes, but it's quite far down in the todo list.
Regards,
Michał
I have done integration between php and ldap before, could you please point me to the file that I should modify?
Thanks!
You can play with system/api/sessionmanager.inc.php, but I don't think it's a matter of modifying a single file.
Regards,
Michał
Here's what I came up with. If you use this solution please understand that there is absolutely no support or warranty and this might break everything that's ever been connected to your network ever.
Limitations:
I hope this is a good starting point for other people interested in implementing LDAP authentication. If you do improve this solution please post it back to this thread so that everyone can benefit.
New Helper Class System_Api_LDAPHelper
Then modify /system/api/sessionmanager.inc.php and add the following code after line 63 (the beginning of the login function):
Note: don't include the PHP tags into sessionmanager.inc.php
Very good solution!
It's working great! Many thanks!!