MDL-52781 auth_db: deprecate clean_data method.
[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                         user_update_user($updateuser, false);
332                         $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
333                     }
334                 }
335             }
336             unset($remove_users);
337         }
339         if (!count($userlist)) {
340             // Exit right here, nothing else to do.
341             $trace->finished();
342             return 0;
343         }
345         // Update existing accounts.
346         if ($do_updates) {
347             // Narrow down what fields we need to update.
348             $all_keys = array_keys(get_object_vars($this->config));
349             $updatekeys = array();
350             foreach ($all_keys as $key) {
351                 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
352                     if ($this->config->{$key} === 'onlogin') {
353                         array_push($updatekeys, $match[1]); // The actual key name.
354                     }
355                 }
356             }
357             unset($all_keys); unset($key);
359             // Only go ahead if we actually have fields to update locally.
360             if (!empty($updatekeys)) {
361                 list($in_sql, $params) = $DB->get_in_or_equal($userlist, SQL_PARAMS_NAMED, 'u', true);
362                 $params['authtype'] = $this->authtype;
363                 $sql = "SELECT u.id, u.username
364                           FROM {user} u
365                          WHERE u.auth=:authtype AND u.deleted=0 AND u.username {$in_sql}";
366                 if ($update_users = $DB->get_records_sql($sql, $params)) {
367                     $trace->output("User entries to update: ".count($update_users));
369                     foreach ($update_users as $user) {
370                         if ($this->update_user_record($user->username, $updatekeys)) {
371                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
372                         } else {
373                             $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
374                         }
375                     }
376                     unset($update_users);
377                 }
378             }
379         }
382         // Create missing accounts.
383         // NOTE: this is very memory intensive and generally inefficient.
384         $suspendselect = "";
385         if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
386             $suspendselect = "AND u.suspended = 0";
387         }
388         $sql = "SELECT u.id, u.username
389                   FROM {user} u
390                  WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
392         $users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
394         // Simplify down to usernames.
395         $usernames = array();
396         if (!empty($users)) {
397             foreach ($users as $user) {
398                 array_push($usernames, $user->username);
399             }
400             unset($users);
401         }
403         $add_users = array_diff($userlist, $usernames);
404         unset($usernames);
406         if (!empty($add_users)) {
407             $trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
408             // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
409             foreach($add_users as $user) {
410                 $username = $user;
411                 if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
412                     if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1,
413                             'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) {
414                         $updateuser = new stdClass();
415                         $updateuser->id = $olduser->id;
416                         $updateuser->suspended = 0;
417                         user_update_user($updateuser);
418                         $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
419                             'id' => $olduser->id)), 1);
420                         continue;
421                     }
422                 }
424                 // Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
426                 // Prep a few params.
427                 $user = $this->get_userinfo_asobj($user);
428                 $user->username   = $username;
429                 $user->confirmed  = 1;
430                 $user->auth       = $this->authtype;
431                 $user->mnethostid = $CFG->mnet_localhost_id;
432                 if (empty($user->lang)) {
433                     $user->lang = $CFG->lang;
434                 }
435                 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')) {
436                     $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
437                     continue;
438                 }
439                 try {
440                     $id = user_create_user($user, false); // It is truly a new user.
441                     $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
442                 } catch (moodle_exception $e) {
443                     $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
444                     continue;
445                 }
446                 // If relevant, tag for password generation.
447                 if ($this->is_internal()) {
448                     set_user_preference('auth_forcepasswordchange', 1, $id);
449                     set_user_preference('create_password',          1, $id);
450                 }
451                 // Make sure user context is present.
452                 context_user::instance($id);
453             }
454             unset($add_users);
455         }
456         $trace->finished();
457         return 0;
458     }
460     function user_exists($username) {
462         // Init result value.
463         $result = false;
465         $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
467         $authdb = $this->db_init();
469         $rs = $authdb->Execute("SELECT *
470                                   FROM {$this->config->table}
471                                  WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
473         if (!$rs) {
474             print_error('auth_dbcantconnect','auth_db');
475         } else if (!$rs->EOF) {
476             // User exists externally.
477             $result = true;
478         }
480         $authdb->Close();
481         return $result;
482     }
485     function get_userlist() {
487         // Init result value.
488         $result = array();
490         $authdb = $this->db_init();
492         // Fetch userlist.
493         $rs = $authdb->Execute("SELECT {$this->config->fielduser}
494                                   FROM {$this->config->table} ");
496         if (!$rs) {
497             print_error('auth_dbcantconnect','auth_db');
498         } else if (!$rs->EOF) {
499             while ($rec = $rs->FetchRow()) {
500                 $rec = array_change_key_case((array)$rec, CASE_LOWER);
501                 array_push($result, $rec[strtolower($this->config->fielduser)]);
502             }
503         }
505         $authdb->Close();
506         return $result;
507     }
509     /**
510      * Reads user information from DB and return it in an object.
511      *
512      * @param string $username username
513      * @return array
514      */
515     function get_userinfo_asobj($username) {
516         $user_array = truncate_userinfo($this->get_userinfo($username));
517         $user = new stdClass();
518         foreach($user_array as $key=>$value) {
519             $user->{$key} = $value;
520         }
521         return $user;
522     }
524     /**
525      * will update a local user record from an external source.
526      * is a lighter version of the one in moodlelib -- won't do
527      * expensive ops such as enrolment.
528      *
529      * If you don't pass $updatekeys, there is a performance hit and
530      * values removed from DB won't be removed from moodle.
531      *
532      * @param string $username username
533      * @param bool $updatekeys
534      * @return stdClass
535      */
536     function update_user_record($username, $updatekeys=false) {
537         global $CFG, $DB;
539         //just in case check text case
540         $username = trim(core_text::strtolower($username));
542         // get the current user record
543         $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
544         if (empty($user)) { // trouble
545             error_log("Cannot update non-existent user: $username");
546             print_error('auth_dbusernotexist','auth_db',$username);
547             die;
548         }
550         // Ensure userid is not overwritten.
551         $userid = $user->id;
552         $needsupdate = false;
554         $updateuser = new stdClass();
555         $updateuser->id = $userid;
556         if ($newinfo = $this->get_userinfo($username)) {
557             $newinfo = truncate_userinfo($newinfo);
559             if (empty($updatekeys)) { // All keys? This does not support removing values.
560                 $updatekeys = array_keys($newinfo);
561             }
563             foreach ($updatekeys as $key) {
564                 if (isset($newinfo[$key])) {
565                     $value = $newinfo[$key];
566                 } else {
567                     $value = '';
568                 }
570                 if (!empty($this->config->{'field_updatelocal_' . $key})) {
571                     if (isset($user->{$key}) and $user->{$key} != $value) { // Only update if it's changed.
572                         $needsupdate = true;
573                         $updateuser->$key = $value;
574                     }
575                 }
576             }
577         }
578         if ($needsupdate) {
579             require_once($CFG->dirroot . '/user/lib.php');
580             user_update_user($updateuser);
581         }
582         return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
583     }
585     /**
586      * Called when the user record is updated.
587      * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
588      * compares information saved modified information to external db.
589      *
590      * @param stdClass $olduser     Userobject before modifications
591      * @param stdClass $newuser     Userobject new modified userobject
592      * @return boolean result
593      *
594      */
595     function user_update($olduser, $newuser) {
596         if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
597             error_log("ERROR:User renaming not allowed in ext db");
598             return false;
599         }
601         if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
602             return true; // Just change auth and skip update.
603         }
605         $curruser = $this->get_userinfo($olduser->username);
606         if (empty($curruser)) {
607             error_log("ERROR:User $olduser->username found in ext db");
608             return false;
609         }
611         $extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
613         $authdb = $this->db_init();
615         $update = array();
616         foreach($curruser as $key=>$value) {
617             if ($key == 'username') {
618                 continue; // Skip this.
619             }
620             if (empty($this->config->{"field_updateremote_$key"})) {
621                 continue; // Remote update not requested.
622             }
623             if (!isset($newuser->$key)) {
624                 continue;
625             }
626             $nuvalue = $newuser->$key;
627             // Support for textarea fields.
628             if (isset($nuvalue['text'])) {
629                 $nuvalue = $nuvalue['text'];
630             }
631             if ($nuvalue != $value) {
632                 $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
633             }
634         }
635         if (!empty($update)) {
636             $authdb->Execute("UPDATE {$this->config->table}
637                                  SET ".implode(',', $update)."
638                                WHERE {$this->config->fielduser}='".$this->ext_addslashes($extusername)."'");
639         }
640         $authdb->Close();
641         return true;
642     }
644     /**
645      * A chance to validate form data, and last chance to
646      * do stuff before it is inserted in config_plugin
647      *
648      * @param stfdClass $form
649      * @param array $err errors
650      * @return void
651      */
652      function validate_form($form, &$err) {
653         if ($form->passtype === 'internal') {
654             $this->config->changepasswordurl = '';
655             set_config('changepasswordurl', '', 'auth/db');
656         }
657     }
659     function prevent_local_passwords() {
660         return !$this->is_internal();
661     }
663     /**
664      * Returns true if this authentication plugin is "internal".
665      *
666      * Internal plugins use password hashes from Moodle user table for authentication.
667      *
668      * @return bool
669      */
670     function is_internal() {
671         if (!isset($this->config->passtype)) {
672             return true;
673         }
674         return ($this->config->passtype === 'internal');
675     }
677     /**
678      * Returns false if this plugin is enabled but not configured.
679      *
680      * @return bool
681      */
682     public function is_configured() {
683         if (!empty($this->config->type)) {
684             return true;
685         }
686         return false;
687     }
689     /**
690      * Indicates if moodle should automatically update internal user
691      * records with data from external sources using the information
692      * from auth_plugin_base::get_userinfo().
693      *
694      * @return bool true means automatically copy data from ext to user table
695      */
696     function is_synchronised_with_external() {
697         return true;
698     }
700     /**
701      * Returns true if this authentication plugin can change the user's
702      * password.
703      *
704      * @return bool
705      */
706     function can_change_password() {
707         return ($this->is_internal() or !empty($this->config->changepasswordurl));
708     }
710     /**
711      * Returns the URL for changing the user's pw, or empty if the default can
712      * be used.
713      *
714      * @return moodle_url
715      */
716     function change_password_url() {
717         if ($this->is_internal() || empty($this->config->changepasswordurl)) {
718             // Standard form.
719             return null;
720         } else {
721             // Use admin defined custom url.
722             return new moodle_url($this->config->changepasswordurl);
723         }
724     }
726     /**
727      * Returns true if plugin allows resetting of internal password.
728      *
729      * @return bool
730      */
731     function can_reset_password() {
732         return $this->is_internal();
733     }
735     /**
736      * Prints a form for configuring this authentication plugin.
737      *
738      * This function is called from admin/auth.php, and outputs a full page with
739      * a form for configuring this plugin.
740      *
741      * @param stdClass $config
742      * @param array $err errors
743      * @param array $user_fields
744      * @return void
745      */
746     function config_form($config, $err, $user_fields) {
747         include 'config.html';
748     }
750     /**
751      * Processes and stores configuration data for this authentication plugin.
752      *
753      * @param srdClass $config
754      * @return bool always true or exception
755      */
756     function process_config($config) {
757         // set to defaults if undefined
758         if (!isset($config->host)) {
759             $config->host = 'localhost';
760         }
761         if (!isset($config->type)) {
762             $config->type = 'mysql';
763         }
764         if (!isset($config->sybasequoting)) {
765             $config->sybasequoting = 0;
766         }
767         if (!isset($config->name)) {
768             $config->name = '';
769         }
770         if (!isset($config->user)) {
771             $config->user = '';
772         }
773         if (!isset($config->pass)) {
774             $config->pass = '';
775         }
776         if (!isset($config->table)) {
777             $config->table = '';
778         }
779         if (!isset($config->fielduser)) {
780             $config->fielduser = '';
781         }
782         if (!isset($config->fieldpass)) {
783             $config->fieldpass = '';
784         }
785         if (!isset($config->passtype)) {
786             $config->passtype = 'plaintext';
787         }
788         if (!isset($config->extencoding)) {
789             $config->extencoding = 'utf-8';
790         }
791         if (!isset($config->setupsql)) {
792             $config->setupsql = '';
793         }
794         if (!isset($config->debugauthdb)) {
795             $config->debugauthdb = 0;
796         }
797         if (!isset($config->removeuser)) {
798             $config->removeuser = AUTH_REMOVEUSER_KEEP;
799         }
800         if (!isset($config->changepasswordurl)) {
801             $config->changepasswordurl = '';
802         }
804         // Save settings.
805         set_config('host',          $config->host,          'auth/db');
806         set_config('type',          $config->type,          'auth/db');
807         set_config('sybasequoting', $config->sybasequoting, 'auth/db');
808         set_config('name',          $config->name,          'auth/db');
809         set_config('user',          $config->user,          'auth/db');
810         set_config('pass',          $config->pass,          'auth/db');
811         set_config('table',         $config->table,         'auth/db');
812         set_config('fielduser',     $config->fielduser,     'auth/db');
813         set_config('fieldpass',     $config->fieldpass,     'auth/db');
814         set_config('passtype',      $config->passtype,      'auth/db');
815         set_config('extencoding',   trim($config->extencoding), 'auth/db');
816         set_config('setupsql',      trim($config->setupsql),'auth/db');
817         set_config('debugauthdb',   $config->debugauthdb,   'auth/db');
818         set_config('removeuser',    $config->removeuser,    'auth/db');
819         set_config('changepasswordurl', trim($config->changepasswordurl), 'auth/db');
821         return true;
822     }
824     /**
825      * Add slashes, we can not use placeholders or system functions.
826      *
827      * @param string $text
828      * @return string
829      */
830     function ext_addslashes($text) {
831         if (empty($this->config->sybasequoting)) {
832             $text = str_replace('\\', '\\\\', $text);
833             $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
834         } else {
835             $text = str_replace("'", "''", $text);
836         }
837         return $text;
838     }
840     /**
841      * Test if settings are ok, print info to output.
842      * @private
843      */
844     public function test_settings() {
845         global $CFG, $OUTPUT;
847         // NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit...
849         raise_memory_limit(MEMORY_HUGE);
851         if (empty($this->config->table)) {
852             echo $OUTPUT->notification('External table not specified.', 'notifyproblem');
853             return;
854         }
856         if (empty($this->config->fielduser)) {
857             echo $OUTPUT->notification('External user field not specified.', 'notifyproblem');
858             return;
859         }
861         $olddebug = $CFG->debug;
862         $olddisplay = ini_get('display_errors');
863         ini_set('display_errors', '1');
864         $CFG->debug = DEBUG_DEVELOPER;
865         $olddebugauthdb = $this->config->debugauthdb;
866         $this->config->debugauthdb = 1;
867         error_reporting($CFG->debug);
869         $adodb = $this->db_init();
871         if (!$adodb or !$adodb->IsConnected()) {
872             $this->config->debugauthdb = $olddebugauthdb;
873             $CFG->debug = $olddebug;
874             ini_set('display_errors', $olddisplay);
875             error_reporting($CFG->debug);
876             ob_end_flush();
878             echo $OUTPUT->notification('Cannot connect the database.', 'notifyproblem');
879             return;
880         }
882         $rs = $adodb->Execute("SELECT *
883                                  FROM {$this->config->table}
884                                 WHERE {$this->config->fielduser} <> 'random_unlikely_username'"); // Any unlikely name is ok here.
886         if (!$rs) {
887             echo $OUTPUT->notification('Can not read external table.', 'notifyproblem');
889         } else if ($rs->EOF) {
890             echo $OUTPUT->notification('External table is empty.', 'notifyproblem');
891             $rs->close();
893         } else {
894             $fields_obj = $rs->FetchObj();
895             $columns = array_keys((array)$fields_obj);
897             echo $OUTPUT->notification('External table contains following columns:<br />'.implode(', ', $columns), 'notifysuccess');
898             $rs->close();
899         }
901         $adodb->Close();
903         $this->config->debugauthdb = $olddebugauthdb;
904         $CFG->debug = $olddebug;
905         ini_set('display_errors', $olddisplay);
906         error_reporting($CFG->debug);
907         ob_end_flush();
908     }
910     /**
911      * Clean the user data that comes from an external database.
912      * @deprecated since 3.1, please use core_user::clean_data() instead.
913      * @param array $user the user data to be validated against properties definition.
914      * @return stdClass $user the cleaned user data.
915      */
916     public function clean_data($user) {
917         debugging('The method clean_data() has been deprecated, please use core_user::clean_data() instead.',
918             DEBUG_DEVELOPER);
919         return core_user::clean_data($user);
920     }