From e34beca17a0ee9a881f7c61e0ece5ddf2721493f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Sat, 29 Dec 2012 18:36:59 +0100 Subject: [PATCH 1/4] MDL-35159 add support for enrol_ldap restore --- enrol/ldap/cli/sync.php | 19 +-- enrol/ldap/lib.php | 250 ++++++++++++++++++++++++++++------------ 2 files changed, 190 insertions(+), 79 deletions(-) diff --git a/enrol/ldap/cli/sync.php b/enrol/ldap/cli/sync.php index d9fb913b58d..1979a35dbf6 100644 --- a/enrol/ldap/cli/sync.php +++ b/enrol/ldap/cli/sync.php @@ -44,17 +44,22 @@ define('CLI_SCRIPT', true); -require(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); +require(__DIR__.'/../../../config.php'); +require_once("$CFG->libdir/clilib.php"); -// Ensure errors are well explained -$CFG->debug = DEBUG_NORMAL; +// Ensure errors are well explained. +$CFG->debug = DEBUG_DEVELOPER; if (!enrol_is_enabled('ldap')) { - error_log('[ENROL LDAP] '.get_string('pluginnotenabled', 'enrol_ldap')); - die; + cli_error(get_string('pluginnotenabled', 'enrol_ldap'), 2); } -// Update enrolments -- these handlers should autocreate courses if required +/** @var enrol_ldap_plugin $enrol */ $enrol = enrol_get_plugin('ldap'); -$enrol->sync_enrolments(); +$trace = new text_progress_trace(); + +// Update enrolments -- these handlers should autocreate courses if required. +$enrol->sync_enrolments($trace); + +exit(0); diff --git a/enrol/ldap/lib.php b/enrol/ldap/lib.php index 2e2b6b6eebd..c7b2c5243c6 100644 --- a/enrol/ldap/lib.php +++ b/enrol/ldap/lib.php @@ -131,8 +131,12 @@ class enrol_ldap_plugin extends enrol_plugin { public function sync_user_enrolments($user) { global $DB; - $ldapconnection = $this->ldap_connect(); + // Do not try to print anything to the output because this method is called during interactive login. + $trace = new error_log_progress_trace($this->errorlogtag); + + $ldapconnection = $this->ldap_connect($trace); if (!$ldapconnection) { + $trace->finished(); return; } @@ -174,7 +178,7 @@ class enrol_ldap_plugin extends enrol_plugin { foreach ($enrolments[$role->id]['ext'] as $enrol) { $course_ext_id = $enrol[$courseidnumber][0]; if (empty($course_ext_id)) { - error_log($this->errorlogtag.get_string('extcourseidinvalid', 'enrol_ldap')); + $trace->output(get_string('extcourseidinvalid', 'enrol_ldap')); continue; // Next; skip this one! } @@ -182,14 +186,13 @@ class enrol_ldap_plugin extends enrol_plugin { $course = $DB->get_record('course', array($this->enrol_localcoursefield=>$course_ext_id)); if (empty($course)) { // Course doesn't exist if ($this->get_config('autocreate')) { // Autocreate - error_log($this->errorlogtag.get_string('createcourseextid', 'enrol_ldap', - array('courseextid'=>$course_ext_id))); - if ($newcourseid = $this->create_course($enrol)) { - $course = $DB->get_record('course', array('id'=>$newcourseid)); + $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id))); + if (!$newcourseid = $this->create_course($enrol, $trace)) { + continue; } + $course = $DB->get_record('course', array('id'=>$newcourseid)); } else { - error_log($this->errorlogtag.get_string('createnotcourseextid', 'enrol_ldap', - array('courseextid'=>$course_ext_id))); + $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$course_ext_id))); continue; // Next; skip this one! } } @@ -226,18 +229,18 @@ class enrol_ldap_plugin extends enrol_plugin { // set it back to active on external re-enrolment. So set it // unconditionnally to cover both cases. $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id)); - error_log($this->errorlogtag.get_string('enroluser', 'enrol_ldap', - array('user_username'=> $user->username, - 'course_shortname'=>$course->shortname, - 'course_id'=>$course->id))); + $trace->output(get_string('enroluser', 'enrol_ldap', + array('user_username'=> $user->username, + 'course_shortname'=>$course->shortname, + 'course_id'=>$course->id))); } else { if ($enrolments[$role->id]['current'][$course->id]->status == ENROL_USER_SUSPENDED) { // Reenable enrolment that was previously disabled. Enrolment refreshed $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$user->id)); - error_log($this->errorlogtag.get_string('enroluserenable', 'enrol_ldap', - array('user_username'=> $user->username, - 'course_shortname'=>$course->shortname, - 'course_id'=>$course->id))); + $trace->output(get_string('enroluserenable', 'enrol_ldap', + array('user_username'=> $user->username, + 'course_shortname'=>$course->shortname, + 'course_id'=>$course->id))); } } @@ -255,10 +258,10 @@ class enrol_ldap_plugin extends enrol_plugin { switch ($this->get_config('unenrolaction')) { case ENROL_EXT_REMOVED_UNENROL: $this->unenrol_user($instance, $user->id); - error_log($this->errorlogtag.get_string('extremovedunenrol', 'enrol_ldap', - array('user_username'=> $user->username, - 'course_shortname'=>$course->shortname, - 'course_id'=>$course->courseid))); + $trace->output(get_string('extremovedunenrol', 'enrol_ldap', + array('user_username'=> $user->username, + 'course_shortname'=>$course->shortname, + 'course_id'=>$course->courseid))); break; case ENROL_EXT_REMOVED_KEEP: // Keep - only adding enrolments @@ -266,10 +269,10 @@ class enrol_ldap_plugin extends enrol_plugin { case ENROL_EXT_REMOVED_SUSPEND: if ($course->status != ENROL_USER_SUSPENDED) { $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id)); - error_log($this->errorlogtag.get_string('extremovedsuspend', 'enrol_ldap', - array('user_username'=> $user->username, - 'course_shortname'=>$course->shortname, - 'course_id'=>$course->courseid))); + $trace->output(get_string('extremovedsuspend', 'enrol_ldap', + array('user_username'=> $user->username, + 'course_shortname'=>$course->shortname, + 'course_id'=>$course->courseid))); } break; case ENROL_EXT_REMOVED_SUSPENDNOROLES: @@ -277,10 +280,10 @@ class enrol_ldap_plugin extends enrol_plugin { $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$user->id)); } role_unassign_all(array('contextid'=>$context->id, 'userid'=>$user->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id)); - error_log($this->errorlogtag.get_string('extremovedsuspendnoroles', 'enrol_ldap', - array('user_username'=> $user->username, - 'course_shortname'=>$course->shortname, - 'course_id'=>$course->courseid))); + $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap', + array('user_username'=> $user->username, + 'course_shortname'=>$course->shortname, + 'course_id'=>$course->courseid))); break; } } @@ -288,19 +291,24 @@ class enrol_ldap_plugin extends enrol_plugin { } $this->ldap_close($ldapconnection); + + $trace->finished(); } /** * Forces synchronisation of all enrolments with LDAP server. * It creates courses if the plugin is configured to do so. * + * @param progress_trace $trace + * @param int|null $onecourse limit sync to one course, null if all courses * @return void */ - public function sync_enrolments() { + public function sync_enrolments(progress_trace $trace, $onecourse = null) { global $CFG, $DB; - $ldapconnection = $this->ldap_connect(); + $ldapconnection = $this->ldap_connect($trace); if (!$ldapconnection) { + $trace->finished(); return; } @@ -310,6 +318,22 @@ class enrol_ldap_plugin extends enrol_plugin { @set_time_limit(0); raise_memory_limit(MEMORY_HUGE); + $oneidnumber = null; + if ($onecourse) { + if (!$course = $DB->get_record('course', array('id'=>$onecourse), 'id,idnumber')) { + // Course does nto exist, nothing to do. + $trace->output("Requested course $onecourse does not exist, no sync performed."); + $trace->finished(); + return; + } + if (empty($course->{$this->enrol_localcoursefield})) { + $trace->output("Requested course $onecourse does not have {$this->enrol_localcoursefield}, no sync performed."); + $trace->finished(); + return; + } + $oneidnumber = textlib::convert(ldap_addslashes($course->idnumber), 'utf-8', $this->get_config('ldapencoding')); + } + // Get enrolments for each type of role. $roles = get_all_roles(); $enrolments = array(); @@ -334,6 +358,10 @@ class enrol_ldap_plugin extends enrol_plugin { // Define the search pattern $ldap_search_pattern = $this->config->objectclass; + if ($oneidnumber !== null) { + $ldap_search_pattern = "(&$ldap_search_pattern({$this->config->course_idnumber}=$oneidnumber))"; + } + $ldap_cookie = ''; foreach ($ldap_contexts as $ldap_context) { $ldap_context = trim($ldap_context); @@ -383,7 +411,7 @@ class enrol_ldap_plugin extends enrol_plugin { // closed and a new one created, to work without paged results from here on. if ($ldap_pagedresults) { $this->ldap_close(true); - $ldapconnection = $this->ldap_connect(); + $ldapconnection = $this->ldap_connect($trace); } if (count($flat_records)) { @@ -391,21 +419,19 @@ class enrol_ldap_plugin extends enrol_plugin { foreach($flat_records as $course) { $course = array_change_key_case($course, CASE_LOWER); $idnumber = $course{$this->config->course_idnumber}[0]; - print_string('synccourserole', 'enrol_ldap', - array('idnumber'=>$idnumber, 'role_shortname'=>$role->shortname)); + $trace->output(get_string('synccourserole', 'enrol_ldap', array('idnumber'=>$idnumber, 'role_shortname'=>$role->shortname))); // Does the course exist in moodle already? $course_obj = $DB->get_record('course', array($this->enrol_localcoursefield=>$idnumber)); if (empty($course_obj)) { // Course doesn't exist if ($this->get_config('autocreate')) { // Autocreate - error_log($this->errorlogtag.get_string('createcourseextid', 'enrol_ldap', - array('courseextid'=>$idnumber))); - if ($newcourseid = $this->create_course($course)) { - $course_obj = $DB->get_record('course', array('id'=>$newcourseid)); + $trace->output(get_string('createcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber))); + if (!$newcourseid = $this->create_course($course, $trace)) { + continue; } + $course_obj = $DB->get_record('course', array('id'=>$newcourseid)); } else { - error_log($this->errorlogtag.get_string('createnotcourseextid', 'enrol_ldap', - array('courseextid'=>$idnumber))); + $trace->output(get_string('createnotcourseextid', 'enrol_ldap', array('courseextid'=>$idnumber))); continue; // Next; skip this one! } } @@ -489,10 +515,10 @@ class enrol_ldap_plugin extends enrol_plugin { switch ($this->get_config('unenrolaction')) { case ENROL_EXT_REMOVED_UNENROL: $this->unenrol_user($instance, $row->userid); - error_log($this->errorlogtag.get_string('extremovedunenrol', 'enrol_ldap', - array('user_username'=> $row->username, - 'course_shortname'=>$course_obj->shortname, - 'course_id'=>$course_obj->id))); + $trace->output(get_string('extremovedunenrol', 'enrol_ldap', + array('user_username'=> $row->username, + 'course_shortname'=>$course_obj->shortname, + 'course_id'=>$course_obj->id))); break; case ENROL_EXT_REMOVED_KEEP: // Keep - only adding enrolments @@ -500,10 +526,10 @@ class enrol_ldap_plugin extends enrol_plugin { case ENROL_EXT_REMOVED_SUSPEND: if ($row->status != ENROL_USER_SUSPENDED) { $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid)); - error_log($this->errorlogtag.get_string('extremovedsuspend', 'enrol_ldap', - array('user_username'=> $row->username, - 'course_shortname'=>$course_obj->shortname, - 'course_id'=>$course_obj->id))); + $trace->output(get_string('extremovedsuspend', 'enrol_ldap', + array('user_username'=> $row->username, + 'course_shortname'=>$course_obj->shortname, + 'course_id'=>$course_obj->id))); } break; case ENROL_EXT_REMOVED_SUSPENDNOROLES: @@ -511,10 +537,10 @@ class enrol_ldap_plugin extends enrol_plugin { $DB->set_field('user_enrolments', 'status', ENROL_USER_SUSPENDED, array('enrolid'=>$instance->id, 'userid'=>$row->userid)); } role_unassign_all(array('contextid'=>$row->contextid, 'userid'=>$row->userid, 'component'=>'enrol_ldap', 'itemid'=>$instance->id)); - error_log($this->errorlogtag.get_string('extremovedsuspendnoroles', 'enrol_ldap', - array('user_username'=> $row->username, - 'course_shortname'=>$course_obj->shortname, - 'course_id'=>$course_obj->id))); + $trace->output(get_string('extremovedsuspendnoroles', 'enrol_ldap', + array('user_username'=> $row->username, + 'course_shortname'=>$course_obj->shortname, + 'course_id'=>$course_obj->id))); break; } } @@ -560,7 +586,7 @@ class enrol_ldap_plugin extends enrol_plugin { JOIN {role_assignments} ra ON (ra.itemid = e.id AND ra.component = 'enrol_ldap') WHERE e.courseid = :courseid AND ue.userid = :userid"; $params = array('courseid'=>$course_obj->id, 'userid'=>$member->id); - $userenrolment = $DB->get_record_sql($sql, $params); + $userenrolment = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE); if(empty($userenrolment)) { $this->enrol_user($instance, $member->id, $role->id); @@ -571,19 +597,19 @@ class enrol_ldap_plugin extends enrol_plugin { // set it back to active on external re-enrolment. So set it // unconditionnally to cover both cases. $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id)); - error_log($this->errorlogtag.get_string('enroluser', 'enrol_ldap', - array('user_username'=> $member->username, - 'course_shortname'=>$course_obj->shortname, - 'course_id'=>$course_obj->id))); + $trace->output(get_string('enroluser', 'enrol_ldap', + array('user_username'=> $member->username, + 'course_shortname'=>$course_obj->shortname, + 'course_id'=>$course_obj->id))); } else { if ($userenrolment->status == ENROL_USER_SUSPENDED) { // Reenable enrolment that was previously disabled. Enrolment refreshed $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id)); - error_log($this->errorlogtag.get_string('enroluserenable', 'enrol_ldap', - array('user_username'=> $member->username, - 'course_shortname'=>$course_obj->shortname, - 'course_id'=>$course_obj->id))); + $trace->output(get_string('enroluserenable', 'enrol_ldap', + array('user_username'=> $member->username, + 'course_shortname'=>$course_obj->shortname, + 'course_id'=>$course_obj->id))); } } } @@ -593,15 +619,17 @@ class enrol_ldap_plugin extends enrol_plugin { } } @$this->ldap_close(); + $trace->finished(); } /** * Connect to the LDAP server, using the plugin configured * settings. It's actually a wrapper around ldap_connect_moodle() * + * @param progress_trace $trace * @return mixed A valid LDAP connection or false. */ - protected function ldap_connect() { + protected function ldap_connect(progress_trace $trace = null) { global $CFG; require_once($CFG->libdir.'/ldaplib.php'); @@ -624,10 +652,11 @@ class enrol_ldap_plugin extends enrol_plugin { return $ldapconnection; } - // Log the problem, but don't show it to the user. She doesn't - // even have a chance to see it, as we redirect instantly to - // the user/front page. - error_log($this->errorlogtag.$debuginfo); + if ($trace) { + $trace->output($debuginfo); + } else { + error_log($this->errorlogtag.$debuginfo); + } return false; } @@ -910,15 +939,12 @@ class enrol_ldap_plugin extends enrol_plugin { /** * Will create the moodle course from the template * course_ext is an array as obtained from ldap -- flattened somewhat - * NOTE: if you pass true for $skip_fix_course_sortorder - * you will want to call fix_course_sortorder() after your are done - * with course creation. * * @param array $course_ext - * @param boolean $skip_fix_course_sortorder + * @param progress_trace $trace * @return mixed false on error, id for the newly created course otherwise. */ - function create_course($course_ext, $skip_fix_course_sortorder=false) { + function create_course($course_ext, progress_trace $trace) { global $CFG, $DB; require_once("$CFG->dirroot/course/lib.php"); @@ -965,8 +991,7 @@ class enrol_ldap_plugin extends enrol_plugin { $course->shortname = $course_ext[$this->get_config('course_shortname')][0]; if (empty($course->idnumber) || empty($course->fullname) || empty($course->shortname)) { // We are in trouble! - error_log($this->errorlogtag.get_string('cannotcreatecourse', 'enrol_ldap')); - error_log($this->errorlogtag.var_export($course, true)); + $trace->output(get_string('cannotcreatecourse', 'enrol_ldap').' '.var_export($course, true)); return false; } @@ -980,5 +1005,86 @@ class enrol_ldap_plugin extends enrol_plugin { $newcourse = create_course($course); return $newcourse->id; } + + /** + * Automatic enrol sync executed during restore. + * Useful for automatic sync by course->idnumber or course category. + * @param stdClass $course course record + */ + public function restore_sync_course($course) { + // TODO: this can not work because restore always nukes the course->idnumber, do not ask me why (MDL-37312) + // NOTE: for now restore does not do any real logging yet, let's do the same here... + $trace = new error_log_progress_trace(); + $this->sync_enrolments($trace, $course->id); + } + + /** + * Restore instance and map settings. + * + * @param restore_enrolments_structure_step $step + * @param stdClass $data + * @param stdClass $course + * @param int $oldid + */ + public function restore_instance(restore_enrolments_structure_step $step, stdClass $data, $course, $oldid) { + global $DB; + // There is only 1 ldap enrol instance per course. + if ($instances = $DB->get_records('enrol', array('courseid'=>$data->courseid, 'enrol'=>'ldap'), 'id')) { + $instance = reset($instances); + $instanceid = $instance->id; + } else { + $instanceid = $this->add_instance($course, (array)$data); + } + $step->set_mapping('enrol', $oldid, $instanceid); + } + + /** + * Restore user enrolment. + * + * @param restore_enrolments_structure_step $step + * @param stdClass $data + * @param stdClass $instance + * @param int $oldinstancestatus + * @param int $userid + */ + public function restore_user_enrolment(restore_enrolments_structure_step $step, $data, $instance, $userid, $oldinstancestatus) { + global $DB; + + if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL) { + // Enrolments were already synchronised in restore_instance(), we do not want any suspended leftovers. + + } else if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_KEEP) { + if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) { + $this->enrol_user($instance, $userid, null, 0, 0, $data->status); + } + + } else { + if (!$DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) { + $this->enrol_user($instance, $userid, null, 0, 0, ENROL_USER_SUSPENDED); + } + } + } + + /** + * Restore role assignment. + * + * @param stdClass $instance + * @param int $roleid + * @param int $userid + * @param int $contextid + */ + public function restore_role_assignment($instance, $roleid, $userid, $contextid) { + global $DB; + + if ($this->get_config('unenrolaction') == ENROL_EXT_REMOVED_UNENROL or $this->get_config('unenrolaction') == ENROL_EXT_REMOVED_SUSPENDNOROLES) { + // Skip any roles restore, they should be already synced automatically. + return; + } + + // Just restore every role. + if ($DB->record_exists('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid))) { + role_assign($roleid, $userid, $contextid, 'enrol_'.$instance->enrol, $instance->id); + } + } } From 1b20e00828d3f3ec6f3dc4d2d4a8fe3f1003271c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Sat, 12 Jan 2013 15:42:21 +0100 Subject: [PATCH 2/4] =?UTF-8?q?MDL-35159=20fix=20regressions=20reported=20?= =?UTF-8?q?by=20In=CC=83aki=20Arenaza?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- enrol/ldap/lib.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/enrol/ldap/lib.php b/enrol/ldap/lib.php index c7b2c5243c6..f71413e9e47 100644 --- a/enrol/ldap/lib.php +++ b/enrol/ldap/lib.php @@ -300,7 +300,7 @@ class enrol_ldap_plugin extends enrol_plugin { * It creates courses if the plugin is configured to do so. * * @param progress_trace $trace - * @param int|null $onecourse limit sync to one course, null if all courses + * @param int|null $onecourse limit sync to one course->id, null if all courses * @return void */ public function sync_enrolments(progress_trace $trace, $onecourse = null) { @@ -320,8 +320,8 @@ class enrol_ldap_plugin extends enrol_plugin { $oneidnumber = null; if ($onecourse) { - if (!$course = $DB->get_record('course', array('id'=>$onecourse), 'id,idnumber')) { - // Course does nto exist, nothing to do. + if (!$course = $DB->get_record('course', array('id'=>$onecourse), 'id,'.$this->enrol_localcoursefield)) { + // Course does not exist, nothing to do. $trace->output("Requested course $onecourse does not exist, no sync performed."); $trace->finished(); return; @@ -331,7 +331,7 @@ class enrol_ldap_plugin extends enrol_plugin { $trace->finished(); return; } - $oneidnumber = textlib::convert(ldap_addslashes($course->idnumber), 'utf-8', $this->get_config('ldapencoding')); + $oneidnumber = ldap_filter_addslashes(textlib::convert($course->idnumber, 'utf-8', $this->get_config('ldapencoding'))); } // Get enrolments for each type of role. From 23f36d50c59cd11961777b83e12a48bababb5f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Sat, 12 Jan 2013 21:18:25 +0100 Subject: [PATCH 3/4] MDL-37391 refactor ldap connection handling --- enrol/ldap/lib.php | 117 ++++++++++++++++++++------------------------- 1 file changed, 51 insertions(+), 66 deletions(-) diff --git a/enrol/ldap/lib.php b/enrol/ldap/lib.php index f71413e9e47..fb2e649c24c 100644 --- a/enrol/ldap/lib.php +++ b/enrol/ldap/lib.php @@ -134,8 +134,7 @@ class enrol_ldap_plugin extends enrol_plugin { // Do not try to print anything to the output because this method is called during interactive login. $trace = new error_log_progress_trace($this->errorlogtag); - $ldapconnection = $this->ldap_connect($trace); - if (!$ldapconnection) { + if (!$this->ldap_connect($trace)) { $trace->finished(); return; } @@ -158,7 +157,7 @@ class enrol_ldap_plugin extends enrol_plugin { $enrolments = array(); foreach($roles as $role) { // Get external enrolments according to LDAP server - $enrolments[$role->id]['ext'] = $this->find_ext_enrolments($ldapconnection, $user->idnumber, $role); + $enrolments[$role->id]['ext'] = $this->find_ext_enrolments($user->idnumber, $role); // Get the list of current user enrolments that come from LDAP $sql= "SELECT e.courseid, ue.status, e.id as enrolid, c.shortname @@ -290,7 +289,7 @@ class enrol_ldap_plugin extends enrol_plugin { $transaction->allow_commit(); } - $this->ldap_close($ldapconnection); + $this->ldap_close(); $trace->finished(); } @@ -306,8 +305,7 @@ class enrol_ldap_plugin extends enrol_plugin { public function sync_enrolments(progress_trace $trace, $onecourse = null) { global $CFG, $DB; - $ldapconnection = $this->ldap_connect($trace); - if (!$ldapconnection) { + if (!$this->ldap_connect($trace)) { $trace->finished(); return; } @@ -372,18 +370,18 @@ class enrol_ldap_plugin extends enrol_plugin { $flat_records = array(); do { if ($ldap_pagedresults) { - ldap_control_paged_result($ldapconnection, $this->config->pagesize, true, $ldap_cookie); + ldap_control_paged_result($this->ldapconnection, $this->config->pagesize, true, $ldap_cookie); } if ($this->config->course_search_sub) { // Use ldap_search to find first user from subtree - $ldap_result = @ldap_search($ldapconnection, + $ldap_result = @ldap_search($this->ldapconnection, $ldap_context, $ldap_search_pattern, $ldap_fields_wanted); } else { // Search only in this context - $ldap_result = @ldap_list($ldapconnection, + $ldap_result = @ldap_list($this->ldapconnection, $ldap_context, $ldap_search_pattern, $ldap_fields_wanted); @@ -393,11 +391,11 @@ class enrol_ldap_plugin extends enrol_plugin { } if ($ldap_pagedresults) { - ldap_control_paged_result_response($ldapconnection, $ldap_result, $ldap_cookie); + ldap_control_paged_result_response($this->ldapconnection, $ldap_result, $ldap_cookie); } // Check and push results - $records = ldap_get_entries($ldapconnection, $ldap_result); + $records = ldap_get_entries($this->ldapconnection, $ldap_result); // LDAP libraries return an odd array, really. fix it: for ($c = 0; $c < $records['count']; $c++) { @@ -410,8 +408,8 @@ class enrol_ldap_plugin extends enrol_plugin { // 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 ($ldap_pagedresults) { - $this->ldap_close(true); - $ldapconnection = $this->ldap_connect($trace); + $this->ldap_close(); + $this->ldap_connect($trace); } if (count($flat_records)) { @@ -455,8 +453,7 @@ class enrol_ldap_plugin extends enrol_plugin { if ($this->config->nested_groups) { $users = array(); foreach ($ldapmembers as $ldapmember) { - $grpusers = $this->ldap_explode_group($ldapconnection, - $ldapmember, + $grpusers = $this->ldap_explode_group($ldapmember, $this->config->{'memberattribute_role'.$role->id}); $users = array_merge($users, $grpusers); @@ -473,10 +470,10 @@ class enrol_ldap_plugin extends enrol_plugin { // as the idnumber does not match their dn and we get dn's from membership. $memberidnumbers = array(); foreach ($ldapmembers as $ldapmember) { - $result = ldap_read($ldapconnection, $ldapmember, '(objectClass=*)', + $result = ldap_read($this->ldapconnection, $ldapmember, '(objectClass=*)', array($this->config->idnumber_attribute)); - $entry = ldap_first_entry($ldapconnection, $result); - $values = ldap_get_values($ldapconnection, $entry, $this->config->idnumber_attribute); + $entry = ldap_first_entry($this->ldapconnection, $result); + $values = ldap_get_values($this->ldapconnection, $entry, $this->config->idnumber_attribute); array_push($memberidnumbers, $values[0]); } @@ -627,29 +624,22 @@ class enrol_ldap_plugin extends enrol_plugin { * settings. It's actually a wrapper around ldap_connect_moodle() * * @param progress_trace $trace - * @return mixed A valid LDAP connection or false. + * @return bool success */ protected function ldap_connect(progress_trace $trace = null) { global $CFG; require_once($CFG->libdir.'/ldaplib.php'); - // Cache ldap connections. They are expensive to set up - // and can drain the TCP/IP ressources on the server if we - // are syncing a lot of users (as we try to open a new connection - // to get the user details). This is the least invasive way - // to reuse existing connections without greater code surgery. - if(!empty($this->ldapconnection)) { - $this->ldapconns++; - return $this->ldapconnection; + if (isset($this->ldapconnection)) { + return true; } if ($ldapconnection = ldap_connect_moodle($this->get_config('host_url'), $this->get_config('ldap_version'), $this->get_config('user_type'), $this->get_config('bind_dn'), $this->get_config('bind_pw'), $this->get_config('opt_deref'), $debuginfo, $this->get_config('start_tls'))) { - $this->ldapconns = 1; $this->ldapconnection = $ldapconnection; - return $ldapconnection; + return true; } if ($trace) { @@ -666,23 +656,22 @@ class enrol_ldap_plugin extends enrol_plugin { * */ protected function ldap_close() { - $this->ldapconns--; - if($this->ldapconns == 0) { + if (isset($this->ldapconnection)) { @ldap_close($this->ldapconnection); - unset($this->ldapconnection); + $this->ldapconnection = null; } + return; } /** * Return multidimensional array with details of user courses (at * least dn and idnumber). * - * @param resource $ldapconnection a valid LDAP connection. * @param string $memberuid user idnumber (without magic quotes). * @param object role is a record from the mdl_role table. * @return array */ - protected function find_ext_enrolments (&$ldapconnection, $memberuid, $role) { + protected function find_ext_enrolments($memberuid, $role) { global $CFG; require_once($CFG->libdir.'/ldaplib.php'); @@ -700,14 +689,14 @@ class enrol_ldap_plugin extends enrol_plugin { $extmemberuid = textlib::convert($memberuid, 'utf-8', $this->get_config('ldapencoding')); if($this->get_config('memberattribute_isdn')) { - if (!($extmemberuid = $this->ldap_find_userdn ($ldapconnection, $extmemberuid))) { + if (!($extmemberuid = $this->ldap_find_userdn($extmemberuid))) { return array(); } } $ldap_search_pattern = ''; if($this->get_config('nested_groups')) { - $usergroups = $this->ldap_find_user_groups($ldapconnection, $extmemberuid); + $usergroups = $this->ldap_find_user_groups($extmemberuid); if(count($usergroups) > 0) { foreach ($usergroups as $group) { $ldap_search_pattern .= '('.$this->get_config('memberattribute_role'.$role->id).'='.$group.')'; @@ -757,18 +746,18 @@ class enrol_ldap_plugin extends enrol_plugin { $flat_records = array(); do { if ($ldap_pagedresults) { - ldap_control_paged_result($ldapconnection, $this->config->pagesize, true, $ldap_cookie); + ldap_control_paged_result($this->ldapconnection, $this->config->pagesize, true, $ldap_cookie); } if ($this->get_config('course_search_sub')) { // Use ldap_search to find first user from subtree - $ldap_result = @ldap_search($ldapconnection, + $ldap_result = @ldap_search($this->ldapconnection, $context, $ldap_search_pattern, $ldap_fields_wanted); } else { // Search only in this context - $ldap_result = @ldap_list($ldapconnection, + $ldap_result = @ldap_list($this->ldapconnection, $context, $ldap_search_pattern, $ldap_fields_wanted); @@ -779,13 +768,13 @@ class enrol_ldap_plugin extends enrol_plugin { } if ($ldap_pagedresults) { - ldap_control_paged_result_response($ldapconnection, $ldap_result, $ldap_cookie); + ldap_control_paged_result_response($this->ldapconnection, $ldap_result, $ldap_cookie); } // Check and push results. ldap_get_entries() already // lowercases the attribute index, so there's no need to // use array_change_key_case() later. - $records = ldap_get_entries($ldapconnection, $ldap_result); + $records = ldap_get_entries($this->ldapconnection, $ldap_result); // LDAP libraries return an odd array, really. Fix it. for ($c = 0; $c < $records['count']; $c++) { @@ -798,8 +787,8 @@ class enrol_ldap_plugin extends enrol_plugin { // 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 ($ldap_pagedresults) { - $this->ldap_close(true); - $ldapconnection = $this->ldap_connect(); + $this->ldap_close(); + $this->ldap_connect(); } if (count($flat_records)) { @@ -815,18 +804,17 @@ class enrol_ldap_plugin extends enrol_plugin { * user dn like: cn=username,ou=suborg,o=org. It's actually a wrapper * around ldap_find_userdn(). * - * @param resource $ldapconnection a valid LDAP connection * @param string $userid the userid to search for (in external LDAP encoding, no magic quotes). * @return mixed the user dn or false */ - protected function ldap_find_userdn($ldapconnection, $userid) { + protected function ldap_find_userdn($userid) { global $CFG; require_once($CFG->libdir.'/ldaplib.php'); $ldap_contexts = explode(';', $this->get_config('user_contexts')); $ldap_defaults = ldap_getdefaults(); - return ldap_find_userdn($ldapconnection, $userid, $ldap_contexts, + return ldap_find_userdn($this->ldapconnection, $userid, $ldap_contexts, '(objectClass='.$ldap_defaults['objectclass'][$this->get_config('user_type')].')', $this->get_config('idnumber_attribute'), $this->get_config('user_search_sub')); } @@ -835,14 +823,13 @@ class enrol_ldap_plugin extends enrol_plugin { * Find the groups a given distinguished name belongs to, both directly * and indirectly via nested groups membership. * - * @param resource $ldapconnection a valid LDAP connection * @param string $memberdn distinguished name to search * @return array with member groups' distinguished names (can be emtpy) */ - protected function ldap_find_user_groups($ldapconnection, $memberdn) { + protected function ldap_find_user_groups($memberdn) { $groups = array(); - $this->ldap_find_user_groups_recursively($ldapconnection, $memberdn, $groups); + $this->ldap_find_user_groups_recursively($memberdn, $groups); return $groups; } @@ -850,23 +837,22 @@ class enrol_ldap_plugin extends enrol_plugin { * Recursively process the groups the given member distinguished name * belongs to, adding them to the already processed groups array. * - * @param resource $ldapconnection * @param string $memberdn distinguished name to search * @param array reference &$membergroups array with already found * groups, where we'll put the newly found * groups. */ - protected function ldap_find_user_groups_recursively($ldapconnection, $memberdn, &$membergroups) { - $result = @ldap_read($ldapconnection, $memberdn, '(objectClass=*)', array($this->get_config('group_memberofattribute'))); + protected function ldap_find_user_groups_recursively($memberdn, &$membergroups) { + $result = @ldap_read($this->ldapconnection, $memberdn, '(objectClass=*)', array($this->get_config('group_memberofattribute'))); if (!$result) { return; } - if ($entry = ldap_first_entry($ldapconnection, $result)) { + if ($entry = ldap_first_entry($this->ldapconnection, $result)) { do { - $attributes = ldap_get_attributes($ldapconnection, $entry); + $attributes = ldap_get_attributes($this->ldapconnection, $entry); for ($j = 0; $j < $attributes['count']; $j++) { - $groups = ldap_get_values_len($ldapconnection, $entry, $attributes[$j]); + $groups = ldap_get_values_len($this->ldapconnection, $entry, $attributes[$j]); foreach ($groups as $key => $group) { if ($key === 'count') { // Skip the entries count continue; @@ -875,12 +861,12 @@ class enrol_ldap_plugin extends enrol_plugin { // Only push and recurse if we haven't 'seen' this group before // to prevent loops (MS Active Directory allows them!!). array_push($membergroups, $group); - $this->ldap_find_user_groups_recursively($ldapconnection, $group, $membergroups); + $this->ldap_find_user_groups_recursively($group, $membergroups); } } } } - while ($entry = ldap_next_entry($ldapconnection, $entry)); + while ($entry = ldap_next_entry($this->ldapconnection, $entry)); } } @@ -890,30 +876,29 @@ class enrol_ldap_plugin extends enrol_plugin { * the intermediate groups and return the full list of users that * directly or indirectly belong to the group. * - * @param resource $ldapconnection a valid LDAP connection * @param string $group the group name to search * @param string $memberattibute the attribute that holds the members of the group * @return array the list of users belonging to the group. If $group * is not actually a group, returns array($group). */ - protected function ldap_explode_group($ldapconnection, $group, $memberattribute) { + protected function ldap_explode_group($group, $memberattribute) { switch ($this->get_config('user_type')) { case 'ad': // $group is already the distinguished name to search. $dn = $group; - $result = ldap_read($ldapconnection, $dn, '(objectClass=*)', array('objectClass')); - $entry = ldap_first_entry($ldapconnection, $result); - $objectclass = ldap_get_values($ldapconnection, $entry, 'objectClass'); + $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array('objectClass')); + $entry = ldap_first_entry($this->ldapconnection, $result); + $objectclass = ldap_get_values($this->ldapconnection, $entry, 'objectClass'); if (!in_array('group', $objectclass)) { // Not a group, so return immediately. return array($group); } - $result = ldap_read($ldapconnection, $dn, '(objectClass=*)', array($memberattribute)); - $entry = ldap_first_entry($ldapconnection, $result); - $members = @ldap_get_values($ldapconnection, $entry, $memberattribute); // Can be empty and throws a warning + $result = ldap_read($this->ldapconnection, $dn, '(objectClass=*)', array($memberattribute)); + $entry = ldap_first_entry($this->ldapconnection, $result); + $members = @ldap_get_values($this->ldapconnection, $entry, $memberattribute); // Can be empty and throws a warning if ($members['count'] == 0) { // There are no members in this group, return nothing. return array(); @@ -922,7 +907,7 @@ class enrol_ldap_plugin extends enrol_plugin { $users = array(); foreach ($members as $member) { - $group_members = $this->ldap_explode_group($ldapconnection, $member, $memberattribute); + $group_members = $this->ldap_explode_group($member, $memberattribute); $users = array_merge($users, $group_members); } From 34183020a70eddfdeb0511543ab6d14136c128e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20S=CC=8Ckoda?= Date: Sat, 12 Jan 2013 23:09:17 +0100 Subject: [PATCH 4/4] MDL-37391 add enrol_ldap tests and fix enrolment reviving --- enrol/ldap/lib.php | 19 +- enrol/ldap/tests/ldap_test.php | 472 +++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+), 7 deletions(-) create mode 100644 enrol/ldap/tests/ldap_test.php diff --git a/enrol/ldap/lib.php b/enrol/ldap/lib.php index fb2e649c24c..3a1cb0fb434 100644 --- a/enrol/ldap/lib.php +++ b/enrol/ldap/lib.php @@ -499,9 +499,9 @@ class enrol_ldap_plugin extends enrol_plugin { unset($params2); } else { $shortname = format_string($course_obj->shortname, true, array('context' => $context)); - print_string('emptyenrolment', 'enrol_ldap', + $trace->output(get_string('emptyenrolment', 'enrol_ldap', array('role_shortname'=> $role->shortname, - 'course_shortname' => $shortname)); + 'course_shortname' => $shortname))); } $todelete = $DB->get_records_sql($sql, $params); @@ -573,26 +573,25 @@ class enrol_ldap_plugin extends enrol_plugin { $sql = 'SELECT id,username,1 FROM {user} WHERE idnumber = ? AND deleted = 0'; $member = $DB->get_record_sql($sql, array($ldapmember)); if(empty($member) || empty($member->id)){ - print_string ('couldnotfinduser', 'enrol_ldap', $ldapmember); + $trace->output(get_string('couldnotfinduser', 'enrol_ldap', $ldapmember)); continue; } $sql= "SELECT ue.status FROM {user_enrolments} ue - JOIN {enrol} e ON (e.id = ue.enrolid) - JOIN {role_assignments} ra ON (ra.itemid = e.id AND ra.component = 'enrol_ldap') + JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = 'ldap') WHERE e.courseid = :courseid AND ue.userid = :userid"; $params = array('courseid'=>$course_obj->id, 'userid'=>$member->id); $userenrolment = $DB->get_record_sql($sql, $params, IGNORE_MULTIPLE); - if(empty($userenrolment)) { + if (empty($userenrolment)) { $this->enrol_user($instance, $member->id, $role->id); // Make sure we set the enrolment status to active. If the user wasn't // previously enrolled to the course, enrol_user() sets it. But if we // configured the plugin to suspend the user enrolments _AND_ remove // the role assignments on external unenrol, then enrol_user() doesn't // set it back to active on external re-enrolment. So set it - // unconditionnally to cover both cases. + // unconditionally to cover both cases. $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id)); $trace->output(get_string('enroluser', 'enrol_ldap', array('user_username'=> $member->username, @@ -600,6 +599,12 @@ class enrol_ldap_plugin extends enrol_plugin { 'course_id'=>$course_obj->id))); } else { + if (!$DB->record_exists('role_assignments', array('roleid'=>$role->id, 'userid'=>$member->id, 'contextid'=>$context->id, 'component'=>'enrol_ldap', 'itemid'=>$instance->id))) { + // This happens when reviving users or when user has multiple roles in one course. + $context = context_course::instance($course_obj->id); + role_assign($role->id, $member->id, $context->id, 'enrol_ldap', $instance->id); + $trace->output("Assign role to user '$member->username' in course '$course_obj->shortname ($course_obj->id)'"); + } if ($userenrolment->status == ENROL_USER_SUSPENDED) { // Reenable enrolment that was previously disabled. Enrolment refreshed $DB->set_field('user_enrolments', 'status', ENROL_USER_ACTIVE, array('enrolid'=>$instance->id, 'userid'=>$member->id)); diff --git a/enrol/ldap/tests/ldap_test.php b/enrol/ldap/tests/ldap_test.php new file mode 100644 index 00000000000..ded95312aec --- /dev/null +++ b/enrol/ldap/tests/ldap_test.php @@ -0,0 +1,472 @@ +. + +/** + * LDAP enrolment plugin tests. + * + * NOTE: in order to execute this test you need to set up + * OpenLDAP server with core, cosine, nis and internet schemas + * and add configuration constants to config.php or phpunit.xml configuration file: + * + * define('TEST_ENROL_LDAP_HOST_URL', 'ldap://127.0.0.1'); + * define('TEST_ENROL_LDAP_BIND_DN', 'cn=someuser,dc=example,dc=local'); + * define('TEST_ENROL_LDAP_BIND_PW', 'somepassword'); + * define('TEST_ENROL_LDAP_DOMAIN', 'dc=example,dc=local'); + * + * @package enrol_ldap + * @category phpunit + * @copyright 2013 Petr Skoda {@link http://skodak.org} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + + +class enrol_ldap_testcase extends advanced_testcase { + + public function test_enrol_ldap() { + global $CFG, $DB; + + if (!extension_loaded('ldap')) { + $this->markTestSkipped('LDAP extension is not loaded.'); + } + + $this->resetAfterTest(); + + require_once($CFG->dirroot.'/enrol/ldap/lib.php'); + require_once($CFG->libdir.'/ldaplib.php'); + + if (!defined('TEST_ENROL_LDAP_HOST_URL') or !defined('TEST_ENROL_LDAP_BIND_DN') or !defined('TEST_ENROL_LDAP_BIND_PW') or !defined('TEST_ENROL_LDAP_DOMAIN')) { + $this->markTestSkipped('External LDAP test server not configured.'); + } + + // Make sure we can connect the server. + $debuginfo = ''; + if (!$connection = ldap_connect_moodle(TEST_ENROL_LDAP_HOST_URL, 3, 'rfc2307', TEST_ENROL_LDAP_BIND_DN, TEST_ENROL_LDAP_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) { + $this->markTestSkipped('Can not connect to LDAP test server.'); + } + + $this->enable_plugin(); + + // Create new empty test container. + $topdn = 'dc=moodletest,'.TEST_ENROL_LDAP_DOMAIN; + + $this->recursive_delete($connection, TEST_ENROL_LDAP_DOMAIN, 'dc=moodletest'); + + $o = array(); + $o['objectClass'] = array('dcObject', 'organizationalUnit'); + $o['dc'] = 'moodletest'; + $o['ou'] = 'MOODLETEST'; + if (!ldap_add($connection, 'dc=moodletest,'.TEST_ENROL_LDAP_DOMAIN, $o)) { + $this->markTestSkipped('Can not create test LDAP container.'); + } + + // Configure enrol plugin. + /** @var enrol_ldap_plugin $enrol */ + $enrol = enrol_get_plugin('ldap'); + $enrol->set_config('host_url', TEST_ENROL_LDAP_HOST_URL); + $enrol->set_config('start_tls', 0); + $enrol->set_config('ldap_version', 3); + $enrol->set_config('ldapencoding', 'utf-8'); + $enrol->set_config('page_size', '2'); + $enrol->set_config('bind_dn', TEST_ENROL_LDAP_BIND_DN); + $enrol->set_config('bind_pw', TEST_ENROL_LDAP_BIND_PW); + $enrol->set_config('course_search_sub', 0); + $enrol->set_config('memberattribute_isdn', 0); + $enrol->set_config('user_contexts', ''); + $enrol->set_config('user_search_sub', 0); + $enrol->set_config('user_type', 'rfc2307'); + $enrol->set_config('opt_deref', LDAP_DEREF_NEVER); + $enrol->set_config('objectclass', '(objectClass=posixGroup)'); + $enrol->set_config('course_idnumber', 'cn'); + $enrol->set_config('course_shortname', 'cn'); + $enrol->set_config('course_fullname', 'cn'); + $enrol->set_config('course_summary', ''); + $enrol->set_config('ignorehiddencourses', 0); + $enrol->set_config('nested_groups', 0); + $enrol->set_config('autocreate', 0); + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_KEEP); + + $roles = get_all_roles(); + foreach ($roles as $role) { + $enrol->set_config('contexts_role'.$role->id, ''); + $enrol->set_config('memberattribute_role'.$role->id, ''); + } + + // Create group for teacher enrolments. + $teacherrole = $DB->get_record('role', array('shortname'=>'teacher')); + $this->assertNotEmpty($teacherrole); + $o = array(); + $o['objectClass'] = array('organizationalUnit'); + $o['ou'] = 'teachers'; + ldap_add($connection, 'ou=teachers,'.$topdn, $o); + $enrol->set_config('contexts_role'.$teacherrole->id, 'ou=teachers,'.$topdn); + $enrol->set_config('memberattribute_role'.$teacherrole->id, 'memberuid'); + + // Create group for student enrolments. + $studentrole = $DB->get_record('role', array('shortname'=>'student')); + $this->assertNotEmpty($studentrole); + $o = array(); + $o['objectClass'] = array('organizationalUnit'); + $o['ou'] = 'students'; + ldap_add($connection, 'ou=students,'.$topdn, $o); + $enrol->set_config('contexts_role'.$studentrole->id, 'ou=students,'.$topdn); + $enrol->set_config('memberattribute_role'.$studentrole->id, 'memberuid'); + + // Create some users and courses. + $user1 = $this->getDataGenerator()->create_user(array('idnumber'=>'user1', 'username'=>'user1')); + $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'user2', 'username'=>'user2')); + $user3 = $this->getDataGenerator()->create_user(array('idnumber'=>'user3', 'username'=>'user3')); + $user4 = $this->getDataGenerator()->create_user(array('idnumber'=>'user4', 'username'=>'user4')); + $user5 = $this->getDataGenerator()->create_user(array('idnumber'=>'user5', 'username'=>'user5')); + $user6 = $this->getDataGenerator()->create_user(array('idnumber'=>'user6', 'username'=>'user6')); + + $course1 = $this->getDataGenerator()->create_course(array('idnumber'=>'course1', 'shortname'=>'course1')); + $course2 = $this->getDataGenerator()->create_course(array('idnumber'=>'course2', 'shortname'=>'course2')); + $course3 = $this->getDataGenerator()->create_course(array('idnumber'=>'course3', 'shortname'=>'course3')); + + // Set up some ldap data. + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3', 'userx'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '2'; + $o['memberUid'] = array('user5'); + ldap_add($connection, 'cn='.$o['cn'].',ou=teachers,'.$topdn, $o); + + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course2'; + $o['gidNumber'] = '3'; + $o['memberUid'] = array('user1', 'user2', 'user3', 'user4'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course4'; + $o['gidNumber'] = '4'; + $o['memberUid'] = array('user1', 'user2'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course4'; + $o['gidNumber'] = '5'; + $o['memberUid'] = array('user5', 'user6'); + ldap_add($connection, 'cn='.$o['cn'].',ou=teachers,'.$topdn, $o); + + + // Test simple test without creation. + + $this->assertEquals(0, $DB->count_records('user_enrolments')); + $this->assertEquals(0, $DB->count_records('role_assignments')); + $this->assertEquals(4, $DB->count_records('course')); + + $enrol->sync_enrolments(new null_progress_trace()); + + $this->assertEquals(8, $DB->count_records('user_enrolments')); + $this->assertEquals(8, $DB->count_records('role_assignments')); + $this->assertEquals(4, $DB->count_records('course')); + + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id); + $this->assertIsEnrolled($course1->id, $user2->id, $studentrole->id); + $this->assertIsEnrolled($course1->id, $user3->id, $studentrole->id); + $this->assertIsEnrolled($course1->id, $user5->id, $teacherrole->id); + + $this->assertIsEnrolled($course2->id, $user1->id, $studentrole->id); + $this->assertIsEnrolled($course2->id, $user2->id, $studentrole->id); + $this->assertIsEnrolled($course2->id, $user3->id, $studentrole->id); + $this->assertIsEnrolled($course2->id, $user4->id, $studentrole->id); + + + // Test course creation. + $enrol->set_config('autocreate', 1); + + $enrol->sync_enrolments(new null_progress_trace()); + + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(12, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + + $course4 = $DB->get_record('course', array('idnumber'=>'course4'), '*', MUST_EXIST); + + $this->assertIsEnrolled($course4->id, $user1->id, $studentrole->id); + $this->assertIsEnrolled($course4->id, $user2->id, $studentrole->id); + $this->assertIsEnrolled($course4->id, $user5->id, $teacherrole->id); + $this->assertIsEnrolled($course4->id, $user6->id, $teacherrole->id); + + + // Test unenrolment. + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_KEEP); + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(12, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND); + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(12, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_SUSPENDED); + $this->assertIsEnrolled($course1->id, $user2->id, $studentrole->id, ENROL_USER_SUSPENDED); + $this->assertIsEnrolled($course1->id, $user3->id, $studentrole->id, ENROL_USER_SUSPENDED); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(12, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + $this->assertIsEnrolled($course1->id, $user2->id, $studentrole->id, ENROL_USER_ACTIVE); + $this->assertIsEnrolled($course1->id, $user3->id, $studentrole->id, ENROL_USER_ACTIVE); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES); + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(9, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, 0, ENROL_USER_SUSPENDED); + $this->assertIsEnrolled($course1->id, $user2->id, 0, ENROL_USER_SUSPENDED); + $this->assertIsEnrolled($course1->id, $user3->id, 0, ENROL_USER_SUSPENDED); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(12, $DB->count_records('user_enrolments')); + $this->assertEquals(12, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + $this->assertIsEnrolled($course1->id, $user2->id, $studentrole->id, ENROL_USER_ACTIVE); + $this->assertIsEnrolled($course1->id, $user3->id, $studentrole->id, ENROL_USER_ACTIVE); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL); + $enrol->sync_enrolments(new null_progress_trace()); + $this->assertEquals(9, $DB->count_records('user_enrolments')); + $this->assertEquals(9, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsNotEnrolled($course1->id, $user1->id); + $this->assertIsNotEnrolled($course1->id, $user2->id); + $this->assertIsNotEnrolled($course1->id, $user3->id); + + + // Individual user enrolments- + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(10, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_KEEP); + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(10, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPEND); + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(10, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_SUSPENDED); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(10, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_SUSPENDNOROLES); + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(9, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, 0, ENROL_USER_SUSPENDED); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user1', 'user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->sync_user_enrolments($user1); + $this->assertEquals(10, $DB->count_records('user_enrolments')); + $this->assertEquals(10, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsEnrolled($course1->id, $user1->id, $studentrole->id, ENROL_USER_ACTIVE); + + ldap_delete($connection, 'cn=course1,ou=students,'.$topdn); + $o = array(); + $o['objectClass'] = array('posixGroup'); + $o['cn'] = 'course1'; + $o['gidNumber'] = '1'; + $o['memberUid'] = array('user2', 'user3'); + ldap_add($connection, 'cn='.$o['cn'].',ou=students,'.$topdn, $o); + + $enrol->set_config('unenrolaction', ENROL_EXT_REMOVED_UNENROL); + $enrol->sync_user_enrolments($user1); + $this->assertEquals(9, $DB->count_records('user_enrolments')); + $this->assertEquals(9, $DB->count_records('role_assignments')); + $this->assertEquals(5, $DB->count_records('course')); + $this->assertIsNotEnrolled($course1->id, $user1->id); + + $this->recursive_delete($connection, TEST_ENROL_LDAP_DOMAIN, 'dc=moodletest'); + ldap_close($connection); + + // NOTE: multiple roles in one course is not supported, sorry + } + + public function assertIsEnrolled($courseid, $userid, $roleid, $status=null) { + global $DB; + + $context = context_course::instance($courseid); + $instance = $DB->get_record('enrol', array('courseid'=>$courseid, 'enrol'=>'ldap')); + $this->assertNotEmpty($instance); + $ue = $DB->get_record('user_enrolments', array('enrolid'=>$instance->id, 'userid'=>$userid)); + $this->assertNotEmpty($ue); + if (isset($status)) { + $this->assertEquals($status, $ue->status); + } + if ($roleid) { + $this->assertTrue($DB->record_exists('role_assignments', array('contextid'=>$context->id, 'userid'=>$userid, 'roleid'=>$roleid, 'component'=>'enrol_ldap'))); + } else { + $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context->id, 'userid'=>$userid, 'component'=>'enrol_ldap'))); + } + } + + public function assertIsNotEnrolled($courseid, $userid) { + $context = context_course::instance($courseid); + $this->assertFalse(is_enrolled($context, $userid)); + } + + protected function enable_plugin() { + $enabled = enrol_get_plugins(true); + $enabled['ldap'] = true; + $enabled = array_keys($enabled); + set_config('enrol_plugins_enabled', implode(',', $enabled)); + } + + protected function disable_plugin() { + $enabled = enrol_get_plugins(true); + unset($enabled['ldap']); + $enabled = array_keys($enabled); + set_config('enrol_plugins_enabled', implode(',', $enabled)); + } + + protected function recursive_delete($connection, $dn, $filter) { + if ($res = ldap_list($connection, $dn, $filter, array('dn'))) { + $info = ldap_get_entries($connection, $res); + ldap_free_result($res); + if ($info['count'] > 0) { + if ($res = ldap_search($connection, "$filter,$dn", 'cn=*', array('dn'))) { + $info = ldap_get_entries($connection, $res); + ldap_free_result($res); + foreach ($info as $i) { + if (isset($i['dn'])) { + ldap_delete($connection, $i['dn']); + } + } + } + if ($res = ldap_search($connection, "$filter,$dn", 'ou=*', array('dn'))) { + $info = ldap_get_entries($connection, $res); + ldap_free_result($res); + foreach ($info as $i) { + if (isset($i['dn']) and $info[0]['dn'] != $i['dn']) { + ldap_delete($connection, $i['dn']); + } + } + } + ldap_delete($connection, "$filter,$dn"); + } + } + } +}