5521f6a7b8a182ffa206208a181aaa393ddba165
[moodle.git] / auth / cas / auth.php
1 <?php
3 /**
4  * @author Martin Dougiamas
5  * @authro Jerome GUTIERREZ
6  * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7  * @package moodle multiauth
8  *
9  * Authentication Plugin: CAS Authentication
10  *
11  * Authentication using CAS (Central Authentication Server).
12  *
13  * 2006-08-28  File created.
14  */
15 if (!defined('MOODLE_INTERNAL')) {
16     die('Direct access to this script is forbidden.');    ///  It must be included from a Moodle page
17 }
18 require_once($CFG->libdir.'/authlib.php');
19 require_once($CFG->dirroot.'/auth/cas/CAS/CAS.php');
20 /**
21  * CAS authentication plugin.
22  */
23 class auth_plugin_cas extends auth_plugin_base {
24     /**
25      * Constructor.
26      */
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';
32         }
33         if (empty($this->config->user_type)) {
34             $this->config->user_type = 'default';
35         }
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];
42             }
43         }
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;
49         }
50     }
52     function prevent_local_passwords() {
53         return true;
54     }
56     /**
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.
60      *
61      * @param string $username The username
62      * @param string $password The password
63      * @return bool Authentication success or failure.
64      */
65     function user_login ($username, $password) {
66         $this->connectCAS();
67         return phpCAS::isAuthenticated() && (trim(moodle_strtolower(phpCAS::getUser())) == $username);
68     }
69     /**
70      * Returns true if this authentication plugin is 'internal'.
71      *
72      * @return bool
73      */
74     function is_internal() {
75         return false;
76     }
77     /**
78      * Returns true if this authentication plugin can change the user's
79      * password.
80      *
81      * @return bool
82      */
83     function can_change_password() {
84         return false;
85     }
86     /**
87      * authentication choice (CAS or other)
88      * redirection to the CAS form or to login/index.php
89      * for other authentication
90      */
91     function loginpage_hook() {
92       global $frm;
93       global $CFG;
94       global $SESSION, $OUTPUT;
96       $site = get_site();
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);
103           return;
104         }
108         // Test si cas activ� et param�tres non remplis
109       if (empty($this->config->hostname)) {
110           return;
111           }
113 // Connection to CAS server
114      $this->connectCAS();
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";
122         return;
123      }
125       if ($_GET["loginguest"]== true) {
126             $frm->username="guest";
127             $frm->password="guest";
128             return;
129       }
131      if ($this->config->multiauth) {
132           $authCAS = optional_param("authCAS", '', PARAM_RAW);
133           if ($authCAS=="NOCAS")
134             return;
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();
146             exit();
147          }
148      }
149 // CAS authentication
150      if (!phpCAS::isAuthenticated())
151         {phpCAS::forceAuthentication();}
153     /**
154      * logout from the cas
155      *
156      * This function is called from admin/auth.php
157      *
158      */
159     function prelogout_hook() {
160         global $CFG;
161       if ($this->config->logoutcas ) {
162             $backurl = $CFG->wwwroot;
163           $this->connectCAS();
164             phpCAS::logout($backurl);
165          }
166     }
167     /**
168      * Connect to the cas (clientcas connection or proxycas connection
169      *
170      * This function is called from admin/auth.php
171      *
172      */
173     function connectCAS() {
175     global $PHPCAS_CLIENT;
176 // mode proxy CAS
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);
180     }
181 // mode client CAS
182     else {
183         phpCAS::client($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri);
184     }
185     }
187     }
188     /**
189      * Prints a form for configuring this authentication plugin.
190      *
191      * This function is called from admin/auth.php, and outputs a full page with
192      * a form for configuring this plugin.
193      *
194      * @param array $page An object containing all the data for this page.
195      */
196     function config_form($config, $err, $user_fields) {
197         global $OUTPUT;
199         include 'config.html';
200     }
201     /**
202      * Returns the URL for changing the user's pw, or empty if the default can
203      * be used.
204      *
205      * @return string
206      */
207     function change_password_url() {
208         return "";
209     }
210     /**
211      * returns predefined usertypes
212      *
213      * @return array of predefined usertypes
214      */
215     function ldap_suppported_usertypes() {
216         $types = array();
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');
223         return $types;
224     }
225     /**
226      * Processes and stores configuration data for this authentication plugin.
227      */
228     function process_config($config) {
229         // set to defaults if undefined
230         // CAS settings
231         if (!isset ($config->hostname))
232             $config->hostname = '';
233         if (!isset ($config->port))
234             $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 = '';
247         // LDAP settings
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; }
280         // save CAS settings
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');
307         return true;
308     }
309     /**
310      * Initializes needed ldap variables for cas-module
311      *
312      * Uses names defined in ldap_supported_usertypes.
313      * $default is first defined as:
314      * $default['pseudoname'] = array(
315      *                      'typename1' => 'value',
316      *                      'typename2' => 'value'
317      *                      ....
318      *                      );
319      *
320      * @return array of default values
321      */
322     function ldap_getdefaults() {
323         $default['objectclass'] = array(
324                             'edir' => 'User',
325                             'rfc2307' => 'posixAccount',
326                             'rfc2307bis' => 'posixAccount',
327                             'samba' => 'sambaSamAccount',
328                             'ad' => 'user',
329                             'default' => '*'
330                             );
331         $default['user_attribute'] = array(
332                             'edir' => 'cn',
333                             'rfc2307' => 'uid',
334                             'rfc2307bis' => 'uid',
335                             'samba' => 'uid',
336                             'ad' => 'cn',
337                             'default' => 'cn'
338                             );
339         $default['memberattribute'] = array(
340                             'edir' => 'member',
341                             'rfc2307' => 'member',
342                             'rfc2307bis' => 'member',
343                             'samba' => 'member',
344                             'ad' => 'member',
345                             'default' => 'member'
346                             );
347         $default['memberattribute_isdn'] = array(
348                             'edir' => '1',
349                             'rfc2307' => '0',
350                             'rfc2307bis' => '1',
351                             'samba' => '0', //is this right?
352                             'ad' => '1',
353                             'default' => '0'
354                             );
355         return $default;
356     }
357     /**
358      * reads userinformation from ldap and return it in array()
359      *
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
363      *
364      * @param string $username username
365      *
366      * @return mixed array with no magic quotes or false on error
367      */
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();
375         $result = array();
376         $search_attribs = array();
378         foreach ($attrmap as $key=>$values) {
379             if (!is_array($values)) {
380                 $values = array($values);
381             }
382             foreach ($values as $value) {
383                 if (!in_array($value, $search_attribs)) {
384                     array_push($search_attribs, $value);
385                 }
386             }
387         }
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!
391         }
392         $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
393         if (empty($user_entry)) {
394             return false; // entry not found
395         }
396         foreach ($attrmap as $key=>$values) {
397             if (!is_array($values)) {
398                 $values = array($values);
399             }
400             $ldapval = NULL;
401             foreach ($values as $value) {
402                 if ($value == 'dn') {
403                     $result[$key] = $user_dn;
404                 }
405                 if (!array_key_exists(strtolower($value), $user_entry[0])) {
406                     continue; // wrong data mapping!
407                 }
408                 if (is_array($user_entry[0][strtolower($value)])) {
409                     $newval = $textlib->convert($user_entry[0][strtolower($value)][0], $this->config->ldapencoding, 'utf-8');
410                 } else {
411                     $newval = $textlib->convert($user_entry[0][strtolower($value)], $this->config->ldapencoding, 'utf-8');
412                 }
414                 if (!empty($newval)) { // favour ldap entries that are set
415                     $ldapval = $newval;
416                 }
417             }
418             if (!is_null($ldapval)) {
419                 $result[$key] = $ldapval;
420             }
421         }
422         $this->ldap_close($ldapconnection);
423         return $result;
424     }
425     /**
426      * reads userinformation from ldap and return it in an object
427      *
428      * @param string $username username (with system magic quotes)
429      * @return mixed object or false on error
430      */
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
435         }
436         $user_array = truncate_userinfo($user_array);
437         $user = new object();
438         foreach ($user_array as $key=>$value) {
439             $user->{$key} = $value;
440         }
441         return $user;
442     }
443     /**
444      * connects to ldap server
445      *
446      * Tries connect to specified ldap servers.
447      * Returns connection result or error.
448      *
449      * @return connection result
450      */
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)) {
458             $this->ldapconns++;
459             return $this->ldapconnection;
460         }
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;
467             }
468             if (!empty($this->config->bind_pw)) {
469                $bindpwd = $this->config->bind_pw;
470             }
471         }
472         $urls = explode(";",$this->config->host_url);
473         foreach ($urls as $server) {
474             $server = trim($server);
475             if (empty($server)) {
476                 continue;
477             }
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);
482             }
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);
487             }
488             else {
489                 //bind anonymously
490                 $bindresult=@ldap_bind($connresult);
491             }
492             if (!empty($this->config->opt_deref)) {
493                 ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref);
494             }
495             if ($bindresult) {
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;
500                 return $connresult;
501             }
502             $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
503         }
504         //If any of servers are alive we have already returned connection
505         print_error('auth_ldap_noconnect_all','auth',$this->config->user_type);
506         return false;
507     }
508     /**
509      * disconnects from a ldap server
510      *
511      */
512     function ldap_close() {
513         $this->ldapconns--;
514         if($this->ldapconns == 0) {
515             @ldap_close($this->ldapconnection);
516             unset($this->ldapconnection);
517         }
518     }
520     /**
521      * retuns user attribute mappings between moodle and ldap
522      *
523      * @return array
524      */
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 ?
532                 }
533             }
534         }
535         $moodleattributes['username'] = $this->config->user_attribute;
536         return $moodleattributes;
537     }
538     /**
539      * retuns dn of username
540      *
541      * Search specified contexts for username and return user dn
542      * like: cn=username,ou=suborg,o=org
543      *
544      * @param mixed $ldapconnection  $ldapconnection result
545      * @param mixed $username username (external encoding no slashes)
546      *
547      */
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);
555         }
556         foreach ($ldap_contexts as $context) {
557             $context = trim($context);
558             if (empty($context)) {
559                 continue;
560             }
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));
564             }
565             else {
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));
568             }
569             $entry = ldap_first_entry($ldapconnection,$ldap_result);
570             if ($entry) {
571                 $ldap_user_dn = ldap_get_dn($ldapconnection, $entry);
572                 break ;
573             }
574         }
575         return $ldap_user_dn;
576     }
577     /**
578      * Quote control characters in quoted "texts" used in ldap
579      *
580      * @param string
581      */
582     function ldap_addslashes($text) {
583         $text = str_replace('\\', '\\\\', $text);
584         $text = str_replace(array('"',   "\0"),
585                             array('\\"', '\\00'), $text);
586         return $text;
587     }
588     /**
589      * returns all usernames from external database
590      *
591      * get_userlist returns all usernames from external database
592      *
593      * @return array
594      */
595     function get_userlist() {
596         return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
597     }
598     /**
599      * checks if user exists on external db
600      *
601      * @param string $username
602      */
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);
609     }
610     /**
611      * syncronizes user fron external db to moodle user table
612      *
613      * Sync is now using username attribute.
614      *
615      * Syncing users removes or suspends users that dont exists anymore in external db.
616      * Creates new users and updates coursecreator status of users.
617      *
618      * @param bool $do_updates will do pull in data updates from ldap if relevant
619      */
620     function sync_users ($do_updates = true) {
622         global $CFG, $DB;
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";
637             exit;
638         }
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);
646             exit;
647         }
649         ////
650         //// get user's list from ldap to sql in a scalable fashion
651         ////
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);
659         }
661         $fresult = array();
662         foreach ($contexts as $context) {
663             $context = trim($context);
664             if (empty($context)) {
665                 continue;
666             }
667             if ($this->config->search_sub) {
668                 //use ldap_search to find first user from subtree
669                 $ldap_result = ldap_search($ldapconnection, $context,
670                                            $filter,
671                                            array($this->config->user_attribute));
672             } else {
673                 //search only in this context
674                 $ldap_result = ldap_list($ldapconnection, $context,
675                                          $filter,
676                                          array($this->config->user_attribute));
677             }
679             if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) {
680                 do {
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));
685             }
686             unset($ldap_result); // free mem
687         }
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}');
693         if ($count < 1) {
694             print "Did not get any users from LDAP -- error? -- exiting\n";
695             exit;
696         } else {
697             print "Got $count records from LDAP\n\n";
698         }
701 /// User removal
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
706                       FROM {user} u
707                       LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?)
708                      WHERE u.auth='cas'
709                            AND u.deleted=0
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";
720                         } else {
721                             echo "\t"; print_string('auth_dbdeleteusererror', 'auth_db', $user->username); echo "\n";
722                         }
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";
729                         } else {
730                             echo "\t"; print_string('auth_dbsuspendusererror', 'auth_db', $user->username); echo "\n";
731                         }
732                     }
733                 }
734             } else {
735                 print "No user entries to be removed\n";
736             }
737             unset($remove_users); // free mem!
738         }
740 /// Revive suspended users
741         if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
742             $sql = "SELECT u.id, u.username
743                       FROM {user} u
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";
757                     } else {
758                         echo "\t"; print_string('auth_dbrevivedusererror', 'auth_db', $user->username); echo "\n";
759                     }
760                 }
761             } else {
762                 print "No user entries to be revived\n";
763             }
765             unset($revive_users);
766         }
769 /// User Updates - time-consuming (optional)
770         if ($do_updates) {
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
778                     // update it on cron
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
782                     }
783                 }
784             }
785             // print_r($all_keys); print_r($updatekeys);
786             unset($all_keys); unset($key);
788         } else {
789             print "No updates to be done\n";
790         }
791         if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant
792             $users = $DB->get_records_sql("SELECT u.username, u.id
793                                              FROM {user} u
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
802                 } else {
803                     $creatorrole = false;
804                 }
806                 $transaction = $DB->start_delegated_transaction();
807                 $xcount = 0;
808                 $maxxcount = 100;
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');
814                     }
815                     echo "\n";
816                     $xcount++;
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');
822                         } else {
823                             role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id, 'cas');
824                         }
825                     }
826                 }
827                 $transaction->allow_commit();
828                 unset($users); // free mem
829             }
830         } else { // end do updates
831             print "No updates to be done\n";
832         }
833 /// User Additions
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
839                   FROM {user} u
840                   LEFT JOIN {tmp_extuser} e ON u.username = e.username
841                  WHERE u.id IS NULL";
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
851             } else {
852                 $creatorrole = false;
853             }
855             $transaction = $DB->start_delegated_transaction();
856             foreach ($add_users as $user) {
857                 $user = $this->get_userinfo_asobj($user->username);
859                 // prep a few params
860                 $user->modified   = time();
861                 $user->confirmed  = 1;
862                 $user->auth       = 'cas';
863                 $user->mnethostid = $CFG->mnet_localhost_id;
864                 if (empty($user->lang)) {
865                     $user->lang = $CFG->lang;
866                 }
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);
874                     }
875                 } else {
876                     echo "\t"; print_string('auth_dbinsertusererror', 'auth_db', $user->username); echo "\n";
877                 }
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');
882                 }
883             }
884             $transaction->allow_commit();
885             unset($add_users); // free mem
886         } else {
887             print "No users to be added\n";
888         }
890         $dbman->drop_temp_table($table);
891         $this->ldap_close();
893         return true;
894     }
896     /**
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.
900      *
901      * If you don't pass $updatekeys, there is a performance hit and
902      * values removed from LDAP won't be removed from moodle.
903      *
904      * @param string $username username
905      */
906     function update_user_record($username, $updatekeys = false) {
907         global $CFG;
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);
917             die;
918         }
920         // Protect the userid from being overwritten
921         $userid = $user->id;
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);
928             }
930             foreach ($updatekeys as $key) {
931                 if (isset($newinfo[$key])) {
932                     $value = $newinfo[$key];
933                 } else {
934                     $value = '';
935                 }
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));
940                     }
941                 }
942             }
943         } else {
944             return false;
945         }
946         return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
947     }
948     /**
949      * Bulk insert in SQL's temp table
950      */
951     function ldap_bulk_insert($username) {
952         global $DB;
954         $username = moodle_strtolower($username); // usernames are __always__ lowercase.
955         $DB->insert_record_raw('tmp_extuser', array('username'=>$username), false, true);
956         print ".";
957     }
958     /**
959      * Returns true if user should be coursecreator.
960      *
961      * @param mixed $username    username (without system magic quotes)
962      * @return boolean result
963      */
964     function iscreator($username) {
965         if ((empty($this->config->attrcreators) && empty($this->config->groupecreators)) or empty($this->config->memberattribute)) {
966             return null;
967         }
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))
973         return true;
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)";
981         else
982             $filter .= "(".$this->config->memberattribute."=$attr)";
983     }
984     $filter .= "))";
985     //search
986     $result = $this->ldap_get_userlist($filter);
987     if (count($result)!=0)
988         return true;
989      }
991     return false;
992     }
993    /**
994      * checks if user belong to specific group(s)
995      *
996      * Returns true if user belongs group in grupdns string.
997      *
998      * @param mixed $username    username
999      * @param mixed $groupdns    string of group dn separated by ;
1000      *
1001      */
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)) {
1007             return false;
1008             }
1009         if ($this->config->memberattribute_isdn) {
1010             $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername);
1011         } else {
1012             $memberuser = $extusername;
1013         }
1014         if (empty($memberuser)) {
1015             return false;
1016         }
1017         $groups = explode(";",$groupdns);
1018         $result = false;
1019         foreach ($groups as $group) {
1020             $group = trim($group);
1021             if (empty($group)) {
1022                 continue;
1023             }
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
1030                     $result = true;
1031                     break;
1032                 }
1033           }
1034         }
1035         $this->ldap_close();
1036         return $result;
1037     }
1038    /**
1039      * return all usernames from ldap
1040      *
1041      * @return array
1042      */
1043     function ldap_get_userlist($filter="*") {
1044     /// returns all users from ldap servers
1045         $fresult = array();
1046         $ldapconnection = $this->ldap_connect();
1047         if ($filter=="*") {
1048            $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
1049         }
1050         $contexts = explode(";",$this->config->contexts);
1051         if (!empty($this->config->create_context)) {
1052               array_push($contexts, $this->config->create_context);
1053         }
1054         foreach ($contexts as $context) {
1055             $context = trim($context);
1056             if (empty($context)) {
1057                 continue;
1058             }
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));
1062             }
1063             else {
1064                 //search only in this context
1065                 $ldap_result = ldap_list($ldapconnection, $context,
1066                                          $filter,
1067                                          array($this->config->user_attribute));
1068             }
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]) );
1073             }
1074         }
1075         $this->ldap_close();
1076         return $fresult;
1077     }
1078     /**
1079      * return entries from ldap
1080      *
1081      * Returns values like ldap_get_entries but is
1082      * binary compatible and return all attributes as array
1083      *
1084      * @return array ldap-entries
1085      */
1086     function ldap_get_entries($conn, $searchresult) {
1087     //Returns values like ldap_get_entries but is
1088     //binary compatible
1089         $i=0;
1090         $fresult=array();
1091         $entry = ldap_first_entry($conn, $searchresult);
1092         do {
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;
1099                 }
1100                 else {
1101                     $fresult[$i][strtolower($attributes[$j])] = array($values);
1102                 }
1103             }
1104             $i++;
1105         }
1106         while ($entry = @ldap_next_entry($conn, $entry));
1107         //were done
1109         return ($fresult);
1110     }
1111     /**
1112      * Sync roles for this user
1113      *
1114      * @param $user object user object (without system magic quotes)
1115      */
1116     function sync_roles($user) {
1117         $iscreator = $this->iscreator($user->username);
1118         if ($iscreator === null) {
1119             return; //nothing to sync - creators not configured
1120         }
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');
1126             } else {
1127                 //unassign only if previously assigned by this plugin!
1128                 role_unassign($creatorrole->id, $user->id, 0, $systemcontext->id, 'cas');
1129             }
1130         }
1131     }
1132    /**
1133      * Quote control characters in texts used in ldap filters - see rfc2254.txt
1134      *
1135      * @param string
1136      */
1137     function filter_addslashes($text) {
1138         $text = str_replace('\\', '\\5c', $text);
1139         $text = str_replace(array('*',    '(',    ')',    "\0"),
1140                             array('\\2a', '\\28', '\\29', '\\00'), $text);
1141         return $text;
1142     }