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