| [ Index ] |
|
Code source de Claroline 188 |
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 ?>
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Thu Nov 29 14:38:42 2007 | par Balluche grâce à PHPXref 0.7 |
|