8c2428de61f1799279f578f09f52de2b604138b2
[moodle.git] / auth / db / auth.php
1 <?php
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/>.
17 /**
18  * Authentication Plugin: External Database Authentication
19  *
20  * Checks against an external database.
21  *
22  * @package    auth_db
23  * @author     Martin Dougiamas
24  * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
25  */
27 defined('MOODLE_INTERNAL') || die();
29 require_once($CFG->libdir.'/authlib.php');
31 /**
32  * External database authentication plugin.
33  */
34 class auth_plugin_db extends auth_plugin_base {
36     /**
37      * Constructor.
38      */
39     function __construct() {
40         global $CFG;
41         require_once($CFG->libdir.'/adodb/adodb.inc.php');
43         $this->authtype = 'db';
44         $this->config = get_config('auth/db');
45         if (empty($this->config->extencoding)) {
46             $this->config->extencoding = 'utf-8';
47         }
48     }
50     /**
51      * Returns true if the username and password work and false if they are
52      * wrong or don't exist.
53      *
54      * @param string $username The username
55      * @param string $password The password
56      * @return bool Authentication success or failure.
57      */
58     function user_login($username, $password) {
59         global $CFG, $DB;
61         if ($this->is_configured() === false) {
62             debugging(get_string('auth_notconfigured', 'auth', $this->authtype));
63             return false;
64         }
66         $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
67         $extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
69         if ($this->is_internal()) {
70             // Lookup username externally, but resolve
71             // password locally -- to support backend that
72             // don't track passwords.
74             if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
75                 // No need to connect to external database in this case because users are never removed and we verify password locally.
76                 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
77                     return validate_internal_user_password($user, $password);
78                 } else {
79                     return false;
80                 }
81             }
83             $authdb = $this->db_init();
85             $rs = $authdb->Execute("SELECT *
86                                       FROM {$this->config->table}
87                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
88             if (!$rs) {
89                 $authdb->Close();
90                 debugging(get_string('auth_dbcantconnect','auth_db'));
91                 return false;
92             }
94             if (!$rs->EOF) {
95                 $rs->Close();
96                 $authdb->Close();
97                 // User exists externally - check username/password internally.
98                 if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
99                     return validate_internal_user_password($user, $password);
100                 }
101             } else {
102                 $rs->Close();
103                 $authdb->Close();
104                 // User does not exist externally.
105                 return false;
106             }
108         } else {
109             // Normal case: use external db for both usernames and passwords.
111             $authdb = $this->db_init();
113             $rs = $authdb->Execute("SELECT {$this->config->fieldpass}
114                                       FROM {$this->config->table}
115                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
116             if (!$rs) {
117                 $authdb->Close();
118                 debugging(get_string('auth_dbcantconnect','auth_db'));
119                 return false;
120             }
122             if ($rs->EOF) {
123                 $authdb->Close();
124                 return false;
125             }
127             $fields = array_change_key_case($rs->fields, CASE_LOWER);
128             $fromdb = $fields[strtolower($this->config->fieldpass)];
129             $rs->Close();
130             $authdb->Close();
132             if ($this->config->passtype === 'plaintext') {
133                 return ($fromdb == $extpassword);
134             } else if ($this->config->passtype === 'md5') {
135                 return (strtolower($fromdb) == md5($extpassword));
136             } else if ($this->config->passtype === 'sha1') {
137                 return (strtolower($fromdb) == sha1($extpassword));
138             } else if ($this->config->passtype === 'saltedcrypt') {
139                 require_once($CFG->libdir.'/password_compat/lib/password.php');
140                 return password_verify($extpassword, $fromdb);
141             } else {
142                 return false;
143             }
145         }
146     }
148     /**
149      * Connect to external database.
150      *
151      * @return ADOConnection
152      * @throws moodle_exception
153      */
154     function db_init() {
155         if ($this->is_configured() === false) {
156             throw new moodle_exception('auth_dbcantconnect', 'auth_db');
157         }
159         // Connect to the external database (forcing new connection).
160         $authdb = ADONewConnection($this->config->type);
161         if (!empty($this->config->debugauthdb)) {
162             $authdb->debug = true;
163             ob_start(); //Start output buffer to allow later use of the page headers.
164         }
165         $authdb->Connect($this->config->host, $this->config->user, $this->config->pass, $this->config->name, true);
166         $authdb->SetFetchMode(ADODB_FETCH_ASSOC);
167         if (!empty($this->config->setupsql)) {
168             $authdb->Execute($this->config->setupsql);
169         }
171         return $authdb;
172     }
174     /**
175      * Returns user attribute mappings between moodle and ldap.
176      *
177      * @return array
178      */
179     function db_attributes() {
180         $moodleattributes = array();
181         // If we have custom fields then merge them with user fields.
182         $customfields = $this->get_custom_user_profile_fields();
183         if (!empty($customfields) && !empty($this->userfields)) {
184             $userfields = array_merge($this->userfields, $customfields);
185         } else {
186             $userfields = $this->userfields;
187         }
189         foreach ($userfields as $field) {
190             if (!empty($this->config->{"field_map_$field"})) {
191                 $moodleattributes[$field] = $this->config->{"field_map_$field"};
192             }
193         }
194         $moodleattributes['username'] = $this->config->fielduser;
195         return $moodleattributes;
196     }
198     /**
199      * Reads any other information for a user from external database,
200      * then returns it in an array.
201      *
202      * @param string $username
203      * @return array
204      */
205     function get_userinfo($username) {
206         global $CFG;
208         $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
210         $authdb = $this->db_init();
212         // Array to map local fieldnames we want, to external fieldnames.
213         $selectfields = $this->db_attributes();
215         $result = array();
216         // If at least one field is mapped from external db, get that mapped data.
217         if ($selectfields) {
218             $select = array();
219             foreach ($selectfields as $localname=>$externalname) {
220                 $select[] = "$externalname";
221             }
222             $select = implode(', ', $select);
223             $sql = "SELECT $select
224                       FROM {$this->config->table}
225                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
227             if ($rs = $authdb->Execute($sql)) {
228                 if (!$rs->EOF) {
229                     $fields = $rs->FetchRow();
230                     // Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
231                     $fields = array_values($fields);
232                     foreach (array_keys($selectfields) as $index => $localname) {
233                         $value = $fields[$index];
234                         $result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
235                      }
236                  }
237                  $rs->Close();
238             }
239         }
240         $authdb->Close();
241         return $result;
242     }
244     /**
245      * Change a user's password.
246      *
247      * @param  stdClass  $user      User table object
248      * @param  string  $newpassword Plaintext password
249      * @return bool                 True on success
250      */
251     function user_update_password($user, $newpassword) {
252         global $DB;
254         if ($this->is_internal()) {
255             $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
256             // This will also update the stored hash to the latest algorithm
257             // if the existing hash is using an out-of-date algorithm (or the
258             // legacy md5 algorithm).
259             if (update_internal_user_password($puser, $newpassword)) {
260                 $user->password = $puser->password;
261                 return true;
262             } else {
263                 return false;
264             }
265         } else {
266             // We should have never been called!
267             return false;
268         }
269     }
271     /**
272      * Synchronizes user from external db to moodle user table.
273      *
274      * Sync should be done by using idnumber attribute, not username.
275      * You need to pass firstsync parameter to function to fill in
276      * idnumbers if they don't exists in moodle user table.
277      *
278      * Syncing users removes (disables) users that don't exists anymore in external db.
279      * Creates new users and updates coursecreator status of users.
280      *
281      * This implementation is simpler but less scalable than the one found in the LDAP module.
282      *
283      * @param progress_trace $trace
284      * @param bool $do_updates  Optional: set to true to force an update of existing accounts
285      * @return int 0 means success, 1 means failure
286      */
287     function sync_users(progress_trace $trace, $do_updates=false) {
288         global $CFG, $DB;
290         require_once($CFG->dirroot . '/user/lib.php');
292         // List external users.
293         $userlist = $this->get_userlist();
295         // Delete obsolete internal users.
296         if (!empty($this->config->removeuser)) {
298             $suspendselect = "";
299             if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
300                 $suspendselect = "AND u.suspended = 0";
301             }
303             // Find obsolete users.
304             if (count($userlist)) {
305                 list($notin_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', false);
306                 $params['authtype'] = $this->authtype;
307                 $sql = "SELECT u.*
308                           FROM {user} u
309                          WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect AND u.username $notin_sql";
310             } else {
311                 $sql = "SELECT u.*
312                           FROM {user} u
313                          WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect";
314                 $params = array();
315                 $params['authtype'] = $this->authtype;
316             }
317             $params['mnethostid'] = $CFG->mnet_localhost_id;
318             $remove_users = $DB->get_records_sql($sql, $params);
320             if (!empty($remove_users)) {
321                 $trace->output(get_string('auth_dbuserstoremove','auth_db', count($remove_users)));
323                 foreach ($remove_users as $user) {
324                     if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
325                         delete_user($user);
326                         $trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
327                     } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
328                         $updateuser = new stdClass();
329                         $updateuser->id   = $user->id;
330                         $updateuser->suspended = 1;
331                         $updateuser = $this->clean_data($updateuser);
332                         user_update_user($updateuser, false);
333                         $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
334                     }
335                 }
336             }
337             unset($remove_users);
338         }
340         if (!count($userlist)) {
341             // Exit right here, nothing else to do.
342             $trace->finished();
343             return 0;
344         }
346         // Update existing accounts.
347         if ($do_updates) {
348             // Narrow down what fields we need to update.
349             $all_keys = array_keys(get_object_vars($this->config));
350             $updatekeys = array();
351             foreach ($all_keys as $key) {
352                 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
353                     if ($this->config->{$key} === 'onlogin') {
354                         array_push($updatekeys, $match[1]); // The actual key name.
355                     }
356                 }
357             }
358             unset($all_keys); unset($key);
360             // Only go ahead if we actually have fields to update locally.
361             if (!empty($updatekeys)) {
362                 list($in_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', true);
363                 $params['authtype'] = $this->authtype;
364                 $sql = "SELECT u.id, u.username
365                           FROM {user} u
366                          WHERE u.auth=:authtype AND u.deleted=0 AND u.username {$in_sql}";
367                 if ($update_users = $DB->get_records_sql($sql, $params)) {
368                     $trace->output("User entries to update: ".count($update_users));
370                     foreach ($update_users as $user) {
371                         if ($this->update_user_record($user->username, $updatekeys)) {
372                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
373                         } else {
374                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
375                         }
376                     }
377                     unset($update_users);
378                 }
379             }
380         }
383         // Create missing accounts.
384         // NOTE: this is very memory intensive and generally inefficient.
385         $suspendselect = "";
386         if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
387             $suspendselect = "AND u.suspended = 0";
388         }
389         $sql = "SELECT u.id, u.username
390                   FROM {user} u
391                  WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
393         $users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
395         // Simplify down to usernames.
396         $usernames = array();
397         if (!empty($users)) {
398             foreach ($users as $user) {
399                 array_push($usernames, $user->username);
400             }
401             unset($users);
402         }
404         $add_users = array_diff($userlist, $usernames);
405         unset($usernames);
407         if (!empty($add_users)) {
408             $trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
409             // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
410             foreach($add_users as $user) {
411                 $username = $user;
412                 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
413                     if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1,
414                             'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) {
415                         $updateuser = new stdClass();
416                         $updateuser->id = $olduser->id;
417                         $updateuser->suspended = 0;
418                         $updateuser = $this->clean_data($updateuser);
419                         user_update_user($updateuser);
420                         $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
421                             'id' => $olduser->id)), 1);
422                         continue;
423                     }
424                 }
426                 // Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
428                 // Prep a few params.
429                 $user = $this->get_userinfo_asobj($user);
430                 $user->username   = $username;
431                 $user->confirmed  = 1;
432                 $user->auth       = $this->authtype;
433                 $user->mnethostid = $CFG->mnet_localhost_id;
434                 if (empty($user->lang)) {
435                     $user->lang = $CFG->lang;
436                 }
437                 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')) {
438                     $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
439                     continue;
440                 }
441                 $user = $this->clean_data($user);
442                 try {
443                     $id = user_create_user($user, false); // It is truly a new user.
444                     $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
445                 } catch (moodle_exception $e) {
446                     $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
447                     continue;
448                 }
449                 // If relevant, tag for password generation.
450                 if ($this->is_internal()) {
451                     set_user_preference('auth_forcepasswordchange', 1, $id);
452                     set_user_preference('create_password',          1, $id);
453                 }
454                 // Make sure user context is present.
455                 context_user::instance($id);
456             }
457             unset($add_users);
458         }
459         $trace->finished();
460         return 0;
461     }
463     function user_exists($username) {
465         // Init result value.
466         $result = false;
468         $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
470         $authdb = $this->db_init();
472         $rs = $authdb->Execute("SELECT *
473                                   FROM {$this->config->table}
474                                  WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
476         if (!$rs) {
477             print_error('auth_dbcantconnect','auth_db');
478         } else if (!$rs->EOF) {
479             // User exists externally.
480             $result = true;
481         }
483         $authdb->Close();
484         return $result;
485     }
488     function get_userlist() {
490         // Init result value.
491         $result = array();
493         $authdb = $this->db_init();
495         // Fetch userlist.
496         $rs = $authdb->Execute("SELECT {$this->config->fielduser}
497                                   FROM {$this->config->table} ");
499         if (!$rs) {
500             print_error('auth_dbcantconnect','auth_db');
501         } else if (!$rs->EOF) {
502             while ($rec = $rs->FetchRow()) {
503                 $rec = array_change_key_case((array)$rec, CASE_LOWER);
504                 array_push($result, $rec[strtolower($this->config->fielduser)]);
505             }
506         }
508         $authdb->Close();
509         return $result;
510     }
512     /**
513      * Reads user information from DB and return it in an object.
514      *
515      * @param string $username username
516      * @return array
517      */
518     function get_userinfo_asobj($username) {
519         $user_array = truncate_userinfo($this->get_userinfo($username));
520         $user = new stdClass();
521         foreach($user_array as $key=>$value) {
522             $user->{$key} = $value;
523         }
524         return $user;
525     }
527     /**
528      * will update a local user record from an external source.
529      * is a lighter version of the one in moodlelib -- won't do
530      * expensive ops such as enrolment.
531      *
532      * If you don't pass $updatekeys, there is a performance hit and
533      * values removed from DB won't be removed from moodle.
534      *
535      * @param string $username username
536      * @param bool $updatekeys
537      * @return stdClass
538      */
539     function update_user_record($username, $updatekeys=false) {
540         global $CFG, $DB;
542         //just in case check text case
543         $username = trim(core_text::strtolower($username));
545         // get the current user record
546         $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
547         if (empty($user)) { // trouble
548             error_log("Cannot update non-existent user: $username");
549             print_error('auth_dbusernotexist','auth_db',$username);
550             die;
551         }
553         // Ensure userid is not overwritten.
554         $userid = $user->id;
555         $needsupdate = false;
557         $updateuser = new stdClass();
558         $updateuser->id = $userid;
559         if ($newinfo = $this->get_userinfo($username)) {
560             $newinfo = truncate_userinfo($newinfo);
562             if (empty($updatekeys)) { // All keys? This does not support removing values.
563                 $updatekeys = array_keys($newinfo);
564             }
566             foreach ($updatekeys as $key) {
567                 if (isset($newinfo[$key])) {
568                     $value = $newinfo[$key];
569                 } else {
570                     $value = '';
571                 }
573                 if (!empty($this->config->{'field_updatelocal_' . $key})) {
574                     if (isset($user->{$key}) and $user->{$key} != $value) { // Only update if it's changed.
575                         $needsupdate = true;
576                         $updateuser->$key = $value;
577                     }
578                 }
579             }
580         }
581         if ($needsupdate) {
582             require_once($CFG->dirroot . '/user/lib.php');
583             $updateuser = $this->clean_data($updateuser);
584             user_update_user($updateuser);
585         }
586         return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
587     }
589     /**
590      * Called when the user record is updated.
591      * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
592      * compares information saved modified information to external db.
593      *
594      * @param stdClass $olduser     Userobject before modifications
595      * @param stdClass $newuser     Userobject new modified userobject
596      * @return boolean result
597      *
598      */
599     function user_update($olduser, $newuser) {
600         if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
601             error_log("ERROR:User renaming not allowed in ext db");
602             return false;
603         }
605         if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
606             return true; // Just change auth and skip update.
607         }
609         $curruser = $this->get_userinfo($olduser->username);
610         if (empty($curruser)) {
611             error_log("ERROR:User $olduser->username found in ext db");
612             return false;
613         }
615         $extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
617         $authdb = $this->db_init();
619         $update = array();
620         foreach($curruser as $key=>$value) {
621             if ($key == 'username') {
622                 continue; // Skip this.
623             }
624             if (empty($this->config->{"field_updateremote_$key"})) {
625                 continue; // Remote update not requested.
626             }
627             if (!isset($newuser->$key)) {
628                 continue;
629             }
630             $nuvalue = $newuser->$key;
631             // Support for textarea fields.
632             if (isset($nuvalue['text'])) {
633                 $nuvalue = $nuvalue['text'];
634             }
635             if ($nuvalue != $value) {
636                 $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
637             }
638         }
639         if (!empty($update)) {
640             $authdb->Execute("UPDATE {$this->config->table}
641                                  SET ".implode(',', $update)."
642                                WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'");
643         }
644         $authdb->Close();
645         return true;
646     }
648     /**
649      * A chance to validate form data, and last chance to
650      * do stuff before it is inserted in config_plugin
651      *
652      * @param stfdClass $form
653      * @param array $err errors
654      * @return void
655      */
656      function validate_form($form, &$err) {
657         if ($form->passtype === 'internal') {
658             $this->config->changepasswordurl = '';
659             set_config('changepasswordurl', '', 'auth/db');
660         }
661     }
663     function prevent_local_passwords() {
664         return !$this->is_internal();
665     }
667     /**
668      * Returns true if this authentication plugin is "internal".
669      *
670      * Internal plugins use password hashes from Moodle user table for authentication.
671      *
672      * @return bool
673      */
674     function is_internal() {
675         if (!isset($this->config->passtype)) {
676             return true;
677         }
678         return ($this->config->passtype === 'internal');
679     }
681     /**
682      * Returns false if this plugin is enabled but not configured.
683      *
684      * @return bool
685      */
686     public function is_configured() {
687         if (!empty($this->config->type)) {
688             return true;
689         }
690         return false;
691     }
693     /**
694      * Indicates if moodle should automatically update internal user
695      * records with data from external sources using the information
696      * from auth_plugin_base::get_userinfo().
697      *
698      * @return bool true means automatically copy data from ext to user table
699      */
700     function is_synchronised_with_external() {
701         return true;
702     }
704     /**
705      * Returns true if this authentication plugin can change the user's
706      * password.
707      *
708      * @return bool
709      */
710     function can_change_password() {
711         return ($this->is_internal() or !empty($this->config->changepasswordurl));
712     }
714     /**
715      * Returns the URL for changing the user's pw, or empty if the default can
716      * be used.
717      *
718      * @return moodle_url
719      */
720     function change_password_url() {
721         if ($this->is_internal() || empty($this->config->changepasswordurl)) {
722             // Standard form.
723             return null;
724         } else {
725             // Use admin defined custom url.
726             return new moodle_url($this->config->changepasswordurl);
727         }
728     }
730     /**
731      * Returns true if plugin allows resetting of internal password.
732      *
733      * @return bool
734      */
735     function can_reset_password() {
736         return $this->is_internal();
737     }
739     /**
740      * Prints a form for configuring this authentication plugin.
741      *
742      * This function is called from admin/auth.php, and outputs a full page with
743      * a form for configuring this plugin.
744      *
745      * @param stdClass $config
746      * @param array $err errors
747      * @param array $user_fields
748      * @return void
749      */
750     function config_form($config, $err, $user_fields) {
751         include 'config.html';
752     }
754     /**
755      * Processes and stores configuration data for this authentication plugin.
756      *
757      * @param srdClass $config
758      * @return bool always true or exception
759      */
760     function process_config($config) {
761         // set to defaults if undefined
762         if (!isset($config->host)) {
763             $config->host = 'localhost';
764         }
765         if (!isset($config->type)) {
766             $config->type = 'mysql';
767         }
768         if (!isset($config->sybasequoting)) {
769             $config->sybasequoting = 0;
770         }
771         if (!isset($config->name)) {
772             $config->name = '';
773         }
774         if (!isset($config->user)) {
775             $config->user = '';
776         }
777         if (!isset($config->pass)) {
778             $config->pass = '';
779         }
780         if (!isset($config->table)) {
781             $config->table = '';
782         }
783         if (!isset($config->fielduser)) {
784             $config->fielduser = '';
785         }
786         if (!isset($config->fieldpass)) {
787             $config->fieldpass = '';
788         }
789         if (!isset($config->passtype)) {
790             $config->passtype = 'plaintext';
791         }
792         if (!isset($config->extencoding)) {
793             $config->extencoding = 'utf-8';
794         }
795         if (!isset($config->setupsql)) {
796             $config->setupsql = '';
797         }
798         if (!isset($config->debugauthdb)) {
799             $config->debugauthdb = 0;
800         }
801         if (!isset($config->removeuser)) {
802             $config->removeuser = AUTH_REMOVEUSER_KEEP;
803         }
804         if (!isset($config->changepasswordurl)) {
805             $config->changepasswordurl = '';
806         }
808         // Save settings.
809         set_config('host',          $config->host,          'auth/db');
810         set_config('type',          $config->type,          'auth/db');
811         set_config('sybasequoting', $config->sybasequoting, 'auth/db');
812         set_config('name',          $config->name,          'auth/db');
813         set_config('user',          $config->user,          'auth/db');
814         set_config('pass',          $config->pass,          'auth/db');
815         set_config('table',         $config->table,         'auth/db');
816         set_config('fielduser',     $config->fielduser,     'auth/db');
817         set_config('fieldpass',     $config->fieldpass,     'auth/db');
818         set_config('passtype',      $config->passtype,      'auth/db');
819         set_config('extencoding',   trim($config->extencoding), 'auth/db');
820         set_config('setupsql',      trim($config->setupsql),'auth/db');
821         set_config('debugauthdb',   $config->debugauthdb,   'auth/db');
822         set_config('removeuser',    $config->removeuser,    'auth/db');
823         set_config('changepasswordurl', trim($config->changepasswordurl), 'auth/db');
825         return true;
826     }
828     /**
829      * Add slashes, we can not use placeholders or system functions.
830      *
831      * @param string $text
832      * @return string
833      */
834     function ext_addslashes($text) {
835         if (empty($this->config->sybasequoting)) {
836             $text = str_replace('\\', '\\\\', $text);
837             $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
838         } else {
839             $text = str_replace("'", "''", $text);
840         }
841         return $text;
842     }
844     /**
845      * Test if settings are ok, print info to output.
846      * @private
847      */
848     public function test_settings() {
849         global $CFG, $OUTPUT;
851         // NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit...
853         raise_memory_limit(MEMORY_HUGE);
855         if (empty($this->config->table)) {
856             echo $OUTPUT->notification('External table not specified.', 'notifyproblem');
857             return;
858         }
860         if (empty($this->config->fielduser)) {
861             echo $OUTPUT->notification('External user field not specified.', 'notifyproblem');
862             return;
863         }
865         $olddebug = $CFG->debug;
866         $olddisplay = ini_get('display_errors');
867         ini_set('display_errors', '1');
868         $CFG->debug = DEBUG_DEVELOPER;
869         $olddebugauthdb = $this->config->debugauthdb;
870         $this->config->debugauthdb = 1;
871         error_reporting($CFG->debug);
873         $adodb = $this->db_init();
875         if (!$adodb or !$adodb->IsConnected()) {
876             $this->config->debugauthdb = $olddebugauthdb;
877             $CFG->debug = $olddebug;
878             ini_set('display_errors', $olddisplay);
879             error_reporting($CFG->debug);
880             ob_end_flush();
882             echo $OUTPUT->notification('Cannot connect the database.', 'notifyproblem');
883             return;
884         }
886         $rs = $adodb->Execute("SELECT *
887                                  FROM {$this->config->table}
888                                 WHERE {$this->config->fielduser} <> 'random_unlikely_username'"); // Any unlikely name is ok here.
890         if (!$rs) {
891             echo $OUTPUT->notification('Can not read external table.', 'notifyproblem');
893         } else if ($rs->EOF) {
894             echo $OUTPUT->notification('External table is empty.', 'notifyproblem');
895             $rs->close();
897         } else {
898             $fields_obj = $rs->FetchObj();
899             $columns = array_keys((array)$fields_obj);
901             echo $OUTPUT->notification('External table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
902             $rs->close();
903         }
905         $adodb->Close();
907         $this->config->debugauthdb = $olddebugauthdb;
908         $CFG->debug = $olddebug;
909         ini_set('display_errors', $olddisplay);
910         error_reporting($CFG->debug);
911         ob_end_flush();
912     }
914     /**
915      * Clean the user data that comes from an external database.
916      *
917      * @param array $user the user data to be validated against properties definition.
918      * @return stdClass $user the cleaned user data.
919      */
920     public function clean_data($user) {
921         if (empty($user)) {
922             return $user;
923         }
925         foreach ($user as $field => $value) {
926             // Get the property parameter type and do the cleaning.
927             try {
928                 $property = core_user::get_property_definition($field);
929                 $user->$field = clean_param($value, $property['type']);
930             } catch (coding_exception $e) {
931                 debugging("The property '$field' could not be cleaned.", DEBUG_DEVELOPER);
932             }
933         }
935         return $user;
936     }