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) { |
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 | ?> |