Skip to content
Snippets Groups Projects
Select Git revision
  • 1c51835a8067a72f91f991e19d825d16b5b80f30
  • master default protected
  • more_logging
  • MDL-58395
  • accountexpires
  • hsh_3.10
  • v3.10-r1
  • v3.9-r2
  • v3.9-r1
  • v3.8-r1
  • v3.7-r1
  • v3.6-r1
  • v3.5-r2
  • v3.5-r1
  • v3.4-r4
  • v3.4-r3
  • v3.4-r2
  • v3.4-r1
  • v3.3-r1
  • v3.2-r4
  • v3.2-r3
  • v3.2-r2
  • v3.2-r1
  • v3.1-r1
  • v3.0-r3
  • v3.0-r2
26 results

auth.php

Blame
  • auth.php 28.63 KiB
    <?php
    // This file is part of Moodle - http://moodle.org/
    //
    // Moodle is free software: you can redistribute it and/or modify
    // it under the terms of the GNU General Public License as published by
    // the Free Software Foundation, either version 3 of the License, or
    // (at your option) any later version.
    //
    // Moodle is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    //
    // You should have received a copy of the GNU General Public License
    // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
    
    /**
     * Auth plugin "LDAP SyncPlus"
     *
     * @package    auth_ldap_syncplus
     * @copyright  2014 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de>
     * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     */
    
    defined('MOODLE_INTERNAL') || die;
    
    // @codingStandardsIgnoreFile
    // Let codechecker ignore this file. This code mostly re-used from auth_ldap and the problems are already there and not made by us.
    
    global $CFG;
    
    require_once($CFG->libdir.'/authlib.php');
    require_once($CFG->libdir.'/ldaplib.php');
    require_once($CFG->dirroot.'/user/lib.php');
    require_once($CFG->dirroot.'/auth/ldap/locallib.php');
    require_once(__DIR__.'/../ldap/auth.php');
    require_once(__DIR__.'/locallib.php');
    
    /**
     * Auth plugin "LDAP SyncPlus" - Auth class
     *
     * @package    auth_ldap_syncplus
     * @copyright  2014 Alexander Bias, Ulm University <alexander.bias@uni-ulm.de>
     * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
     */
    class auth_plugin_ldap_syncplus extends auth_plugin_ldap {
    
        /**
         * Constructor with initialisation.
         */
        public function __construct() {
            $this->authtype = 'ldap_syncplus';
            $this->roleauth = 'auth_ldap';
            $this->errorlogtag = '[AUTH LDAP SYNCPLUS] ';
            $this->init_plugin($this->authtype);
        }
    
        /**
         * Old syntax of class constructor. Deprecated in PHP7.
         *
         * @deprecated since Moodle 3.1
         */
        public function auth_plugin_ldap_syncplus() {
            debugging('Use of class name as constructor is deprecated', DEBUG_DEVELOPER);
            self::__construct();
        }
    
    
        /**
         * Syncronizes user fron external LDAP server to moodle user table
         *
         * Sync is now using username attribute.
         *
         * Syncing users removes or suspends users that dont exists anymore in external LDAP.
         * Creates new users and updates coursecreator status of users.
         *
         * @param bool $do_updates will do pull in data updates from LDAP if relevant
         */
        function sync_users($do_updates=true) {
            global $CFG, $DB;
    
            require_once($CFG->dirroot . '/user/profile/lib.php');
    
            mtrace(get_string('connectingldap', 'auth_ldap'));
            $ldapconnection = $this->ldap_connect();
    
            $dbman = $DB->get_manager();
    
            // Define table user to be created.
            $table = new xmldb_table('tmp_extuser');
            $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
            $table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
            $table->add_field('mnethostid', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, null);
            $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
            $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));
    
            mtrace(get_string('creatingtemptable', 'auth_ldap', 'tmp_extuser'));
            $dbman->create_temp_table($table);
    
            // Get user's list from ldap to sql in a scalable fashion.
            // Prepare some data we'll need.
            $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')';
            $servercontrols = array();
    
            $contexts = explode(';', $this->config->contexts);
    
            if (!empty($this->config->create_context)) {
                array_push($contexts, $this->config->create_context);
            }
    
            $ldappagedresults = ldap_paged_results_supported($this->config->ldap_version, $ldapconnection);
            $ldapcookie = '';
            foreach ($contexts as $context) {
                $context = trim($context);
                if (empty($context)) {
                    continue;
                }
    
                do {
                    if ($ldappagedresults) {
                        // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 4.1).
                        if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                            // Before 7.3, use this function that was deprecated in PHP 7.4.
                            ldap_control_paged_result($ldapconnection, $this->config->pagesize, true, $ldapcookie);
                        } else {
                            // PHP 7.3 and up, use server controls.
                            $servercontrols = array(array(
                                'oid' => LDAP_CONTROL_PAGEDRESULTS, 'value' => array(
                                    'size' => $this->config->pagesize, 'cookie' => $ldapcookie)));
                        }
                    }
                    if ($this->config->search_sub) {
                        // Use ldap_search to find first user from subtree.
                        // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 4.1).
                        if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                            $ldapresult = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute));
                        } else {
                            $ldapresult = ldap_search($ldapconnection, $context, $filter, array($this->config->user_attribute),
                                0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
                        }
                    } else {
                        // Search only in this context.
                        // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 4.1).
                        if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                            $ldapresult = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute));
                        } else {
                            $ldapresult = ldap_list($ldapconnection, $context, $filter, array($this->config->user_attribute),
                                0, -1, -1, LDAP_DEREF_NEVER, $servercontrols);
                        }
                    }
                    if(!$ldapresult) {
                        continue;
                    }
                    if ($ldappagedresults) {
                        // Get next server cookie to know if we'll need to continue searching.
                        $ldapcookie = '';
                        // TODO: Remove the old branch of code once PHP 7.3.0 becomes required (Moodle 4.1).
                        if (version_compare(PHP_VERSION, '7.3.0', '<')) {
                            // Before 7.3, use this function that was deprecated in PHP 7.4.
                            $pagedresp = ldap_control_paged_result_response($ldapconnection, $ldapresult, $ldapcookie);
                            // Function ldap_control_paged_result_response() does not overwrite $ldapcookie if it fails, by
                            // setting this to null we avoid an infinite loop.
                            if ($pagedresp === false) {
                                $ldapcookie = null;
                            }
                        } else {
                            // Get next cookie from controls.
                            ldap_parse_result($ldapconnection, $ldapresult, $errcode, $matcheddn,
                                $errmsg, $referrals, $controls);
                            if (isset($controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'])) {
                                $ldapcookie = $controls[LDAP_CONTROL_PAGEDRESULTS]['value']['cookie'];
                            }
                        }
                    }
                    if ($entry = @ldap_first_entry($ldapconnection, $ldapresult)) {
                        do {
                            $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
                            $value = core_text::convert($value[0], $this->config->ldapencoding, 'utf-8');
                            $value = trim($value);
                            $this->ldap_bulk_insert($value);
                        } while ($entry = ldap_next_entry($ldapconnection, $entry));
                    }
                    unset($ldapresult); // Free mem.
                } while ($ldappagedresults && $ldapcookie !== null && $ldapcookie != '');
            }
    
            // If LDAP paged results were used, the current connection must be completely
            // closed and a new one created, to work without paged results from here on.
            if ($ldappagedresults) {
                $this->ldap_close(true);
                $ldapconnection = $this->ldap_connect();
            }
    
            // Preserve our user database.
            // If the temp table is empty, it probably means that something went wrong, exit
            // so as to avoid mass deletion of users; which is hard to undo.
            $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
            if ($count < 1) {
                mtrace(get_string('didntgetusersfromldap', 'auth_ldap'));
                $dbman->drop_table($table);
                $this->ldap_close();
                return false;
            } else {
                mtrace(get_string('gotcountrecordsfromldap', 'auth_ldap', $count));
            }
    
    
            // Non Grace Period Synchronisation.
            if ($this->config->removeuser != AUTH_REMOVEUSER_DELETEWITHGRACEPERIOD) {
    
                // User removal.
                // Find users in DB that aren't in ldap -- to be removed!
                // this is still not as scalable (but how often do we mass delete?).
    
                if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
                    $sql = "SELECT u.*
                              FROM {user} u
                         LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                             WHERE u.auth = :auth
                                   AND u.deleted = 0
                                   AND e.username IS NULL";
                    $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
    
                    if (!empty($remove_users)) {
                        mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));
    
                        foreach ($remove_users as $user) {
                            if (delete_user($user)) {
                                mtrace("\t".get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                            } else {
                                mtrace("\t".get_string('auth_dbdeleteusererror', 'auth_db', $user->username));
                            }
                        }
                    } else {
                        mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
                    }
                    unset($remove_users); // Free mem!
    
                } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
                    $sql = "SELECT u.*
                              FROM {user} u
                         LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                             WHERE u.auth = :auth
                                   AND u.deleted = 0
                                   AND u.suspended = 0
                                   AND e.username IS NULL";
                    $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
    
                    if (!empty($remove_users)) {
                        mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));
    
                        foreach ($remove_users as $user) {
                            $updateuser = new stdClass();
                            $updateuser->id = $user->id;
                            $updateuser->suspended = 1;
                            user_update_user($updateuser, false);
                            mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                            \core\session\manager::kill_user_sessions($user->id);
                        }
                    } else {
                        mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
                    }
                    unset($remove_users); // Free mem!
                }
    
                // Revive suspended users.
                if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
                    $sql = "SELECT u.id, u.username
                              FROM {user} u
                              JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                             WHERE (u.auth = 'nologin' OR (u.auth = ? AND u.suspended = 1)) AND u.deleted = 0";
                    // Note: 'nologin' is there for backwards compatibility.
                    $revive_users = $DB->get_records_sql($sql, array($this->authtype));
    
                    if (!empty($revive_users)) {
                        mtrace(get_string('userentriestorevive', 'auth_ldap', count($revive_users)));
    
                        foreach ($revive_users as $user) {
                            $updateuser = new stdClass();
                            $updateuser->id = $user->id;
                            $updateuser->auth = $this->authtype;
                            $updateuser->suspended = 0;
                            user_update_user($updateuser, false);
                            mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                        }
                    } else {
                        mtrace(get_string('nouserentriestorevive', 'auth_ldap'));
                    }
    
                    unset($revive_users);
                }
            }
    
            // Grace Period Synchronisation.
            else if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_DELETEWITHGRACEPERIOD) {
    
                // Revive suspended users.
                $sql = "SELECT u.id, u.username
                          FROM {user} u
                          JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE (u.auth = 'nologin' OR (u.auth = ? AND u.suspended = 1)) AND u.deleted = 0";
                // Note: 'nologin' is there for backwards compatibility.
                $revive_users = $DB->get_records_sql($sql, array($this->authtype));
    
                if (!empty($revive_users)) {
                    mtrace(get_string('userentriestorevive', 'auth_ldap', count($revive_users)));
    
                    foreach ($revive_users as $user) {
                        $updateuser = new stdClass();
                        $updateuser->id = $user->id;
                        $updateuser->auth = $this->authtype;
                        $updateuser->suspended = 0;
                        user_update_user($updateuser, false);
                        mtrace("\t".get_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                    }
                } else {
                    mtrace(get_string('nouserentriestorevive', 'auth_ldap'));
                }
                unset($revive_users);
    
                // User temporary suspending.
                $sql = "SELECT u.*
                          FROM {user} u
                     LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE u.auth = :auth
                               AND u.deleted = 0
                               AND u.suspended = 0
                               AND e.username IS NULL";
                $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
    
                if (!empty($remove_users)) {
                    mtrace(get_string('userentriestosuspend', 'auth_ldap_syncplus', count($remove_users)));
    
                    foreach ($remove_users as $user) {
                        $updateuser = new stdClass();
                        $updateuser->id = $user->id;
                        $updateuser->suspended = 1;
                        $updateuser->timemodified = time(); // Remember suspend time, abuse timemodified column for this.
                        user_update_user($updateuser, false);
                        mtrace("\t".get_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                        \core\session\manager::kill_user_sessions($user->id);
                    }
                } else {
                    mtrace(get_string('nouserentriestosuspend', 'auth_ldap_syncplus'));
                }
                unset($remove_users); // Free mem!
    
                // User complete removal.
                $sql = "SELECT u.*
                          FROM {user} u
                     LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = e.mnethostid)
                         WHERE u.auth = :auth
                               AND u.deleted = 0
                               AND e.username IS NULL";
                $remove_users = $DB->get_records_sql($sql, array('auth'=>$this->authtype));
    
                if (!empty($remove_users)) {
                    mtrace(get_string('userentriestoremove', 'auth_ldap', count($remove_users)));
    
                    foreach ($remove_users as $user) {
                        // Do only if user was suspended before grace period.
                        $graceperiod = max(intval($this->config->removeuser_graceperiod), 0);
                                // Fix problems if grace period setting was negative or no number.
                        if (time() - $user->timemodified >= $graceperiod * 24 * 3600) {
                            if (delete_user($user)) {
                                mtrace("\t".get_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)));
                            } else {
                                mtrace("\t".get_string('auth_dbdeleteusererror', 'auth_db', $user->username));
                            }
                            // Otherwise inform about ongoing grace period.
                        } else {
                            mtrace("\t".get_string('waitinginremovalqueue', 'auth_ldap_syncplus', array('days'=>$graceperiod, 'name'=>$user->username, 'id'=>$user->id)));
                        }
                    }
                } else {
                    mtrace(get_string('nouserentriestoremove', 'auth_ldap'));
                }
                unset($remove_users); // Free mem!
            }
    
            // User Updates - time-consuming (optional).
            if ($do_updates) {
                // Narrow down what fields we need to update.
                $updatekeys = $this->get_profile_keys();
            } else {
                mtrace(get_string('noupdatestobedone', 'auth_ldap'));
            }
            if ($do_updates and !empty($updatekeys)) { // run updates only if relevant.
                $users = $DB->get_records_sql('SELECT u.username, u.id
                                                 FROM {user} u
                                                WHERE u.deleted = 0 AND u.auth = ? AND u.mnethostid = ?',
                                              array($this->authtype, $CFG->mnet_localhost_id));
                if (!empty($users)) {
                    mtrace(get_string('userentriestoupdate', 'auth_ldap', count($users)));
    
                    $transaction = $DB->start_delegated_transaction();
                    $xcount = 0;
                    $maxxcount = 100;
    
                    foreach ($users as $user) {
                        $userinfo = $this->get_userinfo($user->username);
                        if (!$this->update_user_record($user->username, $updatekeys, true,
                                $this->is_user_suspended((object) $userinfo))) {
                            $skipped = ' - '.get_string('skipped');
                        }
                        else {
                            $skipped = '';
                        }
                        mtrace("\t".get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)).$skipped);
                        $xcount++;
    
                        // Update system roles, if needed.
                        $this->sync_roles($user);
                    }
                    $transaction->allow_commit();
                    unset($users); // free mem.
                }
            } else { // end do updates.
                mtrace(get_string('noupdatestobedone', 'auth_ldap'));
            }
    
            // User Additions.
            // Find users missing in DB that are in LDAP
            // and gives me a nifty object I don't want.
            // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin.
            if (!empty($this->config->sync_script_createuser_enabled) and $this->config->sync_script_createuser_enabled == 1) {
                $sql = 'SELECT e.id, e.username
                          FROM {tmp_extuser} e
                          LEFT JOIN {user} u ON (e.username = u.username AND e.mnethostid = u.mnethostid)
                         WHERE u.id IS NULL';
                $add_users = $DB->get_records_sql($sql);
    
                if (!empty($add_users)) {
                    mtrace(get_string('userentriestoadd', 'auth_ldap', count($add_users)));
    
                    $transaction = $DB->start_delegated_transaction();
                    foreach ($add_users as $user) {
                        $user = $this->get_userinfo_asobj($user->username);
    
                        // Prep a few params.
                        $user->modified   = time();
                        $user->confirmed  = 1;
                        $user->auth       = $this->authtype;
                        $user->mnethostid = $CFG->mnet_localhost_id;
                        // get_userinfo_asobj() might have replaced $user->username with the value
                        // from the LDAP server (which can be mixed-case). Make sure it's lowercase.
                        $user->username = trim(core_text::strtolower($user->username));
                        // It isn't possible to just rely on the configured suspension attribute since
                        // things like active directory use bit masks, other things using LDAP might
                        // do different stuff as well.
                        //
                        // The cast to int is a workaround for MDL-53959.
                        $user->suspended = (int)$this->is_user_suspended($user);
                        if (empty($user->lang)) {
                            $user->lang = $CFG->lang;
                        }
                        if (empty($user->calendartype)) {
                            $user->calendartype = $CFG->calendartype;
                        }
    
                        $id = user_create_user($user, false);
                        mtrace("\t".get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)));
                        $euser = $DB->get_record('user', array('id' => $id));
    
                        if (!empty($this->config->forcechangepassword)) {
                            set_user_preference('auth_forcepasswordchange', 1, $id);
                        }
    
                        // Save custom profile fields.
                        $this->update_user_record($user->username, $this->get_profile_keys(true), false);
    
                        // Add roles if needed.
                        $this->sync_roles($euser);
                    }
                    $transaction->allow_commit();
                    unset($add_users); // free mem
                } else {
                    mtrace(get_string('nouserstobeadded', 'auth_ldap'));
                }
            } else {
                mtrace(get_string('nouserstobeadded', 'auth_ldap'));
            }
    
            $dbman->drop_table($table);
            $this->ldap_close();
    
            return true;
        }
    
    
        /**
         * Support login via email ($CFG->authloginviaemail) for first-time LDAP logins
         * @return void
         */
        public function loginpage_hook() {
            global $CFG, $frm, $DB;
    
            // If $CFG->authloginviaemail is not set, users don't want to login by mail, call parent hook and return.
            if ($CFG->authloginviaemail != 1) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
    
            // Get submitted form data.
            $frm = data_submitted();
    
            // If there is no username submitted, there's nothing to do, call parent hook and return.
            if (empty($frm->username)) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
    
            // Clean username parameter to make sure that its an email address.
            $email = clean_param($frm->username, PARAM_EMAIL);
    
            // If we don't have an email adress, there's nothing to do, call parent hook and return.
            if ($email == '' || strpos($email, '@') == false) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
    
            // If there is an existing useraccount with this email adress as email address (then a Moodle account already exists and
            // the standard mechanism of $CFG->authloginviaemail will kick in automatically) or if there is an existing useraccount
            // with this email adress as username (which is not forbidden, so this useraccount has to be used), call parent hook and
            // return.
            if ($DB->count_records_select('user', '(username = :p1 OR email = :p2) AND deleted = 0',
                                            array('p1' => $email, 'p2' => $email)) > 0) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
    
            // Get auth plugin.
            $authplugin = get_auth_plugin('ldap_syncplus');
    
            // If there is no email field mapping configured, we don't know where we can find the email adress in LDAP,
            // call parent hook and return.
            if (empty($authplugin->config->field_map_email)) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
    
            // Prepare LDAP search.
            $contexts = explode(';', $authplugin->config->contexts);
            $filter = '(&('.$authplugin->config->field_map_email.'='.ldap_filter_addslashes($email).')'.
                    $authplugin->config->objectclass.')';
    
            // Connect to LDAP.
            $ldapconnection = $authplugin->ldap_connect();
    
            // Array for saving the user's ids which are found in the configured LDAP contexts.
            $uidsfound = array();
    
            // Look for users matching the given email adress in LDAP.
            foreach ($contexts as $context) {
                // Verify that the given context is valid.
                $context = trim($context);
                if (empty($context)) {
                    continue;
                }
    
                // Search LDAP.
                if ($authplugin->config->search_sub) {
                    // Use ldap_search to find first user from subtree.
                    $ldapresult = ldap_search($ldapconnection, $context, $filter, array($authplugin->config->user_attribute));
                } else {
                    // Search only in this context.
                    $ldapresult = ldap_list($ldapconnection, $context, $filter, array($authplugin->config->user_attribute));
                }
    
                // If there is no LDAP result or if the user was not found in this context, continue with next context.
                if (!$ldapresult || ldap_count_entries($ldapconnection, $ldapresult) == 0) {
                    continue;
                }
    
                // If there is not exactly one matching user, we can't continue, call parent hook and return.
                if (ldap_count_entries($ldapconnection, $ldapresult) != 1) {
                    parent::loginpage_hook(); // Call parent function to retain its functionality.
                    return;
                }
    
                // Get this one matching user entry.
                if (!$ldapentry = ldap_first_entry($ldapconnection, $ldapresult)) {
                    parent::loginpage_hook(); // Call parent function to retain its functionality.
                    return;
                }
    
                // Get the uid attribute's value(s) from this user entry.
                $values = ldap_get_values($ldapconnection, $ldapentry, $authplugin->config->user_attribute);
    
                // If there is not exactly one copy of the uid attribute in the LDAP user entry, we don't know which one to use,
                // call parent hook and return.
                if ($values['count'] != 1) {
                    parent::loginpage_hook(); // Call parent function to retain its functionality.
                    return;
                }
    
                // Remember this one user's uid attribute.
                $uidsfound[] = $values[0];
    
                unset($ldapresult); // Free mem!
            }
    
            // After we have checked all contexts, verify that we have found only one user in total.
            // If not, we can't continue, call parent hook and return.
            if (count($uidsfound) != 1) {
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
    
                // Success!
                // Replace the form data's username with the user attribute from LDAP, it will be held in the global $frm variable.
            } else {
                $frm->username = $uidsfound[0];
                parent::loginpage_hook(); // Call parent function to retain its functionality.
                return;
            }
        }
    }