MNET: MyCourses block enriched with links to remote Moodles when roaming
[moodle.git] / auth / db / auth.php
CommitLineData
b9ddb2d5 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// This page cannot be called directly
16if (!isset($CFG)) exit;
17
18/**
19 * External database authentication plugin.
20 */
21class auth_plugin_db {
22
23 /**
24 * The configuration details for the plugin.
25 */
26 var $config;
27
28 /**
29 * Constructor.
30 */
31 function auth_plugin_db() {
32 $this->config = get_config('auth/db');
33 }
34
35 /**
36 * Returns true if the username and password work and false if they are
37 * wrong or don't exist.
38 *
39 * @param string $username The username
40 * @param string $password The password
41 * @returns bool Authentication success or failure.
42 */
43 function user_login ($username, $password) {
44
45 global $CFG;
46
93901eb4 47 // Connect to the external database (forcing new connection)
b9ddb2d5 48 $authdb = &ADONewConnection($this->config->type);
93901eb4 49 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
b9ddb2d5 50 $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
51
52 if ($this->config->passtype === 'internal') {
53 // lookup username externally, but resolve
54 // password locally -- to support backend that
55 // don't track passwords
56 $rs = $authdb->Execute("SELECT * FROM {$this->config->table}
57 WHERE {$this->config->fielduser} = '$username' ");
58 $authdb->Close();
59
60 if (!$rs) {
a9ad3633 61 print_error('auth_dbcantconnect','auth');
b9ddb2d5 62 return false;
63 }
64
65 if ( $rs->RecordCount() ) {
66 // user exists exterally
67 // check username/password internally
b7b50143 68 if ($user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id)) {
b9ddb2d5 69 return validate_internal_user_password($user, $password);
70 }
71 } else {
72 // user does not exist externally
73 return false;
74 }
75
76 } else {
77 // normal case: use external db for passwords
78
79 if ($this->config->passtype === 'md5') { // Re-format password accordingly
80 $password = md5($password);
81 }
82
83 $rs = $authdb->Execute("SELECT * FROM {$this->config->table}
84 WHERE {$this->config->fielduser} = '$username'
85 AND {$this->config->fieldpass} = '$password' ");
86 $authdb->Close();
87
b9ddb2d5 88 if (!$rs) {
a9ad3633 89 print_error('auth_dbcantconnect','auth');
b9ddb2d5 90 return false;
91 }
92
93 if ( $rs->RecordCount() ) {
94 return true;
95 } else {
96 return false;
97 }
98
99 }
100 }
101
102
103 /**
104 * Reads any other information for a user from external database,
105 * then returns it in an array
106 */
107 function get_userinfo($username) {
108
109 global $CFG;
110
93901eb4 111 // Connect to the external database (forcing new connection)
112 $authdb = &ADONewConnection($this->config->type);
113 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
b9ddb2d5 114 $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
115
116 $fields = array("firstname", "lastname", "email", "phone1", "phone2",
117 "department", "address", "city", "country", "description",
118 "idnumber", "lang");
119
120 $result = array();
121
122 foreach ($fields as $field) {
123 if ($this->config->{'field_map_' . $field}) {
de6e7ee2 124 if ($rs = $authdb->Execute("SELECT " . $this->config->{'field_map_' . $field} . " as myfield FROM {$this->config->table}
b9ddb2d5 125 WHERE {$this->config->fielduser} = '$username'")) {
126 if ( $rs->RecordCount() == 1 ) {
127 if (!empty($CFG->unicodedb)) {
de6e7ee2 128 $result["$field"] = addslashes(stripslashes($rs->fields['myfield']));
b9ddb2d5 129 } else {
de6e7ee2 130 $result["$field"] = addslashes(stripslashes(utf8_decode($rs->fields['myfield'])));
b9ddb2d5 131 }
132 }
133 }
134 }
135 }
136 $authdb->Close();
137
138 return $result;
139 }
140
141
142 function user_update_password($username, $newpassword) {
143
b7b50143 144 global $CFG;
b9ddb2d5 145 if ($this->config->passtype === 'internal') {
b7b50143 146 return set_field('user', 'password', md5($newpassword), 'username', $username, 'mnethostid', $CFG->mnet_localhost_id);
b9ddb2d5 147 } else {
148 // we should have never been called!
149 return false;
150 }
151 }
152
153 /**
154 * syncronizes user fron external db to moodle user table
155 *
156 * Sync shouid be done by using idnumber attribute, not username.
157 * You need to pass firstsync parameter to function to fill in
158 * idnumbers if they dont exists in moodle user table.
159 *
160 * Syncing users removes (disables) users that dont exists anymore in external db.
161 * Creates new users and updates coursecreator status of users.
162 *
163 * @param bool $do_updates Optional: set to true to force an update of existing accounts
164 *
165 * This implementation is simpler but less scalable than the one found in the LDAP module.
166 *
167 */
168 function sync_users ($do_updates=0) {
169
170 global $CFG;
171 $pcfg = get_config('auth/db');
172
173 ///
174 /// list external users
175 ///
176 $userlist = $this->get_userlist();
177 $quoteduserlist = implode("', '", $userlist);
178 $quoteduserlist = "'$quoteduserlist'";
179
180 ///
181 /// delete obsolete internal users
182 ///
183
184 // find obsolete users
185 if (count($userlist)) {
186 $sql = 'SELECT u.id, u.username
187 FROM ' . $CFG->prefix .'user u
188 WHERE u.auth=\'db\' AND u.deleted=\'0\' AND u.username NOT IN (' . $quoteduserlist . ')';
189 } else {
190 $sql = 'SELECT u.id, u.username
191 FROM ' . $CFG->prefix .'user u
192 WHERE u.auth=\'db\' AND u.deleted=\'0\' ';
193 }
194 $remove_users = get_records_sql($sql);
195
196 if (!empty($remove_users)) {
a9ad3633 197 print_string('auth_dbuserstoremove','auth', count($remove_users));
198 echo "\n";
b9ddb2d5 199
200 begin_sql();
201 foreach ($remove_users as $user) {
202 //following is copy pasted from admin/user.php
203 //maybe this should moved to function in lib/datalib.php
b7b50143 204 $updateuser = new stdClass();
b9ddb2d5 205 $updateuser->id = $user->id;
206 $updateuser->deleted = "1";
207 $updateuser->timemodified = time();
208 if (update_record("user", $updateuser)) {
209 // unenrol_student($user->id); // From all courses
210 // remove_teacher($user->id); // From all courses
211 // remove_admin($user->id);
212 delete_records('role_assignments', 'userid', $user->id); // unassign all roles
213 notify(get_string("deletedactivity", "", fullname($user, true)) );
214 } else {
215 notify(get_string("deletednot", "", fullname($user, true)));
216 }
217 //copy pasted part ends
218 }
219 commit_sql();
220 }
221 unset($remove_users); // free mem!
222
223 if (!count($userlist)) {
224 // exit right here
225 // nothing else to do
226 return true;
227 }
228
229 ///
230 /// update existing accounts
231 ///
232 if ($do_updates) {
233 // narrow down what fields we need to update
234 $all_keys = array_keys(get_object_vars($this->config));
235 $updatekeys = array();
236 foreach ($all_keys as $key) {
237 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
238 if ($this->config->{$key} === 'onlogin') {
239 array_push($updatekeys, $match[1]); // the actual key name
240 }
241 }
242 }
243 // print_r($all_keys); print_r($updatekeys);
244 unset($all_keys); unset($key);
245
246 // only go ahead if we actually
247 // have fields to update locally
248 if (!empty($updatekeys)) {
249 $sql = 'SELECT u.id, u.username
250 FROM ' . $CFG->prefix .'user u
251 WHERE u.auth=\'db\' AND u.deleted=\'0\' AND u.username IN (' . $quoteduserlist . ')';
252 $update_users = get_records_sql($sql);
253
254 foreach ($update_users as $user) {
255 $this->db_update_user_record($user->username, $updatekeys);
256 }
257 unset($update_users); // free memory
258 }
259 }
260
261
262 ///
263 /// create missing accounts
264 ///
265 // NOTE: this is very memory intensive
266 // and generally inefficient
267 $sql = 'SELECT u.id, u.username
268 FROM ' . $CFG->prefix .'user u
269 WHERE u.auth=\'db\' AND u.deleted=\'0\'';
270
271 $users = get_records_sql($sql);
272
273 // simplify down to usernames
274 $usernames = array();
275 foreach ($users as $user) {
276 array_push($usernames, $user->username);
277 }
278 unset($users);
279
280 $add_users = array_diff($userlist, $usernames);
281 unset($usernames);
282
283 if (!empty($add_users)) {
a9ad3633 284 print_string('auth_dbuserstoadd','auth',count($add_users));
285 echo "\n";
b9ddb2d5 286 begin_sql();
287 foreach($add_users as $user) {
288 $username = $user;
289 $user = $this->get_userinfo_asobj($user);
290
291 // prep a few params
b7b50143 292 $user->username = $username;
293 $user->modified = time();
294 $user->confirmed = 1;
295 $user->auth = 'db';
296 $user->mnethostid = $CFG->mnet_localhost_id;
b9ddb2d5 297
298 // insert it
299 $old_debug=$CFG->debug;
300 $CFG->debug=10;
301
302 // maybe the user has been deleted before
b7b50143 303 if ($old_user = get_record('user', 'username', $user->username, 'deleted', 1, 'mnethostid', $user->mnethostid)) {
b9ddb2d5 304 $user->id = $old_user->id;
305 set_field('user', 'deleted', 0, 'username', $user->username);
a9ad3633 306 print_string('auth_dbrevive','auth',array($user->username, $user->id));
307 echo "\n";
b9ddb2d5 308 } elseif ($id=insert_record ('user',$user)) { // it is truly a new user
a9ad3633 309 print_string('auth_dbinsertuser','auth',array($user->username, $id));
310 echo "\n";
b9ddb2d5 311 $user->id = $id;
312 // if relevant, tag for password generation
313 if ($this->config->passtype === 'internal') {
314 set_user_preference('auth_forcepasswordchange', 1, $id);
315 set_user_preference('create_password', 1, $id);
316 }
317 } else {
a9ad3633 318 print_string('auth_dbinsertusererror', 'auth', $user->username);
319 echo "\n";
b9ddb2d5 320 }
321 $CFG->debug=$old_debug;
322 }
323 commit_sql();
324 unset($add_users); // free mem
325 }
326 return true;
327 }
328
329 function user_exists ($username) {
93901eb4 330
331 // Connect to the external database (forcing new connection)
332 $authdb = &ADONewConnection($this->config->type);
333 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
b9ddb2d5 334 $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
335
336 $rs = $authdb->Execute("SELECT * FROM {$this->config->table}
337 WHERE {$this->config->fielduser} = '$username' ");
338 $authdb->Close();
339
340 if (!$rs) {
a9ad3633 341 print_error('auth_dbcantconnect','auth');
b9ddb2d5 342 return false;
343 }
344
345 if ( $rs->RecordCount() ) {
346 // user exists exterally
347 // check username/password internally
348 // ?? there is no $password variable, so why??
349 /*if ($user = get_record('user', 'username', $username)) {
350 return ($user->password == md5($password));
351 }*/
352 return $rs->RecordCount();
353 } else {
354 // user does not exist externally
355 return false;
356 }
357 }
358
359
360 function get_userlist() {
93901eb4 361
362 // Connect to the external database (forcing new connection)
363 $authdb = &ADONewConnection($this->config->type);
364 $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
b9ddb2d5 365 $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
366
367 // fetch userlist
368 $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username
369 FROM {$this->config->table} ");
370 $authdb->Close();
371
372 if (!$rs) {
a9ad3633 373 print_error('auth_dbcantconnect','auth');
b9ddb2d5 374 return false;
375 }
376
377 if ( $rs->RecordCount() ) {
378 $userlist = array();
379 while ($rec = $rs->FetchRow()) {
380 array_push($userlist, $rec['username']);
381 }
382 return $userlist;
383 } else {
384 return array();
385 }
386 }
387
388 /**
389 * reads userinformation from DB and return it in an object
390 *
391 * @param string $username username
392 * @return array
393 */
394 function get_userinfo_asobj($username) {
395 $user_array = truncate_userinfo($this->get_userinfo($username));
396 $user = new object;
397 foreach($user_array as $key=>$value) {
398 $user->{$key} = $value;
399 }
400 return $user;
401 }
402
403 /*
404 * will update a local user record from an external source.
405 * is a lighter version of the one in moodlelib -- won't do
406 * expensive ops such as enrolment
407 *
408 * If you don't pass $updatekeys, there is a performance hit and
409 * values removed from DB won't be removed from moodle.
410 */
411 function db_update_user_record($username, $updatekeys=false) {
b7b50143 412 global $CFG;
b9ddb2d5 413
414 $pcfg = get_config('auth/db');
415
416 //just in case check text case
417 $username = trim(moodle_strtolower($username));
418
419 // get the current user record
b7b50143 420 $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id);
b9ddb2d5 421 if (empty($user)) { // trouble
422 error_log("Cannot update non-existent user: $username");
a9ad3633 423 print_error('auth_dbusernotexist','auth',$username);
b9ddb2d5 424 die;
425 }
426
b7b50143 427 // Ensure userid is not overwritten
428 $userid = $user->id;
429
b9ddb2d5 430 // TODO: this had a function_exists() - now we have a $this
431 if ($newinfo = $this->get_userinfo($username)) {
432 $newinfo = truncate_userinfo($newinfo);
433
434 if (empty($updatekeys)) { // all keys? this does not support removing values
435 $updatekeys = array_keys($newinfo);
436 }
437
438 foreach ($updatekeys as $key) {
439 unset($value);
440 if (isset($newinfo[$key])) {
441 $value = $newinfo[$key];
442 $value = addslashes(stripslashes($value)); // Just in case
443 } else {
444 $value = '';
445 }
446 if (!empty($this->config->{'field_updatelocal_' . $key})) {
447 if ($user->{$key} != $value) { // only update if it's changed
b7b50143 448 set_field('user', $key, $value, 'id', $userid);
b9ddb2d5 449 }
450 }
451 }
452 }
b7b50143 453 return get_record_select("user", "id = '$userid' AND deleted <> '1'");
b9ddb2d5 454 }
455
456 // A chance to validate form data, and last chance to
457 // do stuff before it is inserted in config_plugin
458 function validate_form(&$form, &$err) {
459 if ($form['passtype'] === 'internal') {
460 $this->config->changepasswordurl = '';
461 set_config('changepasswordurl', '', 'auth/db');
462 }
463 return true;
464 }
465
466 /**
467 * Returns true if this authentication plugin is 'internal'.
468 *
469 * @returns bool
470 */
471 function is_internal() {
472 return false;
473 }
474
475 /**
476 * Returns true if this authentication plugin can change the user's
477 * password.
478 *
479 * @returns bool
480 */
481 function can_change_password() {
482 return ($this->config->passtype === 'internal');
483 }
484
485 /**
486 * Returns the URL for changing the user's pw, or false if the default can
487 * be used.
488 *
489 * @returns bool
490 */
491 function change_password_url() {
492 return $this->config->changepasswordurl;
493 }
494
495 /**
496 * Prints a form for configuring this authentication plugin.
497 *
498 * This function is called from admin/auth.php, and outputs a full page with
499 * a form for configuring this plugin.
500 *
501 * @param array $page An object containing all the data for this page.
502 */
503 function config_form($config, $err) {
504 include "config.html";
505 }
506
507 /**
508 * Processes and stores configuration data for this authentication plugin.
509 */
510 function process_config($config) {
511 // set to defaults if undefined
512 if (!isset($config->host)) {
513 $config->host = "localhost";
514 }
515 if (!isset($config->type)) {
516 $config->type = "mysql";
517 }
518 if (!isset($config->name)) {
519 $config->name = "";
520 }
521 if (!isset($config->user)) {
522 $config->user = "";
523 }
524 if (!isset($config->pass)) {
525 $config->pass = "";
526 }
527 if (!isset($config->table)) {
528 $config->table = "";
529 }
530 if (!isset($config->fielduser)) {
531 $config->fielduser = "";
532 }
533 if (!isset($config->fieldpass)) {
534 $config->fieldpass = "";
535 }
536 if (!isset($config->passtype)) {
537 $config->passtype = "plaintext";
538 }
539 if (!isset($config->changepasswordurl)) {
540 $config->changepasswordurl = '';
541 }
542
543 // save settings
544 set_config('host', $config->host, 'auth/db');
545 set_config('type', $config->type, 'auth/db');
546 set_config('name', $config->name, 'auth/db');
547 set_config('user', $config->user, 'auth/db');
548 set_config('pass', $config->pass, 'auth/db');
549 set_config('table', $config->table, 'auth/db');
550 set_config('fielduser', $config->fielduser, 'auth/db');
551 set_config('fieldpass', $config->fieldpass, 'auth/db');
552 set_config('passtype', $config->passtype, 'auth/db');
553 set_config('changepasswordurl', $config->changepasswordurl, 'auth/db');
554
555 return true;
556 }
557
558}
559
560?>