Skip to content

Commit

Permalink
feat: gestion des permissions via active directoy
Browse files Browse the repository at this point in the history
- ajout de  nouveaux paramètres de configuration pour géré le mapping
- création d'une interface utilisateur pour le mapping
- synchronisation des groupes  AD  avec moodle
- mise en place d'une tâche cron
  • Loading branch information
karimalik committed Jul 31, 2024
1 parent 261888e commit 90b2b48
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 338 deletions.
37 changes: 37 additions & 0 deletions auth/nyxei/admin_ui.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

/**
* User interface for mappings
*
* @package auth_nyxei
* @copyright 2024 Nyx-EI {@link https://nyx-ei.tech}
* @author NYX-EI <help@nyx-ei.tech>
*/

require_once('../../config.php');

require_login();
admin_externalpage_setup('auth_nyxei_settings');

if (!has_capability('moodle/site:config', context_system::instance())) {
print_error('nopermissions', 'error', '', 'access this page');
}

if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['ad_group_role_mappings'])) {
$mappings = trim($_POST['ad_group_role_mappings']);
set_config('ad_group_role_mappings', $mappings, 'auth_nyxei');
echo '<div class="alert alert-success">Mappings saved successfully!</div>';
}

echo $OUTPUT->header();
echo '<h2>' . get_string('ad_group_role_mappings', 'auth_nyxei') . '</h2>';

$current_mappings = get_config('auth_nyxei', 'ad_group_role_mappings');
echo '<form method="post">';
echo '<textarea name="ad_group_role_mappings" rows="10" cols="50">' . s($current_mappings) . '</textarea>';
echo '<br><br>';
echo '<input type="submit" value="' . get_string('savechanges') . '" class="btn btn-primary">';
echo '</form>';

// Affiche le pied de page
echo $OUTPUT->footer();
115 changes: 87 additions & 28 deletions auth/nyxei/auth.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
<?php

//TODO: Gestion des permissions d'utilisateur via AD

/**
* Active directory Authentification plugin
Expand All @@ -14,22 +13,30 @@
*/

defined('MOODLE_INTERNAL') || die();
global $CFG;

// use core\context_system;

require_once($CFG->libdir .'/setuplib.php');
require_once($CFG->libdir . '/moodlelib.php');
// require_once($CFG->libdir . '/lib/contextlib.php');

class auth_plugin_nyxei extends auth_plugin_base {
class auth_plugin_nyxei extends auth_plugin_base
{

const LDAP_PROTOCOL_VERSION = 3;
const LDAP_PORT = 636;
const LDAP_REFERRALS = 0;
const LOGIN_ATTEMPTS = 3;

public function __construct() {
public function __construct()
{
$this->authtype = 'nyxei';
$this->config = get_config('auth_nyxei');
}

public function user_login($username, $password) {
public function user_login($username, $password)
{
// global $DB;

$ldap_host = $this->config->host;
Expand All @@ -51,7 +58,7 @@ public function user_login($username, $password) {
if ($ldap_bind) {
$this->close_ldap_connection($ldap_connection);
return true;
}else {
} else {
$this->failed_login_log($username, 'Invalid Credentials');
$this->close_ldap_connection($ldap_connection);
return false;
Expand Down Expand Up @@ -81,17 +88,17 @@ public function process_config($config)
}

if (empty($config->login_attempts)) {

$config->login_attempts = self::LOGIN_ATTEMPTS; // default value
}

if (empty($config->bind_user)) {

$config->bind_user = '';
}

if (empty($config->bind_password)) {

$config->bind_password = '';
}

Expand All @@ -103,7 +110,7 @@ public function process_config($config)
return true;
}

//save attempts login
//save attempts login
private function failed_login_log($username, $error)
{
global $DB;
Expand All @@ -126,7 +133,7 @@ private function check_failed_attempts($username)
$attempt_count = count($attempts);

if ($attempt_count >= $this->config->login_attempts) {

$this->send_admin_notification($username, $attempt_count);
}
}
Expand All @@ -142,49 +149,50 @@ private function send_admin_notification($username, $attempt_count)
email_to_user($admin, $admin, $subject, $message);
}

public function sync_users() {
public function sync_users()
{
global $DB, $CFG;

$ldap_host = $this->config->host;
$ldap_port = self::LDAP_PORT;
$bind_user = $this->config->bind_user;
$bind_password = $this->config->bind_password;

$ldap_connection = ldap_connect("ldaps://{$ldap_host}", $ldap_port);

if (!$ldap_connection) {
error_log('Could not connect to LDAP server.');
return false;
}

ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, self::LDAP_PROTOCOL_VERSION);
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, self::LDAP_REFERRALS);

$ldap_bind = ldap_bind($ldap_connection, $bind_user, $bind_password);

if (!$ldap_bind) {
$error = ldap_error($ldap_connection);
$this->close_ldap_connection($ldap_connection);
error_log("Could not bind to LDAP server: $error.");
return false;
}

$search = ldap_search($ldap_connection, "dc=nyx-ei,dc=tech", "(objectClass=*)");
$entries = ldap_get_entries($ldap_connection, $search);

if ($entries === false) {
$error = ldap_error($ldap_connection);
$this->close_ldap_connection($ldap_connection);
error_log("LDAP search failed: $error.");
return false;
}

$ad_usernames = [];
foreach ($entries as $entry) {
if (!empty($entry['samaccountname'][0])) {
$username = $entry['samaccountname'][0];
$ad_usernames[] = $username;

if (!$DB->record_exists('user', ['username' => $username])) {
$user = new stdClass();
$user->username = $username;
Expand All @@ -194,38 +202,89 @@ public function sync_users() {
$user->auth = 'auth_nyxei';
$user->confirmed = 1;
$user->mnethostid = $CFG->mnet_localhost_id;

$DB->insert_record('user', $user);
}

if (isset($entry['useraccountcontrol'][0]) && ($entry['useraccountcontrol'][0] & 2)) {
$user = $DB->get_record('user', ['username' => $username]);
$user->suspended = 1;
$DB->update_record('user', $user);
}
}
}

$users = $DB->get_records('user', ['auth' => 'auth_nyxei']);
foreach ($users as $user) {
if (!in_array($user->username, $ad_usernames)) {
$user->suspended = 1;
$DB->update_record('user', $user);
}
}

$this->close_ldap_connection($ldap_connection);
return true;
}


public function sync_ad_groups_to_roles()
{
global $DB;


$mappings = explode("\n", $this->config->ad_group_role_mappings);
$mappings = array_filter(array_map('trim', $mappings));


$ldap_connection = ldap_connect($this->config->host);
ldap_set_option($ldap_connection, LDAP_OPT_PROTOCOL_VERSION, self::LDAP_PROTOCOL_VERSION);
ldap_set_option($ldap_connection, LDAP_OPT_REFERRALS, self::LDAP_REFERRALS);
ldap_start_tls($ldap_connection);

if (!ldap_bind($ldap_connection, $this->config->bind_user, $this->config->bind_password)) {
throw new \moodle_exception('ldapbinderror', 'auth_nyxei');
}

foreach ($mappings as $mapping) {
list($ad_group, $moodle_role) = explode(':', $mapping);

$search = ldap_search($ldap_connection, "dc=nyx-ei,dc=tech", "(memberOf=cn=$ad_group,dc=nyx-ei,dc=tech)");
$entries = ldap_get_entries($ldap_connection, $search);

if ($entries['count'] > 0) {

$roleid = $DB->get_field('role', 'id', ['shortname' => $moodle_role]);

if ($roleid) {
foreach ($entries as $entry) {
if (isset($entry['samaccountname'][0])) {
$username = $entry['samaccountname'][0];
$user = $DB->get_record('user', ['username' => $username]);

if ($user) {

role_assign($roleid, $user->id, context_system::instance());
}
}
}
} else {
error_log("Le rôle Moodle '$moodle_role' n'existe pas.");
}
}
}

$this->close_ldap_connection($ldap_connection);
return true;
}


/**
* Closes the LDAP connection.
*
* @param $ldap_connection
* @return void
*/
private function close_ldap_connection($ldap_connection) {
private function close_ldap_connection($ldap_connection)
{
ldap_unbind($ldap_connection);
}

}
32 changes: 32 additions & 0 deletions auth/nyxei/classes/task/sync_ad_groups.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/**
* A scheduled task for AD groups roles sync.
*
* @package auth_nyxei
* @author NYX-EI <help@nyx-ei.tech>
* @copyright 2024 Nyx-EI <help@nyx-ei.tech>
*/

namespace auth_nyxei\task;

global $CFG;

defined('MOODLE_INTERNAL') || die();


require_once($CFG->dirroot.'/auth/nyxei/auth.php');

class sync_ad_groups extends \core\task\scheduled_task {

public function get_name()
{
return get_string('sync_ad_groups', 'auth_nyxei');
}

public function execute() {
$auth = get_auth_plugin('nyxei');
$auth->sync_ad_groups_to_roles();
}
}

2 changes: 2 additions & 0 deletions auth/nyxei/classes/task/sync_users.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace nyxei\task;

global $CFG;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->dirroot.'/auth/nyxei/auth.php');
Expand Down
13 changes: 11 additions & 2 deletions auth/nyxei/db/tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@
'blocking' => 0,
'minute' => '*/30',
'hour' => '*',
'dayofmonth' => '*',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
),

array(
'classname' => 'auth_nyxei\task\sync_ad_groups',
'blocking' => 0,
'minute' => '*/30',
'hour' => '*/6',
'day' => '*',
'month' => '*',
'wday' => '*'
'dayofweek' => '*',
)
);
4 changes: 3 additions & 1 deletion auth/nyxei/lang/en/auth_nyxei.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@
$string['bind_password'] = 'Bind Password';
$string['bind_password_desc'] = 'The password to bind to the Active Directory server.';
$string['sync_users'] = 'Synchronize Users';
$string['sync_users_desc'] = 'Synchronize users from LDAP to moodle.';
$string['sync_users_desc'] = 'Synchronize users from LDAP to moodle.';
$string['ad_group_role_mappings'] = 'Active directory Group to Moodle Role Mappings';
$string['ad_group_role_mappings_desc'] = 'Enter mappings in the format "AD Group Name:Moodle Role Shortname", one per line.';
4 changes: 3 additions & 1 deletion auth/nyxei/lang/fr/auth_nyxei.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,6 @@
$string['bind_password'] = 'Mot de passe bind';
$string['bind_password_desc'] = 'Mot de passe binaire pour l\'authentification du serveur AD.';
$string['sync_users'] = 'Synchroniser les utilisateurs';
$string['sync_users_desc'] = 'Synchroniser les utilisateurs du serveur AD vers moodle.';
$string['sync_users_desc'] = 'Synchroniser les utilisateurs du serveur AD vers moodle.';
$string['ad_group_role_mappings'] = 'Correspondance entre les groupes de l\'Active Directory et les rôles de Moodle';
$string['ad_group_role_mappings_desc'] = 'Entrez les correspondances dans le format « Nom du groupe AD:Nom abrégé du rôle Moodle », une par ligne.';
7 changes: 7 additions & 0 deletions auth/nyxei/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@
'',
));

$settings->add(new admin_setting_configtext(
'auth_nyxei/ad_group_role_mappings',
get_string('ad_group_role_mappings', 'auth_nyxei'),
get_string('ad_group_role_mappings_desc', 'auth_nyxei'),
'',
PARAM_TEXT
));
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"ext-json": "*",
"ext-hash": "*",
"ext-fileinfo": "*",
"ext-sodium": "*"
"ext-sodium": "*",
"ext-ldap": "*"
},
"suggest": {
"ext-mysqli": "Needed when Moodle uses MySQL or MariaDB database.",
Expand Down
Loading

0 comments on commit 90b2b48

Please sign in to comment.