Merge branch 'MDL-73703-master' of https://github.com/davewoloszyn/moodle

This commit is contained in:
Ilya Tregubov 2024-02-06 12:37:51 +08:00
commit fa91b1e12a
7 changed files with 201 additions and 43 deletions

View File

@ -668,16 +668,29 @@ class auth_plugin_ldap extends auth_plugin_base {
}
/**
* Syncronizes user fron external LDAP server to moodle user table
* Synchronise users from the external LDAP server to Moodle's user table.
*
* Calls sync_users_update_callback() with default callback if appropriate.
*
* @param bool $doupdates will do pull in data updates from LDAP if relevant
* @return bool success
*/
public function sync_users($doupdates = true) {
return $this->sync_users_update_callback($doupdates ? [$this, 'update_users'] : null);
}
/**
* Synchronise users from the external LDAP server to Moodle's user table (callback).
*
* 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
* @param callable|null $updatecallback will do pull in data updates from LDAP if relevant
* @return bool success
*/
function sync_users($do_updates=true) {
public function sync_users_update_callback(?callable $updatecallback = null): bool {
global $CFG, $DB;
require_once($CFG->dirroot . '/user/profile/lib.php');
@ -861,40 +874,24 @@ class auth_plugin_ldap extends auth_plugin_base {
unset($revive_users);
}
/// User Updates - time-consuming (optional)
if ($do_updates) {
// Narrow down what fields we need to update
$updatekeys = $this->get_profile_keys();
} else {
print_string('noupdatestobedone', 'auth_ldap');
}
if ($do_updates and !empty($updatekeys)) { // run updates only if relevant
if ($updatecallback && $updatekeys = $this->get_profile_keys()) { // 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)) {
print_string('userentriestoupdate', 'auth_ldap', count($users));
foreach ($users as $user) {
$transaction = $DB->start_delegated_transaction();
echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id));
$userinfo = $this->get_userinfo($user->username);
if (!$this->update_user_record($user->username, $updatekeys, true,
$this->is_user_suspended((object) $userinfo))) {
echo ' - '.get_string('skipped');
// Update users in chunks as specified in sync_updateuserchunk.
if (!empty($this->config->sync_updateuserchunk)) {
foreach (array_chunk($users, $this->config->sync_updateuserchunk) as $chunk) {
call_user_func($updatecallback, $chunk, $updatekeys);
}
echo "\n";
// Update system roles, if needed.
$this->sync_roles($user);
$transaction->allow_commit();
} else {
call_user_func($updatecallback, $users, $updatekeys);
}
unset($users); // free mem
unset($users); // Free mem.
}
} else { // end do updates
} else {
print_string('noupdatestobedone', 'auth_ldap');
}
@ -974,6 +971,36 @@ class auth_plugin_ldap extends auth_plugin_base {
return true;
}
/**
* Update users from the external LDAP server into Moodle's user table.
*
* Sync helper
*
* @param array $users chunk of users to update
* @param array $updatekeys fields to update
*/
public function update_users(array $users, array $updatekeys): void {
global $DB;
print_string('userentriestoupdate', 'auth_ldap', count($users));
foreach ($users as $user) {
$transaction = $DB->start_delegated_transaction();
echo "\t";
print_string('auth_dbupdatinguser', 'auth_db', ['name' => $user->username, 'id' => $user->id]);
$userinfo = $this->get_userinfo($user->username);
if (!$this->update_user_record($user->username, $updatekeys, true,
$this->is_user_suspended((object) $userinfo))) {
echo ' - '.get_string('skipped');
}
echo "\n";
// Update system roles, if needed.
$this->sync_roles($user);
$transaction->allow_commit();
}
}
/**
* Bulk insert in SQL's temp table
*/

View File

@ -0,0 +1,61 @@
<?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/>.
/**
* Adhoc task for LDAP user sync.
*
* @package auth_ldap
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;
use core\task\adhoc_task;
/**
* Adhoc task class for LDAP user sync.
*
* @package auth_ldap
* @copyright Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class asynchronous_sync_task extends adhoc_task {
/** @var string Message prefix for mtrace */
protected const MTRACE_MSG = 'Synced ldap users';
/**
* Constructor
*/
public function __construct() {
$this->set_blocking(false);
$this->set_component('auth_ldap');
}
/**
* Run users sync.
*/
public function execute() {
$data = $this->get_custom_data();
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$auth->update_users($data->users, $data->updatekeys);
mtrace(sprintf(" %s (%d)", self::MTRACE_MSG, count($data->users)));
}
}

View File

@ -21,6 +21,7 @@
* @copyright 2015 Vadim Dvorovenko <Vadimon@mail.ru>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;
/**
@ -31,6 +32,9 @@ namespace auth_ldap\task;
*/
class sync_task extends \core\task\scheduled_task {
/** @var string Message prefix for mtrace */
protected const MTRACE_MSG = 'Synced ldap users';
/**
* Get a descriptive name for this task (shown to admins).
*
@ -44,11 +48,22 @@ class sync_task extends \core\task\scheduled_task {
* Run users sync.
*/
public function execute() {
global $CFG;
if (is_enabled_auth('ldap')) {
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$auth->sync_users(true);
$count = 0;
$auth->sync_users_update_callback(function ($users, $updatekeys) use (&$count) {
$asynctask = new asynchronous_sync_task();
$asynctask->set_custom_data([
'users' => $users,
'updatekeys' => $updatekeys,
]);
\core\task\manager::queue_adhoc_task($asynctask);
$count++;
mtrace(sprintf(" %s (%d)", self::MTRACE_MSG, $count));
sleep(1);
});
}
}
}

View File

@ -142,6 +142,8 @@ $string['renamingnotallowed'] = 'User renaming not allowed in LDAP';
$string['rootdseerror'] = 'Error querying rootDSE for Active Directory';
$string['syncroles'] = 'Synchronise system roles from LDAP';
$string['synctask'] = 'LDAP users sync job';
$string['sync_updateuserchunk'] = 'Set this value to the number of users you want updated per transaction. Setting this to 0 will update all users in one transaction.';
$string['sync_updateuserchunk_key'] = 'Sync update users chunk size';
$string['systemrolemapping'] = 'System role mapping';
$string['start_tls'] = 'Use regular LDAP service (port 389) with TLS encryption';
$string['start_tls_key'] = 'Use TLS';

View File

@ -289,6 +289,11 @@ if ($ADMIN->fulltree) {
new lang_string('auth_sync_suspended_key', 'auth'),
new lang_string('auth_sync_suspended', 'auth'), 0 , $yesno));
// Sync update users chunk size.
$settings->add(new admin_setting_configtext('auth_ldap/sync_updateuserchunk',
new lang_string('sync_updateuserchunk_key', 'auth_ldap'),
new lang_string('sync_updateuserchunk', 'auth_ldap'), 1000, PARAM_INT));
// NTLM SSO Header.
$settings->add(new admin_setting_heading('auth_ldap/ntlm',
new lang_string('auth_ntlmsso', 'auth_ldap'), ''));

View File

@ -29,11 +29,32 @@ namespace auth_ldap;
* define('TEST_AUTH_LDAP_DOMAIN', 'dc=example,dc=local');
*
* @package auth_ldap
* @category phpunit
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class plugin_test extends \advanced_testcase {
use auth_plugin_ldap;
use auth_ldap\task\{
sync_task,
asynchronous_sync_task,
};
/**
* LDAP authentication plugin tests.
*
* @package auth_ldap
* @copyright 2013 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class auth_ldap_test extends \advanced_testcase {
public static function setUpBeforeClass(): void {
global $CFG;
parent::setUpBeforeClass();
require_once($CFG->dirroot . '/auth/ldap/auth.php');
require_once($CFG->libdir . '/ldaplib.php');
}
/**
* Data provider for auth_ldap tests
@ -65,7 +86,7 @@ class plugin_test extends \advanced_testcase {
* @param int $subcontext Value to be configured in settings controlling searching in subcontexts.
*/
public function test_auth_ldap(int $pagesize, int $subcontext) {
global $CFG, $DB;
global $DB;
if (!extension_loaded('ldap')) {
$this->markTestSkipped('LDAP extension is not loaded.');
@ -73,9 +94,6 @@ class plugin_test extends \advanced_testcase {
$this->resetAfterTest();
require_once($CFG->dirroot.'/auth/ldap/auth.php');
require_once($CFG->libdir.'/ldaplib.php');
if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) {
$this->markTestSkipped('External LDAP test server not configured.');
}
@ -336,6 +354,41 @@ class plugin_test extends \advanced_testcase {
$this->assertEquals(2, $DB->count_records('role_assignments'));
$this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
// Let's test syncing users in chunks of '1'.
set_config('field_updatelocal_email', 'onlogin', 'auth_ldap');
set_config('sync_updateuserchunk', 1, 'auth_ldap');
/** @var auth_plugin_ldap $auth */
$auth = get_auth_plugin('ldap');
$count = 0;
ob_start();
$auth->sync_users_update_callback(function ($users, $updatekeys) use (&$count) {
$count++;
});
ob_end_clean();
// After updating in chunks of '1', we should have counted more than one update.
$this->assertGreaterThan(1, $count);
ob_start();
\core\cron::setup_user();
$cron = new sync_task();
$cron->execute();
$this->runAdhocTasks('\auth_ldap\task\asynchronous_sync_task');
$output = ob_get_contents();
ob_end_clean();
// Use Reflection to make protected constants available.
$rp = new \ReflectionClassConstant(sync_task::class, 'MTRACE_MSG');
$synctaskmsg = $rp->getValue();
$rp = new \ReflectionClassConstant(asynchronous_sync_task::class, 'MTRACE_MSG');
$asynctaskmsg = $rp->getValue();
$this->assertMatchesRegularExpression(
sprintf('/%s.*%s/s', $synctaskmsg, $asynctaskmsg),
$output
);
$this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest');
ldap_close($connection);
@ -347,8 +400,6 @@ class plugin_test extends \advanced_testcase {
public function test_ldap_user_loggedin_event() {
global $CFG, $DB, $USER;
require_once($CFG->dirroot . '/auth/ldap/auth.php');
$this->resetAfterTest();
$this->assertFalse(isloggedin());
@ -412,9 +463,6 @@ class plugin_test extends \advanced_testcase {
$this->resetAfterTest();
require_once($CFG->dirroot.'/auth/ldap/auth.php');
require_once($CFG->libdir.'/ldaplib.php');
if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) {
$this->markTestSkipped('External LDAP test server not configured.');
}

View File

@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2023100900; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2024011900; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2023100400; // Requires this Moodle version.
$plugin->component = 'auth_ldap'; // Full name of the plugin (used for diagnostics)