Another wretched adslashes problem. Roll on placeholder. Thanks to Howard Miller...
[moodle.git] / auth / ldap / auth.php
CommitLineData
b9ddb2d5 1<?php
2
3/**
4 * @author Martin Dougiamas
5 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
6 * @package moodle multiauth
7 *
8 * Authentication Plugin: LDAP Authentication
9 *
10 * Authentication using LDAP (Lightweight Directory Access Protocol).
11 *
12 * 2006-08-28 File created.
13 */
14
15// This page cannot be called directly
16if (!isset($CFG)) exit;
17
18// LDAP functions are reused by other auth libs
19if (!defined('AUTH_LDAP_NAME')) {
20 define('AUTH_LDAP_NAME', 'ldap');
21}
22
23/**
24 * LDAP authentication plugin.
25 */
26class auth_plugin_ldap {
27
28 /**
29 * The configuration details for the plugin.
30 */
31 var $config;
32
33 /**
34 * Constructor.
35 */
36 function auth_plugin_ldap() {
37 $this->config = get_config('auth/ldap');
38 }
39
40 /**
41 * Returns true if the username and password work and false if they are
42 * wrong or don't exist.
43 *
44 * @param string $username The username
45 * @param string $password The password
46 * @returns bool Authentication success or failure.
47 */
48 function user_login($username, $password) {
b7b50143 49 if (! function_exists('ldap_bind')) {
43c6650b 50 print_error('auth_ldapnotinstalled','auth');
b7b50143 51 return false;
52 }
b9ddb2d5 53
54 global $CFG;
55
56 if (!$username or !$password) { // Don't allow blank usernames or passwords
57 return false;
58 }
59
60 // CAS-supplied auth tokens override LDAP auth
61 if ($CFG->auth == "cas" and !empty($CFG->cas_enabled)) {
62 return cas_ldap_auth_user_login($username, $password);
63 }
64
65 $ldapconnection = $this->ldap_connect();
66
67 if ($ldapconnection) {
68 $ldap_user_dn = $this->ldap_find_userdn($ldapconnection, $username);
69
70 //if ldap_user_dn is empty, user does not exist
71 if (!$ldap_user_dn) {
72 ldap_close($ldapconnection);
73 return false;
74 }
75
76 // Try to bind with current username and password
77 $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, stripslashes($password));
78 ldap_close($ldapconnection);
79 if ($ldap_login) {
80 return true;
81 }
82 }
83 else {
84 @ldap_close($ldapconnection);
e8b9d76a 85 print_error('auth_ldap_noconnect','auth',$this->config->host_url);
b9ddb2d5 86 }
87 return false;
88 }
89
90 /**
91 * reads userinformation from ldap and return it in array()
92 *
93 * Read user information from external database and returns it as array().
94 * Function should return all information available. If you are saving
95 * this information to moodle user-table you should honor syncronization flags
96 *
97 * @param string $username username
98 * @return array
99 */
100 function get_userinfo($username) {
101 global $CFG;
102 $ldapconnection = $this->ldap_connect();
103 $config = (array)$CFG;
104 $attrmap = $this->ldap_attributes();
105
106 $result = array();
107 $search_attribs = array();
108
109 foreach ($attrmap as $key=>$values) {
110 if (!is_array($values)) {
111 $values = array($values);
112 }
113 foreach ($values as $value) {
114 if (!in_array($value, $search_attribs)) {
115 array_push($search_attribs, $value);
116 }
117 }
118 }
119
120 $user_dn = $this->ldap_find_userdn($ldapconnection, $username);
121
122 if (empty($this->config->objectclass)) { // Can't send empty filter
123 $this->config->objectclass="objectClass=*";
124 }
125
126 $user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs);
127
128 if ($user_info_result) {
129 $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
130 foreach ($attrmap as $key=>$values) {
131 if (!is_array($values)) {
132 $values = array($values);
133 }
134 $ldapval = NULL;
135 foreach ($values as $value) {
136 if (is_array($user_entry[0][strtolower($value)])) {
137 if (!empty($CFG->unicodedb)) {
138 $newval = addslashes(stripslashes($user_entry[0][strtolower($value)][0]));
139 }
140 else {
141 $newval = addslashes(stripslashes(utf8_decode($user_entry[0][strtolower($value)][0])));
142 }
143 }
144 else {
145 if (!empty($CFG->unicodedb)) {
146 $newval = addslashes(stripslashes($user_entry[0][strtolower($value)]));
147 }
148 else {
149 $newval = addslashes(stripslashes(utf8_decode($user_entry[0][strtolower($value)])));
150 }
151 }
152 if (!empty($newval)) { // favour ldap entries that are set
153 $ldapval = $newval;
154 }
155 }
156 if (!is_null($ldapval)) {
157 $result[$key] = $ldapval;
158 }
159 }
160 }
161
162 @ldap_close($ldapconnection);
163
164 return $result;
165 }
166
167 /**
168 * reads userinformation from ldap and return it in an object
169 *
170 * @param string $username username
171 * @return array
172 */
173 function get_userinfo_asobj($username) {
174 $user_array = truncate_userinfo($this->get_userinfo($username));
175 $user = new object;
176 foreach ($user_array as $key=>$value) {
177 $user->{$key} = $value;
178 }
179 return $user;
180 }
181
182 /**
183 * returns all usernames from external database
184 *
185 * get_userlist returns all usernames from external database
186 *
187 * @return array
188 */
189 function get_userlist() {
190 global $CFG;
191 $this->ldap_init();
192 return $this->ldap_get_userlist("({$this->config->user_attribute}=*)");
193 }
194
195 /**
196 * checks if user exists on external db
197 */
198 function user_exists($username) {
199 global $CFG;
200 $this->ldap_init();
201 //returns true if given usernname exist on ldap
202 $users = $this->ldap_get_userlist("({$this->config->user_attribute}=$username)");
203 return count($users);
204 }
205
206 /**
207 * creates new user on external database
208 *
209 * user_create() creates new user on external database
210 * By using information in userobject
211 * Use user_exists to prevent dublicate usernames
212 *
213 * @param mixed $userobject Moodle userobject
214 * @param mixed $plainpass Plaintext password
215 */
216 function user_create($userobject, $plainpass) {
217 global $CFG;
218 $ldapconnection = $this->ldap_connect();
219 $attrmap = $this->ldap_attributes();
220
221 $newuser = array();
222
223 foreach ($attrmap as $key => $values) {
224 if (!is_array($values)) {
225 $values = array($values);
226 }
227 foreach ($values as $value) {
228 if (!empty($userobject->$key) ) {
229 if (!empty($CFG->unicodedb)) {
230 $newuser[$value] = $userobject->$key;
231 }
232 else {
233 $newuser[$value] = utf8_encode($userobject->$key);
234 }
235 }
236 }
237 }
238
239 //Following sets all mandatory and other forced attribute values
240 //User should be creted as login disabled untill email confirmation is processed
241 //Feel free to add your user type and send patches to paca@sci.fi to add them
242 //Moodle distribution
243
244 switch ($this->config->user_type) {
245 case 'edir':
246 $newuser['objectClass']= array("inetOrgPerson","organizationalPerson","person","top");
247 $newuser['uniqueId']= $userobject->username;
248 $newuser['logindisabled']="TRUE";
249 $newuser['userpassword']=$plainpass;
250 break;
251 default:
e8b9d76a 252 print_error('auth_ldap_unsupportedusertype','auth',$this->config->user_type);
b9ddb2d5 253 }
254 $uadd = $this->ldap_add($ldapconnection, "{$this->config->user_attribute}={$userobject->username},{$this->config->create_context}", $newuser);
255 ldap_close($ldapconnection);
256 return $uadd;
257
258 }
259
260 /**
261 *
262 * get_users() returns userobjects from external database
263 *
264 * Function returns users from external databe as Moodle userobjects
265 * If filter is not present it should return ALL users in external database
266 *
267 * @param mixed $filter substring of username
268 * @returns array of userobjects
269 */
270 function get_users($filter = '*', $dontlistcreated = false) {
271 global $CFG;
272
273 $ldapconnection = $this->ldap_connect();
274 $fresult = array();
275
276 if ($filter=="*") {
277 $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
278 }
279
280 $contexts = explode(";",$this->config->contexts);
281
282 if (!empty($this->config->create_context) and empty($dontlistcreated)) {
283 array_push($contexts, $this->config->create_context);
284 }
285
286 $attrmap = $this->ldap_attributes();
287
288 $search_attribs = array();
289
290 foreach ($attrmap as $key=>$values) {
291 if (!is_array($values)) {
292 $values = array($values);
293 }
294 foreach ($values as $value) {
295 if (!in_array($value, $search_attribs)) {
296 array_push($search_attribs, $value);
297 }
298 }
299 }
300
301
302 foreach ($contexts as $context) {
303
304 $context = trim($context);
305 if (empty($context)) {
306 continue;
307 }
308
309 if ($this->config->search_sub) {
310 //use ldap_search to find first user from subtree
311 $ldap_result = ldap_search($ldapconnection, $context,
312 $filter,
313 $search_attribs);
314 }
315 else {
316 //search only in this context
317 $ldap_result = ldap_list($ldapconnection, $context,
318 $filter,
319 $search_attribs);
320 }
321
322 $users = $this->ldap_get_entries($ldapconnection, $ldap_result);
323
324 //add found users to list
325 foreach ($users as $ldapuser=>$attribs) {
326 $user = new object();
327 foreach ($attrmap as $key=>$value) {
328 if (isset($users[$ldapuser][$value][0])) {
329 $user->$key=$users[$ldapuser][$value][0];
330 }
331 }
332 //quick way to get around binarystrings
333 $user->guid=bin2hex($user->guid);
334 //add authentication source stamp
335 $user->auth = AUTH_LDAP_NAME;
b7b50143 336 //add MNET host id
337 $user->mnethostid = $CFG->mnet_localhost_id;
b9ddb2d5 338 $fresult[$user->username]=$user;
339
340 }
341 }
342
343 return $fresult;
344 }
345
346 /**
347 * return number of days to user password expires
348 *
349 * If userpassword does not expire it should return 0. If password is already expired
350 * it should return negative value.
351 *
352 * @param mixed $username username
353 * @return integer
354 */
355 function password_expire($username) {
356 global $CFG ;
357 $result = false;
358
359 $ldapconnection = $this->ldap_connect();
360 $user_dn = $this->ldap_find_userdn($ldapconnection, $username);
361 $search_attribs = array($this->config->expireattr);
362 $sr = ldap_read($ldapconnection, $user_dn, 'objectclass=*', $search_attribs);
363 if ($sr) {
364 $info=$this->ldap_get_entries($ldapconnection, $sr);
365 if ( empty($info[0][strtolower($this->config->expireattr)][0])) {
366 //error_log("ldap: no expiration value".$info[0][$this->config->expireattr]);
367 // no expiration attribute, password does not expire
368 $result = 0;
369 }
370 else {
371 $now = time();
372 $expiretime = $this->ldap_expirationtime2unix($info[0][strtolower($this->config->expireattr)][0]);
373 if ($expiretime > $now) {
374 $result = ceil(($expiretime - $now) / DAYSECS);
375 }
376 else {
377 $result = floor(($expiretime - $now) / DAYSECS);
378 }
379 }
380 }
381 else {
382 error_log("ldap: password_expire did't find expiration time.");
383 }
384
385 //error_log("ldap: password_expire user $user_dn expires in $result days!");
386 return $result;
387 }
388
389 /**
390 * syncronizes user fron external db to moodle user table
391 *
392 * Sync shouid be done by using idnumber attribute, not username.
393 * You need to pass firstsync parameter to function to fill in
394 * idnumbers if they dont exists in moodle user table.
395 *
396 * Syncing users removes (disables) users that dont exists anymore in external db.
397 * Creates new users and updates coursecreator status of users.
398 *
399 * @param mixed $firstsync Optional: set to true to fill idnumber fields if not filled yet
400 */
401 function sync_users ($bulk_insert_records = 1000, $do_updates = 1) {
402 //Syncronizes userdb with ldap
403 //This will add, rename
404 /// OPTIONAL PARAMETERS
405 /// $bulk_insert_records = 1 // will insert $bulkinsert_records per insert statement
406 /// valid only with $unsafe. increase to a couple thousand for
407 /// blinding fast inserts -- but test it: you may hit mysqld's
408 /// max_allowed_packet limit.
409 /// $do_updates = 1 // will do pull in data updates from ldap if relevant
410
411
fa96bfaa 412 global $CFG;
413
414 $droptablesql = array(); /// sql commands to drop the table (because session scope could be a problem for
415 /// some persistent drivers like ODBTP (mssql) or if this function is invoked
416 /// from within a PHP application using persistent connections
b9ddb2d5 417
418 // configure a temp table
419 print "Configuring temp table\n";
fa96bfaa 420 switch (strtolower($CFG->dbfamily)) {
421 case 'mysql':
422 $temptable = $CFG->prefix . 'extuser';
423 $droptablesql[] = 'DROP TEMPORARY TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
424 execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
425 echo "Creating temp table $temptable\n";
426 execute_sql('CREATE TEMPORARY TABLE ' . $temptable . ' (idnumber VARCHAR(64), PRIMARY KEY (idnumber)) TYPE=MyISAM', false);
427 break;
428 case 'postgres':
429 $temptable = $CFG->prefix . 'extuser';
430 $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
431 execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
432 echo "Creating temp table $temptable\n";
433 $bulk_insert_records = 1; // no support for multiple sets of values
434 execute_sql('CREATE TEMPORARY TABLE '. $temptable . ' (idnumber VARCHAR(64), PRIMARY KEY (idnumber))', false);
435 break;
436 case 'mssql':
437 $temptable = '#'.$CFG->prefix . 'extuser'; /// MSSQL temp tables begin with #
438 $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
439 execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
440 echo "Creating temp table $temptable\n";
441 $bulk_insert_records = 1; // no support for multiple sets of values
442 execute_sql('CREATE TABLE ' . $temptable . ' (idnumber VARCHAR(64), PRIMARY KEY (idnumber))', false);
443 break;
444 case 'oracle':
445 $temptable = $CFG->prefix . 'extuser';
446 $droptablesql[] = 'TRUNCATE TABLE ' . $temptable; // oracle requires truncate before being able to drop a temp table
447 $droptablesql[] = 'DROP TABLE ' . $temptable; // sql command to drop the table (because session scope could be a problem)
448 execute_sql_arr($droptablesql, true, false); /// Drop temp table to avoid persistence problems later
449 echo "Creating temp table $temptable\n";
450 $bulk_insert_records = 1; // no support for multiple sets of values
451 execute_sql('CREATE GLOBAL TEMPORARY TABLE '.$temptable.' (idnumber VARCHAR(64), PRIMARY KEY (idnumber)) ON COMMIT PRESERVE ROWS', false);
452 break;
b9ddb2d5 453 }
454
455 print "connecting to ldap\n";
456 $ldapconnection = $this->ldap_connect();
457
458 if (!$ldapconnection) {
459 @ldap_close($ldapconnection);
e8b9d76a 460 notify(get_string('auth_ldap_noconnect','auth',$this->config->host_url));
b9ddb2d5 461 return false;
462 }
463
464 ////
465 //// get user's list from ldap to sql in a scalable fashion
466 ////
467 // prepare some data we'll need
468 if (! empty($this->config->objectclass)) {
469 $this->config->objectclass="objectClass=*";
470 }
471
472 $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
473
474 $contexts = explode(";",$this->config->contexts);
475
476 if (!empty($this->config->create_context)) {
477 array_push($contexts, $this->config->create_context);
478 }
479
480 $fresult = array();
481 $count = 0;
482 foreach ($contexts as $context) {
483 $context = trim($context);
484 if (empty($context)) {
485 continue;
486 }
487 begin_sql();
488 if ($this->config->search_sub) {
489 //use ldap_search to find first user from subtree
490 $ldap_result = ldap_search($ldapconnection, $context,
491 $filter,
492 array($this->config->user_attribute));
493 }
494 else {
495 //search only in this context
496 $ldap_result = ldap_list($ldapconnection, $context,
497 $filter,
498 array($this->config->user_attribute));
499 }
500
501 if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) {
502 do {
503 $value = ldap_get_values_len($ldapconnection, $entry,$this->config->user_attribute);
504 $value = $value[0];
505 $count++;
506 array_push($fresult, $value);
507 if (count($fresult) >= $bulk_insert_records) {
fa96bfaa 508 $this->ldap_bulk_insert($fresult, $temptable);
b9ddb2d5 509 //print var_dump($fresult);
510 $fresult=array();
511 }
512 }
513 while ($entry = ldap_next_entry($ldapconnection, $entry));
514 }
515
516 // insert any remaining users and release mem
517 if (count($fresult)) {
fa96bfaa 518 $this->ldap_bulk_insert($fresult, $temptable);
b9ddb2d5 519 $fresult=array();
520 }
521 commit_sql();
522 }
523 // free mem
524 $ldap_results = 0;
525
526 /// preserve our user database
527 /// if the temp table is empty, it probably means that something went wrong, exit
528 /// so as to avoid mass deletion of users; which is hard to undo
fa96bfaa 529 $count = get_record_sql('SELECT COUNT(idnumber) AS count, 1 FROM ' . $temptable);
b9ddb2d5 530 $count = $count->{'count'};
531 if ($count < 1) {
532 print "Did not get any users from LDAP -- error? -- exiting\n";
533 exit;
fa96bfaa 534 } else {
535 print "Got $count records from LDAP\n";
b9ddb2d5 536 }
537
538 ////
539 //// User removal
540 ////
541 // find users in DB that aren't in ldap -- to be removed!
542 // this is still not as scalable
543 $sql = 'SELECT u.id, u.username
fa96bfaa 544 FROM ' . $CFG->prefix .'user u LEFT JOIN ' . $temptable . ' e
b9ddb2d5 545 ON u.idnumber = e.idnumber
546 WHERE u.auth=\'' . AUTH_LDAP_NAME . '\' AND u.deleted=\'0\' AND e.idnumber IS NULL';
547 //print($sql);
548 $remove_users = get_records_sql($sql);
549
550 if (!empty($remove_users)) {
551 print "User entries to remove: ". count($remove_users) . "\n";
552
553 begin_sql();
554 foreach ($remove_users as $user) {
555 //following is copy pasted from admin/user.php
556 //maybe this should moved to function in lib/datalib.php
b7b50143 557 $updateuser = new stdClass();
b9ddb2d5 558 $updateuser->id = $user->id;
559 $updateuser->deleted = '1';
560 //$updateuser->username = "$user->username".time(); // Remember it just in case
561 //$updateuser->email = ''; // Clear this field to free it up
562 $updateuser->timemodified = time();
563 if (update_record("user", $updateuser)) {
564 // unenrol_student($user->id); // From all courses
565 // remove_teacher($user->id); // From all courses
566 // remove_admin($user->id);
567 delete_records('role_assignments', 'userid', $user->id); // unassign all roles
568 notify(get_string('deletedactivity', '', fullname($user, true)) );
569 }
570 else {
571 notify(get_string('deletednot', '', fullname($user, true)));
572 }
573 //copy pasted part ends
574 }
575 commit_sql();
fa96bfaa 576 } else {
577 print "No user entries to be removed\n";
578 }
b9ddb2d5 579 $remove_users = 0; // free mem!
580
581 ////
582 //// User Updates
583 //// (time-consuming, optional)
584 ////
585 if ($do_updates) {
586 // narrow down what fields we need to update
587 $all_keys = array_keys(get_object_vars($this->config));
588 $updatekeys = array();
589 foreach ($all_keys as $key) {
590 if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) {
591 // if we have a field to update it from
592 // and it must be updated 'onlogin' we
593 // update it on cron
594 if ( !empty($this->config->{'field_map_'.$match[1]})
595 and $this->config->{$match[0]} === 'onlogin') {
596 array_push($updatekeys, $match[1]); // the actual key name
597 }
598 }
599 }
600 // print_r($all_keys); print_r($updatekeys);
601 unset($all_keys); unset($key);
602
fa96bfaa 603 } else {
604 print "No updates to be done\n";
b9ddb2d5 605 }
606 if ( $do_updates and !(empty($updatekeys)) ) { // run updates only if relevant
607 $users = get_records_sql('SELECT u.username, u.id FROM ' . $CFG->prefix . 'user u WHERE u.deleted=0 and u.auth=\'' . AUTH_LDAP_NAME . '\'' );
608 if (!empty($users)) {
609 print "User entries to update: ". count($users). "\n";
610 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
611
612 if ($creatorroles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
613 $creatorrole = array_shift($creatorroles); // We can only use one, let's use the first one
614
615 begin_sql();
616 $xcount = 0;
617 $maxxcount = 100;
618
619 foreach ($users as $user) {
620 echo "updating user $user->username \n";
621 $this->update_user_record($user->username, $updatekeys);
622
623 // update course creators
624 if (!empty($this->config->creators) and !empty($this->config->memberattribute) ) {
625 if ($this->iscreator($user->username)) { // Following calls will not create duplicates
626 role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
627 $xcount++;
628 } else {
629 role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id);
630 $xcount++;
631 }
632 }
633
634 if ($xcount++ > $maxxcount) {
635 commit_sql();
636 begin_sql();
637 $xcount = 0;
638 }
639 }
640 commit_sql();
641 unset($users); // free mem
642 }
643 }
fa96bfaa 644 } else { // end do updates
645 print "No updates to be done\n";
646 }
b9ddb2d5 647
648 ////
649 //// User Additions
650 ////
651 // find users missing in DB that are in LDAP
652 // note that get_records_sql wants at least 2 fields returned,
653 // and gives me a nifty object I don't want.
654 $sql = 'SELECT e.idnumber,1
fa96bfaa 655 FROM ' . $temptable . ' e LEFT JOIN ' . $CFG->prefix .'user u
b9ddb2d5 656 ON e.idnumber = u.idnumber
657 WHERE u.id IS NULL OR (u.id IS NOT NULL AND u.deleted=1)';
658 $add_users = get_records_sql($sql); // get rid of the fat
659
660 if (!empty($add_users)) {
661 print "User entries to add: ". count($add_users). "\n";
662
b7b50143 663 if ($roles = get_roles_with_capability('moodle/legacy:coursecreator', CAP_ALLOW)) {
b9ddb2d5 664 $creatorrole = array_shift($roles); // We can only use one, let's use the first one
665 }
666
667 begin_sql();
668 foreach ($add_users as $user) {
669 $user = $this->get_userinfo_asobj($user->idnumber);
670 //print $user->username . "\n";
671
672 // prep a few params
b7b50143 673 $user->modified = time();
674 $user->confirmed = 1;
675 $user->auth = AUTH_LDAP_NAME;
676 $user->mnethostid = $CFG->mnet_localhost_id;
b9ddb2d5 677
678 // insert it
679 $old_debug=$CFG->debug;
680 $CFG->debug=10;
681
682 // maybe the user has been deleted before
b7b50143 683 if ($old_user = get_record('user', 'idnumber', $user->idnumber, 'deleted', 1, 'mnethostid', $CFG->mnet_localhost_id)) {
b9ddb2d5 684 $user->id = $old_user->id;
b7b50143 685 set_field('user', 'deleted', 0, 'id', $user->id);
b9ddb2d5 686 echo "Revived user $user->username with idnumber $user->idnumber id $user->id\n";
687 }
688 elseif ($id = insert_record('user',$user)) { // it is truly a new user
689 echo "inserted user $user->username with idnumber $user->idnumber id $id\n";
690 $user->id = $id;
691 }
692 else {
693 echo "error inserting user $user->username with idnumber $user->idnumber \n";
694 }
695 $CFG->debug = $old_debug;
696 $userobj = $this->update_user_record($user->username);
697 if (isset($this->config->forcechangepassword) and $this->config->forcechangepassword) {
698 set_user_preference('auth_forcepasswordchange', 1, $userobj->id);
699 }
700
701 // update course creators
702 if (isset($creatorrole->id) and !empty($this->config->creators) and !empty($this->config->memberattribute)) {
703 if ($this->iscreator($user->username)) {
704 if (user_has_role_assignment($user->id, $creatorrole->id, $sitecontext->id)) {
705 role_unassign($creatorrole->id, $user->id, 0, $sitecontext->id);
706 } else {
707 role_assign($creatorrole->id, $user->id, 0, $sitecontext->id, 0, 0, 0, 'ldap');
708 }
709 }
710 }
711 }
712 commit_sql();
713 unset($add_users); // free mem
fa96bfaa 714 } else {
715 print "No users to be added\n";
b9ddb2d5 716 }
717 return true;
718 }
719
720 /**
721 * Update a local user record from an external source.
722 * This is a lighter version of the one in moodlelib -- won't do
723 * expensive ops such as enrolment.
724 *
725 * If you don't pass $updatekeys, there is a performance hit and
726 * values removed from LDAP won't be removed from moodle.
727 */
728 function update_user_record($username, $updatekeys = false) {
729
730 global $CFG;
731
732 //just in case check text case
733 $username = trim(moodle_strtolower($username));
734
735 // get the current user record
b7b50143 736 $user = get_record('user', 'username', $username, 'mnethostid', $CFG->mnet_localhost_id);
b9ddb2d5 737 if (empty($user)) { // trouble
738 error_log("Cannot update non-existent user: $username");
739 die;
740 }
741
b7b50143 742 // Protect the userid from being overwritten
743 $userid = $user->id;
744
b9ddb2d5 745 if (function_exists('auth_get_userinfo')) {
746 if ($newinfo = auth_get_userinfo($username)) {
747 $newinfo = truncate_userinfo($newinfo);
748
749 if (empty($updatekeys)) { // all keys? this does not support removing values
750 $updatekeys = array_keys($newinfo);
751 }
752
753 foreach ($updatekeys as $key) {
b9ddb2d5 754 if (isset($newinfo[$key])) {
b7b50143 755 $value = addslashes(stripslashes($newinfo[$key]));
b9ddb2d5 756 }
757 else {
758 $value = '';
759 }
760 if (!empty($this->config->{'field_updatelocal_' . $key})) {
761 if ($user->{$key} != $value) { // only update if it's changed
b7b50143 762 set_field('user', $key, $value, 'id', $userid);
b9ddb2d5 763 }
764 }
765 }
766 }
767 }
b7b50143 768 return get_record_select("user", "id = '$userid' AND deleted <> '1'");
b9ddb2d5 769 }
770
fa96bfaa 771
b9ddb2d5 772 // bulk insert in SQL's temp table
773 // $users is an array of usernames
fa96bfaa 774 function ldap_bulk_insert($users, $temptable) {
775
b9ddb2d5 776 // bulk insert -- superfast with $bulk_insert_records
fa96bfaa 777 $sql = 'INSERT INTO ' . $temptable . ' (idnumber) VALUES ';
b9ddb2d5 778 // make those values safe
779 array_map('addslashes', $users);
780 // join and quote the whole lot
781 $sql = $sql . '(\'' . join('\'),(\'', $users) . '\')';
782 print "+ " . count($users) . " users\n";
783 execute_sql($sql, false);
784
785 }
786
787
788 /*
789 * user_activate activates user in external db.
790 *
791 * Activates (enables) user in external db so user can login to external db
792 *
793 * @param mixed $username username
794 * @return boolen result
795 */
796 function user_activate($username) {
797
798 global $CFG;
799
800 $ldapconnection = $this->ldap_connect();
801
802 $userdn = $this->ldap_find_userdn($ldapconnection, $username);
803 switch ($this->config->user_type) {
804 case 'edir':
805 $newinfo['loginDisabled']="FALSE";
806 break;
807 default:
808 error ('auth: ldap user_activate() does not support selected usertype:"'.$this->config->user_type.'" (..yet)');
809 }
810 $result = ldap_modify($ldapconnection, $userdn, $newinfo);
811 ldap_close($ldapconnection);
812 return $result;
813 }
814
815 /*
816 * user_disables disables user in external db.
817 *
818 * Disables user in external db so user can't login to external db
819 *
820 * @param mixed $username username
821 * @return boolean result
822 */
823 function user_disable($username) {
824 global $CFG;
825
826 $ldapconnection = $this->ldap_connect();
827
828 $userdn = $this->ldap_find_userdn($ldapconnection, $username);
829 switch ($this->config->user_type) {
830 case 'edir':
831 $newinfo['loginDisabled']="TRUE";
832 break;
833 default:
834 error ('auth: ldap user_disable() does not support selected usertype (..yet)');
835 }
836 $result = ldap_modify($ldapconnection, $userdn, $newinfo);
837 ldap_close($ldapconnection);
838 return $result;
839 }
840
841 /*
842 * Returns true if user should be coursecreator.
843 *
844 * @param mixed $username username
845 * @return boolean result
846 */
847 function iscreator($username = false) {
848 ///if user is member of creator group return true
849 global $USER, $CFG;
850 $this->ldap_init();
851 if (! $username) {
852 $username = $USER->username;
853 }
854 if ((! $this->config->creators) or (! $this->config->memberattribute)) {
855 return null;
856 }
857 return $this->ldap_isgroupmember($username, $this->config->creators);
858 }
859
860 /*
861 * user_update saves userinformation from moodle to external db
862 *
863 * Called when the user record is updated.
864 * Modifies user in external database. It takes olduser (before changes) and newuser (after changes)
865 * conpares information saved modified information to external db.
866 *
867 * @param mixed $olduser Userobject before modifications
868 * @param mixed $newuser Userobject new modified userobject
869 * @return boolean result
870 *
871 */
872 function user_update($olduser, $newuser) {
873
874 global $USER, $CFG;
875
876 $ldapconnection = $this->ldap_connect();
877
878 $result = array();
879 $search_attribs = array();
880
881 $attrmap = $this->ldap_attributes();
882 foreach ($attrmap as $key => $values) {
883 if (!is_array($values)) {
884 $values = array($values);
885 }
886 foreach ($values as $value) {
887 if (!in_array($value, $search_attribs)) {
888 array_push($search_attribs, $value);
889 }
890 }
891 }
892
893 $user_dn = $this->ldap_find_userdn($ldapconnection, $olduser->username);
894
895 $user_info_result = ldap_read($ldapconnection, $user_dn,
896 $this->config->objectclass, $search_attribs);
897
898 if ($user_info_result) {
899
900 $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result);
901 if (count($user_entry) > 1) {
902 trigger_error("ldap: Strange! More than one user record found in ldap. Only using the first one.");
903 }
904 $user_entry = $user_entry[0];
905
906 //error_log(var_export($user_entry) . 'fpp' );
907
908 foreach ($attrmap as $key => $ldapkeys) {
909
910 // only process if the moodle field ($key) has changed and we
911 // are set to update LDAP with it
912 if ($olduser->$key !== $newuser->$key and
913 !empty($this->config->{'field_updateremote_'. $key})) {
914
915 // for ldap values that could be in more than one
916 // ldap key, we will do our best to match
917 // where they came from
918 $ambiguous = true;
919 $changed = false;
920 if (!is_array($ldapkeys)) {
921 $ldapkeys = array($ldapkeys);
922 }
923 if (count($ldapkeys) < 2) {
924 $ambiguous = false;
925 }
926
927 foreach ($ldapkeys as $ldapkey) {
928 $ldapkey = strtolower($ldapkey);
929 $ldapvalue = $user_entry[$ldapkey][0];
930 if (!$ambiguous) {
931 // skip update if the values already match
932 if ( !($newuser->$key === $ldapvalue) ) {
933 ldap_modify($ldapconnection, $user_dn, array($ldapkey => $newuser->$key));
934 }
935 else {
936 error_log("Skip updating field $key for entry $user_dn: it seems to be already same on LDAP.
937 old moodle value: '{$olduser->$key}'
938 new value: '{$newuser->$key}'
939 current value in ldap entry: '{$ldapvalue}'");
940 }
941 }
942 else {
943 // ambiguous
944 // value empty before in Moodle (and LDAP) - use 1st ldap candidate field
945 // no need to guess
946 if (empty($olduser->$key)) { // value empty before - use 1st ldap candidate
947 if (ldap_modify($ldapconnection, $user_dn, array($ldapkey => $newuser->$key))) {
948 $changed = true;
949 last;
950 }
951 else {
952 error ('Error updating LDAP record. Error code: '
953 . ldap_errno($ldapconnection) . '; Error string : '
954 . ldap_err2str(ldap_errno($ldapconnection)));
955 }
956 }
957
958 // we found which ldap key to update!
959 if (!empty($ldapvalue) and $olduser->$key === $ldapvalue ) {
960 // error_log("Matched: ". $olduser->$key . " === " . $ldapvalue);
961 if (ldap_modify($ldapconnection, $user_dn, array($ldapkey => $newuser->$key))) {
962 $changed = true;
963 last;
964 }
965 else {
966 error ('Error updating LDAP record. Error code: '
967 . ldap_errno($ldapconnection) . '; Error string : '
968 . ldap_err2str(ldap_errno($ldapconnection)));
969 }
970 }
971 }
972 }
973
974 if ($ambiguous and !$changed) {
975 error_log("Failed to update LDAP with ambiguous field $key".
976 " old moodle value: '" . $olduser->$key .
977 "' new value '" . $newuser->$key );
978 }
979 }
980 }
981
982
983 }
984 else {
985 error_log("ERROR:No user found in LDAP");
986 @ldap_close($ldapconnection);
987 return false;
988 }
989
990 @ldap_close($ldapconnection);
991
992 return true;
993
994 }
995
fb5c7739 996 /**
b9ddb2d5 997 * changes userpassword in external db
998 *
999 * called when the user password is updated.
1000 * changes userpassword in external db
1001 *
b7b50143 1002 * @param object $user User table object
1003 * @param mixed $newpassword Plaintext password
b9ddb2d5 1004 * @return boolean result
1005 *
1006 */
b9ddb2d5 1007 function user_update_password($user, $newpassword) {
1008 /// called when the user password is updated -- it assumes it is called by an admin
1009 /// or that you've otherwise checked the user's credentials
1010 /// IMPORTANT: $newpassword must be cleartext, not crypted/md5'ed
1011
1012 global $CFG, $USER;
1013 $result = false;
1014 $username = $user->username;
1015
1016 $ldapconnection = $this->ldap_connect();
1017
1018 $user_dn = $this->ldap_find_userdn($ldapconnection, $username);
1019
1020 if (!$user_dn) {
1021 error_log('LDAP Error in user_update_password(). No DN for: ' . $username);
1022 return false;
1023 }
1024
1025 switch ($this->config->user_type) {
1026 case 'edir':
1027 //Change password
1028 $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $newpassword));
1029 if (!$result) {
1030 error_log('LDAP Error in user_update_password(). Error code: '
1031 . ldap_errno($ldapconnection) . '; Error string : '
1032 . ldap_err2str(ldap_errno($ldapconnection)));
1033 }
1034 //Update password expiration time, grace logins count
1035 $search_attribs = array($this->config->expireattr, 'passwordExpirationInterval','loginGraceLimit' );
1036 $sr = ldap_read($ldapconnection, $user_dn, 'objectclass=*', $search_attribs);
1037 if ($sr) {
1038 $info=$this->ldap_get_entries($ldapconnection, $sr);
1039 $newattrs = array();
1040 if (!empty($info[0][$this->config->expireattr][0])) {
1041 //Set expiration time only if passwordExpirationInterval is defined
1042 if (!empty($info[0]['passwordExpirationInterval'][0])) {
1043 $expirationtime = time() + $info[0]['passwordExpirationInterval'][0];
1044 $ldapexpirationtime = $this->ldap_unix2expirationtime($expirationtime);
1045 $newattrs['passwordExpirationTime'] = $ldapexpirationtime;
1046 }
1047
1048 //set gracelogin count
1049 if (!empty($info[0]['loginGraceLimit'][0])) {
1050 $newattrs['loginGraceRemaining']= $info[0]['loginGraceLimit'][0];
1051 }
1052
1053 //Store attribute changes to ldap
1054 $result = ldap_modify($ldapconnection, $user_dn, $newattrs);
1055 if (!$result) {
1056 error_log('LDAP Error in user_update_password() when modifying expirationtime and/or gracelogins. Error code: '
1057 . ldap_errno($ldapconnection) . '; Error string : '
1058 . ldap_err2str(ldap_errno($ldapconnection)));
1059 }
1060 }
1061 }
1062 else {
1063 error_log('LDAP Error in user_update_password() when reading password expiration time. Error code: '
1064 . ldap_errno($ldapconnection) . '; Error string : '
1065 . ldap_err2str(ldap_errno($ldapconnection)));
1066 }
1067 break;
1068
1069 default:
1070 $usedconnection = &$ldapconnection;
1071 // send ldap the password in cleartext, it will md5 it itself
1072 $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $newpassword));
1073 if (!$result) {
1074 error_log('LDAP Error in user_update_password(). Error code: '
1075 . ldap_errno($ldapconnection) . '; Error string : '
1076 . ldap_err2str(ldap_errno($ldapconnection)));
1077 }
1078
1079 }
1080
1081 @ldap_close($ldapconnection);
1082 return $result;
1083 }
1084
1085 //PRIVATE FUNCTIONS starts
1086 //private functions are named as ldap_*
1087
1088 /**
1089 * returns predefined usertypes
1090 *
1091 * @return array of predefined usertypes
1092 */
1093
1094 function ldap_suppported_usertypes() {
1095 // returns array of supported usertypes (schemas)
1096 // If you like to add our own please name and describe it here
1097 // And then add case clauses in relevant places in functions
1098 // iauth_ldap_init, auth_user_create, auth_check_expire, auth_check_grace
1099 $types['edir']='Novell Edirectory';
1100 $types['rfc2307']='posixAccount (rfc2307)';
1101 $types['rfc2307bis']='posixAccount (rfc2307bis)';
1102 $types['samba']='sambaSamAccount (v.3.0.7)';
1103 $types['ad']='MS ActiveDirectory';
1104 return $types;
1105 }
1106
1107
1108 /**
1109 * initializes needed variables for ldap-module
1110 *
1111 * Uses names defined in ldap_supported_usertypes.
1112 * $default is first defined as:
1113 * $default['pseudoname'] = array(
1114 * 'typename1' => 'value',
1115 * 'typename2' => 'value'
1116 * ....
1117 * );
1118 *
1119 * @return array of default values
1120 */
1121 function ldap_getdefaults() {
1122 $default['objectclass'] = array(
1123 'edir' => 'User',
1124 'rfc2703' => 'posixAccount',
1125 'rfc2703bis' => 'posixAccount',
1126 'samba' => 'sambaSamAccount',
1127 'ad' => 'user',
1128 'default' => '*'
1129 );
1130 $default['user_attribute'] = array(
1131 'edir' => 'cn',
1132 'rfc2307' => 'uid',
1133 'rfc2307bis' => 'uid',
1134 'samba' => 'uid',
1135 'ad' => 'cn',
1136 'default' => 'cn'
1137 );
1138 $default['memberattribute'] = array(
1139 'edir' => 'member',
1140 'rfc2307' => 'member',
1141 'rfc2307bis' => 'member',
1142 'samba' => 'member',
1143 'ad' => 'member',
1144 'default' => 'member'
1145 );
1146 $default['memberattribute_isdn'] = array(
1147 'edir' => '1',
1148 'rfc2307' => '0',
1149 'rfc2307bis' => '1',
1150 'samba' => '0', //is this right?
1151 'ad' => '1',
1152 'default' => '0'
1153 );
1154 $default['expireattr'] = array (
1155 'edir' => 'passwordExpirationTime',
1156 'rfc2307' => 'shadowExpire',
1157 'rfc2307bis' => 'shadowExpire',
1158 'samba' => '', //No support yet
1159 'ad' => '', //No support yet
1160 'default' => ''
1161 );
1162 return $default;
1163 }
1164
1165 /**
1166 * return binaryfields of selected usertype
1167 *
1168 *
1169 * @return array
1170 */
1171 function ldap_getbinaryfields () {
1172 global $CFG;
1173 $binaryfields = array (
1174 'edir' => array('guid'),
1175 'rfc2703' => array(),
1176 'rfc2703bis' => array(),
1177 'samba' => array(),
1178 'ad' => array(),
1179 'default' => '*'
1180 );
1181 if (!empty($this->config->user_type)) {
1182 return $binaryfields[$this->config->user_type];
1183 }
1184 else {
1185 return $binaryfields['default'];
1186 }
1187 }
1188
1189 function ldap_isbinary ($field) {
1190 if (!isset($field)) {
1191 return null ;
1192 }
1193 return array_search($field, $this->ldap_getbinaryfields());
1194 }
1195
1196 /**
1197 * set $CFG-values for ldap_module
1198 *
1199 * Get default configuration values with ldap_getdefaults()
1200 * and by using this information $CFG-> values are set
1201 * If $CFG->value is alredy set current value is honored.
1202 *
1203 *
1204 */
1205 function ldap_init () {
1206 global $CFG;
1207
1208 $default = $this->ldap_getdefaults();
1209
1210 // TODO: do we need set_config calls here?
1211
1212 foreach ($default as $key => $value) {
1213 //set defaults if overriding fields not set
1214 if (empty($this->config->{$key})) {
1215 if (!empty($this->config->user_type) and !empty($default[$key][$this->config->user_type])) {
1216 $this->config->{$key} = $default[$key][$this->config->user_type];
1217 }
1218 else {
1219 //use default value if user_type not set
1220 if (!empty($default[$key]['default'])) {
1221 $this->config->{$key} = $default[$key]['default'];
1222 }
1223 else {
1224 unset($this->config->{$key});
1225 }
1226 }
1227 }
1228 }
1229 //hack prefix to objectclass
1230 if ('objectClass=' != substr($this->config->objectclass, 0, 12)) {
1231 $this->config->objectclass = 'objectClass='.$this->config->objectclass;
1232 }
1233
1234 //all chages go in $CFG , no need to return value
1235 }
1236
1237 /**
1238 * take expirationtime and return it as unixseconds
1239 *
1240 * takes expriration timestamp as readed from ldap
1241 * returns it as unix seconds
1242 * depends on $config->user_type variable
1243 *
1244 * @param mixed time Time stamp readed from ldap as it is.
1245 * @return timestamp
1246 */
1247 function ldap_expirationtime2unix ($time) {
1248
1249 global $CFG;
1250 $result = false;
1251 switch ($this->config->user_type) {
1252 case 'edir':
1253 $yr=substr($time,0,4);
1254 $mo=substr($time,4,2);
1255 $dt=substr($time,6,2);
1256 $hr=substr($time,8,2);
1257 $min=substr($time,10,2);
1258 $sec=substr($time,12,2);
1259 $result = mktime($hr,$min,$sec,$mo,$dt,$yr);
1260 break;
1261 case 'posix':
1262 $result = $time * DAYSECS; //The shadowExpire contains the number of DAYS between 01/01/1970 and the actual expiration date
1263 break;
1264 default:
e8b9d76a 1265 print_error('auth_ldap_usertypeundefined', 'auth');
b9ddb2d5 1266 }
1267 return $result;
1268 }
1269
1270 /**
1271 * takes unixtime and return it formated for storing in ldap
1272 *
1273 * @param integer unix time stamp
1274 */
1275 function ldap_unix2expirationtime($time) {
1276 global $CFG;
1277 $result = false;
1278 switch ($this->config->user_type) {
1279 case 'edir':
1280 $result=date('YmdHis', $time).'Z';
1281 break;
1282 case 'posix':
1283 $result = $time ; //Already in correct format
1284 break;
1285 default:
e8b9d76a 1286 print_error('auth_ldap_usertypeundefined2', 'auth');
b9ddb2d5 1287 }
1288 return $result;
1289
1290 }
1291
1292 /*
1293 * checks if user belong to specific group(s)
1294 *
1295 * Returns true if user belongs group in grupdns string.
1296 *
1297 * @param mixed $username username
1298 * @param mixed $groupdns string of group dn separated by ;
1299 *
1300 */
1301 function ldap_isgroupmember($username='', $groupdns='') {
1302 // Takes username and groupdn(s) , separated by ;
1303 // Returns true if user is member of any given groups
1304
1305 global $CFG ;
1306 $result = false;
1307 $ldapconnection = $this->ldap_connect();
1308
1309 if (empty($username) or empty($groupdns)) {
1310 return $result;
1311 }
1312
1313 if ($this->config->memberattribute_isdn) {
1314 $username=$this->ldap_find_userdn($ldapconnection, $username);
1315 }
1316 if (! $username ) {
1317 return $result;
1318 }
1319
1320 $groups = explode(";",$groupdns);
1321
1322 foreach ($groups as $group) {
1323 $group = trim($group);
1324 if (empty($group)) {
1325 continue;
1326 }
1327 //echo "Checking group $group for member $username\n";
1328 $search = @ldap_read($ldapconnection, $group, '('.$this->config->memberattribute.'='.$username.')', array($this->config->memberattribute));
1329
1330 if (!empty($search) and ldap_count_entries($ldapconnection, $search)) {$info = $this->ldap_get_entries($ldapconnection, $search);
1331
1332 if (count($info) > 0 ) {
1333 // user is member of group
1334 $result = true;
1335 break;
1336 }
1337 }
1338 }
1339
1340 return $result;
1341
1342 }
1343
1344 /**
1345 * connects to ldap server
1346 *
1347 * Tries connect to specified ldap servers.
1348 * Returns connection result or error.
1349 *
1350 * @return connection result
1351 */
1352 function ldap_connect($binddn='',$bindpwd='') {
1353 /// connects and binds to ldap-server
1354 /// Returns connection result
1355
1356 global $CFG;
1357 $this->ldap_init();
1358
1359 //Select bind password, With empty values use
1360 //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty
1361 if ($binddn == '' and $bindpwd == '') {
1362 if (!empty($this->config->bind_dn)) {
1363 $binddn = $this->config->bind_dn;
1364 }
1365 if (!empty($this->config->bind_pw)) {
1366 $bindpwd = $this->config->bind_pw;
1367 }
1368 }
1369
1370 $urls = explode(";",$this->config->host_url);
1371
1372 foreach ($urls as $server) {
1373 $server = trim($server);
1374 if (empty($server)) {
1375 continue;
1376 }
1377
1378 $connresult = ldap_connect($server);
1379 //ldap_connect returns ALWAYS true
1380
1381 if (!empty($this->config->version)) {
1382 ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->version);
1383 }
1384
1385 if (!empty($binddn)) {
1386 //bind with search-user
1387 //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd;
1388 $bindresult=ldap_bind($connresult, $binddn,$bindpwd);
1389 }
1390 else {
1391 //bind anonymously
1392 $bindresult=@ldap_bind($connresult);
1393 }
1394
1395 if (!empty($this->config->opt_deref)) {
1396 ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref);
1397 }
1398
1399 if ($bindresult) {
1400 return $connresult;
1401 }
1402
1403 $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>";
1404 }
1405
1406 //If any of servers are alive we have already returned connection
e8b9d76a 1407 print_error('auth_ldap_noconnect_all','auth',$this->config->user_type);
b9ddb2d5 1408 return false;
1409 }
1410
1411 /**
1412 * retuns dn of username
1413 *
1414 * Search specified contexts for username and return user dn
1415 * like: cn=username,ou=suborg,o=org
1416 *
1417 * @param mixed $ldapconnection $ldapconnection result
1418 * @param mixed $username username
1419 *
1420 */
1421
1422 function ldap_find_userdn ($ldapconnection, $username) {
1423
1424 global $CFG;
1425
1426 //default return value
1427 $ldap_user_dn = FALSE;
1428
1429 //get all contexts and look for first matching user
1430 $ldap_contexts = explode(";",$this->config->contexts);
1431
1432 if (!empty($this->config->create_context)) {
1433 array_push($ldap_contexts, $this->config->create_context);
1434 }
1435
1436 foreach ($ldap_contexts as $context) {
1437
1438 $context = trim($context);
1439 if (empty($context)) {
1440 continue;
1441 }
1442
1443 if ($this->config->search_sub) {
1444 //use ldap_search to find first user from subtree
1445 $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$username.")",array($this->config->user_attribute));
1446
1447 }
1448 else {
1449 //search only in this context
1450 $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$username.")",array($this->config->user_attribute));
1451 }
1452
1453 $entry = ldap_first_entry($ldapconnection,$ldap_result);
1454
1455 if ($entry) {
1456 $ldap_user_dn = ldap_get_dn($ldapconnection, $entry);
1457 break ;
1458 }
1459 }
1460
1461 return $ldap_user_dn;
1462 }
1463
1464 /**
1465 * retuns user attribute mappings between moodle and ldap
1466 *
1467 * @return array
1468 */
1469
1470 function ldap_attributes () {
1471 $fields = array("firstname", "lastname", "email", "phone1", "phone2",
1472 "department", "address", "city", "country", "description",
1473 "idnumber", "lang" );
1474 $moodleattributes = array();
1475 foreach ($fields as $field) {
1476 if (!empty($this->config->{"field_map_$field"})) {
1477 $moodleattributes[$field] = $this->config->{"field_map_$field"};
1478 if (preg_match('/,/',$moodleattributes[$field])) {
1479 $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ?
1480 }
1481 }
1482 }
1483 $moodleattributes['username'] = $this->config->user_attribute;
1484 return $moodleattributes;
1485 }
1486
1487 /**
1488 * return all usernames from ldap
1489 *
1490 * @return array
1491 */
1492
1493 function ldap_get_userlist($filter="*") {
1494 /// returns all users from ldap servers
1495 global $CFG;
1496
1497 $fresult = array();
1498
1499 $ldapconnection = $this->ldap_connect();
1500
1501 if ($filter=="*") {
1502 $filter = "(&(".$this->config->user_attribute."=*)(".$this->config->objectclass."))";
1503 }
1504
1505 $contexts = explode(";",$this->config->contexts);
1506
1507 if (!empty($this->config->create_context)) {
1508 array_push($contexts, $this->config->create_context);
1509 }
1510
1511 foreach ($contexts as $context) {
1512
1513 $context = trim($context);
1514 if (empty($context)) {
1515 continue;
1516 }
1517
1518 if ($this->config->search_sub) {
1519 //use ldap_search to find first user from subtree
1520 $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute));
1521 }
1522 else {
1523 //search only in this context
1524 $ldap_result = ldap_list($ldapconnection, $context,
1525 $filter,
1526 array($this->config->user_attribute));
1527 }
1528
1529 $users = $this->ldap_get_entries($ldapconnection, $ldap_result);
1530
1531 //add found users to list
1532 for ($i=0;$i<count($users);$i++) {
1533 array_push($fresult, ($users[$i][$this->config->user_attribute][0]) );
1534 }
1535 }
1536
1537 return $fresult;
1538 }
1539
1540 /**
1541 * return entries from ldap
1542 *
1543 * Returns values like ldap_get_entries but is
1544 * binary compatible and return all attributes as array
1545 *
1546 * @return array ldap-entries
1547 */
1548
1549 function ldap_get_entries($conn, $searchresult) {
1550 //Returns values like ldap_get_entries but is
1551 //binary compatible
1552 $i=0;
1553 $fresult=array();
1554 $entry = ldap_first_entry($conn, $searchresult);
1555 do {
1556 $attributes = @ldap_get_attributes($conn, $entry);
1557 for ($j=0; $j<$attributes['count']; $j++) {
1558 $values = ldap_get_values_len($conn, $entry,$attributes[$j]);
1559 if (is_array($values)) {
1560 $fresult[$i][$attributes[$j]] = $values;
1561 }
1562 else {
1563 $fresult[$i][$attributes[$j]] = array($values);
1564 }
1565 }
1566 $i++;
1567 }
1568 while ($entry = @ldap_next_entry($conn, $entry));
1569 //were done
1570 return ($fresult);
1571 }
1572
1573 /**
1574 * Returns true if this authentication plugin is 'internal'.
1575 *
1576 * @returns bool
1577 */
1578 function is_internal() {
1579 return false;
1580 }
1581
1582 /**
1583 * Returns true if this authentication plugin can change the user's
1584 * password.
1585 *
1586 * @returns bool
1587 */
1588 function can_change_password() {
1589 return true;
1590 }
1591
1592 /**
1593 * Returns the URL for changing the user's pw, or false if the default can
1594 * be used.
1595 *
1596 * @returns bool
1597 */
1598 function change_password_url() {
1599 return $CFG->changepasswordurl; // TODO: will this be global?
1600 }
1601
1602 /**
1603 * Prints a form for configuring this authentication plugin.
1604 *
1605 * This function is called from admin/auth.php, and outputs a full page with
1606 * a form for configuring this plugin.
1607 *
1608 * @param array $page An object containing all the data for this page.
1609 */
1610 function config_form($config, $err) {
1611 include "config.html";
1612 }
1613
1614 /**
1615 * Processes and stores configuration data for this authentication plugin.
1616 */
1617 function process_config($config) {
1618 // set to defaults if undefined
1619 if (!isset($config->host_url))
1620 { $config->host_url = ''; }
1621 if (!isset($config->contexts))
1622 { $config->contexts = ''; }
1623 if (!isset($config->user_type))
1624 { $config->user_type = ''; }
1625 if (!isset($config->user_attribute))
1626 { $config->user_attribute = ''; }
1627 if (!isset($config->search_sub))
1628 { $config->search_sub = ''; }
1629 if (!isset($config->opt_deref))
1630 { $config->opt_deref = ''; }
1631 if (!isset($config->preventpassindb))
1632 { $config->preventpassindb = 0; }
1633 if (!isset($config->bind_dn))
1634 {$config->bind_dn = ''; }
1635 if (!isset($config->bind_pw))
1636 {$config->bind_pw = ''; }
1637 if (!isset($config->version))
1638 {$config->version = '2'; }
1639 if (!isset($config->objectclass))
1640 {$config->objectclass = ''; }
1641 if (!isset($config->memberattribute))
1642 {$config->memberattribute = ''; }
1643 if (!isset($config->creators))
1644 {$config->creators = ''; }
1645 if (!isset($config->create_context))
1646 {$config->create_context = ''; }
1647 if (!isset($config->expiration))
1648 {$config->expiration = ''; }
1649 if (!isset($config->expiration_warning))
1650 {$config->expiration_warning = '10'; }
1651 if (!isset($config->expireattr))
1652 {$config->expireattr = ''; }
1653 if (!isset($config->gracelogins))
1654 {$config->gracelogins = ''; }
1655 if (!isset($config->graceattr))
1656 {$config->graceattr = ''; }
1657 if (!isset($config->auth_user_create))
1658 {$config->auth_user_create = ''; }
1659 if (!isset($config->forcechangepassword))
1660 {$config->forcechangepassword = false; }
1661 if (!isset($config->stdchangepassword))
1662 {$config->stdchangepassword = false; }
1663 if (!isset($config->changepasswordurl))
1664 {$config->changepasswordurl = ''; }
1665
1666 // save settings
1667 set_config('host_url', $config->host_url, 'auth/ldap');
1668 set_config('contexts', $config->contexts, 'auth/ldap');
1669 set_config('user_type', $config->user_type, 'auth/ldap');
1670 set_config('user_attribute', $config->user_attribute, 'auth/ldap');
1671 set_config('search_sub', $config->search_sub, 'auth/ldap');
1672 set_config('opt_deref', $config->opt_deref, 'auth/ldap');
1673 set_config('preventpassindb', $config->preventpassindb, 'auth/ldap');
1674 set_config('bind_dn', $config->bind_dn, 'auth/ldap');
1675 set_config('bind_pw', $config->bind_pw, 'auth/ldap');
1676 set_config('version', $config->version, 'auth/ldap');
1677 set_config('objectclass', $config->objectclass, 'auth/ldap');
1678 set_config('memberattribute', $config->memberattribute, 'auth/ldap');
1679 set_config('creators', $config->creators, 'auth/ldap');
1680 set_config('create_context', $config->create_context, 'auth/ldap');
1681 set_config('expiration', $config->expiration, 'auth/ldap');
1682 set_config('expiration_warning', $config->expiration_warning, 'auth/ldap');
1683 set_config('expireattr', $config->expireattr, 'auth/ldap');
1684 set_config('gracelogins', $config->gracelogins, 'auth/ldap');
1685 set_config('graceattr', $config->graceattr, 'auth/ldap');
1686 set_config('auth_user_create', $config->auth_user_create, 'auth/ldap');
1687 set_config('forcechangepassword', $config->forcechangepassword, 'auth/ldap');
1688 set_config('stdchangepassword', $config->stdchangepassword, 'auth/ldap');
1689 set_config('changepasswordurl', $config->changepasswordurl, 'auth/ldap');
1690
1691 return true;
1692 }
1693
1694}
1695
1696?>