2d7405eb60db57e66dc7707f66ea5a3f1007eab1
[moodle.git] / lib / authlib.php
1 <?php
3 // This file is part of Moodle - http://moodle.org/
4 //
5 // Moodle is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
9 //
10 // Moodle is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
18 /**
19  * Multiple plugin authentication Support library
20  *
21  * 2006-08-28  File created, AUTH return values defined.
22  *
23  * @package    core
24  * @subpackage auth
25  * @copyright  1999 onwards Martin Dougiamas  http://dougiamas.com
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
29 defined('MOODLE_INTERNAL') || die();
31 /**
32  * Returned when the login was successful.
33  */
34 define('AUTH_OK',     0);
36 /**
37  * Returned when the login was unsuccessful.
38  */
39 define('AUTH_FAIL',   1);
41 /**
42  * Returned when the login was denied (a reason for AUTH_FAIL).
43  */
44 define('AUTH_DENIED', 2);
46 /**
47  * Returned when some error occurred (a reason for AUTH_FAIL).
48  */
49 define('AUTH_ERROR',  4);
51 /**
52  * Authentication - error codes for user confirm
53  */
54 define('AUTH_CONFIRM_FAIL', 0);
55 define('AUTH_CONFIRM_OK', 1);
56 define('AUTH_CONFIRM_ALREADY', 2);
57 define('AUTH_CONFIRM_ERROR', 3);
59 # MDL-14055
60 define('AUTH_REMOVEUSER_KEEP', 0);
61 define('AUTH_REMOVEUSER_SUSPEND', 1);
62 define('AUTH_REMOVEUSER_FULLDELETE', 2);
64 /** Login attempt successful. */
65 define('AUTH_LOGIN_OK', 0);
67 /** Can not login because user does not exist. */
68 define('AUTH_LOGIN_NOUSER', 1);
70 /** Can not login because user is suspended. */
71 define('AUTH_LOGIN_SUSPENDED', 2);
73 /** Can not login, most probably password did not match. */
74 define('AUTH_LOGIN_FAILED', 3);
76 /** Can not login because user is locked out. */
77 define('AUTH_LOGIN_LOCKOUT', 4);
79 /** Can not login becauser user is not authorised. */
80 define('AUTH_LOGIN_UNAUTHORISED', 5);
82 /**
83  * Abstract authentication plugin.
84  *
85  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
86  * @package moodlecore
87  */
88 class auth_plugin_base {
90     /**
91      * The configuration details for the plugin.
92      * @var object
93      */
94     var $config;
96     /**
97      * Authentication plugin type - the same as db field.
98      * @var string
99      */
100     var $authtype;
101     /*
102      * The fields we can lock and update from/to external authentication backends
103      * @var array
104      */
105     var $userfields = array(
106         'firstname',
107         'lastname',
108         'email',
109         'city',
110         'country',
111         'lang',
112         'description',
113         'url',
114         'idnumber',
115         'institution',
116         'department',
117         'phone1',
118         'phone2',
119         'address',
120         'firstnamephonetic',
121         'lastnamephonetic',
122         'middlename',
123         'alternatename'
124     );
126     /**
127      * Moodle custom fields to sync with.
128      * @var array()
129      */
130     var $customfields = null;
132     /**
133      * This is the primary method that is used by the authenticate_user_login()
134      * function in moodlelib.php.
135      *
136      * This method should return a boolean indicating
137      * whether or not the username and password authenticate successfully.
138      *
139      * Returns true if the username and password work and false if they are
140      * wrong or don't exist.
141      *
142      * @param string $username The username (with system magic quotes)
143      * @param string $password The password (with system magic quotes)
144      *
145      * @return bool Authentication success or failure.
146      */
147     function user_login($username, $password) {
148         print_error('mustbeoveride', 'debug', '', 'user_login()' );
149     }
151     /**
152      * Returns true if this authentication plugin can change the users'
153      * password.
154      *
155      * @return bool
156      */
157     function can_change_password() {
158         //override if needed
159         return false;
160     }
162     /**
163      * Returns the URL for changing the users' passwords, or empty if the default
164      * URL can be used.
165      *
166      * This method is used if can_change_password() returns true.
167      * This method is called only when user is logged in, it may use global $USER.
168      * If you are using a plugin config variable in this method, please make sure it is set before using it,
169      * as this method can be called even if the plugin is disabled, in which case the config values won't be set.
170      *
171      * @return moodle_url url of the profile page or null if standard used
172      */
173     function change_password_url() {
174         //override if needed
175         return null;
176     }
178     /**
179      * Returns true if this authentication plugin can edit the users'
180      * profile.
181      *
182      * @return bool
183      */
184     function can_edit_profile() {
185         //override if needed
186         return true;
187     }
189     /**
190      * Returns the URL for editing the users' profile, or empty if the default
191      * URL can be used.
192      *
193      * This method is used if can_edit_profile() returns true.
194      * This method is called only when user is logged in, it may use global $USER.
195      *
196      * @return moodle_url url of the profile page or null if standard used
197      */
198     function edit_profile_url() {
199         //override if needed
200         return null;
201     }
203     /**
204      * Returns true if this authentication plugin is "internal".
205      *
206      * Internal plugins use password hashes from Moodle user table for authentication.
207      *
208      * @return bool
209      */
210     function is_internal() {
211         //override if needed
212         return true;
213     }
215     /**
216      * Returns false if this plugin is enabled but not configured.
217      *
218      * @return bool
219      */
220     public function is_configured() {
221         return false;
222     }
224     /**
225      * Indicates if password hashes should be stored in local moodle database.
226      * @return bool true means md5 password hash stored in user table, false means flag 'not_cached' stored there instead
227      */
228     function prevent_local_passwords() {
229         return !$this->is_internal();
230     }
232     /**
233      * Indicates if moodle should automatically update internal user
234      * records with data from external sources using the information
235      * from get_userinfo() method.
236      *
237      * @return bool true means automatically copy data from ext to user table
238      */
239     function is_synchronised_with_external() {
240         return !$this->is_internal();
241     }
243     /**
244      * Updates the user's password.
245      *
246      * In previous versions of Moodle, the function
247      * auth_user_update_password accepted a username as the first parameter. The
248      * revised function expects a user object.
249      *
250      * @param  object  $user        User table object
251      * @param  string  $newpassword Plaintext password
252      *
253      * @return bool                  True on success
254      */
255     function user_update_password($user, $newpassword) {
256         //override if needed
257         return true;
258     }
260     /**
261      * Called when the user record is updated.
262      * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
263      * compares information saved modified information to external db.
264      *
265      * @param mixed $olduser     Userobject before modifications    (without system magic quotes)
266      * @param mixed $newuser     Userobject new modified userobject (without system magic quotes)
267      * @return boolean true if updated or update ignored; false if error
268      *
269      */
270     function user_update($olduser, $newuser) {
271         //override if needed
272         return true;
273     }
275     /**
276      * User delete requested - internal user record is mared as deleted already, username not present anymore.
277      *
278      * Do any action in external database.
279      *
280      * @param object $user       Userobject before delete    (without system magic quotes)
281      * @return void
282      */
283     function user_delete($olduser) {
284         //override if needed
285         return;
286     }
288     /**
289      * Returns true if plugin allows resetting of internal password.
290      *
291      * @return bool
292      */
293     function can_reset_password() {
294         //override if needed
295         return false;
296     }
298     /**
299      * Returns true if plugin allows resetting of internal password.
300      *
301      * @return bool
302      */
303     function can_signup() {
304         //override if needed
305         return false;
306     }
308     /**
309      * Sign up a new user ready for confirmation.
310      * Password is passed in plaintext.
311      *
312      * @param object $user new user object
313      * @param boolean $notify print notice with link and terminate
314      */
315     function user_signup($user, $notify=true) {
316         //override when can signup
317         print_error('mustbeoveride', 'debug', '', 'user_signup()' );
318     }
320     /**
321      * Return a form to capture user details for account creation.
322      * This is used in /login/signup.php.
323      * @return moodle_form A form which edits a record from the user table.
324      */
325     function signup_form() {
326         global $CFG;
328         require_once($CFG->dirroot.'/login/signup_form.php');
329         return new login_signup_form(null, null, 'post', '', array('autocomplete'=>'on'));
330     }
332     /**
333      * Returns true if plugin allows confirming of new users.
334      *
335      * @return bool
336      */
337     function can_confirm() {
338         //override if needed
339         return false;
340     }
342     /**
343      * Confirm the new user as registered.
344      *
345      * @param string $username
346      * @param string $confirmsecret
347      */
348     function user_confirm($username, $confirmsecret) {
349         //override when can confirm
350         print_error('mustbeoveride', 'debug', '', 'user_confirm()' );
351     }
353     /**
354      * Checks if user exists in external db
355      *
356      * @param string $username (with system magic quotes)
357      * @return bool
358      */
359     function user_exists($username) {
360         //override if needed
361         return false;
362     }
364     /**
365      * return number of days to user password expires
366      *
367      * If userpassword does not expire it should return 0. If password is already expired
368      * it should return negative value.
369      *
370      * @param mixed $username username (with system magic quotes)
371      * @return integer
372      */
373     function password_expire($username) {
374         return 0;
375     }
376     /**
377      * Sync roles for this user - usually creator
378      *
379      * @param $user object user object (without system magic quotes)
380      */
381     function sync_roles($user) {
382         //override if needed
383     }
385     /**
386      * Read user information from external database and returns it as array().
387      * Function should return all information available. If you are saving
388      * this information to moodle user-table you should honour synchronisation flags
389      *
390      * @param string $username username
391      *
392      * @return mixed array with no magic quotes or false on error
393      */
394     function get_userinfo($username) {
395         //override if needed
396         return array();
397     }
399     /**
400      * Prints a form for configuring this authentication plugin.
401      *
402      * This function is called from admin/auth.php, and outputs a full page with
403      * a form for configuring this plugin.
404      *
405      * @param object $config
406      * @param object $err
407      * @param array $user_fields
408      */
409     function config_form($config, $err, $user_fields) {
410         //override if needed
411     }
413     /**
414      * A chance to validate form data, and last chance to
415      * do stuff before it is inserted in config_plugin
416      * @param object object with submitted configuration settings (without system magic quotes)
417      * @param array $err array of error messages
418      */
419      function validate_form($form, &$err) {
420         //override if needed
421     }
423     /**
424      * Processes and stores configuration data for this authentication plugin.
425      *
426      * @param object object with submitted configuration settings (without system magic quotes)
427      */
428     function process_config($config) {
429         //override if needed
430         return true;
431     }
433     /**
434      * Hook for overriding behaviour of login page.
435      * This method is called from login/index.php page for all enabled auth plugins.
436      *
437      * @global object
438      * @global object
439      */
440     function loginpage_hook() {
441         global $frm;  // can be used to override submitted login form
442         global $user; // can be used to replace authenticate_user_login()
444         //override if needed
445     }
447     /**
448      * Hook for overriding behaviour before going to the login page.
449      *
450      * This method is called from require_login from potentially any page for
451      * all enabled auth plugins and gives each plugin a chance to redirect
452      * directly to an external login page, or to instantly login a user where
453      * possible.
454      *
455      * If an auth plugin implements this hook, it must not rely on ONLY this
456      * hook in order to work, as there are many ways a user can browse directly
457      * to the standard login page. As a general rule in this case you should
458      * also implement the loginpage_hook as well.
459      *
460      */
461     function pre_loginpage_hook() {
462         // override if needed, eg by redirecting to an external login page
463         // or logging in a user:
464         // complete_user_login($user);
465     }
467     /**
468      * Pre user_login hook.
469      * This method is called from authenticate_user_login() right after the user
470      * object is generated. This gives the auth plugins an option to make adjustments
471      * before the verification process starts.
472      *
473      * @param object $user user object, later used for $USER
474      */
475     public function pre_user_login_hook(&$user) {
476         // Override if needed.
477     }
479     /**
480      * Post authentication hook.
481      * This method is called from authenticate_user_login() for all enabled auth plugins.
482      *
483      * @param object $user user object, later used for $USER
484      * @param string $username (with system magic quotes)
485      * @param string $password plain text password (with system magic quotes)
486      */
487     function user_authenticated_hook(&$user, $username, $password) {
488         //override if needed
489     }
491     /**
492      * Pre logout hook.
493      * This method is called from require_logout() for all enabled auth plugins,
494      *
495      * @global object
496      */
497     function prelogout_hook() {
498         global $USER; // use $USER->auth to find the plugin used for login
500         //override if needed
501     }
503     /**
504      * Hook for overriding behaviour of logout page.
505      * This method is called from login/logout.php page for all enabled auth plugins.
506      *
507      * @global object
508      * @global string
509      */
510     function logoutpage_hook() {
511         global $USER;     // use $USER->auth to find the plugin used for login
512         global $redirect; // can be used to override redirect after logout
514         //override if needed
515     }
517     /**
518      * Hook called before timing out of database session.
519      * This is useful for SSO and MNET.
520      *
521      * @param object $user
522      * @param string $sid session id
523      * @param int $timecreated start of session
524      * @param int $timemodified user last seen
525      * @return bool true means do not timeout session yet
526      */
527     function ignore_timeout_hook($user, $sid, $timecreated, $timemodified) {
528         return false;
529     }
531     /**
532      * Return the properly translated human-friendly title of this auth plugin
533      *
534      * @todo Document this function
535      */
536     function get_title() {
537         return get_string('pluginname', "auth_{$this->authtype}");
538     }
540     /**
541      * Get the auth description (from core or own auth lang files)
542      *
543      * @return string The description
544      */
545     function get_description() {
546         $authdescription = get_string("auth_{$this->authtype}description", "auth_{$this->authtype}");
547         return $authdescription;
548     }
550     /**
551      * Returns whether or not the captcha element is enabled.
552      *
553      * @abstract Implement in child classes
554      * @return bool
555      */
556     function is_captcha_enabled() {
557         return false;
558     }
560     /**
561      * Returns whether or not this authentication plugin can be manually set
562      * for users, for example, when bulk uploading users.
563      *
564      * This should be overriden by authentication plugins where setting the
565      * authentication method manually is allowed.
566      *
567      * @return bool
568      * @since Moodle 2.6
569      */
570     function can_be_manually_set() {
571         // Override if needed.
572         return false;
573     }
575     /**
576      * Returns a list of potential IdPs that this authentication plugin supports.
577      * This is used to provide links on the login page.
578      *
579      * @param string $wantsurl the relative url fragment the user wants to get to.  You can use this to compose a returnurl, for example
580      *
581      * @return array like:
582      *              array(
583      *                  array(
584      *                      'url' => 'http://someurl',
585      *                      'icon' => new pix_icon(...),
586      *                      'name' => get_string('somename', 'auth_yourplugin'),
587      *                 ),
588      *             )
589      */
590     function loginpage_idp_list($wantsurl) {
591         return array();
592     }
594     /**
595      * Return custom user profile fields.
596      *
597      * @return array list of custom fields.
598      */
599     public function get_custom_user_profile_fields() {
600         global $DB;
601         // If already retrieved then return.
602         if (!is_null($this->customfields)) {
603             return $this->customfields;
604         }
606         $this->customfields = array();
607         if ($proffields = $DB->get_records('user_info_field')) {
608             foreach ($proffields as $proffield) {
609                 $this->customfields[] = 'profile_field_'.$proffield->shortname;
610             }
611         }
612         unset($proffields);
614         return $this->customfields;
615     }
617     /**
618      * Post logout hook.
619      *
620      * This method is used after moodle logout by auth classes to execute server logout.
621      *
622      * @param stdClass $user clone of USER object before the user session was terminated
623      */
624     public function postlogout_hook($user) {
625     }
628 /**
629  * Verify if user is locked out.
630  *
631  * @param stdClass $user
632  * @return bool true if user locked out
633  */
634 function login_is_lockedout($user) {
635     global $CFG;
637     if ($user->mnethostid != $CFG->mnet_localhost_id) {
638         return false;
639     }
640     if (isguestuser($user)) {
641         return false;
642     }
644     if (empty($CFG->lockoutthreshold)) {
645         // Lockout not enabled.
646         return false;
647     }
649     if (get_user_preferences('login_lockout_ignored', 0, $user)) {
650         // This preference may be used for accounts that must not be locked out.
651         return false;
652     }
654     $locked = get_user_preferences('login_lockout', 0, $user);
655     if (!$locked) {
656         return false;
657     }
659     if (empty($CFG->lockoutduration)) {
660         // Locked out forever.
661         return true;
662     }
664     if (time() - $locked < $CFG->lockoutduration) {
665         return true;
666     }
668     login_unlock_account($user);
670     return false;
673 /**
674  * To be called after valid user login.
675  * @param stdClass $user
676  */
677 function login_attempt_valid($user) {
678     global $CFG;
680     // Note: user_loggedin event is triggered in complete_user_login().
682     if ($user->mnethostid != $CFG->mnet_localhost_id) {
683         return;
684     }
685     if (isguestuser($user)) {
686         return;
687     }
689     // Always unlock here, there might be some race conditions or leftovers when switching threshold.
690     login_unlock_account($user);
693 /**
694  * To be called after failed user login.
695  * @param stdClass $user
696  */
697 function login_attempt_failed($user) {
698     global $CFG;
700     if ($user->mnethostid != $CFG->mnet_localhost_id) {
701         return;
702     }
703     if (isguestuser($user)) {
704         return;
705     }
707     $count = get_user_preferences('login_failed_count', 0, $user);
708     $last = get_user_preferences('login_failed_last', 0, $user);
709     $sincescuccess = get_user_preferences('login_failed_count_since_success', $count, $user);
710     $sincescuccess = $sincescuccess + 1;
711     set_user_preference('login_failed_count_since_success', $sincescuccess, $user);
713     if (empty($CFG->lockoutthreshold)) {
714         // No threshold means no lockout.
715         // Always unlock here, there might be some race conditions or leftovers when switching threshold.
716         login_unlock_account($user);
717         return;
718     }
720     if (!empty($CFG->lockoutwindow) and time() - $last > $CFG->lockoutwindow) {
721         $count = 0;
722     }
724     $count = $count+1;
726     set_user_preference('login_failed_count', $count, $user);
727     set_user_preference('login_failed_last', time(), $user);
729     if ($count >= $CFG->lockoutthreshold) {
730         login_lock_account($user);
731     }
734 /**
735  * Lockout user and send notification email.
736  *
737  * @param stdClass $user
738  */
739 function login_lock_account($user) {
740     global $CFG;
742     if ($user->mnethostid != $CFG->mnet_localhost_id) {
743         return;
744     }
745     if (isguestuser($user)) {
746         return;
747     }
749     if (get_user_preferences('login_lockout_ignored', 0, $user)) {
750         // This user can not be locked out.
751         return;
752     }
754     $alreadylockedout = get_user_preferences('login_lockout', 0, $user);
756     set_user_preference('login_lockout', time(), $user);
758     if ($alreadylockedout == 0) {
759         $secret = random_string(15);
760         set_user_preference('login_lockout_secret', $secret, $user);
762         $oldforcelang = force_current_language($user->lang);
764         $site = get_site();
765         $supportuser = core_user::get_support_user();
767         $data = new stdClass();
768         $data->firstname = $user->firstname;
769         $data->lastname  = $user->lastname;
770         $data->username  = $user->username;
771         $data->sitename  = format_string($site->fullname);
772         $data->link      = $CFG->wwwroot.'/login/unlock_account.php?u='.$user->id.'&s='.$secret;
773         $data->admin     = generate_email_signoff();
775         $message = get_string('lockoutemailbody', 'admin', $data);
776         $subject = get_string('lockoutemailsubject', 'admin', format_string($site->fullname));
778         if ($message) {
779             // Directly email rather than using the messaging system to ensure its not routed to a popup or jabber.
780             email_to_user($user, $supportuser, $subject, $message);
781         }
783         force_current_language($oldforcelang);
784     }
787 /**
788  * Unlock user account and reset timers.
789  *
790  * @param stdClass $user
791  */
792 function login_unlock_account($user) {
793     unset_user_preference('login_lockout', $user);
794     unset_user_preference('login_failed_count', $user);
795     unset_user_preference('login_failed_last', $user);
797     // Note: do not clear the lockout secret because user might click on the link repeatedly.
800 /**
801  * Returns whether or not the captcha element is enabled, and the admin settings fulfil its requirements.
802  * @return bool
803  */
804 function signup_captcha_enabled() {
805     global $CFG;
806     $authplugin = get_auth_plugin($CFG->registerauth);
807     return !empty($CFG->recaptchapublickey) && !empty($CFG->recaptchaprivatekey) && $authplugin->is_captcha_enabled();
810 /**
811  * Validates the standard sign-up data (except recaptcha that is validated by the form element).
812  *
813  * @param  array $data  the sign-up data
814  * @param  array $files files among the data
815  * @return array list of errors, being the key the data element name and the value the error itself
816  * @since Moodle 3.2
817  */
818 function signup_validate_data($data, $files) {
819     global $CFG, $DB;
821     $errors = array();
822     $authplugin = get_auth_plugin($CFG->registerauth);
824     if ($DB->record_exists('user', array('username' => $data['username'], 'mnethostid' => $CFG->mnet_localhost_id))) {
825         $errors['username'] = get_string('usernameexists');
826     } else {
827         // Check allowed characters.
828         if ($data['username'] !== core_text::strtolower($data['username'])) {
829             $errors['username'] = get_string('usernamelowercase');
830         } else {
831             if ($data['username'] !== core_user::clean_field($data['username'], 'username')) {
832                 $errors['username'] = get_string('invalidusername');
833             }
835         }
836     }
838     // Check if user exists in external db.
839     // TODO: maybe we should check all enabled plugins instead.
840     if ($authplugin->user_exists($data['username'])) {
841         $errors['username'] = get_string('usernameexists');
842     }
844     if (! validate_email($data['email'])) {
845         $errors['email'] = get_string('invalidemail');
847     } else if ($DB->record_exists('user', array('email' => $data['email']))) {
848         $errors['email'] = get_string('emailexists').' <a href="forgot_password.php">'.get_string('newpassword').'?</a>';
849     }
850     if (empty($data['email2'])) {
851         $errors['email2'] = get_string('missingemail');
853     } else if ($data['email2'] != $data['email']) {
854         $errors['email2'] = get_string('invalidemail');
855     }
856     if (!isset($errors['email'])) {
857         if ($err = email_is_not_allowed($data['email'])) {
858             $errors['email'] = $err;
859         }
860     }
862     $errmsg = '';
863     if (!check_password_policy($data['password'], $errmsg)) {
864         $errors['password'] = $errmsg;
865     }
867     // Validate customisable profile fields. (profile_validation expects an object as the parameter with userid set).
868     $dataobject = (object)$data;
869     $dataobject->id = 0;
870     $errors += profile_validation($dataobject, $files);
872     return $errors;
875 /**
876  * Add the missing fields to a user that is going to be created
877  *
878  * @param  stdClass $user the new user object
879  * @return stdClass the user filled
880  * @since Moodle 3.2
881  */
882 function signup_setup_new_user($user) {
883     global $CFG;
885     $user->confirmed   = 0;
886     $user->lang        = current_language();
887     $user->firstaccess = 0;
888     $user->timecreated = time();
889     $user->mnethostid  = $CFG->mnet_localhost_id;
890     $user->secret      = random_string(15);
891     $user->auth        = $CFG->registerauth;
892     // Initialize alternate name fields to empty strings.
893     $namefields = array_diff(get_all_user_name_fields(), useredit_get_required_name_fields());
894     foreach ($namefields as $namefield) {
895         $user->$namefield = '';
896     }
897     return $user;
900 /**
901  * Check if user confirmation is enabled on this site and return the auth plugin handling registration if enabled.
902  *
903  * @return stdClass the current auth plugin handling user registration or false if registration not enabled
904  * @since Moodle 3.2
905  */
906 function signup_get_user_confirmation_authplugin() {
907     global $CFG;
909     if (empty($CFG->registerauth)) {
910         return false;
911     }
912     $authplugin = get_auth_plugin($CFG->registerauth);
914     if (!$authplugin->can_confirm()) {
915         return false;
916     }
917     return $authplugin;