From 7f23a839936d19b6a79e18f9ac5ac1b944c624e1 Mon Sep 17 00:00:00 2001
From: Alexander Bias <alexander.bias@uni-ulm.de>
Date: Wed, 7 Feb 2018 14:39:53 +0100
Subject: [PATCH] Adopt code changes in Moodle 3.4 core auth_ldap: Assign
 arbitrary system roles via LDAP sync

---
 CHANGES.md                     |  1 +
 README.md                      |  8 ++++++++
 auth.php                       | 35 ++++++--------------------------
 db/tasks.php                   | 10 +++++++++
 db/upgrade.php                 | 15 ++++++++++++++
 lang/en/auth_ldap_syncplus.php |  1 +
 settings.php                   | 37 ++++++++++++++++++++++++++--------
 version.php                    |  2 +-
 8 files changed, 71 insertions(+), 38 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index fa9a53a..d9ce847 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -6,6 +6,7 @@ Changes
 
 ### Unreleased
 
+* 2018-02-07 - Adopt code changes in Moodle 3.4 core auth_ldap: Assign arbitrary system roles via LDAP sync.
 * 2018-02-06 - Check compatibility for Moodle 3.4, no functionality change.
 
 ### v3.3-r1
diff --git a/README.md b/README.md
index 9656639..f92eca2 100644
--- a/README.md
+++ b/README.md
@@ -74,6 +74,14 @@ To leverage the additional LDAP synchronization features of auth_ldap_syncplus,
 If you don't know how to setup LDAP User account syncronisation at all, see https://docs.moodle.org/en/LDAP_authentication#Enabling_the_LDAP_users_sync_job.
 
 
+Configuring LDAP User role synchronisation
+------------------------------------------
+
+In addition to the LDAP user account synchronisation, there is a LDAP user role synchronisation. LDAP user role synchronisation task in auth_ldap_syncplus does not provide any benefits over the LDAP user role synchronisation in Moodle core auth_ldap yet. However, to keep things in one place and if you want to synchronize LDAP user roles, you should activate and configure the scheduled task of auth_ldap_syncplus instead of auth_ldap. This is done on Site administration -> Server -> Scheduled tasks.
+
+If you don't know about the LDAP user role synchronisation at all, see https://docs.moodle.org/en/LDAP_authentication#Assign_system_roles.
+
+
 Migrating from auth_ldap to auth_ldap_syncplus
 ----------------------------------------------
 
diff --git a/auth.php b/auth.php
index c825894..475d20c 100644
--- a/auth.php
+++ b/auth.php
@@ -32,6 +32,7 @@ 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');
 
@@ -343,14 +344,6 @@ class auth_plugin_ldap_syncplus extends auth_plugin_ldap {
             if (!empty($users)) {
                 mtrace(get_string('userentriestoupdate', 'auth_ldap', count($users)));
 
-                $sitecontext = context_system::instance();
-                if (!empty($this->config->creators) and !empty($this->config->memberattribute)
-                  and $roles = get_archetype_roles('coursecreator')) {
-                    $creatorrole = array_shift($roles);      // We can only use one, let's use the first one
-                } else {
-                    $creatorrole = false;
-                }
-
                 $transaction = $DB->start_delegated_transaction();
                 $xcount = 0;
                 $maxxcount = 100;
@@ -365,14 +358,8 @@ class auth_plugin_ldap_syncplus extends auth_plugin_ldap {
                     mtrace("\t".get_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)).$skipped);
                     $xcount++;
 
-                    // Update course creators if needed
-                    if ($creatorrole !== false) {
-                        if ($this->iscreator($user->username)) {
-                            role_assign($creatorrole->id, $user->id, $sitecontext->id, $this->roleauth);
-                        } else {
-                            role_unassign($creatorrole->id, $user->id, $sitecontext->id, $this->roleauth);
-                        }
-                    }
+                    // Update system roles, if needed.
+                    $this->sync_roles($user);
                 }
                 $transaction->allow_commit();
                 unset($users); // free mem
@@ -395,14 +382,6 @@ class auth_plugin_ldap_syncplus extends auth_plugin_ldap {
             if (!empty($add_users)) {
                 mtrace(get_string('userentriestoadd', 'auth_ldap', count($add_users)));
 
-                $sitecontext = context_system::instance();
-                if (!empty($this->config->creators) and !empty($this->config->memberattribute)
-                  and $roles = get_archetype_roles('coursecreator')) {
-                    $creatorrole = array_shift($roles);      // We can only use one, let's use the first one
-                } else {
-                    $creatorrole = false;
-                }
-
                 $transaction = $DB->start_delegated_transaction();
                 foreach ($add_users as $user) {
                     $user = $this->get_userinfo_asobj($user->username);
@@ -436,14 +415,12 @@ class auth_plugin_ldap_syncplus extends auth_plugin_ldap {
                         set_user_preference('auth_forcepasswordchange', 1, $id);
                     }
 
-                    // Add course creators if needed
-                    if ($creatorrole !== false and $this->iscreator($user->username)) {
-                        role_assign($creatorrole->id, $id, $sitecontext->id, $this->roleauth);
-                    }
-
                     // Save custom profile fields.
                     $updatekeys = $this->get_profile_keys(true);
                     $this->update_user_record($user->username, $updatekeys, false);
+
+                    // Add roles if needed.
+                    $this->sync_roles($euser);
                 }
                 $transaction->allow_commit();
                 unset($add_users); // free mem
diff --git a/db/tasks.php b/db/tasks.php
index 0924af3..33fece1 100644
--- a/db/tasks.php
+++ b/db/tasks.php
@@ -25,6 +25,16 @@
 defined('MOODLE_INTERNAL') || die();
 
 $tasks = array(
+    array(
+        'classname' => 'auth_ldap_syncplus\task\sync_roles',
+        'blocking' => 0,
+        'minute' => '0',
+        'hour' => '0',
+        'day' => '*',
+        'month' => '*',
+        'dayofweek' => '*',
+        'disabled' => 1
+    ),
     array(
         'classname' => 'auth_ldap_syncplus\task\sync_task',
         'blocking' => 0,
diff --git a/db/upgrade.php b/db/upgrade.php
index 26aa06d..69a9e11 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -39,5 +39,20 @@ function xmldb_auth_ldap_syncplus_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2018020200, 'auth', 'ldap_syncplus');
     }
 
+    if ($oldversion < 2018020601) {
+        // The "auth_ldap_syncplus/coursecreators" setting was replaced with "auth_ldap_syncplus/coursecreatorcontext" (created
+        // dynamically from system-assignable roles) - so migrate any existing value to the first new slot.
+        if ($ldapcontext = get_config('auth_ldap_syncplus', 'creators')) {
+            // Get info about the role that the old coursecreators setting would apply.
+            $creatorrole = get_archetype_roles('coursecreator');
+            $creatorrole = array_shift($creatorrole); // We can only use one, let's use the first.
+            // Create new setting.
+            set_config($creatorrole->shortname . 'context', $ldapcontext, 'auth_ldap_syncplus');
+            // Delete old setting.
+            set_config('creators', null, 'auth_ldap_syncplus');
+            upgrade_plugin_savepoint(true, 2018020601, 'auth', 'ldap_syncplus');
+        }
+    }
+
     return true;
 }
diff --git a/lang/en/auth_ldap_syncplus.php b/lang/en/auth_ldap_syncplus.php
index 6b83183..977f18b 100644
--- a/lang/en/auth_ldap_syncplus.php
+++ b/lang/en/auth_ldap_syncplus.php
@@ -34,6 +34,7 @@ $string['removeuser_graceperiod'] = 'Fully deleting grace period';
 $string['removeuser_graceperiod_desc'] = 'After suspending a user internally, the synchronization script will wait for this number of days until the user will be fully deleted internal. If the user re-appears in LDAP within this grace period, the user will be reactivated. Note: This setting is only used if "Removed ext user" is set to "Suspend internal and fully delete internal after grace period"';
 $string['sync_script_createuser_enabled'] = 'If enabled (default), the synchronization script will create Moodle accounts for all LDAP users if they have never logged into Moodle before. If disabled, the synchronization script will not create Moodle accounts for all LDAP users.';
 $string['sync_script_createuser_enabled_key'] = 'Add new users';
+$string['syncroles'] = 'LDAP roles sync job (Sync Plus)';
 $string['synctask'] = 'LDAP users sync job (Sync Plus)';
 $string['userentriestosuspend'] = 'User entries to be suspended: {$a}';
 $string['waitinginremovalqueue'] = 'Waiting in removal queue for {$a->days} day grace period: {$a->name} ID {$a->id}';
diff --git a/settings.php b/settings.php
index eaf7881..b552ce3 100644
--- a/settings.php
+++ b/settings.php
@@ -241,14 +241,35 @@ if ($ADMIN->fulltree) {
                 get_string('auth_ldap_create_context_key', 'auth_ldap'),
                 get_string('auth_ldap_create_context', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
 
-        // Course Creators Header.
-        $settings->add(new admin_setting_heading('auth_ldap_syncplus/coursecreators',
-                new lang_string('coursecreators'), ''));
-
-        // Course creators field mapping.
-        $settings->add(new admin_setting_configtext('auth_ldap_syncplus/creators',
-                get_string('auth_ldap_creators_key', 'auth_ldap'),
-                get_string('auth_ldap_creators', 'auth_ldap'), '', PARAM_RAW_TRIMMED));
+        // System roles mapping header.
+        $settings->add(new admin_setting_heading('auth_ldap_syncplus/systemrolemapping',
+                new lang_string('systemrolemapping', 'auth_ldap'), ''));
+
+        // Create system role mapping field for each assignable system role.
+        $roles = get_ldap_assignable_role_names();
+        foreach ($roles as $role) {
+            // Before we can add this setting we need to check a few things.
+            // A) It does not exceed 100 characters otherwise it will break the DB as the 'name' field
+            //    in the 'config_plugins' table is a varchar(100).
+            // B) The setting name does not contain hyphens. If it does then it will fail the check
+            //    in parse_setting_name() and everything will explode. Role short names are validated
+            //    against PARAM_ALPHANUMEXT which is similar to the regex used in parse_setting_name()
+            //    except it also allows hyphens.
+            // Instead of shortening the name and removing/replacing the hyphens we are showing a warning.
+            // If we were to manipulate the setting name by removing the hyphens we may get conflicts, eg
+            // 'thisisashortname' and 'this-is-a-short-name'. The same applies for shortening the setting name.
+            if (core_text::strlen($role['settingname']) > 100 || !preg_match('/^[a-zA-Z0-9_]+$/', $role['settingname'])) {
+                $url = new moodle_url('/admin/roles/define.php', array('action' => 'edit', 'roleid' => $role['id']));
+                $a = (object)['rolename' => $role['localname'], 'shortname' => $role['shortname'], 'charlimit' => 93,
+                              'link' => $url->out()];
+                $settings->add(new admin_setting_heading('auth_ldap_syncplus/role_not_mapped_' . sha1($role['settingname']), '',
+                        get_string('cannotmaprole', 'auth_ldap', $a)));
+            } else {
+                $settings->add(new admin_setting_configtext('auth_ldap_syncplus/' . $role['settingname'],
+                        get_string('auth_ldap_rolecontext', 'auth_ldap', $role),
+                        get_string('auth_ldap_rolecontext_help', 'auth_ldap', $role), '', PARAM_RAW_TRIMMED));
+            }
+        }
 
         // User Account Sync.
         $settings->add(new admin_setting_heading('auth_ldap_syncplus/syncusers',
diff --git a/version.php b/version.php
index 339c383..c84079c 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $plugin->component = 'auth_ldap_syncplus';
-$plugin->version = 2018020600;
+$plugin->version = 2018020601;
 $plugin->release = 'v3.3-r1';
 $plugin->requires = 2017111300;
 $plugin->maturity = MATURITY_STABLE;
-- 
GitLab