| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * @author Martin Dougiamas 5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 6 * @package moodle multiauth 7 * 8 * Authentication Plugin: External Database Authentication 9 * 10 * Checks against an external database. 11 * 12 * 2006-08-28 File created. 13 */ 14 15 if (!defined('MOODLE_INTERNAL')) { 16 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page 17 } 18 19 require_once($CFG->libdir.'/authlib.php'); 20 21 /** 22 * External database authentication plugin. 23 */ 24 class auth_plugin_db extends auth_plugin_base { 25 26 /** 27 * Constructor. 28 */ 29 function auth_plugin_db() { 30 $this->authtype = 'db'; 31 $this->config = get_config('auth/db'); 32 if (empty($this->config->extencoding)) { 33 $this->config->extencoding = 'utf-8'; 34 } 35 } 36 37 /** 38 * Returns true if the username and password work and false if they are 39 * wrong or don't exist. 40 * 41 * @param string $username The username (with system magic quotes) 42 * @param string $password The password (with system magic quotes) 43 * 44 * @return bool Authentication success or failure. 45 */ 46 function user_login($username, $password) { 47 48 global $CFG; 49 50 $textlib = textlib_get_instance(); 51 $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->extencoding); 52 $extpassword = $textlib->convert(stripslashes($password), 'utf-8', $this->config->extencoding); 53 54 $authdb = $this->db_init(); 55 56 if ($this->config->passtype === 'internal') { 57 // lookup username externally, but resolve 58 // password locally -- to support backend that 59 // don't track passwords 60 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 61 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' "); 62 if (!$rs) { 63 $authdb->Close(); 64 print_error('auth_dbcantconnect','auth'); 65 return false; 66 } 67 68 if ( !$rs->EOF ) { 69 $rs->Close(); 70 $authdb->Close(); 71 // user exists exterally 72 // check username/password internally 73 if ($user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id)) { 74 return validate_internal_user_password($user, $password); 75 } 76 } else { 77 $rs->Close(); 78 $authdb->Close(); 79 // user does not exist externally 80 return false; 81 } 82 83 } else { 84 // normal case: use external db for passwords 85 86 if ($this->config->passtype === 'md5') { // Re-format password accordingly 87 $extpassword = md5($extpassword); 88 } else if ($this->config->passtype === 'sha1') { 89 $extpassword = sha1($extpassword); 90 } 91 92 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 93 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' 94 AND {$this->config->fieldpass} = '".$this->ext_addslashes($extpassword)."' "); 95 if (!$rs) { 96 $authdb->Close(); 97 print_error('auth_dbcantconnect','auth'); 98 return false; 99 } 100 101 if (!$rs->EOF) { 102 $rs->Close(); 103 $authdb->Close(); 104 return true; 105 } else { 106 $rs->Close(); 107 $authdb->Close(); 108 return false; 109 } 110 111 } 112 } 113 114 function db_init() { 115 // Connect to the external database (forcing new connection) 116 $authdb = &ADONewConnection($this->config->type); 117 if (!empty($this->config->debugauthdb)) { 118 $authdb->debug = true; 119 ob_start();//start output buffer to allow later use of the page headers 120 } 121 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true); 122 $authdb->SetFetchMode(ADODB_FETCH_ASSOC); 123 if (!empty($this->config->setupsql)) { 124 $authdb->Execute($this->config->setupsql); 125 } 126 127 return $authdb; 128 } 129 /** 130 * retuns user attribute mappings between moodle and ldap 131 * 132 * @return array 133 */ 134 function db_attributes() { 135 $moodleattributes = array(); 136 foreach ($this->userfields as $field) { 137 if (!empty($this->config->{"field_map_$field"})) { 138 $moodleattributes[$field] = $this->config->{"field_map_$field"}; 139 } 140 } 141 $moodleattributes['username'] = $this->config->fielduser; 142 return $moodleattributes; 143 } 144 145 /** 146 * Reads any other information for a user from external database, 147 * then returns it in an array 148 * 149 * @param string $username (with system magic quotes) 150 * 151 * @return array without magic quotes 152 */ 153 function get_userinfo($username) { 154 155 global $CFG; 156 157 $textlib = textlib_get_instance(); 158 $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->extencoding); 159 160 $authdb = $this->db_init(); 161 162 //Array to map local fieldnames we want, to external fieldnames 163 $selectfields = $this->db_attributes(); 164 165 $result = array(); 166 //If at least one field is mapped from external db, get that mapped data: 167 if ($selectfields) { 168 $select = ''; 169 foreach ($selectfields as $localname=>$externalname) { 170 $select .= ", $externalname AS $localname"; 171 } 172 $select = 'SELECT ' . substr($select,1); 173 $sql = $select . 174 " FROM {$this->config->table}" . 175 " WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'"; 176 if ($rs = $authdb->Execute($sql)) { 177 if ( !$rs->EOF ) { 178 $fields_obj = rs_fetch_record($rs); 179 $fields_obj = (object)array_change_key_case((array)$fields_obj , CASE_LOWER); 180 foreach ($selectfields as $localname=>$externalname) { 181 $result[$localname] = $textlib->convert($fields_obj->{$localname}, $this->config->extencoding, 'utf-8'); 182 } 183 } 184 rs_close($rs); 185 } 186 } 187 $authdb->Close(); 188 return $result; 189 190 } 191 192 193 /** 194 * Change a user's password 195 * 196 * @param object $user User table object (with system magic quotes) 197 * @param string $newpassword Plaintext password (with system magic quotes) 198 * 199 * @return bool True on success 200 */ 201 function user_update_password($user, $newpassword) { 202 203 global $CFG; 204 if ($this->config->passtype === 'internal') { 205 return update_internal_user_password($user, $newpassword); 206 } else { 207 // we should have never been called! 208 return false; 209 } 210 } 211 212 /** 213 * syncronizes user fron external db to moodle user table 214 * 215 * Sync shouid be done by using idnumber attribute, not username. 216 * You need to pass firstsync parameter to function to fill in 217 * idnumbers if they dont exists in moodle user table. 218 * 219 * Syncing users removes (disables) users that dont exists anymore in external db. 220 * Creates new users and updates coursecreator status of users. 221 * 222 * @param bool $do_updates Optional: set to true to force an update of existing accounts 223 * 224 * This implementation is simpler but less scalable than the one found in the LDAP module. 225 * 226 */ 227 function sync_users($do_updates=false) { 228 229 global $CFG; 230 $pcfg = get_config('auth/db'); 231 232 /// list external users 233 $userlist = $this->get_userlist(); 234 $quoteduserlist = implode("', '", addslashes_recursive($userlist)); 235 $quoteduserlist = "'$quoteduserlist'"; 236 237 /// delete obsolete internal users 238 if (!empty($this->config->removeuser)) { 239 240 // find obsolete users 241 if (count($userlist)) { 242 $sql = "SELECT u.id, u.username, u.email, u.auth 243 FROM {$CFG->prefix}user u 244 WHERE u.auth='db' AND u.deleted=0 AND u.username NOT IN ($quoteduserlist)"; 245 } else { 246 $sql = "SELECT u.id, u.username, u.email, u.auth 247 FROM {$CFG->prefix}user u 248 WHERE u.auth='db' AND u.deleted=0"; 249 } 250 $remove_users = get_records_sql($sql); 251 252 if (!empty($remove_users)) { 253 print_string('auth_dbuserstoremove','auth', count($remove_users)); echo "\n"; 254 255 foreach ($remove_users as $user) { 256 if ($this->config->removeuser == 2) { 257 if (delete_user($user)) { 258 echo "\t"; print_string('auth_dbdeleteuser', 'auth', array($user->username, $user->id)); echo "\n"; 259 } else { 260 echo "\t"; print_string('auth_dbdeleteusererror', 'auth', $user->username); echo "\n"; 261 } 262 } else if ($this->config->removeuser == 1) { 263 $updateuser = new object(); 264 $updateuser->id = $user->id; 265 $updateuser->auth = 'nologin'; 266 if (update_record('user', $updateuser)) { 267 echo "\t"; print_string('auth_dbsuspenduser', 'auth', array($user->username, $user->id)); echo "\n"; 268 } else { 269 echo "\t"; print_string('auth_dbsuspendusererror', 'auth', $user->username); echo "\n"; 270 } 271 } 272 } 273 } 274 unset($remove_users); // free mem! 275 } 276 277 if (!count($userlist)) { 278 // exit right here 279 // nothing else to do 280 return true; 281 } 282 283 /// 284 /// update existing accounts 285 /// 286 if ($do_updates) { 287 // narrow down what fields we need to update 288 $all_keys = array_keys(get_object_vars($this->config)); 289 $updatekeys = array(); 290 foreach ($all_keys as $key) { 291 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) { 292 if ($this->config->{$key} === 'onlogin') { 293 array_push($updatekeys, $match[1]); // the actual key name 294 } 295 } 296 } 297 // print_r($all_keys); print_r($updatekeys); 298 unset($all_keys); unset($key); 299 300 // only go ahead if we actually 301 // have fields to update locally 302 if (!empty($updatekeys)) { 303 $sql = 'SELECT u.id, u.username 304 FROM ' . $CFG->prefix .'user u 305 WHERE u.auth=\'db\' AND u.deleted=\'0\' AND u.username IN (' . $quoteduserlist . ')'; 306 if ($update_users = get_records_sql($sql)) { 307 print "User entries to update: ". count($update_users). "\n"; 308 309 foreach ($update_users as $user) { 310 echo "\t"; print_string('auth_dbupdatinguser', 'auth', array($user->username, $user->id)); 311 if (!$this->update_user_record(addslashes($user->username), $updatekeys)) { 312 echo " - ".get_string('skipped'); 313 } 314 echo "\n"; 315 } 316 unset($update_users); // free memory 317 } 318 } 319 } 320 321 322 /// 323 /// create missing accounts 324 /// 325 // NOTE: this is very memory intensive 326 // and generally inefficient 327 $sql = 'SELECT u.id, u.username 328 FROM ' . $CFG->prefix .'user u 329 WHERE u.auth=\'db\' AND u.deleted=\'0\''; 330 331 $users = get_records_sql($sql); 332 333 // simplify down to usernames 334 $usernames = array(); 335 if (!empty($users)) { 336 foreach ($users as $user) { 337 array_push($usernames, $user->username); 338 } 339 unset($users); 340 } 341 342 $add_users = array_diff($userlist, $usernames); 343 unset($usernames); 344 345 if (!empty($add_users)) { 346 print_string('auth_dbuserstoadd','auth',count($add_users)); echo "\n"; 347 begin_sql(); 348 foreach($add_users as $user) { 349 $username = $user; 350 $user = $this->get_userinfo_asobj($user); 351 352 // prep a few params 353 $user->username = $username; 354 $user->modified = time(); 355 $user->confirmed = 1; 356 $user->auth = 'db'; 357 $user->mnethostid = $CFG->mnet_localhost_id; 358 if (empty($user->lang)) { 359 $user->lang = $CFG->lang; 360 } 361 362 $user = addslashes_object($user); 363 // maybe the user has been deleted before 364 if ($old_user = get_record('user', 'username', $user->username, 'deleted', 1, 'mnethostid', $user->mnethostid)) { 365 $user->id = $old_user->id; 366 set_field('user', 'deleted', 0, 'username', $user->username); 367 echo "\t"; print_string('auth_dbreviveuser', 'auth', array(stripslashes($user->username), $user->id)); echo "\n"; 368 } elseif ($id = insert_record ('user',$user)) { // it is truly a new user 369 echo "\t"; print_string('auth_dbinsertuser','auth',array(stripslashes($user->username), $id)); echo "\n"; 370 // if relevant, tag for password generation 371 if ($this->config->passtype === 'internal') { 372 set_user_preference('auth_forcepasswordchange', 1, $id); 373 set_user_preference('create_password', 1, $id); 374 } 375 } else { 376 echo "\t"; print_string('auth_dbinsertusererror', 'auth', $user->username); echo "\n"; 377 } 378 } 379 commit_sql(); 380 unset($add_users); // free mem 381 } 382 return true; 383 } 384 385 function user_exists($username) { 386 387 /// Init result value 388 $result = false; 389 390 $textlib = textlib_get_instance(); 391 $extusername = $textlib->convert(stripslashes($username), 'utf-8', $this->config->extencoding); 392 393 $authdb = $this->db_init(); 394 395 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 396 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' "); 397 398 if (!$rs) { 399 print_error('auth_dbcantconnect','auth'); 400 } else if ( !$rs->EOF ) { 401 // user exists exterally 402 $result = true; 403 } 404 405 $authdb->Close(); 406 return $result; 407 } 408 409 410 function get_userlist() { 411 412 /// Init result value 413 $result = array(); 414 415 $authdb = $this->db_init(); 416 417 // fetch userlist 418 $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username 419 FROM {$this->config->table} "); 420 421 if (!$rs) { 422 print_error('auth_dbcantconnect','auth'); 423 } else if ( !$rs->EOF ) { 424 while ($rec = rs_fetch_next_record($rs)) { 425 $rec = (object)array_change_key_case((array)$rec , CASE_LOWER); 426 array_push($result, $rec->username); 427 } 428 } 429 430 $authdb->Close(); 431 return $result; 432 } 433 434 /** 435 * reads userinformation from DB and return it in an object 436 * 437 * @param string $username username (with system magic quotes) 438 * @return array 439 */ 440 function get_userinfo_asobj($username) { 441 $user_array = truncate_userinfo($this->get_userinfo($username)); 442 $user = new object(); 443 foreach($user_array as $key=>$value) { 444 $user->{$key} = $value; 445 } 446 return $user; 447 } 448 449 /** 450 * will update a local user record from an external source. 451 * is a lighter version of the one in moodlelib -- won't do 452 * expensive ops such as enrolment 453 * 454 * If you don't pass $updatekeys, there is a performance hit and 455 * values removed from DB won't be removed from moodle. 456 * 457 * @param string $username username (with system magic quotes) 458 */ 459 function update_user_record($username, $updatekeys=false) { 460 global $CFG; 461 462 //just in case check text case 463 $username = trim(moodle_strtolower($username)); 464 465 // get the current user record 466 $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id); 467 if (empty($user)) { // trouble 468 error_log("Cannot update non-existent user: $username"); 469 print_error('auth_dbusernotexist','auth',$username); 470 die; 471 } 472 473 // Ensure userid is not overwritten 474 $userid = $user->id; 475 476 if ($newinfo = $this->get_userinfo($username)) { 477 $newinfo = truncate_userinfo($newinfo); 478 479 if (empty($updatekeys)) { // all keys? this does not support removing values 480 $updatekeys = array_keys($newinfo); 481 } 482 483 foreach ($updatekeys as $key) { 484 if (isset($newinfo[$key])) { 485 $value = $newinfo[$key]; 486 } else { 487 $value = ''; 488 } 489 490 if (!empty($this->config->{'field_updatelocal_' . $key})) { 491 if ($user->{$key} != $value) { // only update if it's changed 492 set_field('user', $key, addslashes($value), 'id', $userid); 493 } 494 } 495 } 496 } 497 return get_record_select('user', "id = $userid AND deleted = 0"); 498 } 499 500 /** 501 * Called when the user record is updated. 502 * Modifies user in external database. It takes olduser (before changes) and newuser (after changes) 503 * conpares information saved modified information to external db. 504 * 505 * @param mixed $olduser Userobject before modifications (without system magic quotes) 506 * @param mixed $newuser Userobject new modified userobject (without system magic quotes) 507 * @return boolean result 508 * 509 */ 510 function user_update($olduser, $newuser) { 511 if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) { 512 error_log("ERROR:User renaming not allowed in ext db"); 513 return false; 514 } 515 516 if (isset($olduser->auth) and $olduser->auth != 'db') { 517 return true; // just change auth and skip update 518 } 519 520 $curruser = $this->get_userinfo($olduser->username); 521 if (empty($curruser)) { 522 error_log("ERROR:User $olduser->username found in ext db"); 523 return false; 524 } 525 526 $textlib = textlib_get_instance(); 527 $extusername = $textlib->convert($olduser->username, 'utf-8', $this->config->extencoding); 528 529 $authdb = $this->db_init(); 530 531 $update = array(); 532 foreach($curruser as $key=>$value) { 533 if ($key == 'username') { 534 continue; // skip this 535 } 536 if (empty($this->config->{"field_updateremote_$key"})) { 537 continue; // remote update not requested 538 } 539 if (!isset($newuser->$key)) { 540 continue; 541 } 542 $nuvalue = stripslashes($newuser->$key); 543 if ($nuvalue != $value) { 544 $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes($textlib->convert($nuvalue, 'utf-8', $this->config->extencoding))."'"; 545 } 546 } 547 if (!empty($update)) { 548 $authdb->Execute("UPDATE {$this->config->table} 549 SET ".implode(',', $update)." 550 WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'"); 551 } 552 $authdb->Close(); 553 return true; 554 } 555 556 /** 557 * A chance to validate form data, and last chance to 558 * do stuff before it is inserted in config_plugin 559 */ 560 function validate_form(&$form, &$err) { 561 if ($form->passtype === 'internal') { 562 $this->config->changepasswordurl = ''; 563 set_config('changepasswordurl', '', 'auth/db'); 564 } 565 } 566 567 /** 568 * Returns true if this authentication plugin is 'internal'. 569 * 570 * @return bool 571 */ 572 function is_internal() { 573 return ($this->config->passtype == 'internal'); 574 } 575 576 /** 577 * Returns true if this authentication plugin can change the user's 578 * password. 579 * 580 * @return bool 581 */ 582 function can_change_password() { 583 return ($this->config->passtype == 'internal' or !empty($this->config->changepasswordurl)); 584 } 585 586 /** 587 * Returns the URL for changing the user's pw, or empty if the default can 588 * be used. 589 * 590 * @return string 591 */ 592 function change_password_url() { 593 if ($this->config->passtype == 'internal') { 594 // standard form 595 return ''; 596 } else { 597 // use custom url 598 return $this->config->changepasswordurl; 599 } 600 } 601 602 /** 603 * Returns true if plugin allows resetting of internal password. 604 * 605 * @return bool 606 */ 607 function can_reset_password() { 608 return ($this->config->passtype == 'internal'); 609 } 610 611 /** 612 * Prints a form for configuring this authentication plugin. 613 * 614 * This function is called from admin/auth.php, and outputs a full page with 615 * a form for configuring this plugin. 616 * 617 * @param array $page An object containing all the data for this page. 618 */ 619 function config_form($config, $err, $user_fields) { 620 include 'config.html'; 621 } 622 623 /** 624 * Processes and stores configuration data for this authentication plugin. 625 */ 626 function process_config($config) { 627 // set to defaults if undefined 628 if (!isset($config->host)) { 629 $config->host = 'localhost'; 630 } 631 if (!isset($config->type)) { 632 $config->type = 'mysql'; 633 } 634 if (!isset($config->sybasequoting)) { 635 $config->sybasequoting = 0; 636 } 637 if (!isset($config->name)) { 638 $config->name = ''; 639 } 640 if (!isset($config->user)) { 641 $config->user = ''; 642 } 643 if (!isset($config->pass)) { 644 $config->pass = ''; 645 } 646 if (!isset($config->table)) { 647 $config->table = ''; 648 } 649 if (!isset($config->fielduser)) { 650 $config->fielduser = ''; 651 } 652 if (!isset($config->fieldpass)) { 653 $config->fieldpass = ''; 654 } 655 if (!isset($config->passtype)) { 656 $config->passtype = 'plaintext'; 657 } 658 if (!isset($config->extencoding)) { 659 $config->extencoding = 'utf-8'; 660 } 661 if (!isset($config->setupsql)) { 662 $config->setupsql = ''; 663 } 664 if (!isset($config->debugauthdb)) { 665 $config->debugauthdb = 0; 666 } 667 if (!isset($config->removeuser)) { 668 $config->removeuser = 0; 669 } 670 if (!isset($config->changepasswordurl)) { 671 $config->changepasswordurl = ''; 672 } 673 674 $config = stripslashes_recursive($config); 675 // save settings 676 set_config('host', $config->host, 'auth/db'); 677 set_config('type', $config->type, 'auth/db'); 678 set_config('sybasequoting', $config->sybasequoting, 'auth/db'); 679 set_config('name', $config->name, 'auth/db'); 680 set_config('user', $config->user, 'auth/db'); 681 set_config('pass', $config->pass, 'auth/db'); 682 set_config('table', $config->table, 'auth/db'); 683 set_config('fielduser', $config->fielduser, 'auth/db'); 684 set_config('fieldpass', $config->fieldpass, 'auth/db'); 685 set_config('passtype', $config->passtype, 'auth/db'); 686 set_config('extencoding', trim($config->extencoding), 'auth/db'); 687 set_config('setupsql', trim($config->setupsql),'auth/db'); 688 set_config('debugauthdb', $config->debugauthdb, 'auth/db'); 689 set_config('removeuser', $config->removeuser, 'auth/db'); 690 set_config('changepasswordurl', trim($config->changepasswordurl), 'auth/db'); 691 692 return true; 693 } 694 695 function ext_addslashes($text) { 696 // using custom made function for now 697 if (empty($this->config->sybasequoting)) { 698 $text = str_replace('\\', '\\\\', $text); 699 $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text); 700 } else { 701 $text = str_replace("'", "''", $text); 702 } 703 return $text; 704 } 705 } 706 707 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |