[ Index ]
 

Code source de Claroline 188

Accédez au Source d'autres logiciels libres

Classes | Fonctions | Variables | Constantes | Tables

title

Body

[fermer]

/claroline/inc/lib/pear/Auth/Container/ -> LDAP.php (source)

   1  <?php
   2  //
   3  // +----------------------------------------------------------------------+
   4  // | PHP Version 4                                                        |
   5  // +----------------------------------------------------------------------+
   6  // | Copyright (c) 1997-2005 The PHP Group                                |
   7  // +----------------------------------------------------------------------+
   8  // | This source file is subject to version 2.02 of the PHP license,      |
   9  // | that is bundled with this package in the file LICENSE, and is        |
  10  // | available at through the world-wide-web at                           |
  11  // | http://www.php.net/license/2_02.txt.                                 |
  12  // | If you did not receive a copy of the PHP license and are unable to   |
  13  // | obtain it through the world-wide-web, please send a note to          |
  14  // | license@php.net so we can mail you a copy immediately.               |
  15  // +----------------------------------------------------------------------+
  16  // | Authors: Jan Wagner <wagner@netsols.de>                              |
  17  // +----------------------------------------------------------------------+
  18  //
  19  // $Id: LDAP.php,v 1.7 2005/11/03 12:46:17 peeters Exp $
  20  //
  21  
  22  require_once "Auth/Container.php";
  23  require_once "PEAR.php";
  24  
  25  /**
  26   * Storage driver for fetching login data from LDAP
  27   *
  28   * This class is heavily based on the DB and File containers. By default it
  29   * connects to localhost:389 and searches for uid=$username with the scope
  30   * "sub". If no search base is specified, it will try to determine it via
  31   * the namingContexts attribute. It takes its parameters in a hash, connects
  32   * to the ldap server, binds anonymously, searches for the user, and tries
  33   * to bind as the user with the supplied password. When a group was set, it
  34   * will look for group membership of the authenticated user. If all goes
  35   * well the authentication was successful.
  36   *
  37   * Parameters:
  38   *
  39   * host:        localhost (default), ldap.netsols.de or 127.0.0.1
  40   * port:        389 (default) or 636 or whereever your server runs
  41   * url:         ldap://localhost:389/
  42   *              useful for ldaps://, works only with openldap2 ?
  43   *              it will be preferred over host and port
  44   * version:     LDAP version to use, ususally 2 (default) or 3,
  45   *              must be an integer!
  46   * binddn:      If set, searching for user will be done after binding
  47   *              as this user, if not set the bind will be anonymous.
  48   *              This is reported to make the container work with MS
  49   *              Active Directory, but should work with any server that
  50   *              is configured this way.
  51   *              This has to be a complete dn for now (basedn and
  52   *              userdn will not be appended).
  53   * bindpw:      The password to use for binding with binddn
  54   * basedn:      the base dn of your server
  55   * userdn:      gets prepended to basedn when searching for user
  56   * userscope:   Scope for user searching: one, sub (default), or base
  57   * userattr:    the user attribute to search for (default: uid)
  58   * userfilter:  filter that will be added to the search filter
  59   *              this way: (&(userattr=username)(userfilter))
  60   *              default: (objectClass=posixAccount)
  61   * attributes:  array of additional attributes to fetch from entry.
  62   *              these will added to auth data and can be retrieved via
  63   *              Auth::getAuthData(). An empty array will fetch all attributes,
  64   *              array('') will fetch no attributes at all (default)
  65   * attrformat:  The returned format of the additional data defined in the
  66   *              'attributes' option. Two formats are available :
  67   *              'LDAP' returns data formatted in a multidimensional array 
  68   *              where each array starts with a 'count' element providing the 
  69   *              number of attributes in the entry, or the number of values for 
  70   *              attributes. When set to this format, the only way to retrieve 
  71   *              data from the Auth object is by calling getAuthData('attributes').
  72   *              'AUTH ' returns data formatted in a structure more compliant 
  73   *              with other Auth containers, where each attribute element can be 
  74   *              directly called by getAuthData() method from Auth.
  75   *              For compatibily with previous LDAP container versions,
  76   *              the default format is LDAP.
  77   * groupdn:     gets prepended to basedn when searching for group
  78   * groupattr:   the group attribute to search for (default: cn)
  79   * groupfilter: filter that will be added to the search filter when
  80   *              searching for a group:
  81   *              (&(groupattr=group)(memberattr=username)(groupfilter))
  82   *              default: (objectClass=groupOfUniqueNames)
  83   * memberattr : the attribute of the group object where the user dn
  84   *              may be found (default: uniqueMember)
  85   * memberisdn:  whether the memberattr is the dn of the user (default)
  86   *              or the value of userattr (usually uid)
  87   * group:       the name of group to search for
  88   * groupscope:  Scope for group searching: one, sub (default), or base
  89   * debug:       Enable/Disable debugging output (default: false)
  90   *
  91   * To use this storage container, you have to use the following syntax:
  92   *
  93   * <?php
  94   * ...
  95   *
  96   * $a = new Auth("LDAP", array(
  97   *       'host' => 'localhost',
  98   *       'port' => '389',
  99   *       'version' => 3,
 100   *       'basedn' => 'o=netsols,c=de',
 101   *       'userattr' => 'uid'
 102   *       'binddn' => 'cn=admin,o=netsols,c=de',
 103   *       'bindpw' => 'password'));
 104   *
 105   * $a2 = new Auth('LDAP', array(
 106   *       'url' => 'ldaps://ldap.netsols.de',
 107   *       'basedn' => 'o=netsols,c=de',
 108   *       'userscope' => 'one',
 109   *       'userdn' => 'ou=People',
 110   *       'groupdn' => 'ou=Groups',
 111   *       'groupfilter' => '(objectClass=posixGroup)',
 112   *       'memberattr' => 'memberUid',
 113   *       'memberisdn' => false,
 114   *       'group' => 'admin'
 115   *       ));
 116   *
 117   * This is a full blown example with user/group checking to an Active Directory
 118   *
 119   * $a3 = new Auth('LDAP', array(
 120   *       'host' => 'ldap.netsols.de',
 121   *       'port' => 389,
 122   *       'version' => 3,
 123   *       'basedn' => 'dc=netsols,dc=de',
 124   *       'binddn' => 'cn=Jan Wagner,cn=Users,dc=netsols,dc=de',
 125   *       'bindpw' => 'password',
 126   *       'userattr' => 'samAccountName',
 127   *       'userfilter' => '(objectClass=user)',
 128   *       'attributes' => array(''),
 129   *       'group' => 'testing',
 130   *       'groupattr' => 'samAccountName',
 131   *       'groupfilter' => '(objectClass=group)',
 132   *       'memberattr' => 'member',
 133   *       'memberisdn' => true,
 134   *       'groupdn' => 'cn=Users',
 135   *       'groupscope' => 'one',
 136   *       'debug' => true);
 137   *
 138   * The parameter values have to correspond
 139   * to the ones for your LDAP server of course.
 140   *
 141   * When talking to a Microsoft ActiveDirectory server you have to
 142   * use 'samaccountname' as the 'userattr' and follow special rules
 143   * to translate the ActiveDirectory directory names into 'basedn'.
 144   * The 'basedn' for the default 'Users' folder on an ActiveDirectory
 145   * server for the ActiveDirectory Domain (which is not related to
 146   * its DNS name) "win2000.example.org" would be:
 147   * "CN=Users, DC=win2000, DC=example, DC=org'
 148   * where every component of the domain name becomes a DC attribute
 149   * of its own. If you want to use a custom users folder you have to
 150   * replace "CN=Users" with a sequence of "OU" attributes that specify
 151   * the path to your custom folder in reverse order.
 152   * So the ActiveDirectory folder
 153   *   "win2000.example.org\Custom\Accounts"
 154   * would become
 155   *   "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
 156   *
 157   * It seems that binding anonymously to an Active Directory
 158   * is not allowed, so you have to set binddn and bindpw for
 159   * user searching,
 160   *
 161   * Example a3 shows a tested example for connection to Windows 2000
 162   * Active Directory
 163   *
 164   * @author   Jan Wagner <wagner@netsols.de>
 165   * @package  Auth
 166   * @version  $Revision: 1.7 $
 167   */
 168  
 169  class Auth_Container_LDAP extends Auth_Container
 170  {
 171      /**
 172       * Options for the class
 173       * @var array
 174       */
 175      var $options = array();
 176  
 177      /**
 178       * Connection ID of LDAP Link
 179       * @var string
 180       */
 181      var $conn_id = false;
 182  
 183      /**
 184       * Constructor of the container class
 185       *
 186       * @param  $params, associative hash with host,port,basedn and userattr key
 187       * @return object Returns an error object if something went wrong
 188       */
 189      function Auth_Container_LDAP($params)
 190      {
 191          if (false === extension_loaded('ldap')) {
 192              return PEAR::raiseError('Auth_Container_LDAP: LDAP Extension not loaded', 41, PEAR_ERROR_DIE);
 193          }
 194  
 195          $this->_setDefaults();
 196  
 197          if (is_array($params)) {
 198              $this->_parseOptions($params);
 199          }
 200      }
 201  
 202      // }}}
 203      // {{{ _connect()
 204  
 205      /**
 206       * Connect to the LDAP server using the global options
 207       *
 208       * @access private
 209       * @return object  Returns a PEAR error object if an error occurs.
 210       */
 211      function _connect()
 212      {
 213          // connect
 214          if (isset($this->options['url']) && $this->options['url'] != '') {
 215              $this->_debug('Connecting with URL', __LINE__);
 216              $conn_params = array($this->options['url']);
 217          } else {
 218              $this->_debug('Connecting with host:port', __LINE__);
 219              $conn_params = array($this->options['host'], $this->options['port']);
 220          }
 221  
 222          if (($this->conn_id = @call_user_func_array('ldap_connect', $conn_params)) === false) {
 223              return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.', 41, PEAR_ERROR_DIE);
 224          }
 225          $this->_debug('Successfully connected to server', __LINE__);
 226  
 227          // switch LDAP version
 228          if (is_int($this->options['version']) && $this->options['version'] > 2) {
 229              $this->_debug("Switching to LDAP version {$this->options['version']}", __LINE__);
 230              @ldap_set_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $this->options['version']);
 231          }
 232  
 233          // bind with credentials or anonymously
 234          if ($this->options['binddn'] && $this->options['bindpw']) {
 235              $this->_debug('Binding with credentials', __LINE__);
 236              $bind_params = array($this->conn_id, $this->options['binddn'], $this->options['bindpw']);
 237          } else {
 238              $this->_debug('Binding anonymously', __LINE__);
 239              $bind_params = array($this->conn_id);
 240          }
 241          // bind for searching
 242          if ((@call_user_func_array('ldap_bind', $bind_params)) == false) {
 243              $this->_debug();
 244              $this->_disconnect();
 245              return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server.", 41, PEAR_ERROR_DIE);
 246          }
 247          $this->_debug('Binding was successful', __LINE__);
 248      }
 249  
 250      /**
 251       * Disconnects (unbinds) from ldap server
 252       *
 253       * @access private
 254       */
 255      function _disconnect()
 256      {
 257          if ($this->_isValidLink()) {
 258              $this->_debug('disconnecting from server');
 259              @ldap_unbind($this->conn_id);
 260          }
 261      }
 262  
 263      /**
 264       * Tries to find Basedn via namingContext Attribute
 265       *
 266       * @access private
 267       */
 268      function _getBaseDN()
 269      {
 270          if ($this->options['basedn'] == "" && $this->_isValidLink()) {
 271              $this->_debug("basedn not set, searching via namingContexts.", __LINE__);
 272  
 273              $result_id = @ldap_read($this->conn_id, "", "(objectclass=*)", array("namingContexts"));
 274  
 275              if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
 276  
 277                  $this->_debug("got result for namingContexts", __LINE__);
 278  
 279                  $entry_id = @ldap_first_entry($this->conn_id, $result_id);
 280                  $attrs = @ldap_get_attributes($this->conn_id, $entry_id);
 281                  $basedn = $attrs['namingContexts'][0];
 282  
 283                  if ($basedn != "") {
 284                      $this->_debug("result for namingContexts was $basedn", __LINE__);
 285                      $this->options['basedn'] = $basedn;
 286                  }
 287              }
 288              @ldap_free_result($result_id);
 289          }
 290  
 291          // if base ist still not set, raise error
 292          if ($this->options['basedn'] == "") {
 293              return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!", 41, PEAR_ERROR_DIE);
 294          }
 295          return true;
 296      }
 297  
 298      /**
 299       * determines whether there is a valid ldap conenction or not
 300       *
 301       * @accessd private
 302       * @return boolean
 303       */
 304      function _isValidLink()
 305      {
 306          if (is_resource($this->conn_id)) {
 307              if (get_resource_type($this->conn_id) == 'ldap link') {
 308                  return true;
 309              }
 310          }
 311          return false;
 312      }
 313  
 314      /**
 315       * Set some default options
 316       *
 317       * @access private
 318       */
 319      function _setDefaults()
 320      {
 321          $this->options['url']         = '';
 322          $this->options['host']        = 'localhost';
 323          $this->options['port']        = '389';
 324          $this->options['version']     = 2;
 325          $this->options['binddn']      = '';
 326          $this->options['bindpw']      = '';
 327          $this->options['basedn']      = '';
 328          $this->options['userdn']      = '';
 329          $this->options['userscope']   = 'sub';
 330          $this->options['userattr']    = 'uid';
 331          $this->options['userfilter']  = '(objectClass=posixAccount)';
 332          $this->options['attributes']  = array(''); // no attributes
 333          $this->options['attrformat']  = 'LDAP'; // returns attribute array as PHP LDAP functions return it
 334       // $this->options['attrformat']  = 'AUTH'; // returns attribute like other Auth containers
 335          $this->options['group']       = '';
 336          $this->options['groupdn']     = '';
 337          $this->options['groupscope']  = 'sub';
 338          $this->options['groupattr']   = 'cn';
 339          $this->options['groupfilter'] = '(objectClass=groupOfUniqueNames)';
 340          $this->options['memberattr']  = 'uniqueMember';
 341          $this->options['memberisdn']  = true;
 342          $this->options['debug']       = false;
 343      }
 344  
 345      /**
 346       * Parse options passed to the container class
 347       *
 348       * @access private
 349       * @param  array
 350       */
 351      function _parseOptions($array)
 352      {
 353          foreach ($array as $key => $value) {
 354              if (array_key_exists($key, $this->options)) {
 355                  $this->options[$key] = $value;
 356              }
 357          }
 358      }
 359  
 360      /**
 361       * Get search function for scope
 362       *
 363       * @param  string scope
 364       * @return string ldap search function
 365       */
 366      function _scope2function($scope)
 367      {
 368          switch($scope) {
 369          case 'one':
 370              $function = 'ldap_list';
 371              break;
 372          case 'base':
 373              $function = 'ldap_read';
 374              break;
 375          default:
 376              $function = 'ldap_search';
 377              break;
 378          }
 379          return $function;
 380      }
 381  
 382      /**
 383       * Fetch data from LDAP server
 384       *
 385       * Searches the LDAP server for the given username/password
 386       * combination.
 387       *
 388       * @param  string Username
 389       * @param  string Password
 390       * @return boolean
 391       */
 392      function fetchData($username, $password)
 393      {
 394          $this->_connect();
 395          $this->_getBaseDN();
 396  
 397          // UTF8 Encode username for LDAPv3
 398          if (@ldap_get_option($this->conn_id, LDAP_OPT_PROTOCOL_VERSION, $ver) && $ver == 3) {
 399              $this->_debug('UTF8 encoding username for LDAPv3', __LINE__);
 400              $username = utf8_encode($username);
 401          }
 402          // make search filter
 403          $filter = sprintf('(&(%s=%s)%s)',
 404                            $this->options['userattr'],
 405                            $username,
 406                            $this->options['userfilter']);
 407          // make search base dn
 408          $search_basedn = $this->options['userdn'];
 409          if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
 410              $search_basedn .= ',';
 411          }
 412          $search_basedn .= $this->options['basedn'];
 413  
 414          // attributes
 415          $attributes = $this->options['attributes'];
 416  
 417          // make functions params array
 418          $func_params = array($this->conn_id, $search_basedn, $filter, $attributes);
 419  
 420          // search function to use
 421          $func_name = $this->_scope2function($this->options['userscope']);
 422  
 423          $this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__);
 424  
 425          // search
 426          if (($result_id = @call_user_func_array($func_name, $func_params)) == false) {
 427              $this->_debug('User not found', __LINE__);
 428          } elseif (@ldap_count_entries($this->conn_id, $result_id) == 1) { // did we get just one entry?
 429  
 430              $this->_debug('User was found', __LINE__);
 431  
 432              // then get the user dn
 433              $entry_id = @ldap_first_entry($this->conn_id, $result_id);
 434              $user_dn  = @ldap_get_dn($this->conn_id, $entry_id);
 435  
 436              // fetch attributes
 437              if ($attributes = @ldap_get_attributes($this->conn_id, $entry_id)) {
 438  
 439                  if (is_array($attributes) && isset($attributes['count']) &&
 440                       $attributes['count'] > 0) {
 441  
 442                      // ldap_get_attributes() returns a specific multi dimensional array
 443                      // format containing all the attributes and where each array starts
 444                      // with a 'count' element providing the number of attributes in the
 445                      // entry, or the number of values for attribute. For compatibility
 446                      // reasons, it remains the default format returned by LDAP container
 447                      // setAuthData().
 448                      // The code below optionally returns attributes in another format,
 449                      // more compliant with other Auth containers, where each attribute
 450                      // element are directly set in the 'authData' list. This option is
 451                      // enabled by setting 'attrformat' to
 452                      // 'AUTH' in the 'options' array.
 453                      // eg. $this->options['attrformat'] = 'AUTH'
 454  
 455                      if ( strtoupper($this->options['attrformat']) == 'AUTH' ) {
 456                          
 457                          $this->_debug('Saving attributes to Auth data in AUTH format', __LINE__);
 458                          unset ($attributes['count']);
 459  
 460                          foreach ($attributes as $attributeName => $attributeValue ) {
 461                              if (is_int($attributeName)) continue;
 462                              if (is_array($attributeValue) && isset($attributeValue['count'])) {
 463                                  unset ($attributeValue['count']);
 464                              }
 465                              if (count($attributeValue)<=1) $attributeValue = $attributeValue[0];
 466                              $this->_auth_obj->setAuthData($attributeName, $attributeValue);
 467                          }
 468                      }
 469                      else
 470                      {
 471                          $this->_debug('Saving attributes to Auth data in LDAP format', __LINE__);
 472                          $this->_auth_obj->setAuthData('attributes', $attributes);
 473                      }
 474                  }
 475              }
 476              @ldap_free_result($result_id);
 477  
 478              // need to catch an empty password as openldap seems to return TRUE
 479              // if anonymous binding is allowed
 480              if ($password != "") {
 481                  $this->_debug("Bind as $user_dn", __LINE__);
 482  
 483                  // try binding as this user with the supplied password
 484                  if (@ldap_bind($this->conn_id, $user_dn, $password)) {
 485                      $this->_debug('Bind successful', __LINE__);
 486  
 487                      // check group if appropiate
 488                      if (strlen($this->options['group'])) {
 489                          // decide whether memberattr value is a dn or the username
 490                          $this->_debug('Checking group membership', __LINE__);
 491                          return $this->checkGroup(($this->options['memberisdn']) ? $user_dn : $username);
 492                      } else {
 493                          $this->_debug('Authenticated', __LINE__);
 494                          $this->_disconnect();
 495                          return true; // user authenticated
 496                      } // checkGroup
 497                  } // bind
 498              } // non-empty password
 499          } // one entry
 500          // default
 501          $this->_debug('NOT authenticated!', __LINE__);
 502          $this->_disconnect();
 503          return false;
 504      }
 505  
 506      /**
 507       * Validate group membership
 508       *
 509       * Searches the LDAP server for group membership of the
 510       * authenticated user
 511       *
 512       * @param  string Distinguished Name of the authenticated User
 513       * @return boolean
 514       */
 515      function checkGroup($user)
 516      {
 517          // make filter
 518          $filter = sprintf('(&(%s=%s)(%s=%s)%s)',
 519                            $this->options['groupattr'],
 520                            $this->options['group'],
 521                            $this->options['memberattr'],
 522                            $user,
 523                            $this->options['groupfilter']);
 524  
 525          // make search base dn
 526          $search_basedn = $this->options['groupdn'];
 527          if ($search_basedn != '' && substr($search_basedn, -1) != ',') {
 528              $search_basedn .= ',';
 529          }
 530          $search_basedn .= $this->options['basedn'];
 531  
 532          $func_params = array($this->conn_id, $search_basedn, $filter,
 533                               array($this->options['memberattr']));
 534          $func_name = $this->_scope2function($this->options['groupscope']);
 535  
 536          $this->_debug("Searching with $func_name and filter $filter in $search_basedn", __LINE__);
 537  
 538          // search
 539          if (($result_id = @call_user_func_array($func_name, $func_params)) != false) {
 540              if (@ldap_count_entries($this->conn_id, $result_id) == 1) {
 541                  @ldap_free_result($result_id);
 542                  $this->_debug('User is member of group', __LINE__);
 543                  $this->_disconnect();
 544                  return true;
 545              }
 546          }
 547          // default
 548          $this->_debug('User is NOT member of group', __LINE__);
 549          $this->_disconnect();
 550          return false;
 551      }
 552  
 553      /**
 554       * Outputs debugging messages
 555       *
 556       * @access private
 557       * @param string Debugging Message
 558       * @param integer Line number
 559       */
 560      function _debug($msg = '', $line = 0)
 561      {
 562          if ($this->options['debug'] === true) {
 563              if ($msg == '' && $this->_isValidLink()) {
 564                  $msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id));
 565              }
 566              print("$line: $msg <br />");
 567          }
 568      }
 569  }
 570  
 571  ?>


Généré le : Thu Nov 29 14:38:42 2007 par Balluche grâce à PHPXref 0.7
  Clicky Web Analytics