Skip to content

Commit

Permalink
MDL-30634 auth_ldap: Assign arbitrary system roles via LDAP sync.
Browse files Browse the repository at this point in the history
Loosely based on a 2.7 branch by Mark Johnson.
  • Loading branch information
David Balch committed Jul 20, 2017
1 parent 16a68a2 commit 8fb9a27
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 52 deletions.
101 changes: 60 additions & 41 deletions auth/ldap/auth.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
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');

/**
* LDAP authentication plugin.
Expand Down Expand Up @@ -865,14 +866,6 @@ function sync_users($do_updates=true) {
if (!empty($users)) {
print_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;
Expand All @@ -885,14 +878,8 @@ function sync_users($do_updates=true) {
echo "\n";
$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
Expand All @@ -914,14 +901,6 @@ function sync_users($do_updates=true) {
if (!empty($add_users)) {
print_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);
Expand Down Expand Up @@ -955,10 +934,8 @@ function sync_users($do_updates=true) {
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);
}
// Add roles if needed.
$this->sync_roles($user);

}
$transaction->allow_commit();
Expand Down Expand Up @@ -1102,8 +1079,12 @@ function user_activate($username) {
*
* @param mixed $username username (without system magic quotes)
* @return mixed result null if course creators is not configured, boolean otherwise.
*
* @deprecated since Moodle 3.4 MDL-30634 - please do not use this function any more.
*/
function iscreator($username) {
debugging('iscreator() is deprecated. Please use auth_plugin_ldap::is_role() instead.', DEBUG_DEVELOPER);

if (empty($this->config->creators) or empty($this->config->memberattribute)) {
return null;
}
Expand All @@ -1128,6 +1109,40 @@ function iscreator($username) {
return $creator;
}

/**
* Check if user has LDAP group membership.
*
* Returns true if user should be assigned role.
*
* @param mixed $username username (without system magic quotes).
* @param array $role Array of role's shortname, localname, and settingname for the config value.
* @return mixed result null if role/LDAP context is not configured, boolean otherwise.
*/
private function is_role($username, $role) {
if (empty($this->config->{$role['settingname']}) or empty($this->config->memberattribute)) {
return null;
}

$extusername = core_text::convert($username, 'utf-8', $this->config->ldapencoding);

$ldapconnection = $this->ldap_connect();

if ($this->config->memberattribute_isdn) {
if (!($userid = $this->ldap_find_userdn($ldapconnection, $extusername))) {
return false;
}
} else {
$userid = $extusername;
}

$groupdns = explode(';', $this->config->{$role['settingname']});
$isrole = ldap_isgroupmember($ldapconnection, $userid, $groupdns, $this->config->memberattribute);

$this->ldap_close();

return $isrole;
}

/**
* Called when the user record is updated.
*
Expand Down Expand Up @@ -1792,25 +1807,29 @@ function ntlmsso_finish() {
}

/**
* Sync roles for this user
* Sync roles for this user.
*
* @param $user object user object (without system magic quotes)
* @param object $user The user to sync (without system magic quotes).
*/
function sync_roles($user) {
$iscreator = $this->iscreator($user->username);
if ($iscreator === null) {
return; // Nothing to sync - creators not configured
}
global $DB;

if ($roles = get_archetype_roles('coursecreator')) {
$creatorrole = array_shift($roles); // We can only use one, let's use the first one
$systemcontext = context_system::instance();
$roles = get_ldap_assignable_role_names(2); // Admin user.

if ($iscreator) { // Following calls will not create duplicates
role_assign($creatorrole->id, $user->id, $systemcontext->id, $this->roleauth);
foreach ($roles as $role) {
$isrole = $this->is_role($user->username, $role);
if ($isrole === null) {
continue; // Nothing to sync - role/LDAP contexts not configured.
}

// Sync user.
$systemcontext = context_system::instance();
if ($isrole) {
// Following calls will not create duplicates.
role_assign($role['id'], $user->id, $systemcontext->id, $this->roleauth);
} else {
// Unassign only if previously assigned by this plugin!
role_unassign($creatorrole->id, $user->id, $systemcontext->id, $this->roleauth);
// Unassign only if previously assigned by this plugin.
role_unassign($role['id'], $user->id, $systemcontext->id, $this->roleauth);
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions auth/ldap/classes/task/sync_roles.php
Original file line number Diff line number Diff line change
@@ -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/>.

/**
* A scheduled task for LDAP roles sync.
*
* @package auth_ldap
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace auth_ldap\task;

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

/**
* A scheduled task class for LDAP roles sync.
*
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class sync_roles extends \core\task\scheduled_task {

/**
* Get a descriptive name for this task (shown to admins).
*
* @return string
*/
public function get_name() {
return get_string('syncroles', 'auth_ldap');
}

/**
* Synchronise role assignments from LDAP.
*/
public function execute() {
global $DB;
if (is_enabled_auth('ldap')) {
$auth = get_auth_plugin('ldap');
$users = $DB->get_records('user', array('auth' => 'ldap'));
foreach ($users as $user) {
$auth->sync_roles($user);
}
}
}

}
10 changes: 10 additions & 0 deletions auth/ldap/db/tasks.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@
defined('MOODLE_INTERNAL') || die();

$tasks = array(
array(
'classname' => 'auth_ldap\task\sync_roles',
'blocking' => 0,
'minute' => '0',
'hour' => '0',
'day' => '*',
'month' => '*',
'dayofweek' => '*',
'disabled' => 1
),
array(
'classname' => 'auth_ldap\task\sync_task',
'blocking' => 0,
Expand Down
18 changes: 18 additions & 0 deletions auth/ldap/db/upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,23 @@ function xmldb_auth_ldap_upgrade($oldversion) {
// Automatically generated Moodle v3.3.0 release upgrade line.
// Put any upgrade step following this.

if ($oldversion < 2017051501) {
// The "auth_ldap/coursecreators" setting was replaced with "auth_ldap/coursecreatorcontext" (created
// dynamically from system-assignable roles) - so migrate any existing value to the first new slot.
if ($ldapcontext = get_config('auth_ldap', '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');

// Delete old setting.
set_config('creators', null, 'auth_ldap');

upgrade_plugin_savepoint(true, 2017051501, 'auth', 'ldap');
}
}

return true;
}
10 changes: 8 additions & 2 deletions auth/ldap/lang/en/auth_ldap.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,6 @@
$string['auth_ldap_create_context'] = 'If you enable user creation with email confirmation, specify the context where users are created. This context should be different from other users to prevent security issues. You don\'t need to add this context to ldap_context-variable, Moodle will search for users from this context automatically.<br /><b>Note!</b> You have to modify the method user_create() in file auth/ldap/auth.php to make user creation work';
$string['auth_ldap_create_context_key'] = 'Context for new users';
$string['auth_ldap_create_error'] = 'Error creating user in LDAP.';
$string['auth_ldap_creators'] = 'List of groups or contexts whose members are allowed to create new courses. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
$string['auth_ldap_creators_key'] = 'Creators';
$string['auth_ldapdescription'] = 'This method provides authentication against an external LDAP server.
If the given username and password are valid, Moodle creates a new user
entry in its database. This module can read user attributes from LDAP and prefill
Expand Down Expand Up @@ -80,6 +78,8 @@
$string['auth_ldap_passwdexpire_settings'] = 'LDAP password expiration settings';
$string['auth_ldap_preventpassindb'] = 'Select yes to prevent passwords from being stored in Moodle\'s DB.';
$string['auth_ldap_preventpassindb_key'] = 'Prevent password caching';
$string['auth_ldap_rolecontext'] = '{$a->localname} context';
$string['auth_ldap_rolecontext_help'] = 'LDAP context used to select for <i>{$a->localname}</i> mapping. Separate multiple groups with \';\'. Usually something like "cn={$a->shortname},ou=staff,o=myorg".';
$string['auth_ldap_search_sub'] = 'Search users from subcontexts.';
$string['auth_ldap_search_sub_key'] = 'Search subcontexts';
$string['auth_ldap_server_settings'] = 'LDAP server settings';
Expand Down Expand Up @@ -141,7 +141,9 @@
$string['pluginnotenabled'] = 'Plugin not enabled!';
$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['systemrolemapping'] = 'System role mapping';
$string['start_tls'] = 'Use regular LDAP service (port 389) with TLS encryption';
$string['start_tls_key'] = 'Use TLS';
$string['updateremfail'] = 'Error updating LDAP record. Error code: {$a->errno}; Error string: {$a->errstring}<br/>Key ({$a->key}) - old moodle value: \'{$a->ouvalue}\' new value: \'{$a->nuvalue}\'';
Expand All @@ -158,3 +160,7 @@
$string['userentriestoupdate'] = "User entries to be updated: {\$a}\n";
$string['usernotfound'] = 'User not found in LDAP';
$string['useracctctrlerror'] = 'Error getting userAccountControl for {$a}';

// Deprecated since Moodle 3.4.
$string['auth_ldap_creators'] = 'List of groups or contexts whose members are allowed to create new courses. Separate multiple groups with \';\'. Usually something like \'cn=teachers,ou=staff,o=myorg\'';
$string['auth_ldap_creators_key'] = 'Creators';
2 changes: 2 additions & 0 deletions auth/ldap/lang/en/deprecated.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
auth_ldap_creators,auth_ldap
auth_ldap_creators_key,auth_ldap
53 changes: 53 additions & 0 deletions auth/ldap/locallib.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?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/>.

/**
* Internal library of functions for module auth_ldap
*
* @package auth_ldap
* @author David Balch <david.balch@conted.ox.ac.uk>
* @copyright 2017 The Chancellor Masters and Scholars of the University of Oxford {@link http://www.tall.ox.ac.uk/}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

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

/**
* Get a list of system roles assignable by the current or a specified user, including their localised names.
*
* @param integer|object $user A user id or object. By default (null) checks the permissions of the current user.
* @return array $roles, each role as an array with id, shortname, localname, and settingname for the config value.
*/
function get_ldap_assignable_role_names($user = null) {
$roles = array();

if ($assignableroles = get_assignable_roles(context_system::instance(), ROLENAME_SHORT, false, $user)) {
$systemroles = role_fix_names(get_all_roles(), context_system::instance(), ROLENAME_ORIGINAL);
foreach ($assignableroles as $shortname) {
foreach ($systemroles as $systemrole) {
if ($systemrole->shortname == $shortname) {
$roles[] = array('id' => $systemrole->id,
'shortname' => $shortname,
'localname' => $systemrole->localname,
'settingname' => $shortname . 'context');
break;
}
}
}
}

return $roles;
}
19 changes: 11 additions & 8 deletions auth/ldap/settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,17 @@
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/coursecreators',
new lang_string('coursecreators'), ''));

// Course creators field mapping.
$settings->add(new admin_setting_configtext('auth_ldap/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/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) {
$settings->add(new admin_setting_configtext('auth_ldap/' . $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/syncusers',
Expand Down
5 changes: 5 additions & 0 deletions auth/ldap/upgrade.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
This files describes API changes in the auth_ldap code.

=== 3.4 ===

* The "auth_ldap/coursecreators" setting was replaced with dynamically generated "auth_ldap/<role>context" settings,
migrating any existing value to a new setting in this style.

=== 3.3 ===

* The config.html file was migrated to use the admin settings API.
Expand Down
Loading

0 comments on commit 8fb9a27

Please sign in to comment.