Voici une classe PHP que j'ai trouvé sur phpclasses.org et que j'ai
modifié quelque peu. Je n'ai pas eu de problème avec jusqu'à
maintenant. (La function setAttribute n'a jamais été testée...)
-yanick
---------------------------------- LDAPAuth.class.php
-----------------------------------------
<?php
/**
* Class : LDAPauth.class.php
* Version : 0.3
*
* Based on class.AuthLdap.php, version 0.2
* Mark Round, April 2002 - http://www.markround.com/unix
*
* Modified by Yanick Rochon, September 2006 -
***@collegeletendre.net
*/
// constants for : useraccountcontrol (see findUsers())
define( 'LDAP_UAC_SCRIPT', 1 );
define( 'LDAP_UAC_ACCOUNTDISABLE', 2 );
define( 'LDAP_UAC_HOMEDIR_REQUIRED', 8 );
define( 'LDAP_UAC_LOCKOUT', 16 );
define( 'LDAP_UAC_PASSWD_NOTREQUIRED', 32 );
define( 'LDAP_UAC_PASSWD_CANT_CHANGE', 64 );
define( 'LDAP_UAC_ENCRYPTED_TEXT_PWD_ALLOWED', 128 );
define( 'LDAP_UAC_TEMP_DUPLICATE_ACCOUNT', 256 );
define( 'LDAP_UAC_NORMAL_ACCOUNT', 512 );
define( 'LDAP_UAC_INTERDOMAIN_TRUST_ACCOUNT', 2048 );
define( 'LDAP_UAC_WORKSTATION_TRUST_ACCOUNT', 4096 );
define( 'LDAP_UAC_SERVER_TRUST_ACCOUNT', 8192 );
define( 'LDAP_UAC_DONT_EXPIRE_PASSWORD', 65536 );
define( 'LDAP_UAC_MNS_LOGON_ACCOUNT', 131072 );
define( 'LDAP_UAC_SMARTCARD_REQUIRED', 262144 );
define( 'LDAP_UAC_TRUSTED_FOR_DELEGATION', 524288 );
define( 'LDAP_UAC_NOT_DELEGATED', 1048576 );
define( 'LDAP_UAC_USE_DES_KEY_ONLY', 2097152 );
define( 'LDAP_UAC_DONT_REQ_PREAUTH', 4194304 );
define( 'LDAP_UAC_PASSWORD_EXPIRED', 8388608 );
define( 'LDAP_UAC_TRUSTED_TO_AUTH_FOR_DELEGATION', 16777216 );
class AuthLdap {
// 1.1 Public properties
-----------------------------------------------------
/**
* Array of server IP address or hostnames
*/
public $server;
/**
* The base DN (e.g. "dc=foo,dc=com")
*/
public $baseDn;
/**
* Active Directory authenticates using ***@domain
*/
public $domain;
/**
* The user to authenticate with when searching
* Active Directory doesn't support anonymous access
*/
public $searchUser;
/**
* The password to authenticate with when searching
* Active Directory doesn't support anonymous access
*/
public $searchPassword;
/**
* The last error code returned by the LDAP server
*/
public $ldapErrorCode;
/**
* Text of the error message
*/
public $ldapErrorText;
/**
* The Organizational Units to search in
*/
public $searchOU;
// 1.2 Private properties
----------------------------------------------------
/**
* The internal LDAP connection handle
*/
private $connection;
/**
* We have a bind to the server
*/
private $bound;
/**
* Result of any connections etc.
*/
private $result;
// 1.3 Constructor
-----------------------------------------------------------
/**
* Constructor- creates a new instance of the authentication class
*
* @param string or array the ldap server(s) to connect to
* @param string the base dn
* @param string the server type- current supports iPlanet and
ActiveDirectory
* @param string the domain to use when authenticating against
Active Directory
* @param string the username to authenticate with when searching
if anonymous binding is not supported
* @param string the password to authenticate with when searching
if anonymous binding is not supported
* @param array the base OU to search from
*/
function __construct( $sLdapServer, $sBaseDN, $sDomain = '',
$searchUser = '', $searchPassword = '',
$searchOU = NULL ) {
if ( !is_array( $sLdapServer ) ) $sLdapServer array($sLdapServer);
$this->server = $sLdapServer;
$this->baseDn = $sBaseDN;
$this->domain = $sDomain;
$this->searchUser = $searchUser;
$this->searchPassword = $searchPassword;
$this->searchOU = $searchOU;
}
// 2.1 Connection handling methods
-------------------------------------------
/**
* 2.1.1 : Connects to the server. Just creates a connection which
is used
* in all later access to the LDAP server. If it can't connect and
bind
* anonymously, it creates an error code of -1. Returns true if
connected,
* false if failed. Takes an array of possible servers - if one
doesn't work,
* it tries the next and so on.
*/
function connect() {
foreach ($this->server as $key => $host) {
if ( $this->connection = @ldap_connect( $host) ) {
if (!@ldap_set_option($this->connection,
LDAP_OPT_PROTOCOL_VERSION, 3)) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Unable to switch to LDAP version
3";
// we won't return an error code here since it's not a
necessary
// setting, but we will still set the error text !
}
return true; // return now that we've connected
successfully
}
}
$this->connection = NULL; // no connection
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Unable to connect to any server";
return false;
}
/**
* Returns TRUE if a connection to an LDAP server have been
established
*
* @see connect()
*/
function isConnected() {
return !empty( $this->connection );
}
/**
* Returns TRUE if a user have successfully been bound to the
connection
* (Binding users may be required on LDAP servers that do not
allow
* anonymous search)
*
* @see bind()
*/
function isBound() {
return $this->bound == TRUE;
}
/**
* 2.1.2 : Simply closes the connection set up earlier.
* Returns true if OK, false if there was an error.
*/
function close() {
if ( !@ldap_close( $this->connection)) {
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return false;
} else {
return true;
}
}
/**
* 2.1.3 : Binds with the LDAP server. If $uname is provided, the
bind
* will be attempted with that user. If not, the bind will be
attempted
* with the searchUser/searchPass.
*/
function bind( $uname = NULL, $pass = NULL ) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
if ( !empty( $uname ) ) {
if ( empty( $pass ) ) $pass = '';
// try to bind with the function specified user and password
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return FALSE;
}
} else if ( !empty( $this->searchUser ) ) {
// try to bind with the constructor specified user and
password
searchUser,$this->searchPassword ) ) {
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return FALSE;
}
} else {
// try to bind anonymously
if ( !$this->result=@ldap_bind( $this->connection) ) {
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return FALSE;
}
}
$this->bound = TRUE;
return TRUE;
}
// 2.2 Password methods
------------------------------------------------------
/**
* 2.2.1 : Checks a username and password - does this by logging
on to the
* server as a user - specified in the DN. There are several
reasons why
* this login could fail - these are listed below.
*/
function checkPass( $uname,$pass) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
// Try and connect...
domain}", $pass );
if ( $this->result) {
// Connected OK - login credentials are fine!
$this->ldapErrorCode = 0;
$this->ldapErrorText = 'Ok';
return true;
} else {
/* Login failed. Return false, together with the error code
and text from
** the LDAP server. The common error codes and reasons are
listed below :
** (for iPlanet, other servers may differ)
** 19 - Account locked out (too many invalid login attempts)
** 32 - User does not exist
** 49 - Wrong password
** 53 - Account inactive (manually locked out by
administrator)
*/
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return false;
}
}
// 2.3 Group methods
---------------------------------------------------------
/**
* 2.3.1 : Checks to see if a user is in a given group. If so, it
returns
* true, and returns false if the user isn't in the group, or any
other
* error occurs (eg:- no such user, no group by that name etc.)
*/
function checkGroup ( $uname, $group ) {
if ( $groups = $this->getGroups( $uname ) ) {
foreach ( $groups as $g ) {
if ( preg_match( "/{$group}/i", $g ) ) {
return true;
}
}
} else {
// error set in getGroups
return FALSE;
}
}
/**
* Return all groups of a user. The function returns FALSE if user
is not found.
*
* @param $uname string a user name
* @return array
*/
function getGroups( $uname ) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
$groups = array();
// We need to search for the group in order to get it's entry.
if ( $this->searchUser( $uname, array( 'memberof' ) ) ) {
$info = @ldap_get_entries( $this->connection, $this->result);
// Only one entry should be returned(no groups will have the
same name)
$entry = @ldap_first_entry( $this->connection,$this->result);
if ( !$entry) {
var_dump( $info );
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return FALSE; // Couldn't find the group...
}
// Get all the member DNs
if ( !$values = @ldap_get_values( $this->connection, $entry,
"memberof")) {
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return FALSE; // No users in the group
}
for ( $i=0; $i<$values['count']; $i++ ) {
preg_match( '/^CN=[^,]*/i', $values[$i], $container );
list( $cn, $value ) = explode( "=",$container[0] );
array_push( $groups, $value );
}
}
return $groups;
}
// 2.4 Attribute methods
-----------------------------------------------------
/**
* 2.4.1 : Returns an array containing a set of attribute values.
* For most searches, this will just be one row, but sometimes
multiple
* results are returned (eg:- multiple email addresses)
* an empty array returns ALL attributes from the user
*
* If $attributes is not an empty array, the keys are the
attributes to return, and
* values are the default value if the attribute cannot be found.
*/
function getAttributes( $uname, $attributes = array() ) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
// builds the appropriate dn, based on whether $this->people and/
or $this->group is set
if ( !is_array( $attributes ) ) {
throw Exception('LDAP: attributes parameter is not an array');
}
// We need to search for this user in order to get their entry.
if ( $this->searchUser( $uname, array_keys( $attributes ) ) ) {
$info = @ldap_get_entries( $this->connection, $this->result );
// Only one entry should ever be returned (no user will have
the same uid)
$entry = @ldap_first_entry( $this->connection, $this->result);
} else {
$entry = NULL;
}
if ( !$entry) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Couldn't find user";
return false; // Couldn't find the user...
}
// Get all the member DNs
$attributesValues = array();
for ( $v=0; $v<$info[0]['count']; $v++ ) {
if ( $values = @ldap_get_values( $this->connection, $entry,
$info[0][$v] )) {
for ( $i=0; $i<$values['count']; $i++ ) {
$attributesValues[$info[0][$v]][] utf8_decode( $values[$i] );
}
}
}
foreach ( $attributes AS $a => $v ) {
if ( !isset( $attributesValues[$a] ) ) {
$attributesValues[$a] = is_array( $v ) ? $v : array( $v );
}
}
// Return an array containing the attributes.
return $attributesValues;
}
/**
* 2.4.2 : Allows an attribute value to be set.
* This can only usually be done after an authenticated bind as a
* directory manager - otherwise, read/write access will not be
granted.
*/
function setAttribute( $uname, $attribute, $value) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
// Construct a full DN...
// builds the appropriate dn, based on whether $this->people and/
or $this->group is set
if ( $attrib_dn = $this->getAttributes( $uname, 'dn' ) ) {
$info[$attribute] = $value;
// make sure we have an LDAP bind...
if ( !$this->isBound() ) $this->bind();
// Change attribute
if ( $this->result = @ldap_modify( $this->connection,
$attrib_dn[0]['dn'], $info ) ) {
// Change went OK
return true;
} else {
// Couldn't change password...
$this->ldapErrorCode = ldap_errno( $this->connection);
$this->ldapErrorText = ldap_error( $this->connection);
return false;
}
} else {
// error is set in getAttributes
return false;
}
}
// 2.5 User methods
----------------------------------------------------------
/**
* 2.5.1 : Search for a user in the database, and return it's
informations
*
* @see searchUser for more informations about parameters
*
* @return array the list of the users found, or an empty array if
none found
*/
public function findUsers( $uname, $fields = NULL, $searchOU NULL ) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
if ( is_null( $fields ) ) {
$fields = array( $this->getUserIdentifier(), 'dn' );
}
$userList = array();
// We need to search for the group in order to get it's entry.
if ( $this->searchUser( $uname, $fields, $searchOU ) ) {
$info = @ldap_get_entries( $this->connection, $this->result );
if ( is_array( $searchOU ) ) {
$filter = '';
foreach ( $searchOU AS $ou ) {
$filter .= "|(OU={$ou})|";
}
$filter = trim($filter, '|');
} else {
$filter = NULL;
}
for ( $u=0; $u<$info['count']; $u++ ) {
if ( empty($filter) || preg_match( "/{$filter}/i", $info[$u]
['dn'] ) ) {
array_push( $userList, $info[$u] );
}
}
}
return $userList;
}
/**
* 2.5.2 : Search for a user in the database. The function sets
$this->result with the returned
* ressource identifier. The function returns TRUE or FALSE,
depending on the search
* status.
*
* The argument $uname may contain wildcards. For example, 'roc*'
will return all users
* that starts with 'roc' in their user name.
*
* Please note that a search may NOT be performed unless at least
one OU is specified.
* For this function to work properly, you may specify
Organizational Units from the
* class constructor, or as parameter in this function. It seems
that LDAP doesn't like
* to search from the base DN only...
*
* @param string $uname user identification
* @param array $fields the LDAP fields values to return (filter)
* @param array $searchOU the list of OU to search in. If empty,
$this->searchOU is used
* @return bool if the function succeeded or not
*/
private function searchUser( $uname, $fields = NULL, $searchOU NULL ) {
if ( !$this->isConnected() ) {
$this->ldapErrorCode = -1;
$this->ldapErrorText = "Not connected !";
return FALSE;
}
// if organizational units are not specified, look into defaults
if ( empty( $searchOU ) ) $searchOU = $this->searchOU;
// make sure it is an array
$organisationalUnits = ( is_array( $searchOU ) ? $searchOU :
array( $searchOU ) );
if ( empty( $fields ) ) {
$fields = array();
}
$filter = "(&({$this->getUserIdentifier()}={$uname})
(objectclass=user))";
// this is an attempt to retrieve all results from any OU that
should be found...
// but it does not seem to work...
/*
if ( !empty( $organisationalUnits ) ) {
$ouFilter = '';
foreach ( $organisationalUnits AS $ou ) {
// if a specified DN is used, we only want the first part of
it
// e.g. 'Administration,OU=Users,OU=' => 'Administration'
if ( ($offset = strpos( $ou, ',' )) !== FALSE ) {
$ou = substr( $ou, 0, $offset );
}
$ou = "(ou={$ou})";
if ( !empty( $ouFilter ) ) $ouFilter = "(|{$ouFilter}
{$ou})"; else $ouFilter = $ou;
}
//echo "(|($ouFilter))";
//$filter = "(&{$filter}{$ouFilter})";
//echo $filter;
}
*/
// make sure we have a bind to the LDAP server first
if ( !$this->isBound() ) $this->bind();
if ( empty( $organisationalUnits ) ) {
// if no organizational units are specified, we try to search
from the base DN only
$this->result = ldap_search( $this->connection,$this->getDn(),
$filter,$fields );
if ( ldap_first_entry( $this->connection, $this->result) ) {
return TRUE;
}
} else {
// else, we search from all the OU specified...
foreach ( $organisationalUnits AS $ou ) {
$this->result = ldap_search( $this->connection,$this-
getDn("ou={$ou}"),$filter,$fields );
if ( ldap_first_entry( $this->connection, $this->result) ) {
return TRUE;
}
}
}
$this->ldapErrorCode = ldap_errno( $this->connection );
$this->ldapErrorText = ldap_error( $this->connection );
return FALSE;
} // searchUser
/**
* Returns a list of all organizational units inside a given dn.
This
* methods searches recursively and returns an array of arrays...
* The parameter $dn must be a complete dn as
'ou=Something,dc=domain.dc=net'
* (an initial top level OU is required on W2K3 servers)
*
* @param $dn string the base dn of the search
*
* @return array
*/
public function getAllOU( $dn = NULL, $asDn = FALSE ) {
$ouList = array();
if ( is_null( $dn ) ) $dn = $this->getDn();
$fields = array( 'dn' );
$filter = "objectclass=organizationalUnit";
// make sure we have a bind to the LDAP server first
if ( !$this->isBound() ) $this->bind();
if ( $this->result = @ldap_search( $this->connection,$dn,$filter,
$fields ) ) {
$info = @ldap_get_entries( $this->connection, $this->result );
if ( !empty( $info ) ) {
for ($i=0; $i<$info['count']; $i++ ) {
if ( $asDn ) {
array_push( $ouList, $info[$i]['dn'] );
} else {
if ( preg_match_all( '/OU=([^,]*)/i', $info[$i]['dn'],
$ou ) ) {
$ouFound = $ou[1];
// OUs are returned as the first element being the top
level OU, and the last element
// being the OU closest to the base DN, we outta
reverse the array
$ouFound = array_reverse( $ouFound );
$_ouList = &$ouList;
foreach ( $ouFound AS $o ) {
$o = utf8_decode($o); // unscramble utf8 encoding
characters
if ( !isset( $_ouList[$o] ) ) {
$_ouList[$o] = array();
}
$_ouList = &$_ouList[$o];
}
natsort( $_ouList );
$_ouList = NULL; // makes the lowest level of the
tree being NULL, and not an empty array
// (change this value to make it any
other value...)
}
}
}
}
}
natsort( $ouList );
return $ouList;
} // getOU
// 2.6 helper methods
/**
* Returns the appropriate dn. To specify a more detailed DN, the
$prefix parameter will be added
* in front of the baseDn. (Do not happen a comma ',' after the
$prefix value.)
*
* For example (if baseDn = 'dc=domain,dc=net';) :
*
* $this->getDn(); // will output
'dc=domain,dc=net'
* $this->getDn('OU=group'); // will output
'OU=group,dc=domain,dc=net'
* $this->getDn('OU=group,CN=Users'); // will output
'OU=group,CN=Users,dc=domain,dc=net'
*
* @param $prefix string specifies extra groups / containers / ou
to the base DN
* @return string if true ou=$this->people,$this->dn, else ou$this->groups,$this->dn
*/
function getDn( $prefix = NULL ) {
return ( !empty( $prefix ) ? "{$prefix}," : '' ) .
$this->baseDn;
}
/**
* Returns the correct user identifier to use, based on the ldap
server type
*
* (only support Microsoft LDAP for now)
*/
function getUserIdentifier() {
return "samaccountname";
}
} // End of class
?>
------------------------------------------- End Of File
------------------------------------