Merge branch 'MDL-42069' of https://github.com/appalachianstate/moodle
[moodle.git] / auth / db / auth.php
CommitLineData
b9ddb2d5 1<?php
e7aeaa65
PS
2// This file is part of Moodle - http://moodle.org/
3//
4// Moodle is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Moodle is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
16
b9ddb2d5 17/**
b9ddb2d5 18 * Authentication Plugin: External Database Authentication
19 *
20 * Checks against an external database.
21 *
e7aeaa65 22 * @package auth_db
7415aed1
PS
23 * @author Martin Dougiamas
24 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
b9ddb2d5 25 */
26
7415aed1 27defined('MOODLE_INTERNAL') || die();
b9ddb2d5 28
6bc1e5d5 29require_once($CFG->libdir.'/authlib.php');
30
b9ddb2d5 31/**
32 * External database authentication plugin.
33 */
6bc1e5d5 34class auth_plugin_db extends auth_plugin_base {
b9ddb2d5 35
36 /**
37 * Constructor.
38 */
e7aeaa65
PS
39 function __construct() {
40 global $CFG;
41 require_once($CFG->libdir.'/adodb/adodb.inc.php');
42
6bc1e5d5 43 $this->authtype = 'db';
b9ddb2d5 44 $this->config = get_config('auth/db');
8ae42b8d 45 if (empty($this->config->extencoding)) {
46 $this->config->extencoding = 'utf-8';
47 }
b9ddb2d5 48 }
49
50 /**
51 * Returns true if the username and password work and false if they are
52 * wrong or don't exist.
53 *
576c063b 54 * @param string $username The username
55 * @param string $password The password
8ae42b8d 56 * @return bool Authentication success or failure.
b9ddb2d5 57 */
139ebfdb 58 function user_login($username, $password) {
576c063b 59 global $CFG, $DB;
b9ddb2d5 60
2f1e464a
PS
61 $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
62 $extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
8ae42b8d 63
7415aed1 64 if ($this->is_internal()) {
e7aeaa65 65 // Lookup username externally, but resolve
b9ddb2d5 66 // password locally -- to support backend that
e7aeaa65 67 // don't track passwords.
ba87b41b
PS
68
69 if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
70 // No need to connect to external database in this case because users are never removed and we verify password locally.
71 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
72 return validate_internal_user_password($user, $password);
73 } else {
74 return false;
75 }
76 }
77
78 $authdb = $this->db_init();
79
e7aeaa65
PS
80 $rs = $authdb->Execute("SELECT *
81 FROM {$this->config->table}
82 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
b9ddb2d5 83 if (!$rs) {
03cedd62 84 $authdb->Close();
03ea0b32 85 debugging(get_string('auth_dbcantconnect','auth_db'));
b9ddb2d5 86 return false;
87 }
8ae42b8d 88
7415aed1 89 if (!$rs->EOF) {
03cedd62 90 $rs->Close();
91 $authdb->Close();
e7aeaa65 92 // User exists externally - check username/password internally.
a0a5ca25 93 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
b9ddb2d5 94 return validate_internal_user_password($user, $password);
95 }
96 } else {
03cedd62 97 $rs->Close();
98 $authdb->Close();
e7aeaa65 99 // User does not exist externally.
b9ddb2d5 100 return false;
8ae42b8d 101 }
b9ddb2d5 102
8ae42b8d 103 } else {
e7aeaa65 104 // Normal case: use external db for both usernames and passwords.
b9ddb2d5 105
ba87b41b
PS
106 $authdb = $this->db_init();
107
e7aeaa65 108 if ($this->config->passtype === 'md5') { // Re-format password accordingly.
8ae42b8d 109 $extpassword = md5($extpassword);
110 } else if ($this->config->passtype === 'sha1') {
111 $extpassword = sha1($extpassword);
b9ddb2d5 112 }
113
e7aeaa65
PS
114 $rs = $authdb->Execute("SELECT *
115 FROM {$this->config->table}
116 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'
117 AND {$this->config->fieldpass} = '".$this->ext_addslashes($extpassword)."'");
b9ddb2d5 118 if (!$rs) {
03cedd62 119 $authdb->Close();
03ea0b32 120 debugging(get_string('auth_dbcantconnect','auth_db'));
b9ddb2d5 121 return false;
122 }
8ae42b8d 123
03cedd62 124 if (!$rs->EOF) {
125 $rs->Close();
126 $authdb->Close();
b9ddb2d5 127 return true;
128 } else {
03cedd62 129 $rs->Close();
130 $authdb->Close();
b9ddb2d5 131 return false;
8ae42b8d 132 }
133
b9ddb2d5 134 }
135 }
136
139ebfdb 137 function db_init() {
e7aeaa65 138 // Connect to the external database (forcing new connection).
ab6e0848 139 $authdb = ADONewConnection($this->config->type);
8ae42b8d 140 if (!empty($this->config->debugauthdb)) {
141 $authdb->debug = true;
e7aeaa65 142 ob_start(); //Start output buffer to allow later use of the page headers.
8ae42b8d 143 }
144 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
b9ddb2d5 145 $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
8ae42b8d 146 if (!empty($this->config->setupsql)) {
147 $authdb->Execute($this->config->setupsql);
148 }
b9ddb2d5 149
139ebfdb 150 return $authdb;
151 }
7415aed1 152
139ebfdb 153 /**
e7aeaa65 154 * Returns user attribute mappings between moodle and ldap.
139ebfdb 155 *
156 * @return array
157 */
158 function db_attributes() {
139ebfdb 159 $moodleattributes = array();
4105caff 160 foreach ($this->userfields as $field) {
139ebfdb 161 if (!empty($this->config->{"field_map_$field"})) {
162 $moodleattributes[$field] = $this->config->{"field_map_$field"};
0f02788f 163 }
164 }
5261baf1 165 $moodleattributes['username'] = $this->config->fielduser;
139ebfdb 166 return $moodleattributes;
167 }
168
169 /**
170 * Reads any other information for a user from external database,
e7aeaa65 171 * then returns it in an array.
139ebfdb 172 *
be544ec3 173 * @param string $username
e7aeaa65 174 * @return array
139ebfdb 175 */
176 function get_userinfo($username) {
139ebfdb 177 global $CFG;
178
2f1e464a 179 $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
139ebfdb 180
181 $authdb = $this->db_init();
182
e7aeaa65 183 // Array to map local fieldnames we want, to external fieldnames.
139ebfdb 184 $selectfields = $this->db_attributes();
185
0f02788f 186 $result = array();
e7aeaa65 187 // If at least one field is mapped from external db, get that mapped data.
0f02788f 188 if ($selectfields) {
e7aeaa65 189 $select = array();
0f02788f 190 foreach ($selectfields as $localname=>$externalname) {
e7aeaa65 191 $select[] = "$externalname AS $localname";
0f02788f 192 }
e7aeaa65
PS
193 $select = implode(', ', $select);
194 $sql = "SELECT $select
195 FROM {$this->config->table}
196 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
0f02788f 197 if ($rs = $authdb->Execute($sql)) {
e7aeaa65 198 if (!$rs->EOF) {
245ac557 199 $fields_obj = $rs->FetchObj();
1ed6ae07 200 $fields_obj = (object)array_change_key_case((array)$fields_obj , CASE_LOWER);
0f02788f 201 foreach ($selectfields as $localname=>$externalname) {
2f1e464a 202 $result[$localname] = core_text::convert($fields_obj->{$localname}, $this->config->extencoding, 'utf-8');
0f02788f 203 }
204 }
245ac557 205 $rs->Close();
b9ddb2d5 206 }
207 }
208 $authdb->Close();
b9ddb2d5 209 return $result;
210 }
211
fb5c7739 212 /**
e7aeaa65 213 * Change a user's password.
fb5c7739 214 *
e7aeaa65 215 * @param stdClass $user User table object
ae040d4b 216 * @param string $newpassword Plaintext password
e7aeaa65 217 * @return bool True on success
fb5c7739 218 */
da249a30 219 function user_update_password($user, $newpassword) {
5c28e3a8
PS
220 global $DB;
221
7415aed1 222 if ($this->is_internal()) {
5c28e3a8 223 $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
ec2d8ceb
SC
224 // This will also update the stored hash to the latest algorithm
225 // if the existing hash is using an out-of-date algorithm (or the
226 // legacy md5 algorithm).
5c28e3a8
PS
227 if (update_internal_user_password($puser, $newpassword)) {
228 $user->password = $puser->password;
229 return true;
230 } else {
231 return false;
232 }
b9ddb2d5 233 } else {
e7aeaa65 234 // We should have never been called!
b9ddb2d5 235 return false;
236 }
237 }
238
239 /**
e7aeaa65 240 * Synchronizes user from external db to moodle user table.
b9ddb2d5 241 *
ab6e0848 242 * Sync should be done by using idnumber attribute, not username.
b9ddb2d5 243 * You need to pass firstsync parameter to function to fill in
ab6e0848 244 * idnumbers if they don't exists in moodle user table.
8ae42b8d 245 *
ab6e0848 246 * Syncing users removes (disables) users that don't exists anymore in external db.
8ae42b8d 247 * Creates new users and updates coursecreator status of users.
248 *
b9ddb2d5 249 * This implementation is simpler but less scalable than the one found in the LDAP module.
250 *
e7aeaa65 251 * @param progress_trace $trace
7415aed1 252 * @param bool $do_updates Optional: set to true to force an update of existing accounts
ab6e0848 253 * @return int 0 means success, 1 means failure
b9ddb2d5 254 */
e7aeaa65 255 function sync_users(progress_trace $trace, $do_updates=false) {
70ca450a 256 global $CFG, $DB;
b9ddb2d5 257
e7aeaa65 258 // List external users.
b9ddb2d5 259 $userlist = $this->get_userlist();
b9ddb2d5 260
e7aeaa65 261 // Delete obsolete internal users.
139ebfdb 262 if (!empty($this->config->removeuser)) {
b9ddb2d5 263
28fd4d6c
PS
264 $suspendselect = "";
265 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
266 $suspendselect = "AND u.suspended = 0";
267 }
268
e7aeaa65 269 // Find obsolete users.
139ebfdb 270 if (count($userlist)) {
20d8d5c7
EL
271 list($notin_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', false);
272 $params['authtype'] = $this->authtype;
f91f3f63 273 $sql = "SELECT u.*
bc31625a 274 FROM {user} u
28fd4d6c 275 WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect AND u.username $notin_sql";
139ebfdb 276 } else {
f91f3f63 277 $sql = "SELECT u.*
bc31625a 278 FROM {user} u
28fd4d6c 279 WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect";
bc31625a 280 $params = array();
20d8d5c7 281 $params['authtype'] = $this->authtype;
139ebfdb 282 }
28fd4d6c 283 $params['mnethostid'] = $CFG->mnet_localhost_id;
bc31625a 284 $remove_users = $DB->get_records_sql($sql, $params);
139ebfdb 285
286 if (!empty($remove_users)) {
bb78e249 287 require_once($CFG->dirroot.'/user/lib.php');
e7aeaa65 288 $trace->output(get_string('auth_dbuserstoremove','auth_db', count($remove_users)));
139ebfdb 289
139ebfdb 290 foreach ($remove_users as $user) {
6f87ef52 291 if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
ab6e0848 292 delete_user($user);
e7aeaa65 293 $trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
6f87ef52 294 } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
1dffbae2 295 $updateuser = new stdClass();
139ebfdb 296 $updateuser->id = $user->id;
28fd4d6c 297 $updateuser->suspended = 1;
bb78e249 298 user_update_user($updateuser, false);
e7aeaa65 299 $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
139ebfdb 300 }
b9ddb2d5 301 }
8ae42b8d 302 }
e7aeaa65 303 unset($remove_users);
8ae42b8d 304 }
b9ddb2d5 305
306 if (!count($userlist)) {
e7aeaa65
PS
307 // Exit right here, nothing else to do.
308 $trace->finished();
ab6e0848 309 return 0;
b9ddb2d5 310 }
311
e7aeaa65 312 // Update existing accounts.
b9ddb2d5 313 if ($do_updates) {
e7aeaa65 314 // Narrow down what fields we need to update.
b9ddb2d5 315 $all_keys = array_keys(get_object_vars($this->config));
316 $updatekeys = array();
317 foreach ($all_keys as $key) {
318 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
319 if ($this->config->{$key} === 'onlogin') {
e7aeaa65 320 array_push($updatekeys, $match[1]); // The actual key name.
b9ddb2d5 321 }
322 }
323 }
b9ddb2d5 324 unset($all_keys); unset($key);
325
e7aeaa65 326 // Only go ahead if we actually have fields to update locally.
b9ddb2d5 327 if (!empty($updatekeys)) {
20d8d5c7 328 list($in_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', true);
bc31625a
PS
329 $params['authtype'] = $this->authtype;
330 $sql = "SELECT u.id, u.username
331 FROM {user} u
332 WHERE u.auth=:authtype AND u.deleted=0 AND u.username {$in_sql}";
333 if ($update_users = $DB->get_records_sql($sql, $params)) {
e7aeaa65 334 $trace->output("User entries to update: ".count($update_users));
8ae42b8d 335
336 foreach ($update_users as $user) {
ab6e0848 337 if ($this->update_user_record($user->username, $updatekeys)) {
e7aeaa65 338 $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
ab6e0848 339 } else {
e7aeaa65 340 $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
139ebfdb 341 }
8ae42b8d 342 }
e7aeaa65 343 unset($update_users);
b9ddb2d5 344 }
b9ddb2d5 345 }
346 }
347
348
e7aeaa65
PS
349 // Create missing accounts.
350 // NOTE: this is very memory intensive and generally inefficient.
28fd4d6c
PS
351 $suspendselect = "";
352 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
353 $suspendselect = "AND u.suspended = 0";
354 }
355 $sql = "SELECT u.id, u.username
356 FROM {user} u
357 WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
b9ddb2d5 358
28fd4d6c 359 $users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
8ae42b8d 360
e7aeaa65 361 // Simplify down to usernames.
b9ddb2d5 362 $usernames = array();
2b214bc1 363 if (!empty($users)) {
364 foreach ($users as $user) {
365 array_push($usernames, $user->username);
366 }
367 unset($users);
b9ddb2d5 368 }
b9ddb2d5 369
370 $add_users = array_diff($userlist, $usernames);
371 unset($usernames);
372
373 if (!empty($add_users)) {
e7aeaa65 374 $trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
bee02209 375 // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
b9ddb2d5 376 foreach($add_users as $user) {
377 $username = $user;
28fd4d6c
PS
378 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
379 if ($old_user = $DB->get_record('user', array('username'=>$username, 'deleted'=>0, 'suspended'=>1, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
380 $DB->set_field('user', 'suspended', 0, array('id'=>$old_user->id));
e7aeaa65 381 $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name'=>$username, 'id'=>$old_user->id)), 1);
28fd4d6c
PS
382 continue;
383 }
384 }
f0364be6
PS
385
386 // Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
8ae42b8d 387
e7aeaa65 388 // Prep a few params.
f0364be6 389 $user = $this->get_userinfo_asobj($user);
b7b50143 390 $user->username = $username;
b7b50143 391 $user->confirmed = 1;
5211c7ec 392 $user->auth = $this->authtype;
b7b50143 393 $user->mnethostid = $CFG->mnet_localhost_id;
8ae42b8d 394 if (empty($user->lang)) {
395 $user->lang = $CFG->lang;
396 }
f0364be6
PS
397 $user->timecreated = time();
398 $user->timemodified = $user->timecreated;
bee02209 399 if ($collision = $DB->get_record_select('user', "username = :username AND mnethostid = :mnethostid AND auth <> :auth", array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype), 'id,username,auth')) {
e7aeaa65 400 $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
bee02209
PS
401 continue;
402 }
f0364be6 403 try {
ab6e0848 404 $id = $DB->insert_record ('user', $user); // it is truly a new user
e7aeaa65 405 $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
f0364be6 406 } catch (moodle_exception $e) {
e7aeaa65 407 $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
bee02209 408 continue;
b9ddb2d5 409 }
e7aeaa65 410 // If relevant, tag for password generation.
f0364be6
PS
411 if ($this->is_internal()) {
412 set_user_preference('auth_forcepasswordchange', 1, $id);
413 set_user_preference('create_password', 1, $id);
414 }
bee02209
PS
415 // Make sure user context is present.
416 context_user::instance($id);
b9ddb2d5 417 }
e7aeaa65 418 unset($add_users);
b9ddb2d5 419 }
e7aeaa65 420 $trace->finished();
ab6e0848 421 return 0;
b9ddb2d5 422 }
423
139ebfdb 424 function user_exists($username) {
93901eb4 425
e7aeaa65 426 // Init result value.
a7e32367 427 $result = false;
428
2f1e464a 429 $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
8ae42b8d 430
139ebfdb 431 $authdb = $this->db_init();
b9ddb2d5 432
e7aeaa65
PS
433 $rs = $authdb->Execute("SELECT *
434 FROM {$this->config->table}
435 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
b9ddb2d5 436
437 if (!$rs) {
2b06294b 438 print_error('auth_dbcantconnect','auth_db');
7415aed1 439 } else if (!$rs->EOF) {
e7aeaa65 440 // User exists externally.
03cedd62 441 $result = true;
8ae42b8d 442 }
a7e32367 443
444 $authdb->Close();
445 return $result;
b9ddb2d5 446 }
447
448
449 function get_userlist() {
93901eb4 450
e7aeaa65 451 // Init result value.
a7e32367 452 $result = array();
453
139ebfdb 454 $authdb = $this->db_init();
b9ddb2d5 455
e7aeaa65 456 // Fetch userlist.
b9ddb2d5 457 $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username
e7aeaa65 458 FROM {$this->config->table} ");
b9ddb2d5 459
460 if (!$rs) {
2b06294b 461 print_error('auth_dbcantconnect','auth_db');
7415aed1 462 } else if (!$rs->EOF) {
245ac557 463 while ($rec = $rs->FetchRow()) {
e9366bf8 464 $rec = (object)array_change_key_case((array)$rec , CASE_LOWER);
465 array_push($result, $rec->username);
b9ddb2d5 466 }
8ae42b8d 467 }
a7e32367 468
469 $authdb->Close();
470 return $result;
b9ddb2d5 471 }
472
473 /**
e7aeaa65 474 * Reads user information from DB and return it in an object.
b9ddb2d5 475 *
e7aeaa65 476 * @param string $username username
b9ddb2d5 477 * @return array
478 */
479 function get_userinfo_asobj($username) {
480 $user_array = truncate_userinfo($this->get_userinfo($username));
1dffbae2 481 $user = new stdClass();
b9ddb2d5 482 foreach($user_array as $key=>$value) {
483 $user->{$key} = $value;
484 }
485 return $user;
486 }
487
8ae42b8d 488 /**
489 * will update a local user record from an external source.
490 * is a lighter version of the one in moodlelib -- won't do
e7aeaa65 491 * expensive ops such as enrolment.
b9ddb2d5 492 *
8ae42b8d 493 * If you don't pass $updatekeys, there is a performance hit and
b9ddb2d5 494 * values removed from DB won't be removed from moodle.
8ae42b8d 495 *
185721a4 496 * @param string $username username
ab6e0848
PS
497 * @param bool $updatekeys
498 * @return stdClass
b9ddb2d5 499 */
139ebfdb 500 function update_user_record($username, $updatekeys=false) {
185721a4 501 global $CFG, $DB;
b9ddb2d5 502
b9ddb2d5 503 //just in case check text case
2f1e464a 504 $username = trim(core_text::strtolower($username));
8ae42b8d 505
b9ddb2d5 506 // get the current user record
185721a4 507 $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
b9ddb2d5 508 if (empty($user)) { // trouble
509 error_log("Cannot update non-existent user: $username");
2b06294b 510 print_error('auth_dbusernotexist','auth_db',$username);
b9ddb2d5 511 die;
512 }
513
e7aeaa65 514 // Ensure userid is not overwritten.
b7b50143 515 $userid = $user->id;
a4d25731 516 $updated = false;
b7b50143 517
b9ddb2d5 518 if ($newinfo = $this->get_userinfo($username)) {
519 $newinfo = truncate_userinfo($newinfo);
8ae42b8d 520
e7aeaa65 521 if (empty($updatekeys)) { // All keys? This does not support removing values.
b9ddb2d5 522 $updatekeys = array_keys($newinfo);
523 }
8ae42b8d 524
b9ddb2d5 525 foreach ($updatekeys as $key) {
b9ddb2d5 526 if (isset($newinfo[$key])) {
527 $value = $newinfo[$key];
b9ddb2d5 528 } else {
529 $value = '';
530 }
8ae42b8d 531
532 if (!empty($this->config->{'field_updatelocal_' . $key})) {
e7aeaa65 533 if (isset($user->{$key}) and $user->{$key} != $value) { // Only update if it's changed.
185721a4 534 $DB->set_field('user', $key, $value, array('id'=>$userid));
a4d25731 535 $updated = true;
139ebfdb 536 }
b9ddb2d5 537 }
538 }
539 }
a4d25731
PS
540 if ($updated) {
541 $DB->set_field('user', 'timemodified', time(), array('id'=>$userid));
542 }
185721a4 543 return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
139ebfdb 544 }
545
546 /**
547 * Called when the user record is updated.
548 * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
ab6e0848 549 * compares information saved modified information to external db.
139ebfdb 550 *
e7aeaa65
PS
551 * @param stdClass $olduser Userobject before modifications
552 * @param stdClass $newuser Userobject new modified userobject
139ebfdb 553 * @return boolean result
554 *
555 */
556 function user_update($olduser, $newuser) {
557 if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
558 error_log("ERROR:User renaming not allowed in ext db");
559 return false;
560 }
561
5211c7ec 562 if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
e7aeaa65 563 return true; // Just change auth and skip update.
139ebfdb 564 }
565
566 $curruser = $this->get_userinfo($olduser->username);
567 if (empty($curruser)) {
568 error_log("ERROR:User $olduser->username found in ext db");
569 return false;
570 }
571
2f1e464a 572 $extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
139ebfdb 573
574 $authdb = $this->db_init();
575
576 $update = array();
577 foreach($curruser as $key=>$value) {
578 if ($key == 'username') {
e7aeaa65 579 continue; // Skip this.
139ebfdb 580 }
581 if (empty($this->config->{"field_updateremote_$key"})) {
e7aeaa65 582 continue; // Remote update not requested.
139ebfdb 583 }
584 if (!isset($newuser->$key)) {
585 continue;
586 }
70ca450a 587 $nuvalue = $newuser->$key;
139ebfdb 588 if ($nuvalue != $value) {
2f1e464a 589 $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
139ebfdb 590 }
591 }
592 if (!empty($update)) {
593 $authdb->Execute("UPDATE {$this->config->table}
7415aed1
PS
594 SET ".implode(',', $update)."
595 WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'");
139ebfdb 596 }
597 $authdb->Close();
598 return true;
b9ddb2d5 599 }
600
8ae42b8d 601 /**
602 * A chance to validate form data, and last chance to
603 * do stuff before it is inserted in config_plugin
ab6e0848 604 *
e7aeaa65
PS
605 * @param stfdClass $form
606 * @param array $err errors
ab6e0848 607 * @return void
8ae42b8d 608 */
ab6e0848 609 function validate_form($form, &$err) {
150b5fb0 610 if ($form->passtype === 'internal') {
b9ddb2d5 611 $this->config->changepasswordurl = '';
612 set_config('changepasswordurl', '', 'auth/db');
613 }
b9ddb2d5 614 }
615
edb5da83 616 function prevent_local_passwords() {
7415aed1 617 return !$this->is_internal();
edb5da83
PS
618 }
619
b9ddb2d5 620 /**
7415aed1
PS
621 * Returns true if this authentication plugin is "internal".
622 *
623 * Internal plugins use password hashes from Moodle user table for authentication.
b9ddb2d5 624 *
139ebfdb 625 * @return bool
b9ddb2d5 626 */
627 function is_internal() {
e79781f7
PS
628 if (!isset($this->config->passtype)) {
629 return true;
630 }
7415aed1
PS
631 return ($this->config->passtype === 'internal');
632 }
633
634 /**
635 * Indicates if moodle should automatically update internal user
636 * records with data from external sources using the information
637 * from auth_plugin_base::get_userinfo().
638 *
639 * @return bool true means automatically copy data from ext to user table
640 */
641 function is_synchronised_with_external() {
642 return true;
b9ddb2d5 643 }
644
645 /**
646 * Returns true if this authentication plugin can change the user's
647 * password.
648 *
139ebfdb 649 * @return bool
b9ddb2d5 650 */
651 function can_change_password() {
7415aed1 652 return ($this->is_internal() or !empty($this->config->changepasswordurl));
b9ddb2d5 653 }
654
655 /**
430759a5 656 * Returns the URL for changing the user's pw, or empty if the default can
b9ddb2d5 657 * be used.
658 *
99f9f85f 659 * @return moodle_url
b9ddb2d5 660 */
661 function change_password_url() {
7415aed1 662 if ($this->is_internal()) {
e7aeaa65 663 // Standard form.
99f9f85f 664 return null;
430759a5 665 } else {
e7aeaa65 666 // Use admin defined custom url.
99f9f85f 667 return new moodle_url($this->config->changepasswordurl);
430759a5 668 }
b9ddb2d5 669 }
670
ab6ff8a4 671 /**
672 * Returns true if plugin allows resetting of internal password.
673 *
674 * @return bool
675 */
676 function can_reset_password() {
7415aed1 677 return $this->is_internal();
ab6ff8a4 678 }
679
b9ddb2d5 680 /**
681 * Prints a form for configuring this authentication plugin.
682 *
683 * This function is called from admin/auth.php, and outputs a full page with
684 * a form for configuring this plugin.
685 *
ab6e0848
PS
686 * @param stdClass $config
687 * @param array $err errors
688 * @param array $user_fields
689 * @return void
b9ddb2d5 690 */
139ebfdb 691 function config_form($config, $err, $user_fields) {
8ae42b8d 692 include 'config.html';
b9ddb2d5 693 }
694
695 /**
696 * Processes and stores configuration data for this authentication plugin.
e7aeaa65 697 *
ab6e0848
PS
698 * @param srdClass $config
699 * @return bool always true or exception
b9ddb2d5 700 */
701 function process_config($config) {
702 // set to defaults if undefined
703 if (!isset($config->host)) {
8ae42b8d 704 $config->host = 'localhost';
b9ddb2d5 705 }
706 if (!isset($config->type)) {
8ae42b8d 707 $config->type = 'mysql';
708 }
709 if (!isset($config->sybasequoting)) {
710 $config->sybasequoting = 0;
b9ddb2d5 711 }
712 if (!isset($config->name)) {
8ae42b8d 713 $config->name = '';
b9ddb2d5 714 }
715 if (!isset($config->user)) {
8ae42b8d 716 $config->user = '';
b9ddb2d5 717 }
718 if (!isset($config->pass)) {
8ae42b8d 719 $config->pass = '';
b9ddb2d5 720 }
721 if (!isset($config->table)) {
8ae42b8d 722 $config->table = '';
b9ddb2d5 723 }
724 if (!isset($config->fielduser)) {
8ae42b8d 725 $config->fielduser = '';
b9ddb2d5 726 }
727 if (!isset($config->fieldpass)) {
8ae42b8d 728 $config->fieldpass = '';
b9ddb2d5 729 }
730 if (!isset($config->passtype)) {
8ae42b8d 731 $config->passtype = 'plaintext';
732 }
733 if (!isset($config->extencoding)) {
734 $config->extencoding = 'utf-8';
735 }
736 if (!isset($config->setupsql)) {
737 $config->setupsql = '';
738 }
739 if (!isset($config->debugauthdb)) {
740 $config->debugauthdb = 0;
b9ddb2d5 741 }
139ebfdb 742 if (!isset($config->removeuser)) {
6f87ef52 743 $config->removeuser = AUTH_REMOVEUSER_KEEP;
139ebfdb 744 }
b9ddb2d5 745 if (!isset($config->changepasswordurl)) {
746 $config->changepasswordurl = '';
747 }
748
e7aeaa65 749 // Save settings.
8ae42b8d 750 set_config('host', $config->host, 'auth/db');
751 set_config('type', $config->type, 'auth/db');
752 set_config('sybasequoting', $config->sybasequoting, 'auth/db');
753 set_config('name', $config->name, 'auth/db');
754 set_config('user', $config->user, 'auth/db');
755 set_config('pass', $config->pass, 'auth/db');
756 set_config('table', $config->table, 'auth/db');
757 set_config('fielduser', $config->fielduser, 'auth/db');
758 set_config('fieldpass', $config->fieldpass, 'auth/db');
759 set_config('passtype', $config->passtype, 'auth/db');
760 set_config('extencoding', trim($config->extencoding), 'auth/db');
139ebfdb 761 set_config('setupsql', trim($config->setupsql),'auth/db');
8ae42b8d 762 set_config('debugauthdb', $config->debugauthdb, 'auth/db');
139ebfdb 763 set_config('removeuser', $config->removeuser, 'auth/db');
8ae42b8d 764 set_config('changepasswordurl', trim($config->changepasswordurl), 'auth/db');
765
b9ddb2d5 766 return true;
767 }
768
e7aeaa65
PS
769 /**
770 * Add slashes, we can not use placeholders or system functions.
771 *
772 * @param string $text
773 * @return string
774 */
8ae42b8d 775 function ext_addslashes($text) {
8ae42b8d 776 if (empty($this->config->sybasequoting)) {
777 $text = str_replace('\\', '\\\\', $text);
778 $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
779 } else {
780 $text = str_replace("'", "''", $text);
781 }
782 return $text;
783 }
b9ddb2d5 784}
785
5117d598 786