4 * @author Martin Dougiamas
5 * @authro Jerome GUTIERREZ
6 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7 * @package moodle multiauth
9 * Authentication Plugin: CAS Authentication
11 * Authentication using CAS (Central Authentication Server).
13 * 2006-08-28 File created.
15 if (!defined('MOODLE_INTERNAL')) {
16 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
18 require_once($CFG->libdir.'/authlib.php');
19 require_once($CFG->dirroot.'/auth/cas/CAS/CAS.php');
21 * CAS authentication plugin.
23 class auth_plugin_cas extends auth_plugin_base {
27 function auth_plugin_cas() {
28 $this->authtype = 'cas';
29 $this->config = get_config('auth/cas');
30 if (empty($this->config->ldapencoding)) {
31 $this->config->ldapencoding = 'utf-8';
33 if (empty($this->config->user_type)) {
34 $this->config->user_type = 'default';
36 $default = $this->ldap_getdefaults();
37 //use defaults if values not given
38 foreach ($default as $key => $value) {
39 // watch out - 0, false are correct values too
40 if (!isset($this->config->{$key}) or $this->config->{$key} == '') {
41 $this->config->{$key} = $value[$this->config->user_type];
44 //hack prefix to objectclass
45 if (empty($this->config->objectclass)) { // Can't send empty filter
46 $this->config->objectclass='objectClass=*';
47 } else if (stripos($this->config->objectclass, 'objectClass=') !== 0) {
48 $this->config->objectclass = 'objectClass='.$this->config->objectclass;
52 function prevent_local_passwords() {
57 * Authenticates user againt CAS
58 * Returns true if the username and password work and false if they are
59 * wrong or don't exist.
61 * @param string $username The username
62 * @param string $password The password
63 * @return bool Authentication success or failure.
65 function user_login ($username, $password) {
67 return phpCAS::isAuthenticated() && (trim(moodle_strtolower(phpCAS::getUser())) == $username);
70 * Returns true if this authentication plugin is 'internal'.
74 function is_internal() {
78 * Returns true if this authentication plugin can change the user's
83 function can_change_password() {
87 * authentication choice (CAS or other)
88 * redirection to the CAS form or to login/index.php
89 * for other authentication
91 function loginpage_hook() {
94 global $SESSION, $OUTPUT;
97 $CASform = get_string("CASform","auth_cas");
98 $username = optional_param("username", '', PARAM_RAW);
100 if (!empty($username)) {
101 if (strstr($SESSION->wantsurl,'ticket') || strstr($SESSION->wantsurl,'NOCAS'))
102 unset($SESSION->wantsurl);
108 // Test si cas activ� et param�tres non remplis
109 if (empty($this->config->hostname)) {
113 // Connection to CAS server
116 // Gestion de la connection CAS si acc�s direct d'un ent ou autre
117 if (phpCAS::checkAuthentication()) {
118 $frm->username=phpCAS::getUser();
119 // if (phpCAS::getUser()=='esup9992')
120 // $frm->username='erhar0062';
121 $frm->password="passwdCas";
125 if ($_GET["loginguest"]== true) {
126 $frm->username="guest";
127 $frm->password="guest";
131 if ($this->config->multiauth) {
132 $authCAS = optional_param("authCAS", '', PARAM_RAW);
133 if ($authCAS=="NOCAS")
136 // choice authentication form for multi-authentication
137 // test pgtIou parameter for proxy mode (https connection
138 // in background from CAS server to the php server)
139 if ($authCAS!="CAS" && !isset($_GET["pgtIou"])) {
140 $PAGE->navbar->add($CASform);
141 $PAGE->set_title("$site->fullname: $CASform");
142 $PAGE->set_heading($site->fullname);
143 echo $OUTPUT->header();
144 include($CFG->dirroot."/auth/cas/cas_form.html");
145 echo $OUTPUT->footer();
149 // CAS authentication
150 if (!phpCAS::isAuthenticated())
151 {phpCAS::forceAuthentication();}
154 * logout from the cas
156 * This function is called from admin/auth.php
159 function prelogout_hook() {
161 if ($this->config->logoutcas ) {
162 $backurl = $CFG->wwwroot;
164 phpCAS::logout($backurl);
168 * Connect to the cas (clientcas connection or proxycas connection
170 * This function is called from admin/auth.php
173 function connectCAS() {
175 global $PHPCAS_CLIENT;
177 if ( !is_object($PHPCAS_CLIENT) ) {
178 if ($this->config->proxycas) {
179 phpCAS::proxy($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri);
183 phpCAS::client($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri);
189 * Prints a form for configuring this authentication plugin.
191 * This function is called from admin/auth.php, and outputs a full page with
192 * a form for configuring this plugin.
194 * @param array $page An object containing all the data for this page.
196 function config_form($config, $err, $user_fields) {
199 include 'config.html';
202 * Returns the URL for changing the user's pw, or empty if the default can
207 function change_password_url() {
211 * returns predefined usertypes
213 * @return array of predefined usertypes
215 function ldap_suppported_usertypes() {
217 $types['edir']='Novell Edirectory';
218 $types['rfc2307']='posixAccount (rfc2307)';
219 $types['rfc2307bis']='posixAccount (rfc2307bis)';
220 $types['samba']='sambaSamAccount (v.3.0.7)';
221 $types['ad']='MS ActiveDirectory';
222 $types['default']=get_string('default');
226 * Processes and stores configuration data for this authentication plugin.
228 function process_config($config) {
229 // set to defaults if undefined
231 if (!isset ($config->hostname))
232 $config->hostname = '';
233 if (!isset ($config->port))
235 if (!isset ($config->casversion))
236 $config->casversion = '';
237 if (!isset ($config->baseuri))
238 $config->baseuri = '';
239 if (!isset ($config->language))
240 $config->language = '';
241 if (!isset ($config->proxycas))
242 $config->proxycas = '';
243 if (!isset ($config->logoutcas))
244 $config->logoutcas = '';
245 if (!isset ($config->multiauth))
246 $config->multiauth = '';
248 if (!isset($config->host_url))
249 { $config->host_url = ''; }
250 if (empty($config->ldapencoding))
251 { $config->ldapencoding = 'utf-8'; }
252 if (!isset($config->contexts))
253 { $config->contexts = ''; }
254 if (!isset($config->user_type))
255 { $config->user_type = 'default'; }
256 if (!isset($config->user_attribute))
257 { $config->user_attribute = ''; }
258 if (!isset($config->search_sub))
259 { $config->search_sub = ''; }
260 if (!isset($config->opt_deref))
261 { $config->opt_deref = ''; }
262 if (!isset($config->bind_dn))
263 {$config->bind_dn = ''; }
264 if (!isset($config->bind_pw))
265 {$config->bind_pw = ''; }
266 if (!isset($config->version))
267 {$config->version = '2'; }
268 if (!isset($config->objectclass))
269 {$config->objectclass = ''; }
270 if (!isset($config->memberattribute))
271 {$config->memberattribute = ''; }
272 if (!isset($config->memberattribute_isdn))
273 {$config->memberattribute_isdn = ''; }
274 if (!isset($config->attrcreators))
275 {$config->attrcreators = ''; }
276 if (!isset($config->groupecreators))
277 {$config->groupecreators = ''; }
278 if (!isset($config->removeuser))
279 {$config->removeuser = AUTH_REMOVEUSER_KEEP; }
281 set_config('hostname', $config->hostname, 'auth/cas');
282 set_config('port', $config->port, 'auth/cas');
283 set_config('casversion', $config->casversion, 'auth/cas');
284 set_config('baseuri', $config->baseuri, 'auth/cas');
285 set_config('language', $config->language, 'auth/cas');
286 set_config('proxycas', $config->proxycas, 'auth/cas');
287 set_config('logoutcas', $config->logoutcas, 'auth/cas');
288 set_config('multiauth', $config->multiauth, 'auth/cas');
289 // save LDAP settings
290 set_config('host_url', $config->host_url, 'auth/cas');
291 set_config('ldapencoding', $config->ldapencoding, 'auth/cas');
292 set_config('host_url', $config->host_url, 'auth/cas');
293 set_config('contexts', $config->contexts, 'auth/cas');
294 set_config('user_type', $config->user_type, 'auth/cas');
295 set_config('user_attribute', $config->user_attribute, 'auth/cas');
296 set_config('search_sub', $config->search_sub, 'auth/cas');
297 set_config('opt_deref', $config->opt_deref, 'auth/cas');
298 set_config('bind_dn', $config->bind_dn, 'auth/cas');
299 set_config('bind_pw', $config->bind_pw, 'auth/cas');
300 set_config('version', $config->version, 'auth/cas');
301 set_config('objectclass', $config->objectclass, 'auth/cas');
302 set_config('memberattribute', $config->memberattribute, 'auth/cas');
303 set_config('memberattribute_isdn', $config->memberattribute_isdn, 'auth/cas');
304 set_config('attrcreators', $config->attrcreators, 'auth/cas');
305 set_config('groupecreators', $config->groupecreators, 'auth/cas');
306 set_config('removeuser', $config->removeuser, 'auth/cas');
310 * Initializes needed ldap variables for cas-module
312 * Uses names defined in ldap_supported_usertypes.
313 * $default is first defined as:
314 * $default['pseudoname'] = array(
315 * 'typename1' => 'value',
316 * 'typename2' => 'value'
320 * @return array of default values
322 function ldap_getdefaults() {
323 $default['objectclass'] = array(
325 'rfc2307' => 'posixAccount',
326 'rfc2307bis' => 'posixAccount',
327 'samba' => 'sambaSamAccount',
331 $default['user_attribute'] = array(
334 'rfc2307bis' => 'uid',
339 $default['memberattribute'] = array(
341 'rfc2307' => 'member',
342 'rfc2307bis' => 'member',
345 'default' => 'member'
347 $default['memberattribute_isdn'] = array(
351 'samba' => '0', //is this right?
358 * reads userinformation from ldap and return it in array()
360 * Read user information from external database and returns it as array().
361 * Function should return all information available. If you are saving
362 * this information to moodle user-table you should honor syncronization flags
364 * @param string $username username
366 * @return mixed array with no magic quotes or false on error
368 function get_userinfo($username) {
369 $textlib = textlib_get_instance();
370 $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
372 $ldapconnection = $this->ldap_connect();
373 $attrmap = $this->ldap_attributes();
376 $search_attribs = array();
378 foreach ($attrmap as $key=>$values) {
379 if (!is_array($values)) {
380 $values = array($values);
382 foreach ($values as $value) {
383 if (!in_array($value, $search_attribs)) {
384 array_push($search_attribs, $value);
388 $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername);
389 if (!$user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs)) {
390 return false; // error!
392 $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
393 if (empty($user_entry)) {
394 return false; // entry not found
396 foreach ($attrmap as $key=>$values) {
397 if (!is_array($values)) {
398 $values = array($values);
401 foreach ($values as $value) {
402 if ($value == 'dn') {
403 $result[$key] = $user_dn;
405 if (!array_key_exists(strtolower($value), $user_entry[0])) {
406 continue; // wrong data mapping!
408 if (is_array($user_entry[0][strtolower($value)])) {
409 $newval = $textlib->convert($user_entry[0][strtolower($value)][0], $this->config->ldapencoding, 'utf-8');
411 $newval = $textlib->convert($user_entry[0][strtolower($value)], $this->config->ldapencoding, 'utf-8');
414 if (!empty($newval)) { // favour ldap entries that are set
418 if (!is_null($ldapval)) {
419 $result[$key] = $ldapval;
422 $this->ldap_close($ldapconnection);
426 * reads userinformation from ldap and return it in an object
428 * @param string $username username (with system magic quotes)
429 * @return mixed object or false on error
431 function get_userinfo_asobj($username) {
432 $user_array = $this->get_userinfo($username);
433 if ($user_array == false) {
434 return false; //error or not found
436 $user_array = truncate_userinfo($user_array);
437 $user = new object();
438 foreach ($user_array as $key=>$value) {
439 $user->{$key} = $value;
444 * connects to ldap server
446 * Tries connect to specified ldap servers.
447 * Returns connection result or error.
449 * @return connection result
451 function ldap_connect($binddn='',$bindpwd='') {
452 // Cache ldap connections (they are expensive to set up
453 // and can drain the TCP/IP ressources on the server if we
454 // are syncing a lot of users (as we try to open a new connection
455 // to get the user details). This is the least invasive way
456 // to reuse existing connections without greater code surgery.
457 if(!empty($this->ldapconnection)) {
459 return $this->ldapconnection;
462 //Select bind password, With empty values use
463 //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty
464 if ($binddn == '' and $bindpwd == '') {
465 if (!empty($this->config->bind_dn)) {
466 $binddn = $this->config->bind_dn;
468 if (!empty($this->config->bind_pw)) {
469 $bindpwd = $this->config->bind_pw;
472 $urls = explode(";",$this->config->host_url);
473 foreach ($urls as $server) {
474 $server = trim($server);
475 if (empty($server)) {
478 $connresult = ldap_connect($server);
479 //ldap_connect returns ALWAYS true
480 if (!empty($this->config->version)) {
481 ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->version);
483 if (!empty($binddn)) {
484 //bind with search-user
485 //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd;
486 $bindresult=ldap_bind($connresult, $binddn,$bindpwd);
490 $bindresult=@ldap_bind($connresult);
492 if (!empty($this->config->opt_deref)) {
493 ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref);
496 // Set the connection counter so we can call PHP's ldap_close()
497 // when we call $this->ldap_close() for the last 'open' connection.
498 $this->ldapconns = 1;
499 $this->ldapconnection = $connresult;
502 $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
504 //If any of servers are alive we have already returned connection
505 print_error('auth_ldap_noconnect_all','auth',$this->config->user_type);
509 * disconnects from a ldap server
512 function ldap_close() {
514 if($this->ldapconns == 0) {
515 @ldap_close($this->ldapconnection);
516 unset($this->ldapconnection);
521 * retuns user attribute mappings between moodle and ldap
525 function ldap_attributes () {
526 $moodleattributes = array();
527 foreach ($this->userfields as $field) {
528 if (!empty($this->config->{"field_map_$field"})) {
529 $moodleattributes[$field] = $this->config->{"field_map_$field"};
530 if (preg_match('/,/',$moodleattributes[$field])) {
531 $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ?
535 $moodleattributes['username'] = $this->config->user_attribute;
536 return $moodleattributes;
539 * retuns dn of username
541 * Search specified contexts for username and return user dn
542 * like: cn=username,ou=suborg,o=org
544 * @param mixed $ldapconnection $ldapconnection result
545 * @param mixed $username username (external encoding no slashes)
548 function ldap_find_userdn ($ldapconnection, $extusername) {
549 //default return value
550 $ldap_user_dn = FALSE;
551 //get all contexts and look for first matching user
552 $ldap_contexts = explode(";",$this->config->contexts);
553 if (!empty($this->config->create_context)) {
554 array_push($ldap_contexts, $this->config->create_context);
556 foreach ($ldap_contexts as $context) {
557 $context = trim($context);
558 if (empty($context)) {
561 if ($this->config->search_sub) {
562 //use ldap_search to find first user from subtree
563 $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
566 //search only in this context
567 $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute));
569 $entry = ldap_first_entry($ldapconnection,$ldap_result);
571 $ldap_user_dn = ldap_get_dn($ldapconnection, $entry);
575 return $ldap_user_dn;
578 * Quote control characters in quoted "texts" used in ldap
582 function ldap_addslashes($text) {
583 $text = str_replace('\\', '\\\\', $text);
584 $text = str_replace(array('"', "\0"),
585 array('\\"', '\\00'), $text);
589 * returns all usernames from external database
591 * get_userlist returns all usernames from external database
595 function get_userlist() {
596 return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
599 * checks if user exists on external db
601 * @param string $username
603 function user_exists($username) {
604 $textlib = textlib_get_instance();
605 $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
606 //returns true if given username exist on ldap
607 $users = $this->ldap_get_userlist("({$this->config->user_attribute}=".$this->filter_addslashes($extusername).")");
608 return count($users);
611 * syncronizes user fron external db to moodle user table
613 * Sync is now using username attribute.
615 * Syncing users removes or suspends users that dont exists anymore in external db.
616 * Creates new users and updates coursecreator status of users.
618 * @param bool $do_updates will do pull in data updates from ldap if relevant
620 function sync_users ($do_updates = true) {
624 $textlib = textlib_get_instance();
625 $dbman = $DB->get_manager();
627 /// Define table user to be created
628 $table = new xmldb_table('tmp_extuser');
629 $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
630 $table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null);
631 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
632 $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));
634 echo "Creating temp table $temptable\n";
635 if (!$dbman->create_temp_table($table)) {
636 print "Failed to create temporary users table - aborting\n";
640 print "Connecting to ldap...\n";
641 $ldapconnection = $this->ldap_connect();
643 if (!$ldapconnection) {
644 $this->ldap_close($ldapconnection);
645 print get_string('auth_ldap_noconnect','auth_ldap',$this->config->host_url);
650 //// get user's list from ldap to sql in a scalable fashion
652 // prepare some data we'll need
653 $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
655 $contexts = explode(";",$this->config->contexts);
657 if (!empty($this->config->create_context)) {
658 array_push($contexts, $this->config->create_context);
662 foreach ($contexts as $context) {
663 $context = trim($context);
664 if (empty($context)) {
667 if ($this->config->search_sub) {
668 //use ldap_search to find first user from subtree
669 $ldap_result = ldap_search($ldapconnection, $context,
671 array($this->config->user_attribute));
673 //search only in this context
674 $ldap_result = ldap_list($ldapconnection, $context,
676 array($this->config->user_attribute));
679 if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) {
681 $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute);
682 $value = $textlib->convert($value[0], $this->config->ldapencoding, 'utf-8');
683 $this->ldap_bulk_insert($value);
684 } while ($entry = ldap_next_entry($ldapconnection, $entry));
686 unset($ldap_result); // free mem
689 /// preserve our user database
690 /// if the temp table is empty, it probably means that something went wrong, exit
691 /// so as to avoid mass deletion of users; which is hard to undo
692 $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
694 print "Did not get any users from LDAP -- error? -- exiting\n";
697 print "Got $count records from LDAP\n\n";
702 // find users in DB that aren't in ldap -- to be removed!
703 // this is still not as scalable (but how often do we mass delete?)
704 if (!empty($this->config->removeuser)) {
705 $sql = "SELECT u.id, u.username, u.email, u.auth
707 LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?)
710 AND e.username IS NULL";
711 $remove_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
713 if (!empty($remove_users)) {
714 print "User entries to remove: ". count($remove_users) . "\n";
716 foreach ($remove_users as $user) {
717 if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
718 if (delete_user($user)) {
719 echo "\t"; print_string('auth_dbdeleteuser', 'auth_db', array($user->username, $user->id)); echo "\n";
721 echo "\t"; print_string('auth_dbdeleteusererror', 'auth_db', $user->username); echo "\n";
723 } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
724 $updateuser = new object();
725 $updateuser->id = $user->id;
726 $updateuser->auth = 'nologin';
727 if ($DB->update_record('user', $updateuser)) {
728 echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array($user->username, $user->id)); echo "\n";
730 echo "\t"; print_string('auth_dbsuspendusererror', 'auth_db', $user->username); echo "\n";
735 print "No user entries to be removed\n";
737 unset($remove_users); // free mem!
740 /// Revive suspended users
741 if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
742 $sql = "SELECT u.id, u.username
744 JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?)
745 WHERE u.auth='nologin' AND u.deleted=0";
746 $revive_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id));
748 if (!empty($revive_users)) {
749 print "User entries to be revived: ". count($revive_users) . "\n";
751 foreach ($revive_users as $user) {
752 $updateuser = new object();
753 $updateuser->id = $user->id;
754 $updateuser->auth = 'ldap';
755 if ($DB->pdate_record('user', $updateuser)) {
756 echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array($user->username, $user->id)); echo "\n";
758 echo "\t"; print_string('auth_dbrevivedusererror', 'auth_db', $user->username); echo "\n";
762 print "No user entries to be revived\n";
765 unset($revive_users);
769 /// User Updates - time-consuming (optional)
771 // narrow down what fields we need to update
772 $all_keys = array_keys(get_object_vars($this->config));
773 $updatekeys = array();
774 foreach ($all_keys as $key) {
775 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
776 // if we have a field to update it from
777 // and it must be updated 'onlogin' we
779 if ( !empty($this->config->{'field_map_'.$match[1]})
780 and $this->config->{$match[0]} === 'onlogin') {
781 array_push($updatekeys, $match[1]); // the actual key name
785 // print_r($all_keys); print_r($updatekeys);
786 unset($all_keys); unset($key);
789 print "No updates to be done\n";
791 if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant
792 $users = $DB->get_records_sql("SELECT u.username, u.id
794 WHERE u.deleted=0 AND u.auth='cas' AND u.mnethostid = ?", array($CFG->mnet_localhost_id));
795 if (!empty($users)) {
796 print "User entries to update: ". count($users). "\n";
798 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
799 if (!empty($this->config->creators) and !empty($this->config->memberattribute)
800 and $roles = get_archetype_roles('coursecreator')) {
801 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
803 $creatorrole = false;
806 $transaction = $DB->start_delegated_transaction();
810 foreach ($users as $user) {
811 echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array($user->username, $user->id));
812 if (!$this->update_user_record($user->username, $updatekeys)) {
813 echo " - ".get_string('skipped');
818 // update course creators if needed
819 if ($creatorrole !== false) {
820 if ($this->iscreator($user->username)) {
821 role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
823 role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'cas');
827 $transaction->allow_commit();
828 unset($users); // free mem
830 } else { // end do updates
831 print "No updates to be done\n";
834 // find users missing in DB that are in LDAP
835 // and gives me a nifty object I don't want.
836 // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin
837 // mnetid not used here in join because we can not add users with the same username
838 $sql = "SELECT e.id, e.username
840 LEFT JOIN {tmp_extuser} e ON u.username = e.username
842 $add_users = $DB->get_records_sql($sql); // get rid of the fat
844 if (!empty($add_users)) {
845 print "User entries to add: ". count($add_users). "\n";
847 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
848 if (!empty($this->config->creators) and !empty($this->config->memberattribute)
849 and $roles = get_archetype_roles('coursecreator')) {
850 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
852 $creatorrole = false;
855 $transaction = $DB->start_delegated_transaction();
856 foreach ($add_users as $user) {
857 $user = $this->get_userinfo_asobj($user->username);
860 $user->modified = time();
861 $user->confirmed = 1;
863 $user->mnethostid = $CFG->mnet_localhost_id;
864 if (empty($user->lang)) {
865 $user->lang = $CFG->lang;
868 //TODO - username required to use PARAM_USERNAME before inserting into user table (MDL-16919)
869 if ($id = $DB->insert_record('user', $user)) {
870 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array($user->username, $id)); echo "\n";
871 $userobj = $this->update_user_record($user->username);
872 if (!empty($this->config->forcechangepassword)) {
873 set_user_preference('auth_forcepasswordchange', 1, $userobj->id);
876 echo "\t"; print_string('auth_dbinsertusererror', 'auth_db', $user->username); echo "\n";
879 // add course creators if needed
880 if ($creatorrole !== false and $this->iscreator($user->username)) {
881 role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
884 $transaction->allow_commit();
885 unset($add_users); // free mem
887 print "No users to be added\n";
890 $dbman->drop_temp_table($table);
897 * Update a local user record from an external source.
898 * This is a lighter version of the one in moodlelib -- won't do
899 * expensive ops such as enrolment.
901 * If you don't pass $updatekeys, there is a performance hit and
902 * values removed from LDAP won't be removed from moodle.
904 * @param string $username username
906 function update_user_record($username, $updatekeys = false) {
909 //just in case check text case
910 $username = trim(moodle_strtolower($username));
912 // get the current user record
913 $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
914 if (empty($user)) { // trouble
915 error_log("Cannot update non-existent user: ".$username);
916 print_error('auth_dbusernotexist','auth_db','',$username);
920 // Protect the userid from being overwritten
923 if ($newinfo = $this->get_userinfo($username)) {
924 $newinfo = truncate_userinfo($newinfo);
926 if (empty($updatekeys)) { // all keys? this does not support removing values
927 $updatekeys = array_keys($newinfo);
930 foreach ($updatekeys as $key) {
931 if (isset($newinfo[$key])) {
932 $value = $newinfo[$key];
937 if (!empty($this->config->{'field_updatelocal_' . $key})) {
938 if ($user->{$key} != $value) { // only update if it's changed
939 $DB->set_field('user', $key, $value, array('id'=>$userid));
946 return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
949 * Bulk insert in SQL's temp table
951 function ldap_bulk_insert($username) {
954 $username = moodle_strtolower($username); // usernames are __always__ lowercase.
955 $DB->insert_record_raw('tmp_extuser', array('username'=>$username), false, true);
959 * Returns true if user should be coursecreator.
961 * @param mixed $username username (without system magic quotes)
962 * @return boolean result
964 function iscreator($username) {
965 if ((empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
968 $textlib = textlib_get_instance();
969 $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
970 //test for groupe creator
971 if (!empty($this->config->groupecreators))
972 if ((boolean)$this->ldap_isgroupmember($extusername, $this->config->groupecreators))
974 //build filter for attrcreator
975 if (!empty($this->config->attrcreators)) {
976 $attrs = explode(";",$this->config->attrcreators);
977 $filter = "(& (".$this->config->user_attribute."=$username)(|";
978 foreach ($attrs as $attr){
979 if(strpos($attr, "="))
980 $filter .= "($attr)";
982 $filter .= "(".$this->config->memberattribute."=$attr)";
986 $result = $this->ldap_get_userlist($filter);
987 if (count($result)!=0)
994 * checks if user belong to specific group(s)
996 * Returns true if user belongs group in grupdns string.
998 * @param mixed $username username
999 * @param mixed $groupdns string of group dn separated by ;
1002 function ldap_isgroupmember($extusername='', $groupdns='') {
1003 // Takes username and groupdn(s) , separated by ;
1004 // Returns true if user is member of any given groups
1005 $ldapconnection = $this->ldap_connect();
1006 if (empty($extusername) or empty($groupdns)) {
1009 if ($this->config->memberattribute_isdn) {
1010 $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername);
1012 $memberuser = $extusername;
1014 if (empty($memberuser)) {
1017 $groups = explode(";",$groupdns);
1019 foreach ($groups as $group) {
1020 $group = trim($group);
1021 if (empty($group)) {
1024 //echo "Checking group $group for member $username\n";
1025 $search = ldap_read($ldapconnection, $group, '('.$this->config->memberattribute.'='.$this->filter_addslashes($memberuser).')', array($this->config->memberattribute));
1026 if (!empty($search) and ldap_count_entries($ldapconnection, $search)) {
1027 $info = $this->ldap_get_entries($ldapconnection, $search);
1028 if (count($info) > 0 ) {
1029 // user is member of group
1035 $this->ldap_close();
1039 * return all usernames from ldap
1043 function ldap_get_userlist($filter="*") {
1044 /// returns all users from ldap servers
1046 $ldapconnection = $this->ldap_connect();
1048 $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
1050 $contexts = explode(";",$this->config->contexts);
1051 if (!empty($this->config->create_context)) {
1052 array_push($contexts, $this->config->create_context);
1054 foreach ($contexts as $context) {
1055 $context = trim($context);
1056 if (empty($context)) {
1059 if ($this->config->search_sub) {
1060 //use ldap_search to find first user from subtree
1061 $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute));
1064 //search only in this context
1065 $ldap_result = ldap_list($ldapconnection, $context,
1067 array($this->config->user_attribute));
1069 $users = $this->ldap_get_entries($ldapconnection, $ldap_result);
1070 //add found users to list
1071 for ($i=0;$i<count($users);$i++) {
1072 array_push($fresult, ($users[$i][$this->config->user_attribute][0]) );
1075 $this->ldap_close();
1079 * return entries from ldap
1081 * Returns values like ldap_get_entries but is
1082 * binary compatible and return all attributes as array
1084 * @return array ldap-entries
1086 function ldap_get_entries($conn, $searchresult) {
1087 //Returns values like ldap_get_entries but is
1091 $entry = ldap_first_entry($conn, $searchresult);
1093 $attributes = @ldap_get_attributes($conn, $entry);
1094 for ($j=0; $j<$attributes['count']; $j++) {
1095 $values = ldap_get_values_len($conn, $entry,$attributes[$j]);
1097 if (is_array($values)) {
1098 $fresult[$i][strtolower($attributes[$j])] = $values;
1101 $fresult[$i][strtolower($attributes[$j])] = array($values);
1106 while ($entry = @ldap_next_entry($conn, $entry));
1112 * Sync roles for this user
1114 * @param $user object user object (without system magic quotes)
1116 function sync_roles($user) {
1117 $iscreator = $this->iscreator($user->username);
1118 if ($iscreator === null) {
1119 return; //nothing to sync - creators not configured
1121 if ($roles = get_archetype_roles('coursecreator')) {
1122 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
1123 $systemcontext = get_context_instance(CONTEXT_SYSTEM);
1124 if ($iscreator) { // Following calls will not create duplicates
1125 role_assign($creatorrole->id, $user->id, 0, $systemcontext->id, 0, 0, 0, 'cas');
1127 //unassign only if previously assigned by this plugin!
1128 role_unassign($creatorrole->id, $user->id, 0, $systemcontext->id, 'cas');
1133 * Quote control characters in texts used in ldap filters - see rfc2254.txt
1137 function filter_addslashes($text) {
1138 $text = str_replace('\\', '\\5c', $text);
1139 $text = str_replace(array('*', '(', ')', "\0"),
1140 array('\\2a', '\\28', '\\29', '\\00'), $text);