|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00013 defined('MOODLE_INTERNAL') || die(); 00014 00015 require_once($CFG->libdir.'/authlib.php'); 00016 require_once($CFG->libdir.'/adodb/adodb.inc.php'); 00017 00021 class auth_plugin_db extends auth_plugin_base { 00022 00026 function auth_plugin_db() { 00027 $this->authtype = 'db'; 00028 $this->config = get_config('auth/db'); 00029 if (empty($this->config->extencoding)) { 00030 $this->config->extencoding = 'utf-8'; 00031 } 00032 } 00033 00043 function user_login($username, $password) { 00044 global $CFG, $DB; 00045 00046 $extusername = textlib::convert($username, 'utf-8', $this->config->extencoding); 00047 $extpassword = textlib::convert($password, 'utf-8', $this->config->extencoding); 00048 00049 $authdb = $this->db_init(); 00050 00051 if ($this->is_internal()) { 00052 // lookup username externally, but resolve 00053 // password locally -- to support backend that 00054 // don't track passwords 00055 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 00056 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' "); 00057 if (!$rs) { 00058 $authdb->Close(); 00059 debugging(get_string('auth_dbcantconnect','auth_db')); 00060 return false; 00061 } 00062 00063 if (!$rs->EOF) { 00064 $rs->Close(); 00065 $authdb->Close(); 00066 // user exists externally 00067 // check username/password internally 00068 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id))) { 00069 return validate_internal_user_password($user, $password); 00070 } 00071 } else { 00072 $rs->Close(); 00073 $authdb->Close(); 00074 // user does not exist externally 00075 return false; 00076 } 00077 00078 } else { 00079 // normal case: use external db for both usernames and passwords 00080 00081 if ($this->config->passtype === 'md5') { // Re-format password accordingly 00082 $extpassword = md5($extpassword); 00083 } else if ($this->config->passtype === 'sha1') { 00084 $extpassword = sha1($extpassword); 00085 } 00086 00087 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 00088 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' 00089 AND {$this->config->fieldpass} = '".$this->ext_addslashes($extpassword)."' "); 00090 if (!$rs) { 00091 $authdb->Close(); 00092 debugging(get_string('auth_dbcantconnect','auth_db')); 00093 return false; 00094 } 00095 00096 if (!$rs->EOF) { 00097 $rs->Close(); 00098 $authdb->Close(); 00099 return true; 00100 } else { 00101 $rs->Close(); 00102 $authdb->Close(); 00103 return false; 00104 } 00105 00106 } 00107 } 00108 00109 function db_init() { 00110 // Connect to the external database (forcing new connection) 00111 $authdb = ADONewConnection($this->config->type); 00112 if (!empty($this->config->debugauthdb)) { 00113 $authdb->debug = true; 00114 ob_start();//start output buffer to allow later use of the page headers 00115 } 00116 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true); 00117 $authdb->SetFetchMode(ADODB_FETCH_ASSOC); 00118 if (!empty($this->config->setupsql)) { 00119 $authdb->Execute($this->config->setupsql); 00120 } 00121 00122 return $authdb; 00123 } 00124 00130 function db_attributes() { 00131 $moodleattributes = array(); 00132 foreach ($this->userfields as $field) { 00133 if (!empty($this->config->{"field_map_$field"})) { 00134 $moodleattributes[$field] = $this->config->{"field_map_$field"}; 00135 } 00136 } 00137 $moodleattributes['username'] = $this->config->fielduser; 00138 return $moodleattributes; 00139 } 00140 00149 function get_userinfo($username) { 00150 global $CFG; 00151 00152 $extusername = textlib::convert($username, 'utf-8', $this->config->extencoding); 00153 00154 $authdb = $this->db_init(); 00155 00156 //Array to map local fieldnames we want, to external fieldnames 00157 $selectfields = $this->db_attributes(); 00158 00159 $result = array(); 00160 //If at least one field is mapped from external db, get that mapped data: 00161 if ($selectfields) { 00162 $select = ''; 00163 foreach ($selectfields as $localname=>$externalname) { 00164 $select .= ", $externalname AS $localname"; 00165 } 00166 $select = 'SELECT ' . substr($select,1); 00167 $sql = $select . 00168 " FROM {$this->config->table}" . 00169 " WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'"; 00170 if ($rs = $authdb->Execute($sql)) { 00171 if ( !$rs->EOF ) { 00172 $fields_obj = $rs->FetchObj(); 00173 $fields_obj = (object)array_change_key_case((array)$fields_obj , CASE_LOWER); 00174 foreach ($selectfields as $localname=>$externalname) { 00175 $result[$localname] = textlib::convert($fields_obj->{$localname}, $this->config->extencoding, 'utf-8'); 00176 } 00177 } 00178 $rs->Close(); 00179 } 00180 } 00181 $authdb->Close(); 00182 return $result; 00183 } 00184 00193 function user_update_password($user, $newpassword) { 00194 if ($this->is_internal()) { 00195 return update_internal_user_password($user, $newpassword); 00196 } else { 00197 // we should have never been called! 00198 return false; 00199 } 00200 } 00201 00218 function sync_users($do_updates=false, $verbose=false) { 00219 global $CFG, $DB; 00220 00221 // list external users 00222 $userlist = $this->get_userlist(); 00223 00224 // delete obsolete internal users 00225 if (!empty($this->config->removeuser)) { 00226 00227 // find obsolete users 00228 if (count($userlist)) { 00229 list($notin_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', false); 00230 $params['authtype'] = $this->authtype; 00231 $sql = "SELECT u.* 00232 FROM {user} u 00233 WHERE u.auth=:authtype AND u.deleted=0 AND u.username $notin_sql"; 00234 } else { 00235 $sql = "SELECT u.* 00236 FROM {user} u 00237 WHERE u.auth=:authtype AND u.deleted=0"; 00238 $params = array(); 00239 $params['authtype'] = $this->authtype; 00240 } 00241 $remove_users = $DB->get_records_sql($sql, $params); 00242 00243 if (!empty($remove_users)) { 00244 if ($verbose) { 00245 mtrace(print_string('auth_dbuserstoremove','auth_db', count($remove_users))); 00246 } 00247 00248 foreach ($remove_users as $user) { 00249 if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) { 00250 delete_user($user); 00251 if ($verbose) { 00252 mtrace("\t".get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))); 00253 } 00254 } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { 00255 $updateuser = new stdClass(); 00256 $updateuser->id = $user->id; 00257 $updateuser->auth = 'nologin'; 00258 $updateuser->timemodified = time(); 00259 $DB->update_record('user', $updateuser); 00260 if ($verbose) { 00261 mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))); 00262 } 00263 } 00264 } 00265 } 00266 unset($remove_users); // free mem! 00267 } 00268 00269 if (!count($userlist)) { 00270 // exit right here 00271 // nothing else to do 00272 return 0; 00273 } 00274 00278 if ($do_updates) { 00279 // narrow down what fields we need to update 00280 $all_keys = array_keys(get_object_vars($this->config)); 00281 $updatekeys = array(); 00282 foreach ($all_keys as $key) { 00283 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) { 00284 if ($this->config->{$key} === 'onlogin') { 00285 array_push($updatekeys, $match[1]); // the actual key name 00286 } 00287 } 00288 } 00289 // print_r($all_keys); print_r($updatekeys); 00290 unset($all_keys); unset($key); 00291 00292 // only go ahead if we actually 00293 // have fields to update locally 00294 if (!empty($updatekeys)) { 00295 list($in_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', true); 00296 $params['authtype'] = $this->authtype; 00297 $sql = "SELECT u.id, u.username 00298 FROM {user} u 00299 WHERE u.auth=:authtype AND u.deleted=0 AND u.username {$in_sql}"; 00300 if ($update_users = $DB->get_records_sql($sql, $params)) { 00301 if ($verbose) { 00302 mtrace("User entries to update: ".count($update_users)); 00303 } 00304 00305 foreach ($update_users as $user) { 00306 if ($this->update_user_record($user->username, $updatekeys)) { 00307 if ($verbose) { 00308 mtrace("\t".get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))); 00309 } 00310 } else { 00311 if ($verbose) { 00312 mtrace("\t".get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped')); 00313 } 00314 } 00315 } 00316 unset($update_users); // free memory 00317 } 00318 } 00319 } 00320 00321 00325 // NOTE: this is very memory intensive 00326 // and generally inefficient 00327 $sql = 'SELECT u.id, u.username 00328 FROM {user} u 00329 WHERE u.auth=\'' . $this->authtype . '\' AND u.deleted=\'0\''; 00330 00331 $users = $DB->get_records_sql($sql); 00332 00333 // simplify down to usernames 00334 $usernames = array(); 00335 if (!empty($users)) { 00336 foreach ($users as $user) { 00337 array_push($usernames, $user->username); 00338 } 00339 unset($users); 00340 } 00341 00342 $add_users = array_diff($userlist, $usernames); 00343 unset($usernames); 00344 00345 if (!empty($add_users)) { 00346 if ($verbose) { 00347 mtrace(get_string('auth_dbuserstoadd','auth_db',count($add_users))); 00348 } 00349 $transaction = $DB->start_delegated_transaction(); 00350 foreach($add_users as $user) { 00351 $username = $user; 00352 $user = $this->get_userinfo_asobj($user); 00353 00354 // prep a few params 00355 $user->username = $username; 00356 $user->confirmed = 1; 00357 $user->auth = $this->authtype; 00358 $user->mnethostid = $CFG->mnet_localhost_id; 00359 if (empty($user->lang)) { 00360 $user->lang = $CFG->lang; 00361 } 00362 00363 // maybe the user has been deleted before 00364 if ($old_user = $DB->get_record('user', array('username'=>$user->username, 'deleted'=>1, 'mnethostid'=>$user->mnethostid, 'auth'=>$user->auth))) { 00365 // note: this undeleting is deprecated and will be eliminated soon 00366 $DB->set_field('user', 'deleted', 0, array('id'=>$old_user->id)); 00367 $DB->set_field('user', 'timemodified', time(), array('id'=>$old_user->id)); 00368 if ($verbose) { 00369 mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$old_user->username, 'id'=>$old_user->id))); 00370 } 00371 00372 } else { 00373 $user->timecreated = time(); 00374 $user->timemodified = $user->timecreated; 00375 $id = $DB->insert_record ('user', $user); // it is truly a new user 00376 if ($verbose) { 00377 mtrace("\t".get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id))); 00378 } 00379 // if relevant, tag for password generation 00380 if ($this->is_internal()) { 00381 set_user_preference('auth_forcepasswordchange', 1, $id); 00382 set_user_preference('create_password', 1, $id); 00383 } 00384 } 00385 } 00386 $transaction->allow_commit(); 00387 unset($add_users); // free mem 00388 } 00389 return 0; 00390 } 00391 00392 function user_exists($username) { 00393 00395 $result = false; 00396 00397 $extusername = textlib::convert($username, 'utf-8', $this->config->extencoding); 00398 00399 $authdb = $this->db_init(); 00400 00401 $rs = $authdb->Execute("SELECT * FROM {$this->config->table} 00402 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' "); 00403 00404 if (!$rs) { 00405 print_error('auth_dbcantconnect','auth_db'); 00406 } else if (!$rs->EOF) { 00407 // user exists externally 00408 $result = true; 00409 } 00410 00411 $authdb->Close(); 00412 return $result; 00413 } 00414 00415 00416 function get_userlist() { 00417 00419 $result = array(); 00420 00421 $authdb = $this->db_init(); 00422 00423 // fetch userlist 00424 $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username 00425 FROM {$this->config->table} "); 00426 00427 if (!$rs) { 00428 print_error('auth_dbcantconnect','auth_db'); 00429 } else if (!$rs->EOF) { 00430 while ($rec = $rs->FetchRow()) { 00431 $rec = (object)array_change_key_case((array)$rec , CASE_LOWER); 00432 array_push($result, $rec->username); 00433 } 00434 } 00435 00436 $authdb->Close(); 00437 return $result; 00438 } 00439 00446 function get_userinfo_asobj($username) { 00447 $user_array = truncate_userinfo($this->get_userinfo($username)); 00448 $user = new stdClass(); 00449 foreach($user_array as $key=>$value) { 00450 $user->{$key} = $value; 00451 } 00452 return $user; 00453 } 00454 00467 function update_user_record($username, $updatekeys=false) { 00468 global $CFG, $DB; 00469 00470 //just in case check text case 00471 $username = trim(textlib::strtolower($username)); 00472 00473 // get the current user record 00474 $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id)); 00475 if (empty($user)) { // trouble 00476 error_log("Cannot update non-existent user: $username"); 00477 print_error('auth_dbusernotexist','auth_db',$username); 00478 die; 00479 } 00480 00481 // Ensure userid is not overwritten 00482 $userid = $user->id; 00483 $updated = false; 00484 00485 if ($newinfo = $this->get_userinfo($username)) { 00486 $newinfo = truncate_userinfo($newinfo); 00487 00488 if (empty($updatekeys)) { // all keys? this does not support removing values 00489 $updatekeys = array_keys($newinfo); 00490 } 00491 00492 foreach ($updatekeys as $key) { 00493 if (isset($newinfo[$key])) { 00494 $value = $newinfo[$key]; 00495 } else { 00496 $value = ''; 00497 } 00498 00499 if (!empty($this->config->{'field_updatelocal_' . $key})) { 00500 if (isset($user->{$key}) and $user->{$key} != $value) { // only update if it's changed 00501 $DB->set_field('user', $key, $value, array('id'=>$userid)); 00502 $updated = true; 00503 } 00504 } 00505 } 00506 } 00507 if ($updated) { 00508 $DB->set_field('user', 'timemodified', time(), array('id'=>$userid)); 00509 } 00510 return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0)); 00511 } 00512 00523 function user_update($olduser, $newuser) { 00524 if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) { 00525 error_log("ERROR:User renaming not allowed in ext db"); 00526 return false; 00527 } 00528 00529 if (isset($olduser->auth) and $olduser->auth != $this->authtype) { 00530 return true; // just change auth and skip update 00531 } 00532 00533 $curruser = $this->get_userinfo($olduser->username); 00534 if (empty($curruser)) { 00535 error_log("ERROR:User $olduser->username found in ext db"); 00536 return false; 00537 } 00538 00539 $extusername = textlib::convert($olduser->username, 'utf-8', $this->config->extencoding); 00540 00541 $authdb = $this->db_init(); 00542 00543 $update = array(); 00544 foreach($curruser as $key=>$value) { 00545 if ($key == 'username') { 00546 continue; // skip this 00547 } 00548 if (empty($this->config->{"field_updateremote_$key"})) { 00549 continue; // remote update not requested 00550 } 00551 if (!isset($newuser->$key)) { 00552 continue; 00553 } 00554 $nuvalue = $newuser->$key; 00555 if ($nuvalue != $value) { 00556 $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(textlib::convert($nuvalue, 'utf-8', $this->config->extencoding))."'"; 00557 } 00558 } 00559 if (!empty($update)) { 00560 $authdb->Execute("UPDATE {$this->config->table} 00561 SET ".implode(',', $update)." 00562 WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'"); 00563 } 00564 $authdb->Close(); 00565 return true; 00566 } 00567 00576 function validate_form($form, &$err) { 00577 if ($form->passtype === 'internal') { 00578 $this->config->changepasswordurl = ''; 00579 set_config('changepasswordurl', '', 'auth/db'); 00580 } 00581 } 00582 00583 function prevent_local_passwords() { 00584 return !$this->is_internal(); 00585 } 00586 00594 function is_internal() { 00595 if (!isset($this->config->passtype)) { 00596 return true; 00597 } 00598 return ($this->config->passtype === 'internal'); 00599 } 00600 00608 function is_synchronised_with_external() { 00609 return true; 00610 } 00611 00618 function can_change_password() { 00619 return ($this->is_internal() or !empty($this->config->changepasswordurl)); 00620 } 00621 00628 function change_password_url() { 00629 if ($this->is_internal()) { 00630 // standard form 00631 return null; 00632 } else { 00633 // use admin defined custom url 00634 return new moodle_url($this->config->changepasswordurl); 00635 } 00636 } 00637 00643 function can_reset_password() { 00644 return $this->is_internal(); 00645 } 00646 00658 function config_form($config, $err, $user_fields) { 00659 include 'config.html'; 00660 } 00661 00667 function process_config($config) { 00668 // set to defaults if undefined 00669 if (!isset($config->host)) { 00670 $config->host = 'localhost'; 00671 } 00672 if (!isset($config->type)) { 00673 $config->type = 'mysql'; 00674 } 00675 if (!isset($config->sybasequoting)) { 00676 $config->sybasequoting = 0; 00677 } 00678 if (!isset($config->name)) { 00679 $config->name = ''; 00680 } 00681 if (!isset($config->user)) { 00682 $config->user = ''; 00683 } 00684 if (!isset($config->pass)) { 00685 $config->pass = ''; 00686 } 00687 if (!isset($config->table)) { 00688 $config->table = ''; 00689 } 00690 if (!isset($config->fielduser)) { 00691 $config->fielduser = ''; 00692 } 00693 if (!isset($config->fieldpass)) { 00694 $config->fieldpass = ''; 00695 } 00696 if (!isset($config->passtype)) { 00697 $config->passtype = 'plaintext'; 00698 } 00699 if (!isset($config->extencoding)) { 00700 $config->extencoding = 'utf-8'; 00701 } 00702 if (!isset($config->setupsql)) { 00703 $config->setupsql = ''; 00704 } 00705 if (!isset($config->debugauthdb)) { 00706 $config->debugauthdb = 0; 00707 } 00708 if (!isset($config->removeuser)) { 00709 $config->removeuser = AUTH_REMOVEUSER_KEEP; 00710 } 00711 if (!isset($config->changepasswordurl)) { 00712 $config->changepasswordurl = ''; 00713 } 00714 00715 // save settings 00716 set_config('host', $config->host, 'auth/db'); 00717 set_config('type', $config->type, 'auth/db'); 00718 set_config('sybasequoting', $config->sybasequoting, 'auth/db'); 00719 set_config('name', $config->name, 'auth/db'); 00720 set_config('user', $config->user, 'auth/db'); 00721 set_config('pass', $config->pass, 'auth/db'); 00722 set_config('table', $config->table, 'auth/db'); 00723 set_config('fielduser', $config->fielduser, 'auth/db'); 00724 set_config('fieldpass', $config->fieldpass, 'auth/db'); 00725 set_config('passtype', $config->passtype, 'auth/db'); 00726 set_config('extencoding', trim($config->extencoding), 'auth/db'); 00727 set_config('setupsql', trim($config->setupsql),'auth/db'); 00728 set_config('debugauthdb', $config->debugauthdb, 'auth/db'); 00729 set_config('removeuser', $config->removeuser, 'auth/db'); 00730 set_config('changepasswordurl', trim($config->changepasswordurl), 'auth/db'); 00731 00732 return true; 00733 } 00734 00735 function ext_addslashes($text) { 00736 // using custom made function for now 00737 if (empty($this->config->sybasequoting)) { 00738 $text = str_replace('\\', '\\\\', $text); 00739 $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text); 00740 } else { 00741 $text = str_replace("'", "''", $text); 00742 } 00743 return $text; 00744 } 00745 } 00746 00747