Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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/>.
16
 
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
 */
26
 
27
defined('MOODLE_INTERNAL') || die();
28
 
29
require_once($CFG->libdir.'/authlib.php');
30
 
31
/**
32
 * External database authentication plugin.
33
 */
34
class auth_plugin_db extends auth_plugin_base {
35
 
36
    /**
37
     * Constructor.
38
     */
39
    function __construct() {
40
        global $CFG;
41
        require_once($CFG->libdir.'/adodb/adodb.inc.php');
42
 
43
        $this->authtype = 'db';
44
        $this->config = get_config('auth_db');
45
        $this->errorlogtag = '[AUTH DB] ';
46
        if (empty($this->config->extencoding)) {
47
            $this->config->extencoding = 'utf-8';
48
        }
49
    }
50
 
51
    /**
52
     * Returns true if the username and password work and false if they are
53
     * wrong or don't exist.
54
     *
55
     * @param string $username The username
56
     * @param string $password The password
57
     * @return bool Authentication success or failure.
58
     */
59
    function user_login($username, $password) {
60
        global $CFG, $DB;
61
 
62
        if ($this->is_configured() === false) {
63
            debugging(get_string('auth_notconfigured', 'auth', $this->authtype));
64
            return false;
65
        }
66
 
67
        $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
68
        $extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
69
 
70
        if ($this->is_internal()) {
71
            // Lookup username externally, but resolve
72
            // password locally -- to support backend that
73
            // don't track passwords.
74
 
75
            if (isset($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_KEEP) {
76
                // No need to connect to external database in this case because users are never removed and we verify password locally.
77
                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
78
                    return validate_internal_user_password($user, $password);
79
                } else {
80
                    return false;
81
                }
82
            }
83
 
84
            $authdb = $this->db_init();
85
 
86
            $rs = $authdb->Execute("SELECT *
87
                                      FROM {$this->config->table}
88
                                     WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
89
            if (!$rs) {
90
                $authdb->Close();
91
                debugging(get_string('auth_dbcantconnect','auth_db'));
92
                return false;
93
            }
94
 
95
            if (!$rs->EOF) {
96
                $rs->Close();
97
                $authdb->Close();
98
                // User exists externally - check username/password internally.
99
                if ($user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
100
                    return validate_internal_user_password($user, $password);
101
                }
102
            } else {
103
                $rs->Close();
104
                $authdb->Close();
105
                // User does not exist externally.
106
                return false;
107
            }
108
 
109
        } else {
110
            // Normal case: use external db for both usernames and passwords.
111
 
112
            $authdb = $this->db_init();
113
 
114
            $rs = $authdb->Execute("SELECT {$this->config->fieldpass}
115
                                      FROM {$this->config->table}
116
                                     WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
117
            if (!$rs) {
118
                $authdb->Close();
119
                debugging(get_string('auth_dbcantconnect','auth_db'));
120
                return false;
121
            }
122
 
123
            if ($rs->EOF) {
124
                $authdb->Close();
125
                return false;
126
            }
127
 
128
            $fields = array_change_key_case($rs->fields, CASE_LOWER);
129
            $fromdb = $fields[strtolower($this->config->fieldpass)];
130
            $rs->Close();
131
            $authdb->Close();
132
 
133
            if ($this->config->passtype === 'plaintext') {
134
                return ($fromdb === $extpassword);
135
            } else if ($this->config->passtype === 'md5') {
136
                return (strtolower($fromdb) === md5($extpassword));
137
            } else if ($this->config->passtype === 'sha1') {
138
                return (strtolower($fromdb) === sha1($extpassword));
139
            } else if ($this->config->passtype === 'saltedcrypt') {
140
                return password_verify($extpassword, $fromdb);
141
            } else {
142
                return false;
143
            }
144
 
145
        }
146
    }
147
 
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
        }
158
 
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
        }
170
 
171
        return $authdb;
172
    }
173
 
174
    /**
175
     * Returns user attribute mappings between moodle and the external database.
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
        }
188
 
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
    }
197
 
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;
207
 
208
        $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
209
 
210
        $authdb = $this->db_init();
211
 
212
        // Array to map local fieldnames we want, to external fieldnames.
213
        $selectfields = $this->db_attributes();
214
 
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
            $fieldcount = 0;
220
            foreach ($selectfields as $localname=>$externalname) {
221
                // Without aliasing, multiple occurrences of the same external
222
                // name can coalesce in only occurrence in the result.
223
                $select[] = "$externalname AS F".$fieldcount;
224
                $fieldcount++;
225
            }
226
            $select = implode(', ', $select);
227
            $sql = "SELECT $select
228
                      FROM {$this->config->table}
229
                     WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
230
 
231
            if ($rs = $authdb->Execute($sql)) {
232
                if (!$rs->EOF) {
233
                    $fields = $rs->FetchRow();
234
                    // Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
235
                    $fields = array_values($fields);
236
                    foreach (array_keys($selectfields) as $index => $localname) {
237
                        $value = $fields[$index];
238
                        $result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
239
                     }
240
                 }
241
                 $rs->Close();
242
            }
243
        }
244
        $authdb->Close();
245
        return $result;
246
    }
247
 
248
    /**
249
     * Change a user's password.
250
     *
251
     * @param  stdClass  $user      User table object
252
     * @param  string  $newpassword Plaintext password
253
     * @return bool                 True on success
254
     */
255
    function user_update_password($user, $newpassword) {
256
        global $DB;
257
 
258
        if ($this->is_internal()) {
259
            $puser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
260
            // This will also update the stored hash to the latest algorithm
261
            // if the existing hash is using an out-of-date algorithm (or the
262
            // legacy md5 algorithm).
263
            if (update_internal_user_password($puser, $newpassword)) {
264
                $user->password = $puser->password;
265
                return true;
266
            } else {
267
                return false;
268
            }
269
        } else {
270
            // We should have never been called!
271
            return false;
272
        }
273
    }
274
 
275
    /**
276
     * Synchronizes user from external db to moodle user table.
277
     *
278
     * Sync should be done by using idnumber attribute, not username.
279
     * You need to pass firstsync parameter to function to fill in
280
     * idnumbers if they don't exists in moodle user table.
281
     *
282
     * Syncing users removes (disables) users that don't exists anymore in external db.
283
     * Creates new users and updates coursecreator status of users.
284
     *
285
     * This implementation is simpler but less scalable than the one found in the LDAP module.
286
     *
287
     * @param progress_trace $trace
288
     * @param bool $do_updates  Optional: set to true to force an update of existing accounts
289
     * @return int 0 means success, 1 means failure
290
     */
291
    function sync_users(progress_trace $trace, $do_updates=false) {
292
        global $CFG, $DB;
293
 
294
        require_once($CFG->dirroot . '/user/lib.php');
295
 
296
        // List external users.
297
        $userlist = $this->get_userlist();
298
 
299
        // Delete obsolete internal users.
300
        if (!empty($this->config->removeuser)) {
301
 
302
            $suspendselect = "";
303
            if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
304
                $suspendselect = "AND u.suspended = 0";
305
            }
306
 
307
            // Find obsolete users.
308
            if (count($userlist)) {
309
                $removeusers = array();
310
                $params['authtype'] = $this->authtype;
311
                $sql = "SELECT u.id, u.username
312
                          FROM {user} u
313
                         WHERE u.auth=:authtype
314
                           AND u.deleted=0
315
                           AND u.mnethostid=:mnethostid
316
                           $suspendselect";
317
                $params['mnethostid'] = $CFG->mnet_localhost_id;
318
                $internalusersrs = $DB->get_recordset_sql($sql, $params);
319
 
320
                $usernamelist = array_flip($userlist);
321
                foreach ($internalusersrs as $internaluser) {
322
                    if (!array_key_exists($internaluser->username, $usernamelist)) {
323
                        $removeusers[] = $internaluser;
324
                    }
325
                }
326
                $internalusersrs->close();
327
            } else {
328
                $sql = "SELECT u.id, u.username
329
                          FROM {user} u
330
                         WHERE u.auth=:authtype AND u.deleted=0 AND u.mnethostid=:mnethostid $suspendselect";
331
                $params = array();
332
                $params['authtype'] = $this->authtype;
333
                $params['mnethostid'] = $CFG->mnet_localhost_id;
334
                $removeusers = $DB->get_records_sql($sql, $params);
335
            }
336
 
337
            if (!empty($removeusers)) {
338
                $trace->output(get_string('auth_dbuserstoremove', 'auth_db', count($removeusers)));
339
 
340
                foreach ($removeusers as $user) {
341
                    if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
342
                        delete_user($user);
343
                        $trace->output(get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
344
                    } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
345
                        $updateuser = new stdClass();
346
                        $updateuser->id   = $user->id;
347
                        $updateuser->suspended = 1;
348
                        user_update_user($updateuser, false);
349
                        $trace->output(get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
350
                    }
351
                }
352
            }
353
            unset($removeusers);
354
        }
355
 
356
        if (!count($userlist)) {
357
            // Exit right here, nothing else to do.
358
            $trace->finished();
359
            return 0;
360
        }
361
 
362
        // Update existing accounts.
363
        if ($do_updates) {
364
            // Narrow down what fields we need to update.
365
            $all_keys = array_keys(get_object_vars($this->config));
366
            $updatekeys = array();
367
            foreach ($all_keys as $key) {
368
                if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
369
                    if ($this->config->{$key} === 'onlogin') {
370
                        array_push($updatekeys, $match[1]); // The actual key name.
371
                    }
372
                }
373
            }
374
            unset($all_keys); unset($key);
375
 
376
            // Only go ahead if we actually have fields to update locally.
377
            if (!empty($updatekeys)) {
378
                $update_users = array();
379
                // All the drivers can cope with chunks of 10,000. See line 4491 of lib/dml/tests/dml_est.php
380
                $userlistchunks = array_chunk($userlist , 10000);
381
                foreach($userlistchunks as $userlistchunk) {
382
                    list($in_sql, $params) = $DB->get_in_or_equal($userlistchunk, SQL_PARAMS_NAMED, 'u', true);
383
                    $params['authtype'] = $this->authtype;
384
                    $params['mnethostid'] = $CFG->mnet_localhost_id;
385
                    $sql = "SELECT u.id, u.username, u.suspended
386
                          FROM {user} u
387
                         WHERE u.auth = :authtype AND u.deleted = 0 AND u.mnethostid = :mnethostid AND u.username {$in_sql}";
388
                    $update_users = $update_users + $DB->get_records_sql($sql, $params);
389
                }
390
 
391
                if ($update_users) {
392
                    $trace->output("User entries to update: ".count($update_users));
393
 
394
                    foreach ($update_users as $user) {
395
                        if ($this->update_user_record($user->username, $updatekeys, false, (bool) $user->suspended)) {
396
                            $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)), 1);
397
                        } else {
398
                            $trace->output(get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id))." - ".get_string('skipped'), 1);
399
                        }
400
                    }
401
                    unset($update_users);
402
                }
403
            }
404
        }
405
 
406
 
407
        // Create missing accounts.
408
        // NOTE: this is very memory intensive and generally inefficient.
409
        $suspendselect = "";
410
        if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
411
            $suspendselect = "AND u.suspended = 0";
412
        }
413
        $sql = "SELECT u.id, u.username
414
                  FROM {user} u
415
                 WHERE u.auth=:authtype AND u.deleted='0' AND mnethostid=:mnethostid $suspendselect";
416
 
417
        $users = $DB->get_records_sql($sql, array('authtype'=>$this->authtype, 'mnethostid'=>$CFG->mnet_localhost_id));
418
 
419
        // Simplify down to usernames.
420
        $usernames = array();
421
        if (!empty($users)) {
422
            foreach ($users as $user) {
423
                array_push($usernames, $user->username);
424
            }
425
            unset($users);
426
        }
427
 
428
        $add_users = array_diff($userlist, $usernames);
429
        unset($usernames);
430
 
431
        if (!empty($add_users)) {
432
            $trace->output(get_string('auth_dbuserstoadd','auth_db',count($add_users)));
433
            // Do not use transactions around this foreach, we want to skip problematic users, not revert everything.
434
            foreach($add_users as $user) {
435
                $username = $user;
436
                if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
437
                    if ($olduser = $DB->get_record('user', array('username' => $username, 'deleted' => 0, 'suspended' => 1,
438
                            'mnethostid' => $CFG->mnet_localhost_id, 'auth' => $this->authtype))) {
439
                        $updateuser = new stdClass();
440
                        $updateuser->id = $olduser->id;
441
                        $updateuser->suspended = 0;
442
                        user_update_user($updateuser);
443
                        $trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name' => $username,
444
                            'id' => $olduser->id)), 1);
445
                        continue;
446
                    }
447
                }
448
 
449
                // Do not try to undelete users here, instead select suspending if you ever expect users will reappear.
450
 
451
                // Prep a few params.
452
                $user = $this->get_userinfo_asobj($user);
453
                $user->username   = $username;
454
                $user->confirmed  = 1;
455
                $user->auth       = $this->authtype;
456
                $user->mnethostid = $CFG->mnet_localhost_id;
457
 
458
                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')) {
459
                    $trace->output(get_string('auth_dbinsertuserduplicate', 'auth_db', array('username'=>$user->username, 'auth'=>$collision->auth)), 1);
460
                    continue;
461
                }
462
                try {
463
                    $id = user_create_user($user, false, false); // It is truly a new user.
464
                    $trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
465
                } catch (moodle_exception $e) {
466
                    $trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
467
                    continue;
468
                }
469
                // If relevant, tag for password generation.
470
                if ($this->is_internal()) {
471
                    set_user_preference('auth_forcepasswordchange', 1, $id);
472
                    set_user_preference('create_password',          1, $id);
473
                }
474
 
475
                // Save custom profile fields here.
476
                require_once($CFG->dirroot . '/user/profile/lib.php');
477
                $user->id = $id;
478
                profile_save_data($user);
479
 
480
                // Make sure user context is present.
481
                context_user::instance($id);
482
 
483
                \core\event\user_created::create_from_userid($id)->trigger();
484
            }
485
            unset($add_users);
486
        }
487
        $trace->finished();
488
        return 0;
489
    }
490
 
491
    function user_exists($username) {
492
 
493
        // Init result value.
494
        $result = false;
495
 
496
        $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
497
 
498
        $authdb = $this->db_init();
499
 
500
        $rs = $authdb->Execute("SELECT *
501
                                  FROM {$this->config->table}
502
                                 WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."' ");
503
 
504
        if (!$rs) {
505
            throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
506
        } else if (!$rs->EOF) {
507
            // User exists externally.
508
            $result = true;
509
        }
510
 
511
        $authdb->Close();
512
        return $result;
513
    }
514
 
515
 
516
    function get_userlist() {
517
 
518
        // Init result value.
519
        $result = array();
520
 
521
        $authdb = $this->db_init();
522
 
523
        // Fetch userlist.
524
        $rs = $authdb->Execute("SELECT {$this->config->fielduser}
525
                                  FROM {$this->config->table} ");
526
 
527
        if (!$rs) {
528
            throw new \moodle_exception('auth_dbcantconnect', 'auth_db');
529
        } else if (!$rs->EOF) {
530
            while ($rec = $rs->FetchRow()) {
531
                $rec = array_change_key_case((array)$rec, CASE_LOWER);
532
                array_push($result, $rec[strtolower($this->config->fielduser)]);
533
            }
534
        }
535
 
536
        $authdb->Close();
537
        return $result;
538
    }
539
 
540
    /**
541
     * Reads user information from DB and return it in an object.
542
     *
543
     * @param string $username username
544
     * @return stdClass
545
     */
546
    function get_userinfo_asobj($username) {
547
        $user_array = truncate_userinfo($this->get_userinfo($username));
548
        $user = new stdClass();
549
        foreach($user_array as $key=>$value) {
550
            $user->{$key} = $value;
551
        }
552
        return $user;
553
    }
554
 
555
    /**
556
     * Called when the user record is updated.
557
     * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
558
     * compares information saved modified information to external db.
559
     *
560
     * @param stdClass $olduser     Userobject before modifications
561
     * @param stdClass $newuser     Userobject new modified userobject
562
     * @return boolean result
563
     *
564
     */
565
    function user_update($olduser, $newuser) {
566
        if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) {
567
            error_log("ERROR:User renaming not allowed in ext db");
568
            return false;
569
        }
570
 
571
        if (isset($olduser->auth) and $olduser->auth != $this->authtype) {
572
            return true; // Just change auth and skip update.
573
        }
574
 
575
        $curruser = $this->get_userinfo($olduser->username);
576
        if (empty($curruser)) {
577
            error_log("ERROR:User $olduser->username found in ext db");
578
            return false;
579
        }
580
 
581
        $extusername = core_text::convert($olduser->username, 'utf-8', $this->config->extencoding);
582
 
583
        $authdb = $this->db_init();
584
 
585
        $update = array();
586
        foreach($curruser as $key=>$value) {
587
            if ($key == 'username') {
588
                continue; // Skip this.
589
            }
590
            if (empty($this->config->{"field_updateremote_$key"})) {
591
                continue; // Remote update not requested.
592
            }
593
            if (!isset($newuser->$key)) {
594
                continue;
595
            }
596
            $nuvalue = $newuser->$key;
597
            // Support for textarea fields.
598
            if (isset($nuvalue['text'])) {
599
                $nuvalue = $nuvalue['text'];
600
            }
601
            if ($nuvalue != $value) {
602
                $update[] = $this->config->{"field_map_$key"}."='".$this->ext_addslashes(core_text::convert($nuvalue, 'utf-8', $this->config->extencoding))."'";
603
            }
604
        }
605
        if (!empty($update)) {
606
            $sql = "UPDATE {$this->config->table}
607
                       SET ".implode(',', $update)."
608
                     WHERE {$this->config->fielduser} = ?";
609
            if (!$authdb->Execute($sql, array($this->ext_addslashes($extusername)))) {
610
                throw new \moodle_exception('auth_dbupdateerror', 'auth_db');
611
            }
612
        }
613
        $authdb->Close();
614
        return true;
615
    }
616
 
617
    function prevent_local_passwords() {
618
        return !$this->is_internal();
619
    }
620
 
621
    /**
622
     * Returns true if this authentication plugin is "internal".
623
     *
624
     * Internal plugins use password hashes from Moodle user table for authentication.
625
     *
626
     * @return bool
627
     */
628
    function is_internal() {
629
        if (!isset($this->config->passtype)) {
630
            return true;
631
        }
632
        return ($this->config->passtype === 'internal');
633
    }
634
 
635
    /**
636
     * Returns false if this plugin is enabled but not configured.
637
     *
638
     * @return bool
639
     */
640
    public function is_configured() {
641
        if (!empty($this->config->type)) {
642
            return true;
643
        }
644
        return false;
645
    }
646
 
647
    /**
648
     * Indicates if moodle should automatically update internal user
649
     * records with data from external sources using the information
650
     * from auth_plugin_base::get_userinfo().
651
     *
652
     * @return bool true means automatically copy data from ext to user table
653
     */
654
    function is_synchronised_with_external() {
655
        return true;
656
    }
657
 
658
    /**
659
     * Returns true if this authentication plugin can change the user's
660
     * password.
661
     *
662
     * @return bool
663
     */
664
    function can_change_password() {
665
        return ($this->is_internal() or !empty($this->config->changepasswordurl));
666
    }
667
 
668
    /**
669
     * Returns the URL for changing the user's pw, or empty if the default can
670
     * be used.
671
     *
672
     * @return moodle_url
673
     */
674
    function change_password_url() {
675
        if ($this->is_internal() || empty($this->config->changepasswordurl)) {
676
            // Standard form.
677
            return null;
678
        } else {
679
            // Use admin defined custom url.
680
            return new moodle_url($this->config->changepasswordurl);
681
        }
682
    }
683
 
684
    /**
685
     * Returns true if plugin allows resetting of internal password.
686
     *
687
     * @return bool
688
     */
689
    function can_reset_password() {
690
        return $this->is_internal();
691
    }
692
 
693
    /**
694
     * Add slashes, we can not use placeholders or system functions.
695
     *
696
     * @param string $text
697
     * @return string
698
     */
699
    function ext_addslashes($text) {
700
        if (empty($this->config->sybasequoting)) {
701
            $text = str_replace('\\', '\\\\', $text);
702
            $text = str_replace(array('\'', '"', "\0"), array('\\\'', '\\"', '\\0'), $text);
703
        } else {
704
            $text = str_replace("'", "''", $text);
705
        }
706
        return $text;
707
    }
708
 
709
    /**
710
     * Test if settings are ok, print info to output.
711
     * @private
712
     */
713
    public function test_settings() {
714
        global $CFG, $OUTPUT;
715
 
716
        // NOTE: this is not localised intentionally, admins are supposed to understand English at least a bit...
717
 
718
        raise_memory_limit(MEMORY_HUGE);
719
 
720
        if (empty($this->config->table)) {
721
            echo $OUTPUT->notification(get_string('auth_dbnoexttable', 'auth_db'), 'notifyproblem');
722
            return;
723
        }
724
 
725
        if (empty($this->config->fielduser)) {
726
            echo $OUTPUT->notification(get_string('auth_dbnouserfield', 'auth_db'), 'notifyproblem');
727
            return;
728
        }
729
 
730
        $olddebug = $CFG->debug;
731
        $olddisplay = ini_get('display_errors');
732
        ini_set('display_errors', '1');
733
        $CFG->debug = DEBUG_DEVELOPER;
734
        $olddebugauthdb = $this->config->debugauthdb;
735
        $this->config->debugauthdb = 1;
736
        error_reporting($CFG->debug);
737
 
738
        $adodb = $this->db_init();
739
 
740
        if (!$adodb or !$adodb->IsConnected()) {
741
            $this->config->debugauthdb = $olddebugauthdb;
742
            $CFG->debug = $olddebug;
743
            ini_set('display_errors', $olddisplay);
744
            error_reporting($CFG->debug);
745
            ob_end_flush();
746
 
747
            echo $OUTPUT->notification(get_string('auth_dbcannotconnect', 'auth_db'), 'notifyproblem');
748
            return;
749
        }
750
 
751
        $rs = $adodb->Execute("SELECT *
752
                                 FROM {$this->config->table}
753
                                WHERE {$this->config->fielduser} <> 'random_unlikely_username'"); // Any unlikely name is ok here.
754
 
755
        if (!$rs) {
756
            echo $OUTPUT->notification(get_string('auth_dbcannotreadtable', 'auth_db'), 'notifyproblem');
757
 
758
        } else if ($rs->EOF) {
759
            echo $OUTPUT->notification(get_string('auth_dbtableempty', 'auth_db'), 'notifyproblem');
760
            $rs->close();
761
 
762
        } else {
763
            $columns = array_keys($rs->fetchRow());
764
            echo $OUTPUT->notification(get_string('auth_dbcolumnlist', 'auth_db', implode(', ', $columns)), 'notifysuccess');
765
            $rs->close();
766
        }
767
 
768
        $adodb->Close();
769
 
770
        $this->config->debugauthdb = $olddebugauthdb;
771
        $CFG->debug = $olddebug;
772
        ini_set('display_errors', $olddisplay);
773
        error_reporting($CFG->debug);
774
        ob_end_flush();
775
    }
776
 
777
    /**
778
     * Clean the user data that comes from an external database.
779
     * @deprecated since 3.1, please use core_user::clean_data() instead.
780
     * @param array $user the user data to be validated against properties definition.
781
     * @return stdClass $user the cleaned user data.
782
     */
783
    public function clean_data($user) {
784
        debugging('The method clean_data() has been deprecated, please use core_user::clean_data() instead.',
785
            DEBUG_DEVELOPER);
786
        return core_user::clean_data($user);
787
    }
788
}