auth cas/db/ldap: MDL-18689 Fix typos in auth/{cas,db,ldap}/auth.php
[moodle.git] / auth / cas / auth.php
CommitLineData
b9ddb2d5 1<?php
df884cd8 2
b9ddb2d5 3/**
4 * @author Martin Dougiamas
0c121d99 5 * @authro Jerome GUTIERREZ
b9ddb2d5 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 */
139ebfdb 15if (!defined('MOODLE_INTERNAL')) {
16 die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page
17}
6bc1e5d5 18require_once($CFG->libdir.'/authlib.php');
c8afa50b 19require_once($CFG->dirroot.'/auth/cas/CAS/CAS.php');
b9ddb2d5 20/**
21 * CAS authentication plugin.
22 */
6bc1e5d5 23class auth_plugin_cas extends auth_plugin_base {
b9ddb2d5 24 /**
25 * Constructor.
26 */
27 function auth_plugin_cas() {
6bc1e5d5 28 $this->authtype = 'cas';
b9ddb2d5 29 $this->config = get_config('auth/cas');
0c121d99 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=*';
df884cd8 47 } else if (stripos($this->config->objectclass, 'objectClass=') !== 0) {
0c121d99 48 $this->config->objectclass = 'objectClass='.$this->config->objectclass;
49 }
b9ddb2d5 50 }
edb5da83
PS
51
52 function prevent_local_passwords() {
53 return true;
54 }
55
b9ddb2d5 56 /**
e295df44 57 * Authenticates user againt CAS
b9ddb2d5 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
139ebfdb 63 * @return bool Authentication success or failure.
b9ddb2d5 64 */
65 function user_login ($username, $password) {
4454447d 66 $this->connectCAS();
afacbf00 67 return phpCAS::isAuthenticated() && (trim(moodle_strtolower(phpCAS::getUser())) == $username);
b9ddb2d5 68 }
b9ddb2d5 69 /**
70 * Returns true if this authentication plugin is 'internal'.
71 *
139ebfdb 72 * @return bool
b9ddb2d5 73 */
74 function is_internal() {
75 return false;
76 }
b9ddb2d5 77 /**
78 * Returns true if this authentication plugin can change the user's
79 * password.
80 *
139ebfdb 81 * @return bool
b9ddb2d5 82 */
83 function can_change_password() {
0c121d99 84 return false;
b9ddb2d5 85 }
0c121d99 86 /**
87 * authentication choice (CAS or other)
88 * redirection to the CAS form or to login/index.php
89 * for other authentication
90 */
f5fd4347 91 function loginpage_hook() {
0c121d99 92 global $frm;
0c121d99 93 global $CFG;
4454447d 94 global $SESSION, $OUTPUT;
6dfddff7 95
0c121d99 96 $site = get_site();
2b06294b 97 $CASform = get_string("CASform","auth_cas");
ebdd9fed 98 $username = optional_param("username", '', PARAM_RAW);
6dfddff7 99
0c121d99 100 if (!empty($username)) {
4454447d
PS
101 if (strstr($SESSION->wantsurl,'ticket') || strstr($SESSION->wantsurl,'NOCAS'))
102 unset($SESSION->wantsurl);
103 return;
6bc1e5d5 104 }
6dfddff7 105
c39e7213 106
4454447d
PS
107
108 // Test si cas activ� et param�tres non remplis
109 if (empty($this->config->hostname)) {
110 return;
111 }
209596cb 112
0c121d99 113// Connection to CAS server
4454447d
PS
114 $this->connectCAS();
115
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 }
124
125 if ($_GET["loginguest"]== true) {
126 $frm->username="guest";
127 $frm->password="guest";
128 return;
129 }
130
0c121d99 131 if ($this->config->multiauth) {
ebdd9fed 132 $authCAS = optional_param("authCAS", '', PARAM_RAW);
e295df44 133 if ($authCAS=="NOCAS")
0c121d99 134 return;
e295df44 135
0c121d99 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)
209596cb 139 if ($authCAS!="CAS" && !isset($_GET["pgtIou"])) {
cfc5b79b 140 $PAGE->navbar->add($CASform);
141 $PAGE->set_title("$site->fullname: $CASform");
142 $PAGE->set_heading($site->fullname);
143 echo $OUTPUT->header();
0c121d99 144 include($CFG->dirroot."/auth/cas/cas_form.html");
119550a5 145 echo $OUTPUT->footer();
0c121d99 146 exit();
4454447d 147 }
0c121d99 148 }
149// CAS authentication
150 if (!phpCAS::isAuthenticated())
151 {phpCAS::forceAuthentication();}
0c121d99 152}
153 /**
e295df44 154 * logout from the cas
0c121d99 155 *
156 * This function is called from admin/auth.php
157 *
158 */
f5fd4347 159 function prelogout_hook() {
160 global $CFG;
4454447d
PS
161 if ($this->config->logoutcas ) {
162 $backurl = $CFG->wwwroot;
163 $this->connectCAS();
164 phpCAS::logout($backurl);
165 }
f5fd4347 166 }
0c121d99 167 /**
e295df44 168 * Connect to the cas (clientcas connection or proxycas connection
0c121d99 169 *
170 * This function is called from admin/auth.php
171 *
172 */
173 function connectCAS() {
4454447d
PS
174
175 global $PHPCAS_CLIENT;
0c121d99 176// mode proxy CAS
177if ( !is_object($PHPCAS_CLIENT) ) {
4454447d
PS
178 if ($this->config->proxycas) {
179 phpCAS::proxy($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri);
180 }
0c121d99 181// mode client CAS
4454447d
PS
182 else {
183 phpCAS::client($this->config->casversion, $this-> config->hostname, (int) $this->config->port, $this->config->baseuri);
184 }
185 }
186
0c121d99 187 }
b9ddb2d5 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 */
139ebfdb 196 function config_form($config, $err, $user_fields) {
95cb3955
I
197 global $OUTPUT;
198
b9ddb2d5 199 include 'config.html';
200 }
b9ddb2d5 201 /**
430759a5 202 * Returns the URL for changing the user's pw, or empty if the default can
b9ddb2d5 203 * be used.
204 *
430759a5 205 * @return string
b9ddb2d5 206 */
207 function change_password_url() {
0c121d99 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;
b9ddb2d5 224 }
b9ddb2d5 225 /**
226 * Processes and stores configuration data for this authentication plugin.
227 */
228 function process_config($config) {
229 // set to defaults if undefined
0c121d99 230 // CAS settings
e295df44 231 if (!isset ($config->hostname))
b9ddb2d5 232 $config->hostname = '';
e295df44 233 if (!isset ($config->port))
b9ddb2d5 234 $config->port = '';
e295df44 235 if (!isset ($config->casversion))
b9ddb2d5 236 $config->casversion = '';
e295df44 237 if (!isset ($config->baseuri))
b9ddb2d5 238 $config->baseuri = '';
e295df44 239 if (!isset ($config->language))
b9ddb2d5 240 $config->language = '';
e295df44 241 if (!isset ($config->proxycas))
0c121d99 242 $config->proxycas = '';
e295df44 243 if (!isset ($config->logoutcas))
0c121d99 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))
6f87ef52 279 {$config->removeuser = AUTH_REMOVEUSER_KEEP; }
b9ddb2d5 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');
0c121d99 286 set_config('proxycas', $config->proxycas, 'auth/cas');
287 set_config('logoutcas', $config->logoutcas, 'auth/cas');
288 set_config('multiauth', $config->multiauth, 'auth/cas');
b9ddb2d5 289 // save LDAP settings
0c121d99 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');
b9ddb2d5 307 return true;
308 }
0c121d99 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 *
be544ec3 364 * @param string $username username
0c121d99 365 *
366 * @return mixed array with no magic quotes or false on error
367 */
368 function get_userinfo($username) {
369 $textlib = textlib_get_instance();
be544ec3 370 $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
df884cd8 371
0c121d99 372 $ldapconnection = $this->ldap_connect();
373 $attrmap = $this->ldap_attributes();
df884cd8 374
0c121d99 375 $result = array();
376 $search_attribs = array();
df884cd8 377
0c121d99 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;
df884cd8 401 foreach ($values as $value) {
0c121d99 402 if ($value == 'dn') {
403 $result[$key] = $user_dn;
404 }
74c9f514 405 if (!array_key_exists(strtolower($value), $user_entry[0])) {
0c121d99 406 continue; // wrong data mapping!
407 }
74c9f514 408 if (is_array($user_entry[0][strtolower($value)])) {
409 $newval = $textlib->convert($user_entry[0][strtolower($value)][0], $this->config->ldapencoding, 'utf-8');
0c121d99 410 } else {
74c9f514 411 $newval = $textlib->convert($user_entry[0][strtolower($value)], $this->config->ldapencoding, 'utf-8');
0c121d99 412 }
74c9f514 413
0c121d99 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 }
eee34307 422 $this->ldap_close($ldapconnection);
0c121d99 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='') {
eee34307 452 // Cache ldap connections (they are expensive to set up
5117d598 453 // and can drain the TCP/IP ressources on the server if we
eee34307 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 }
461
0c121d99 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) {
eee34307 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.
5117d598 498 $this->ldapconns = 1;
eee34307 499 $this->ldapconnection = $connresult;
0c121d99 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 }
eee34307 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 }
519
0c121d99 520 /**
521 * retuns user attribute mappings between moodle and ldap
522 *
523 * @return array
524 */
525 function ldap_attributes () {
0c121d99 526 $moodleattributes = array();
4105caff 527 foreach ($this->userfields as $field) {
0c121d99 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 }
5261baf1 535 $moodleattributes['username'] = $this->config->user_attribute;
0c121d99 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 *
df884cd8 601 * @param string $username
0c121d99 602 */
603 function user_exists($username) {
604 $textlib = textlib_get_instance();
df884cd8 605 $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding);
0c121d99 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 *
0c121d99 618 * @param bool $do_updates will do pull in data updates from ldap if relevant
619 */
df884cd8 620 function sync_users ($do_updates = true) {
621
622 global $CFG, $DB;
623
0c121d99 624 $textlib = textlib_get_instance();
df884cd8 625 $dbman = $DB->get_manager();
626
627 /// Define table user to be created
628 $table = new xmldb_table('tmp_extuser');
2a88f626 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);
df884cd8 631 $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
632 $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username'));
633
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;
0c121d99 638 }
df884cd8 639
0c121d99 640 print "Connecting to ldap...\n";
641 $ldapconnection = $this->ldap_connect();
df884cd8 642
0c121d99 643 if (!$ldapconnection) {
eee34307 644 $this->ldap_close($ldapconnection);
2b06294b 645 print get_string('auth_ldap_noconnect','auth_ldap',$this->config->host_url);
0c121d99 646 exit;
647 }
df884cd8 648
0c121d99 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."))";
df884cd8 654
0c121d99 655 $contexts = explode(";",$this->config->contexts);
df884cd8 656
0c121d99 657 if (!empty($this->config->create_context)) {
658 array_push($contexts, $this->config->create_context);
659 }
df884cd8 660
0c121d99 661 $fresult = array();
662 foreach ($contexts as $context) {
663 $context = trim($context);
664 if (empty($context)) {
665 continue;
666 }
0c121d99 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 }
df884cd8 678
0c121d99 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');
df884cd8 683 $this->ldap_bulk_insert($value);
0c121d99 684 } while ($entry = ldap_next_entry($ldapconnection, $entry));
685 }
686 unset($ldap_result); // free mem
0c121d99 687 }
df884cd8 688
0c121d99 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
df884cd8 692 $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}');
0c121d99 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 }
df884cd8 699
700
0c121d99 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)) {
5117d598 705 $sql = "SELECT u.id, u.username, u.email, u.auth
df884cd8 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));
712
0c121d99 713 if (!empty($remove_users)) {
714 print "User entries to remove: ". count($remove_users) . "\n";
df884cd8 715
0c121d99 716 foreach ($remove_users as $user) {
6f87ef52 717 if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) {
90afcf32 718 if (delete_user($user)) {
2b06294b 719 echo "\t"; print_string('auth_dbdeleteuser', 'auth_db', array($user->username, $user->id)); echo "\n";
0c121d99 720 } else {
2b06294b 721 echo "\t"; print_string('auth_dbdeleteusererror', 'auth_db', $user->username); echo "\n";
0c121d99 722 }
6f87ef52 723 } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
0c121d99 724 $updateuser = new object();
725 $updateuser->id = $user->id;
726 $updateuser->auth = 'nologin';
df884cd8 727 if ($DB->update_record('user', $updateuser)) {
2b06294b 728 echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array($user->username, $user->id)); echo "\n";
0c121d99 729 } else {
2b06294b 730 echo "\t"; print_string('auth_dbsuspendusererror', 'auth_db', $user->username); echo "\n";
0c121d99 731 }
732 }
733 }
0c121d99 734 } else {
735 print "No user entries to be removed\n";
736 }
737 unset($remove_users); // free mem!
738 }
df884cd8 739
0c121d99 740/// Revive suspended users
6f87ef52 741 if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) {
0c121d99 742 $sql = "SELECT u.id, u.username
df884cd8 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));
747
0c121d99 748 if (!empty($revive_users)) {
749 print "User entries to be revived: ". count($revive_users) . "\n";
df884cd8 750
0c121d99 751 foreach ($revive_users as $user) {
752 $updateuser = new object();
753 $updateuser->id = $user->id;
df884cd8 754 $updateuser->auth = 'ldap';
755 if ($DB->pdate_record('user', $updateuser)) {
b8fc9582 756 echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array($user->username, $user->id)); echo "\n";
0c121d99 757 } else {
b8fc9582 758 echo "\t"; print_string('auth_dbrevivedusererror', 'auth_db', $user->username); echo "\n";
0c121d99 759 }
760 }
0c121d99 761 } else {
762 print "No user entries to be revived\n";
763 }
df884cd8 764
0c121d99 765 unset($revive_users);
766 }
df884cd8 767
768
0c121d99 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);
df884cd8 787
0c121d99 788 } else {
789 print "No updates to be done\n";
790 }
791 if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant
df884cd8 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));
0c121d99 795 if (!empty($users)) {
796 print "User entries to update: ". count($users). "\n";
df884cd8 797
0c121d99 798 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
799 if (!empty($this->config->creators) and !empty($this->config->memberattribute)
4f0c2d00 800 and $roles = get_archetype_roles('coursecreator')) {
0c121d99 801 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
802 } else {
803 $creatorrole = false;
804 }
df884cd8 805
d5a8d9aa 806 $transaction = $DB->start_delegated_transaction();
0c121d99 807 $xcount = 0;
808 $maxxcount = 100;
df884cd8 809
0c121d99 810 foreach ($users as $user) {
2b06294b 811 echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array($user->username, $user->id));
185721a4 812 if (!$this->update_user_record($user->username, $updatekeys)) {
0c121d99 813 echo " - ".get_string('skipped');
814 }
815 echo "\n";
816 $xcount++;
df884cd8 817
0c121d99 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 }
0c121d99 826 }
d5a8d9aa 827 $transaction->allow_commit();
0c121d99 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
0c121d99 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
df884cd8 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
843
0c121d99 844 if (!empty($add_users)) {
845 print "User entries to add: ". count($add_users). "\n";
df884cd8 846
0c121d99 847 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
848 if (!empty($this->config->creators) and !empty($this->config->memberattribute)
4f0c2d00 849 and $roles = get_archetype_roles('coursecreator')) {
0c121d99 850 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
851 } else {
852 $creatorrole = false;
853 }
df884cd8 854
d5a8d9aa 855 $transaction = $DB->start_delegated_transaction();
0c121d99 856 foreach ($add_users as $user) {
df884cd8 857 $user = $this->get_userinfo_asobj($user->username);
858
0c121d99 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 }
df884cd8 867
07ed083e 868 //TODO - username required to use PARAM_USERNAME before inserting into user table (MDL-16919)
df884cd8 869 if ($id = $DB->insert_record('user', $user)) {
2b06294b 870 echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array($user->username, $id)); echo "\n";
0c121d99 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 {
2b06294b 876 echo "\t"; print_string('auth_dbinsertusererror', 'auth_db', $user->username); echo "\n";
0c121d99 877 }
df884cd8 878
0c121d99 879 // add course creators if needed
df884cd8 880 if ($creatorrole !== false and $this->iscreator($user->username)) {
0c121d99 881 role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'cas');
882 }
883 }
d5a8d9aa 884 $transaction->allow_commit();
0c121d99 885 unset($add_users); // free mem
886 } else {
887 print "No users to be added\n";
888 }
df884cd8 889
890 $dbman->drop_temp_table($table);
eee34307 891 $this->ldap_close();
df884cd8 892
0c121d99 893 return true;
894 }
df884cd8 895
0c121d99 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 *
185721a4 904 * @param string $username username
0c121d99 905 */
906 function update_user_record($username, $updatekeys = false) {
df884cd8 907 global $CFG;
185721a4 908
0c121d99 909 //just in case check text case
910 $username = trim(moodle_strtolower($username));
df884cd8 911
0c121d99 912 // get the current user record
185721a4 913 $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id));
0c121d99 914 if (empty($user)) { // trouble
df884cd8 915 error_log("Cannot update non-existent user: ".$username);
2b06294b 916 print_error('auth_dbusernotexist','auth_db','',$username);
0c121d99 917 die;
918 }
df884cd8 919
0c121d99 920 // Protect the userid from being overwritten
921 $userid = $user->id;
df884cd8 922
0c121d99 923 if ($newinfo = $this->get_userinfo($username)) {
924 $newinfo = truncate_userinfo($newinfo);
df884cd8 925
0c121d99 926 if (empty($updatekeys)) { // all keys? this does not support removing values
927 $updatekeys = array_keys($newinfo);
928 }
df884cd8 929
0c121d99 930 foreach ($updatekeys as $key) {
931 if (isset($newinfo[$key])) {
932 $value = $newinfo[$key];
933 } else {
934 $value = '';
935 }
df884cd8 936
0c121d99 937 if (!empty($this->config->{'field_updatelocal_' . $key})) {
938 if ($user->{$key} != $value) { // only update if it's changed
185721a4 939 $DB->set_field('user', $key, $value, array('id'=>$userid));
0c121d99 940 }
941 }
942 }
943 } else {
944 return false;
945 }
185721a4 946 return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
0c121d99 947 }
948 /**
949 * Bulk insert in SQL's temp table
0c121d99 950 */
df884cd8 951 function ldap_bulk_insert($username) {
952 global $DB;
953
954 $username = moodle_strtolower($username); // usernames are __always__ lowercase.
955 $DB->insert_record_raw('tmp_extuser', array('username'=>$username), false, true);
956 print ".";
0c121d99 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
971if (!empty($this->config->groupecreators))
972 if ((boolean)$this->ldap_isgroupmember($extusername, $this->config->groupecreators))
973 return true;
974//build filter for attrcreator
975if (!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, "="))
4454447d 980 $filter .= "($attr)";
0c121d99 981 else
4454447d 982 $filter .= "(".$this->config->memberattribute."=$attr)";
0c121d99 983 }
984 $filter .= "))";
985 //search
986 $result = $this->ldap_get_userlist($filter);
987 if (count($result)!=0)
4454447d
PS
988 return true;
989 }
74c9f514 990
0c121d99 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 }
eee34307 1035 $this->ldap_close();
0c121d99 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 }
eee34307 1075 $this->ldap_close();
0c121d99 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++) {
4454447d 1095 $values = ldap_get_values_len($conn, $entry,$attributes[$j]);
74c9f514 1096
0c121d99 1097 if (is_array($values)) {
74c9f514 1098 $fresult[$i][strtolower($attributes[$j])] = $values;
0c121d99 1099 }
1100 else {
74c9f514 1101 $fresult[$i][strtolower($attributes[$j])] = array($values);
0c121d99 1102 }
1103 }
1104 $i++;
1105 }
1106 while ($entry = @ldap_next_entry($conn, $entry));
1107 //were done
74c9f514 1108
0c121d99 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 }
4f0c2d00 1121 if ($roles = get_archetype_roles('coursecreator')) {
0c121d99 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 }
b9ddb2d5 1143}
5117d598 1144