Commit | Line | Data |
---|---|---|
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 | ||
139ebfdb | 15 | if (!defined('MOODLE_INTERNAL')) { |
16 | die('Direct access to this script is forbidden.'); /// It must be included from a Moodle page | |
b9ddb2d5 | 17 | } |
18 | ||
81fb221d | 19 | // See http://support.microsoft.com/kb/305144 to interprete these values. |
20 | if (!defined('AUTH_AD_ACCOUNTDISABLE')) { | |
21 | define('AUTH_AD_ACCOUNTDISABLE', 0x0002); | |
22 | } | |
23 | if (!defined('AUTH_AD_NORMAL_ACCOUNT')) { | |
24 | define('AUTH_AD_NORMAL_ACCOUNT', 0x0200); | |
25 | } | |
355bd271 | 26 | if (!defined('AUTH_NTLMTIMEOUT')) { // timewindow for the NTLM SSO process, in secs... |
27 | define('AUTH_NTLMTIMEOUT', 10); | |
28 | } | |
29 | ||
81fb221d | 30 | |
6bc1e5d5 | 31 | require_once($CFG->libdir.'/authlib.php'); |
32 | ||
b9ddb2d5 | 33 | /** |
34 | * LDAP authentication plugin. | |
35 | */ | |
6bc1e5d5 | 36 | class auth_plugin_ldap extends auth_plugin_base { |
b9ddb2d5 | 37 | |
38 | /** | |
139ebfdb | 39 | * Constructor with initialisation. |
b9ddb2d5 | 40 | */ |
41 | function auth_plugin_ldap() { | |
6bc1e5d5 | 42 | $this->authtype = 'ldap'; |
b9ddb2d5 | 43 | $this->config = get_config('auth/ldap'); |
139ebfdb | 44 | if (empty($this->config->ldapencoding)) { |
45 | $this->config->ldapencoding = 'utf-8'; | |
46 | } | |
47 | if (empty($this->config->user_type)) { | |
48 | $this->config->user_type = 'default'; | |
49 | } | |
50 | ||
51 | $default = $this->ldap_getdefaults(); | |
52 | ||
53 | //use defaults if values not given | |
54 | foreach ($default as $key => $value) { | |
55 | // watch out - 0, false are correct values too | |
56 | if (!isset($this->config->{$key}) or $this->config->{$key} == '') { | |
57 | $this->config->{$key} = $value[$this->config->user_type]; | |
58 | } | |
59 | } | |
cfcb7a17 | 60 | |
61 | // Hack prefix to objectclass | |
62 | if (empty($this->config->objectclass)) { | |
63 | // Can't send empty filter | |
64 | $this->config->objectclass='(objectClass=*)'; | |
65 | } else if (stripos($this->config->objectclass, 'objectClass=') === 0) { | |
66 | // Value is 'objectClass=some-string-here', so just add () | |
67 | // around the value (filter _must_ have them). | |
68 | $this->config->objectclass = '('.$this->config->objectclass.')'; | |
69 | } else if (stripos($this->config->objectclass, '(') !== 0) { | |
70 | // Value is 'some-string-not-starting-with-left-parentheses', | |
71 | // which is assumed to be the objectClass matching value. | |
72 | // So build a valid filter with it. | |
73 | $this->config->objectclass = '(objectClass='.$this->config->objectclass.')'; | |
74 | } else { | |
75 | // There is an additional possible value | |
76 | // '(some-string-here)', that can be used to specify any | |
77 | // valid filter string, to select subsets of users based | |
78 | // on any criteria. For example, we could select the users | |
79 | // whose objectClass is 'user' and have the | |
80 | // 'enabledMoodleUser' attribute, with something like: | |
81 | // | |
82 | // (&(objectClass=user)(enabledMoodleUser=1)) | |
83 | // | |
84 | // This is only used in the functions that deal with the | |
85 | // whole potential set of users (currently sync_users() | |
86 | // and get_user_list() only). | |
87 | // | |
88 | // In this particular case we don't need to do anything, | |
89 | // so leave $this->config->objectclass as is. | |
139ebfdb | 90 | } |
430759a5 | 91 | |
b9ddb2d5 | 92 | } |
93 | ||
94 | /** | |
95 | * Returns true if the username and password work and false if they are | |
96 | * wrong or don't exist. | |
97 | * | |
139ebfdb | 98 | * @param string $username The username (with system magic quotes) |
99 | * @param string $password The password (with system magic quotes) | |
100 | * | |
101 | * @return bool Authentication success or failure. | |
b9ddb2d5 | 102 | */ |
103 | function user_login($username, $password) { | |
b7b50143 | 104 | if (! function_exists('ldap_bind')) { |
2b06294b | 105 | print_error('auth_ldapnotinstalled','auth_ldap'); |
b7b50143 | 106 | return false; |
107 | } | |
b9ddb2d5 | 108 | |
b9ddb2d5 | 109 | if (!$username or !$password) { // Don't allow blank usernames or passwords |
110 | return false; | |
111 | } | |
139ebfdb | 112 | |
6221a321 | 113 | $textlib = textlib_get_instance(); |
576c063b | 114 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
115 | $extpassword = $textlib->convert($password, 'utf-8', $this->config->ldapencoding); | |
6221a321 | 116 | |
decd8016 | 117 | // |
118 | // Before we connect to LDAP, check if this is an AD SSO login | |
355bd271 | 119 | // if we succeed in this block, we'll return success early. |
decd8016 | 120 | // |
83cd2dce | 121 | $key = sesskey(); |
122 | if (!empty($this->config->ntlmsso_enabled) && $key === $password) { | |
0cbcc8ef | 123 | $cf = get_cache_flags('auth/ldap/ntlmsess'); |
124 | // We only get the cache flag if we retrieve it before | |
125 | // it expires (AUTH_NTLMTIMEOUT seconds). | |
126 | if (!isset($cf[$key]) || $cf[$key] === '') { | |
127 | return false; | |
128 | } | |
129 | ||
130 | $sessusername = $cf[$key]; | |
131 | if ($username === $sessusername) { | |
132 | unset($sessusername); | |
133 | unset($cf); | |
134 | ||
135 | // Check that the user is inside one of the configured LDAP contexts | |
136 | $validuser = false; | |
137 | $ldapconnection = $this->ldap_connect(); | |
138 | if ($ldapconnection) { | |
139 | // if the user is not inside the configured contexts, | |
140 | // ldap_find_userdn returns false. | |
141 | if ($this->ldap_find_userdn($ldapconnection, $extusername)) { | |
142 | $validuser = true; | |
decd8016 | 143 | } |
eee34307 | 144 | $this->ldap_close(); |
decd8016 | 145 | } |
0cbcc8ef | 146 | |
147 | // Shortcut here - SSO confirmed | |
148 | return $validuser; | |
decd8016 | 149 | } |
355bd271 | 150 | } // End SSO processing |
83cd2dce | 151 | unset($key); |
decd8016 | 152 | |
b9ddb2d5 | 153 | $ldapconnection = $this->ldap_connect(); |
b9ddb2d5 | 154 | if ($ldapconnection) { |
139ebfdb | 155 | $ldap_user_dn = $this->ldap_find_userdn($ldapconnection, $extusername); |
156 | ||
b9ddb2d5 | 157 | //if ldap_user_dn is empty, user does not exist |
158 | if (!$ldap_user_dn) { | |
eee34307 | 159 | $this->ldap_close(); |
b9ddb2d5 | 160 | return false; |
161 | } | |
162 | ||
163 | // Try to bind with current username and password | |
139ebfdb | 164 | $ldap_login = @ldap_bind($ldapconnection, $ldap_user_dn, $extpassword); |
eee34307 | 165 | $this->ldap_close(); |
b9ddb2d5 | 166 | if ($ldap_login) { |
167 | return true; | |
168 | } | |
169 | } | |
170 | else { | |
eee34307 | 171 | $this->ldap_close(); |
2b06294b | 172 | print_error('auth_ldap_noconnect','auth_ldap','',$this->config->host_url); |
b9ddb2d5 | 173 | } |
174 | return false; | |
175 | } | |
176 | ||
177 | /** | |
178 | * reads userinformation from ldap and return it in array() | |
179 | * | |
180 | * Read user information from external database and returns it as array(). | |
181 | * Function should return all information available. If you are saving | |
182 | * this information to moodle user-table you should honor syncronization flags | |
183 | * | |
be544ec3 | 184 | * @param string $username username |
139ebfdb | 185 | * |
186 | * @return mixed array with no magic quotes or false on error | |
b9ddb2d5 | 187 | */ |
188 | function get_userinfo($username) { | |
139ebfdb | 189 | $textlib = textlib_get_instance(); |
be544ec3 | 190 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
139ebfdb | 191 | |
b9ddb2d5 | 192 | $ldapconnection = $this->ldap_connect(); |
b9ddb2d5 | 193 | $attrmap = $this->ldap_attributes(); |
139ebfdb | 194 | |
b9ddb2d5 | 195 | $result = array(); |
196 | $search_attribs = array(); | |
139ebfdb | 197 | |
b9ddb2d5 | 198 | foreach ($attrmap as $key=>$values) { |
199 | if (!is_array($values)) { | |
200 | $values = array($values); | |
201 | } | |
202 | foreach ($values as $value) { | |
203 | if (!in_array($value, $search_attribs)) { | |
204 | array_push($search_attribs, $value); | |
139ebfdb | 205 | } |
b9ddb2d5 | 206 | } |
207 | } | |
208 | ||
139ebfdb | 209 | $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername); |
b9ddb2d5 | 210 | |
139ebfdb | 211 | if (!$user_info_result = ldap_read($ldapconnection, $user_dn, $this->config->objectclass, $search_attribs)) { |
212 | return false; // error! | |
213 | } | |
214 | $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result); | |
215 | if (empty($user_entry)) { | |
216 | return false; // entry not found | |
217 | } | |
218 | ||
219 | foreach ($attrmap as $key=>$values) { | |
220 | if (!is_array($values)) { | |
221 | $values = array($values); | |
222 | } | |
223 | $ldapval = NULL; | |
224 | foreach ($values as $value) { | |
a8d58c58 | 225 | if ($value == 'dn') { |
226 | $result[$key] = $user_dn; | |
227 | } | |
139ebfdb | 228 | if (!array_key_exists($value, $user_entry[0])) { |
229 | continue; // wrong data mapping! | |
b9ddb2d5 | 230 | } |
139ebfdb | 231 | if (is_array($user_entry[0][$value])) { |
232 | $newval = $textlib->convert($user_entry[0][$value][0], $this->config->ldapencoding, 'utf-8'); | |
233 | } else { | |
234 | $newval = $textlib->convert($user_entry[0][$value], $this->config->ldapencoding, 'utf-8'); | |
b9ddb2d5 | 235 | } |
139ebfdb | 236 | if (!empty($newval)) { // favour ldap entries that are set |
237 | $ldapval = $newval; | |
b9ddb2d5 | 238 | } |
239 | } | |
139ebfdb | 240 | if (!is_null($ldapval)) { |
241 | $result[$key] = $ldapval; | |
242 | } | |
b9ddb2d5 | 243 | } |
244 | ||
eee34307 | 245 | $this->ldap_close(); |
b9ddb2d5 | 246 | return $result; |
247 | } | |
248 | ||
249 | /** | |
250 | * reads userinformation from ldap and return it in an object | |
251 | * | |
139ebfdb | 252 | * @param string $username username (with system magic quotes) |
253 | * @return mixed object or false on error | |
b9ddb2d5 | 254 | */ |
255 | function get_userinfo_asobj($username) { | |
139ebfdb | 256 | $user_array = $this->get_userinfo($username); |
257 | if ($user_array == false) { | |
258 | return false; //error or not found | |
259 | } | |
260 | $user_array = truncate_userinfo($user_array); | |
261 | $user = new object(); | |
b9ddb2d5 | 262 | foreach ($user_array as $key=>$value) { |
263 | $user->{$key} = $value; | |
264 | } | |
265 | return $user; | |
266 | } | |
267 | ||
268 | /** | |
269 | * returns all usernames from external database | |
270 | * | |
271 | * get_userlist returns all usernames from external database | |
272 | * | |
139ebfdb | 273 | * @return array |
b9ddb2d5 | 274 | */ |
275 | function get_userlist() { | |
b9ddb2d5 | 276 | return $this->ldap_get_userlist("({$this->config->user_attribute}=*)"); |
277 | } | |
278 | ||
279 | /** | |
280 | * checks if user exists on external db | |
139ebfdb | 281 | * |
df884cd8 | 282 | * @param string $username |
b9ddb2d5 | 283 | */ |
284 | function user_exists($username) { | |
139ebfdb | 285 | |
286 | $textlib = textlib_get_instance(); | |
df884cd8 | 287 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
139ebfdb | 288 | |
289 | //returns true if given username exist on ldap | |
290 | $users = $this->ldap_get_userlist("({$this->config->user_attribute}=".$this->filter_addslashes($extusername).")"); | |
291 | return count($users); | |
b9ddb2d5 | 292 | } |
293 | ||
294 | /** | |
139ebfdb | 295 | * Creates a new user on external database. |
b9ddb2d5 | 296 | * By using information in userobject |
297 | * Use user_exists to prevent dublicate usernames | |
298 | * | |
df884cd8 | 299 | * @param mixed $userobject Moodle userobject |
300 | * @param mixed $plainpass Plaintext password | |
b9ddb2d5 | 301 | */ |
302 | function user_create($userobject, $plainpass) { | |
139ebfdb | 303 | $textlib = textlib_get_instance(); |
df884cd8 | 304 | $extusername = $textlib->convert($userobject->username, 'utf-8', $this->config->ldapencoding); |
305 | $extpassword = $textlib->convert($plainpass, 'utf-8', $this->config->ldapencoding); | |
139ebfdb | 306 | |
344514fc | 307 | switch ($this->config->passtype) { |
308 | case 'md5': | |
309 | $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword))); | |
310 | break; | |
311 | case 'sha1': | |
312 | $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword))); | |
313 | break; | |
314 | case 'plaintext': | |
315 | default: | |
316 | break; // plaintext | |
317 | } | |
318 | ||
b9ddb2d5 | 319 | $ldapconnection = $this->ldap_connect(); |
320 | $attrmap = $this->ldap_attributes(); | |
139ebfdb | 321 | |
b9ddb2d5 | 322 | $newuser = array(); |
139ebfdb | 323 | |
b9ddb2d5 | 324 | foreach ($attrmap as $key => $values) { |
325 | if (!is_array($values)) { | |
326 | $values = array($values); | |
327 | } | |
328 | foreach ($values as $value) { | |
329 | if (!empty($userobject->$key) ) { | |
df884cd8 | 330 | $newuser[$value] = $textlib->convert($userobject->$key, 'utf-8', $this->config->ldapencoding); |
b9ddb2d5 | 331 | } |
332 | } | |
333 | } | |
139ebfdb | 334 | |
b9ddb2d5 | 335 | //Following sets all mandatory and other forced attribute values |
336 | //User should be creted as login disabled untill email confirmation is processed | |
139ebfdb | 337 | //Feel free to add your user type and send patches to paca@sci.fi to add them |
b9ddb2d5 | 338 | //Moodle distribution |
339 | ||
340 | switch ($this->config->user_type) { | |
341 | case 'edir': | |
139ebfdb | 342 | $newuser['objectClass'] = array("inetOrgPerson","organizationalPerson","person","top"); |
343 | $newuser['uniqueId'] = $extusername; | |
344 | $newuser['logindisabled'] = "TRUE"; | |
345 | $newuser['userpassword'] = $extpassword; | |
7a4025d0 | 346 | $uadd = ldap_add($ldapconnection, $this->config->user_attribute.'="'.$this->ldap_addslashes($userobject->username).','.$this->config->create_context.'"', $newuser); |
81fb221d | 347 | break; |
348 | case 'ad': | |
349 | // User account creation is a two step process with AD. First you | |
350 | // create the user object, then you set the password. If you try | |
351 | // to set the password while creating the user, the operation | |
352 | // fails. | |
e295df44 | 353 | |
81fb221d | 354 | // Passwords in Active Directory must be encoded as Unicode |
355 | // strings (UCS-2 Little Endian format) and surrounded with | |
356 | // double quotes. See http://support.microsoft.com/?kbid=269190 | |
357 | if (!function_exists('mb_convert_encoding')) { | |
2b06294b | 358 | print_error('auth_ldap_no_mbstring', 'auth_ldap'); |
81fb221d | 359 | } |
e295df44 | 360 | |
81fb221d | 361 | // First create the user account, and mark it as disabled. |
362 | $newuser['objectClass'] = array('top','person','user','organizationalPerson'); | |
363 | $newuser['sAMAccountName'] = $extusername; | |
e295df44 | 364 | $newuser['userAccountControl'] = AUTH_AD_NORMAL_ACCOUNT | |
81fb221d | 365 | AUTH_AD_ACCOUNTDISABLE; |
366 | $userdn = 'cn=' . $this->ldap_addslashes($extusername) . | |
367 | ',' . $this->config->create_context; | |
368 | if (!ldap_add($ldapconnection, $userdn, $newuser)) { | |
2b06294b | 369 | print_error('auth_ldap_ad_create_req', 'auth_ldap'); |
81fb221d | 370 | } |
e295df44 | 371 | |
81fb221d | 372 | // Now set the password |
373 | unset($newuser); | |
374 | $newuser['unicodePwd'] = mb_convert_encoding('"' . $extpassword . '"', | |
375 | "UCS-2LE", "UTF-8"); | |
376 | if(!ldap_modify($ldapconnection, $userdn, $newuser)) { | |
377 | // Something went wrong: delete the user account and error out | |
378 | ldap_delete ($ldapconnection, $userdn); | |
2b06294b | 379 | print_error('auth_ldap_ad_create_req', 'auth_ldap'); |
81fb221d | 380 | } |
381 | $uadd = true; | |
b9ddb2d5 | 382 | break; |
383 | default: | |
2b06294b | 384 | print_error('auth_ldap_unsupportedusertype','auth_ldap','',$this->config->user_type); |
b9ddb2d5 | 385 | } |
eee34307 | 386 | $this->ldap_close(); |
b9ddb2d5 | 387 | return $uadd; |
b9ddb2d5 | 388 | |
b9ddb2d5 | 389 | } |
390 | ||
4225d4ba | 391 | function can_reset_password() { |
392 | return !empty($this->config->stdchangepassword); | |
393 | } | |
394 | ||
4db13f94 | 395 | function can_signup() { |
396 | return (!empty($this->config->auth_user_create) and !empty($this->config->create_context)); | |
397 | } | |
398 | ||
399 | /** | |
400 | * Sign up a new user ready for confirmation. | |
401 | * Password is passed in plaintext. | |
402 | * | |
5d910388 | 403 | * @param object $user new user object |
4db13f94 | 404 | * @param boolean $notify print notice with link and terminate |
405 | */ | |
406 | function user_signup($user, $notify=true) { | |
1aa66713 | 407 | global $CFG, $DB, $OUTPUT, $PAGE; |
f679d730 | 408 | |
831d450e | 409 | require_once($CFG->dirroot.'/user/profile/lib.php'); |
f679d730 | 410 | |
4db13f94 | 411 | if ($this->user_exists($user->username)) { |
2b06294b | 412 | print_error('auth_ldap_user_exists', 'auth_ldap'); |
4db13f94 | 413 | } |
414 | ||
415 | $plainslashedpassword = $user->password; | |
416 | unset($user->password); | |
417 | ||
418 | if (! $this->user_create($user, $plainslashedpassword)) { | |
2b06294b | 419 | print_error('auth_ldap_create_error', 'auth_ldap'); |
4db13f94 | 420 | } |
421 | ||
5d910388 | 422 | if (! ($user->id = $DB->insert_record('user', $user)) ) { |
2b06294b | 423 | print_error('auth_emailnoinsert', 'auth_email'); |
4db13f94 | 424 | } |
425 | ||
831d450e | 426 | /// Save any custom profile field information |
427 | profile_save_data($user); | |
428 | ||
4db13f94 | 429 | $this->update_user_record($user->username); |
430 | update_internal_user_password($user, $plainslashedpassword); | |
431 | ||
f679d730 | 432 | $user = $DB->get_record('user', array('id'=>$user->id)); |
2942a5cd | 433 | events_trigger('user_created', $user); |
434 | ||
4db13f94 | 435 | if (! send_confirmation_email($user)) { |
2b06294b | 436 | print_error('auth_emailnoemail', 'auth_email'); |
4db13f94 | 437 | } |
438 | ||
439 | if ($notify) { | |
440 | global $CFG; | |
441 | $emailconfirm = get_string('emailconfirm'); | |
cfc5b79b | 442 | $PAGE->navbar->add($emailconfirm); |
443 | $PAGE->set_title($emailconfirm); | |
444 | $PAGE->set_heading($emailconfirm); | |
445 | echo $OUTPUT->header(); | |
4db13f94 | 446 | notice(get_string('emailconfirmsent', '', $user->email), "$CFG->wwwroot/index.php"); |
447 | } else { | |
448 | return true; | |
449 | } | |
450 | } | |
451 | ||
452 | /** | |
453 | * Returns true if plugin allows confirming of new users. | |
454 | * | |
455 | * @return bool | |
456 | */ | |
457 | function can_confirm() { | |
458 | return $this->can_signup(); | |
459 | } | |
460 | ||
461 | /** | |
462 | * Confirm the new user as registered. | |
463 | * | |
b9a66360 | 464 | * @param string $username |
465 | * @param string $confirmsecret | |
4db13f94 | 466 | */ |
467 | function user_confirm($username, $confirmsecret) { | |
df884cd8 | 468 | global $DB; |
469 | ||
4db13f94 | 470 | $user = get_complete_user_data('username', $username); |
471 | ||
472 | if (!empty($user)) { | |
473 | if ($user->confirmed) { | |
474 | return AUTH_CONFIRM_ALREADY; | |
475 | ||
476 | } else if ($user->auth != 'ldap') { | |
477 | return AUTH_CONFIRM_ERROR; | |
478 | ||
b9a66360 | 479 | } else if ($user->secret == $confirmsecret) { // They have provided the secret key to get in |
4db13f94 | 480 | if (!$this->user_activate($username)) { |
481 | return AUTH_CONFIRM_FAIL; | |
482 | } | |
df884cd8 | 483 | if (!$DB->set_field("user", "confirmed", 1, array("id"=>$user->id))) { |
4db13f94 | 484 | return AUTH_CONFIRM_FAIL; |
485 | } | |
df884cd8 | 486 | if (!$DB->set_field("user", "firstaccess", time(), array("id"=>$user->id))) { |
4db13f94 | 487 | return AUTH_CONFIRM_FAIL; |
488 | } | |
489 | return AUTH_CONFIRM_OK; | |
490 | } | |
491 | } else { | |
492 | return AUTH_CONFIRM_ERROR; | |
493 | } | |
494 | } | |
495 | ||
b9ddb2d5 | 496 | /** |
497 | * return number of days to user password expires | |
498 | * | |
499 | * If userpassword does not expire it should return 0. If password is already expired | |
500 | * it should return negative value. | |
501 | * | |
df884cd8 | 502 | * @param mixed $username username |
b9ddb2d5 | 503 | * @return integer |
504 | */ | |
505 | function password_expire($username) { | |
2cef74f9 | 506 | $result = 0; |
139ebfdb | 507 | |
508 | $textlib = textlib_get_instance(); | |
df884cd8 | 509 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
139ebfdb | 510 | |
b9ddb2d5 | 511 | $ldapconnection = $this->ldap_connect(); |
139ebfdb | 512 | $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername); |
b9ddb2d5 | 513 | $search_attribs = array($this->config->expireattr); |
cfcb7a17 | 514 | $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs); |
b9ddb2d5 | 515 | if ($sr) { |
139ebfdb | 516 | $info = $this->ldap_get_entries($ldapconnection, $sr); |
2cef74f9 | 517 | if (!empty ($info) and !empty($info[0][$this->config->expireattr][0])) { |
518 | $expiretime = $this->ldap_expirationtime2unix($info[0][$this->config->expireattr][0], $ldapconnection, $user_dn); | |
519 | if ($expiretime != 0) { | |
520 | $now = time(); | |
521 | if ($expiretime > $now) { | |
522 | $result = ceil(($expiretime - $now) / DAYSECS); | |
523 | } | |
524 | else { | |
525 | $result = floor(($expiretime - $now) / DAYSECS); | |
526 | } | |
139ebfdb | 527 | } |
b9ddb2d5 | 528 | } |
139ebfdb | 529 | } else { |
b9ddb2d5 | 530 | error_log("ldap: password_expire did't find expiration time."); |
531 | } | |
532 | ||
533 | //error_log("ldap: password_expire user $user_dn expires in $result days!"); | |
534 | return $result; | |
535 | } | |
536 | ||
537 | /** | |
538 | * syncronizes user fron external db to moodle user table | |
539 | * | |
139ebfdb | 540 | * Sync is now using username attribute. |
541 | * | |
542 | * Syncing users removes or suspends users that dont exists anymore in external db. | |
543 | * Creates new users and updates coursecreator status of users. | |
544 | * | |
139ebfdb | 545 | * @param bool $do_updates will do pull in data updates from ldap if relevant |
b9ddb2d5 | 546 | */ |
df884cd8 | 547 | function sync_users($do_updates=true) { |
548 | global $CFG, $DB; | |
fa96bfaa | 549 | |
139ebfdb | 550 | $textlib = textlib_get_instance(); |
df884cd8 | 551 | $dbman = $DB->get_manager(); |
139ebfdb | 552 | |
df884cd8 | 553 | /// Define table user to be created |
554 | $table = new xmldb_table('tmp_extuser'); | |
2a88f626 | 555 | $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); |
556 | $table->add_field('username', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, null); | |
df884cd8 | 557 | $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); |
558 | $table->add_index('username', XMLDB_INDEX_UNIQUE, array('mnethostid', 'username')); | |
b9ddb2d5 | 559 | |
63b1cf1f | 560 | echo "Creating temp table $temptable\n"; |
df884cd8 | 561 | if (!$dbman->create_temp_table($table)) { |
63b1cf1f | 562 | print "Failed to create temporary users table - aborting\n"; |
563 | exit; | |
564 | } | |
565 | ||
139ebfdb | 566 | print "Connecting to ldap...\n"; |
b9ddb2d5 | 567 | $ldapconnection = $this->ldap_connect(); |
568 | ||
569 | if (!$ldapconnection) { | |
eee34307 | 570 | $this->ldap_close(); |
2b06294b | 571 | print get_string('auth_ldap_noconnect','auth_ldap',$this->config->host_url); |
139ebfdb | 572 | exit; |
b9ddb2d5 | 573 | } |
574 | ||
575 | //// | |
576 | //// get user's list from ldap to sql in a scalable fashion | |
577 | //// | |
578 | // prepare some data we'll need | |
cfcb7a17 | 579 | $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')'; |
b9ddb2d5 | 580 | |
581 | $contexts = explode(";",$this->config->contexts); | |
139ebfdb | 582 | |
b9ddb2d5 | 583 | if (!empty($this->config->create_context)) { |
584 | array_push($contexts, $this->config->create_context); | |
585 | } | |
586 | ||
587 | $fresult = array(); | |
b9ddb2d5 | 588 | foreach ($contexts as $context) { |
589 | $context = trim($context); | |
590 | if (empty($context)) { | |
591 | continue; | |
592 | } | |
b9ddb2d5 | 593 | if ($this->config->search_sub) { |
594 | //use ldap_search to find first user from subtree | |
595 | $ldap_result = ldap_search($ldapconnection, $context, | |
596 | $filter, | |
597 | array($this->config->user_attribute)); | |
139ebfdb | 598 | } else { |
b9ddb2d5 | 599 | //search only in this context |
600 | $ldap_result = ldap_list($ldapconnection, $context, | |
601 | $filter, | |
602 | array($this->config->user_attribute)); | |
603 | } | |
604 | ||
605 | if ($entry = ldap_first_entry($ldapconnection, $ldap_result)) { | |
606 | do { | |
139ebfdb | 607 | $value = ldap_get_values_len($ldapconnection, $entry, $this->config->user_attribute); |
608 | $value = $textlib->convert($value[0], $this->config->ldapencoding, 'utf-8'); | |
df884cd8 | 609 | $this->ldap_bulk_insert($value); |
139ebfdb | 610 | } while ($entry = ldap_next_entry($ldapconnection, $entry)); |
b9ddb2d5 | 611 | } |
139ebfdb | 612 | unset($ldap_result); // free mem |
b9ddb2d5 | 613 | } |
b9ddb2d5 | 614 | |
615 | /// preserve our user database | |
616 | /// if the temp table is empty, it probably means that something went wrong, exit | |
617 | /// so as to avoid mass deletion of users; which is hard to undo | |
df884cd8 | 618 | $count = $DB->count_records_sql('SELECT COUNT(username) AS count, 1 FROM {tmp_extuser}'); |
b9ddb2d5 | 619 | if ($count < 1) { |
620 | print "Did not get any users from LDAP -- error? -- exiting\n"; | |
621 | exit; | |
fa96bfaa | 622 | } else { |
139ebfdb | 623 | print "Got $count records from LDAP\n\n"; |
b9ddb2d5 | 624 | } |
625 | ||
b9ddb2d5 | 626 | |
139ebfdb | 627 | /// User removal |
628 | // find users in DB that aren't in ldap -- to be removed! | |
629 | // this is still not as scalable (but how often do we mass delete?) | |
630 | if (!empty($this->config->removeuser)) { | |
5117d598 | 631 | $sql = "SELECT u.id, u.username, u.email, u.auth |
df884cd8 | 632 | FROM {user} u |
633 | LEFT JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?) | |
634 | WHERE u.auth='ldap' | |
635 | AND u.deleted=0 | |
636 | AND e.username IS NULL"; | |
637 | $remove_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id)); | |
139ebfdb | 638 | |
639 | if (!empty($remove_users)) { | |
640 | print "User entries to remove: ". count($remove_users) . "\n"; | |
641 | ||
139ebfdb | 642 | foreach ($remove_users as $user) { |
6f87ef52 | 643 | if ($this->config->removeuser == AUTH_REMOVEUSER_FULLDELETE) { |
90afcf32 | 644 | if (delete_user($user)) { |
2c10db3b | 645 | echo "\t"; print_string('auth_dbdeleteuser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n"; |
139ebfdb | 646 | } else { |
2b06294b | 647 | echo "\t"; print_string('auth_dbdeleteusererror', 'auth_db', $user->username); echo "\n"; |
139ebfdb | 648 | } |
6f87ef52 | 649 | } else if ($this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { |
139ebfdb | 650 | $updateuser = new object(); |
651 | $updateuser->id = $user->id; | |
652 | $updateuser->auth = 'nologin'; | |
df884cd8 | 653 | if ($DB->update_record('user', $updateuser)) { |
2c10db3b | 654 | echo "\t"; print_string('auth_dbsuspenduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n"; |
139ebfdb | 655 | } else { |
2b06294b | 656 | echo "\t"; print_string('auth_dbsuspendusererror', 'auth_db', $user->username); echo "\n"; |
139ebfdb | 657 | } |
658 | } | |
b9ddb2d5 | 659 | } |
139ebfdb | 660 | } else { |
661 | print "No user entries to be removed\n"; | |
662 | } | |
663 | unset($remove_users); // free mem! | |
664 | } | |
665 | ||
666 | /// Revive suspended users | |
6f87ef52 | 667 | if (!empty($this->config->removeuser) and $this->config->removeuser == AUTH_REMOVEUSER_SUSPEND) { |
139ebfdb | 668 | $sql = "SELECT u.id, u.username |
df884cd8 | 669 | FROM {user} u |
670 | JOIN {tmp_extuser} e ON (u.username = e.username AND u.mnethostid = ?) | |
671 | WHERE u.auth='nologin' AND u.deleted=0"; | |
672 | $revive_users = $DB->get_records_sql($sql, array($CFG->mnet_localhost_id)); | |
139ebfdb | 673 | |
674 | if (!empty($revive_users)) { | |
675 | print "User entries to be revived: ". count($revive_users) . "\n"; | |
676 | ||
139ebfdb | 677 | foreach ($revive_users as $user) { |
678 | $updateuser = new object(); | |
679 | $updateuser->id = $user->id; | |
680 | $updateuser->auth = 'ldap'; | |
df884cd8 | 681 | if ($DB->pdate_record('user', $updateuser)) { |
2c10db3b | 682 | echo "\t"; print_string('auth_dbreviveduser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); echo "\n"; |
139ebfdb | 683 | } else { |
b8fc9582 | 684 | echo "\t"; print_string('auth_dbrevivedusererror', 'auth_db', $user->username); echo "\n"; |
139ebfdb | 685 | } |
b9ddb2d5 | 686 | } |
139ebfdb | 687 | } else { |
688 | print "No user entries to be revived\n"; | |
689 | } | |
690 | ||
691 | unset($revive_users); | |
fa96bfaa | 692 | } |
b9ddb2d5 | 693 | |
139ebfdb | 694 | |
695 | /// User Updates - time-consuming (optional) | |
b9ddb2d5 | 696 | if ($do_updates) { |
697 | // narrow down what fields we need to update | |
698 | $all_keys = array_keys(get_object_vars($this->config)); | |
699 | $updatekeys = array(); | |
700 | foreach ($all_keys as $key) { | |
701 | if (preg_match('/^field_updatelocal_(.+)$/',$key, $match)) { | |
702 | // if we have a field to update it from | |
139ebfdb | 703 | // and it must be updated 'onlogin' we |
b9ddb2d5 | 704 | // update it on cron |
705 | if ( !empty($this->config->{'field_map_'.$match[1]}) | |
139ebfdb | 706 | and $this->config->{$match[0]} === 'onlogin') { |
b9ddb2d5 | 707 | array_push($updatekeys, $match[1]); // the actual key name |
708 | } | |
709 | } | |
710 | } | |
711 | // print_r($all_keys); print_r($updatekeys); | |
712 | unset($all_keys); unset($key); | |
139ebfdb | 713 | |
fa96bfaa | 714 | } else { |
715 | print "No updates to be done\n"; | |
b9ddb2d5 | 716 | } |
139ebfdb | 717 | if ( $do_updates and !empty($updatekeys) ) { // run updates only if relevant |
df884cd8 | 718 | $users = $DB->get_records_sql("SELECT u.username, u.id |
719 | FROM {user} u | |
720 | WHERE u.deleted=0 AND u.auth='ldap' AND u.mnethostid = ?", array($CFG->mnet_localhost_id)); | |
b9ddb2d5 | 721 | if (!empty($users)) { |
722 | print "User entries to update: ". count($users). "\n"; | |
139ebfdb | 723 | |
b9ddb2d5 | 724 | $sitecontext = get_context_instance(CONTEXT_SYSTEM); |
139ebfdb | 725 | if (!empty($this->config->creators) and !empty($this->config->memberattribute) |
4f0c2d00 | 726 | and $roles = get_archetype_roles('coursecreator')) { |
139ebfdb | 727 | $creatorrole = array_shift($roles); // We can only use one, let's use the first one |
728 | } else { | |
729 | $creatorrole = false; | |
730 | } | |
b9ddb2d5 | 731 | |
d5a8d9aa | 732 | $transaction = $DB->start_delegated_transaction(); |
139ebfdb | 733 | $xcount = 0; |
734 | $maxxcount = 100; | |
b9ddb2d5 | 735 | |
139ebfdb | 736 | foreach ($users as $user) { |
2c10db3b | 737 | echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id)); |
185721a4 | 738 | if (!$this->update_user_record($user->username, $updatekeys)) { |
139ebfdb | 739 | echo " - ".get_string('skipped'); |
740 | } | |
741 | echo "\n"; | |
742 | $xcount++; | |
743 | ||
744 | // update course creators if needed | |
745 | if ($creatorrole !== false) { | |
746 | if ($this->iscreator($user->username)) { | |
df997f84 | 747 | role_assign($creatorrole->id, $user->id, $sitecontext->id, 'auth_ldap'); |
139ebfdb | 748 | } else { |
df997f84 | 749 | role_unassign($creatorrole->id, $user->id, $sitecontext->id, 'auth_ldap'); |
b9ddb2d5 | 750 | } |
139ebfdb | 751 | } |
b9ddb2d5 | 752 | } |
d5a8d9aa | 753 | $transaction->allow_commit(); |
139ebfdb | 754 | unset($users); // free mem |
b9ddb2d5 | 755 | } |
fa96bfaa | 756 | } else { // end do updates |
757 | print "No updates to be done\n"; | |
758 | } | |
139ebfdb | 759 | |
760 | /// User Additions | |
b9ddb2d5 | 761 | // find users missing in DB that are in LDAP |
b9ddb2d5 | 762 | // and gives me a nifty object I don't want. |
139ebfdb | 763 | // note: we do not care about deleted accounts anymore, this feature was replaced by suspending to nologin auth plugin |
df884cd8 | 764 | // mnetid not used here in join because we can not add users with the same username |
765 | $sql = "SELECT e.id, e.username | |
766 | FROM {user} u | |
767 | LEFT JOIN {tmp_extuser} e ON u.username = e.username | |
768 | WHERE u.id IS NULL"; | |
769 | $add_users = $DB->get_records_sql($sql); // get rid of the fat | |
139ebfdb | 770 | |
b9ddb2d5 | 771 | if (!empty($add_users)) { |
772 | print "User entries to add: ". count($add_users). "\n"; | |
773 | ||
139ebfdb | 774 | $sitecontext = get_context_instance(CONTEXT_SYSTEM); |
775 | if (!empty($this->config->creators) and !empty($this->config->memberattribute) | |
4f0c2d00 | 776 | and $roles = get_archetype_roles('coursecreator')) { |
b9ddb2d5 | 777 | $creatorrole = array_shift($roles); // We can only use one, let's use the first one |
139ebfdb | 778 | } else { |
779 | $creatorrole = false; | |
b9ddb2d5 | 780 | } |
781 | ||
d5a8d9aa | 782 | $transaction = $DB->start_delegated_transaction(); |
b9ddb2d5 | 783 | foreach ($add_users as $user) { |
df884cd8 | 784 | $user = $this->get_userinfo_asobj($user->username); |
139ebfdb | 785 | |
b9ddb2d5 | 786 | // prep a few params |
b7b50143 | 787 | $user->modified = time(); |
788 | $user->confirmed = 1; | |
139ebfdb | 789 | $user->auth = 'ldap'; |
b7b50143 | 790 | $user->mnethostid = $CFG->mnet_localhost_id; |
997bcd9e | 791 | // get_userinfo_asobj() might have replaced $user->username with the value |
792 | // from the LDAP server (which can be mixed-case). Make sure it's lowercase | |
793 | $user->username = trim(moodle_strtolower($user->username)); | |
139ebfdb | 794 | if (empty($user->lang)) { |
795 | $user->lang = $CFG->lang; | |
b9ddb2d5 | 796 | } |
139ebfdb | 797 | |
df884cd8 | 798 | if ($id = $DB->insert_record('user', $user)) { |
2c10db3b | 799 | echo "\t"; print_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)); echo "\n"; |
139ebfdb | 800 | $userobj = $this->update_user_record($user->username); |
801 | if (!empty($this->config->forcechangepassword)) { | |
802 | set_user_preference('auth_forcepasswordchange', 1, $userobj->id); | |
b9ddb2d5 | 803 | } |
139ebfdb | 804 | } else { |
2b06294b | 805 | echo "\t"; print_string('auth_dbinsertusererror', 'auth_db', $user->username); echo "\n"; |
139ebfdb | 806 | } |
807 | ||
808 | // add course creators if needed | |
df884cd8 | 809 | if ($creatorrole !== false and $this->iscreator($user->username)) { |
df997f84 | 810 | role_assign($creatorrole->id, $user->id, $sitecontext->id, 'auth_ldap'); |
b9ddb2d5 | 811 | } |
812 | } | |
d5a8d9aa | 813 | $transaction->allow_commit(); |
b9ddb2d5 | 814 | unset($add_users); // free mem |
fa96bfaa | 815 | } else { |
816 | print "No users to be added\n"; | |
b9ddb2d5 | 817 | } |
df884cd8 | 818 | |
819 | $dbman->drop_temp_table($table); | |
eee34307 | 820 | $this->ldap_close(); |
df884cd8 | 821 | |
b9ddb2d5 | 822 | return true; |
823 | } | |
824 | ||
139ebfdb | 825 | /** |
826 | * Update a local user record from an external source. | |
827 | * This is a lighter version of the one in moodlelib -- won't do | |
b9ddb2d5 | 828 | * expensive ops such as enrolment. |
829 | * | |
139ebfdb | 830 | * If you don't pass $updatekeys, there is a performance hit and |
831 | * values removed from LDAP won't be removed from moodle. | |
832 | * | |
185721a4 | 833 | * @param string $username username |
b9ddb2d5 | 834 | */ |
835 | function update_user_record($username, $updatekeys = false) { | |
1aa66713 | 836 | global $CFG, $DB; |
b9ddb2d5 | 837 | |
838 | //just in case check text case | |
839 | $username = trim(moodle_strtolower($username)); | |
139ebfdb | 840 | |
b9ddb2d5 | 841 | // get the current user record |
185721a4 | 842 | $user = $DB->get_record('user', array('username'=>$username, 'mnethostid'=>$CFG->mnet_localhost_id)); |
b9ddb2d5 | 843 | if (empty($user)) { // trouble |
185721a4 | 844 | error_log("Cannot update non-existent user: ".$username); |
2b06294b | 845 | print_error('auth_dbusernotexist','auth_db','',$username); |
b9ddb2d5 | 846 | die; |
847 | } | |
848 | ||
b7b50143 | 849 | // Protect the userid from being overwritten |
850 | $userid = $user->id; | |
851 | ||
139ebfdb | 852 | if ($newinfo = $this->get_userinfo($username)) { |
853 | $newinfo = truncate_userinfo($newinfo); | |
854 | ||
855 | if (empty($updatekeys)) { // all keys? this does not support removing values | |
856 | $updatekeys = array_keys($newinfo); | |
857 | } | |
858 | ||
859 | foreach ($updatekeys as $key) { | |
860 | if (isset($newinfo[$key])) { | |
861 | $value = $newinfo[$key]; | |
862 | } else { | |
863 | $value = ''; | |
b9ddb2d5 | 864 | } |
139ebfdb | 865 | |
866 | if (!empty($this->config->{'field_updatelocal_' . $key})) { | |
867 | if ($user->{$key} != $value) { // only update if it's changed | |
185721a4 | 868 | $DB->set_field('user', $key, $value, array('id'=>$userid)); |
b9ddb2d5 | 869 | } |
870 | } | |
871 | } | |
139ebfdb | 872 | } else { |
873 | return false; | |
b9ddb2d5 | 874 | } |
185721a4 | 875 | return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0)); |
b9ddb2d5 | 876 | } |
877 | ||
139ebfdb | 878 | /** |
879 | * Bulk insert in SQL's temp table | |
139ebfdb | 880 | */ |
df884cd8 | 881 | function ldap_bulk_insert($username) { |
882 | global $DB; | |
883 | ||
884 | $username = moodle_strtolower($username); // usernames are __always__ lowercase. | |
885 | $DB->insert_record_raw('tmp_extuser', array('username'=>$username), false, true); | |
886 | print "."; | |
b9ddb2d5 | 887 | } |
888 | ||
889 | ||
139ebfdb | 890 | /** |
b9ddb2d5 | 891 | * Activates (enables) user in external db so user can login to external db |
892 | * | |
df884cd8 | 893 | * @param mixed $username |
b9ddb2d5 | 894 | * @return boolen result |
895 | */ | |
896 | function user_activate($username) { | |
139ebfdb | 897 | $textlib = textlib_get_instance(); |
df884cd8 | 898 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
139ebfdb | 899 | |
b9ddb2d5 | 900 | $ldapconnection = $this->ldap_connect(); |
901 | ||
139ebfdb | 902 | $userdn = $this->ldap_find_userdn($ldapconnection, $extusername); |
b9ddb2d5 | 903 | switch ($this->config->user_type) { |
904 | case 'edir': | |
905 | $newinfo['loginDisabled']="FALSE"; | |
906 | break; | |
81fb221d | 907 | case 'ad': |
908 | // We need to unset the ACCOUNTDISABLE bit in the | |
909 | // userAccountControl attribute ( see | |
910 | // http://support.microsoft.com/kb/305144 ) | |
911 | $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)', | |
912 | array('userAccountControl')); | |
913 | $info = ldap_get_entries($ldapconnection, $sr); | |
914 | $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0] | |
915 | & (~AUTH_AD_ACCOUNTDISABLE); | |
916 | break; | |
b9ddb2d5 | 917 | default: |
771dc7b2 | 918 | print_error('user_activatenotsupportusertype', 'auth', '', $this->config->user_type); |
139ebfdb | 919 | } |
b9ddb2d5 | 920 | $result = ldap_modify($ldapconnection, $userdn, $newinfo); |
eee34307 | 921 | $this->ldap_close(); |
b9ddb2d5 | 922 | return $result; |
923 | } | |
924 | ||
139ebfdb | 925 | /** |
b9ddb2d5 | 926 | * Disables user in external db so user can't login to external db |
927 | * | |
928 | * @param mixed $username username | |
929 | * @return boolean result | |
930 | */ | |
139ebfdb | 931 | /* function user_disable($username) { |
932 | $textlib = textlib_get_instance(); | |
df884cd8 | 933 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
b9ddb2d5 | 934 | |
935 | $ldapconnection = $this->ldap_connect(); | |
936 | ||
139ebfdb | 937 | $userdn = $this->ldap_find_userdn($ldapconnection, $extusername); |
b9ddb2d5 | 938 | switch ($this->config->user_type) { |
939 | case 'edir': | |
940 | $newinfo['loginDisabled']="TRUE"; | |
941 | break; | |
81fb221d | 942 | case 'ad': |
943 | // We need to set the ACCOUNTDISABLE bit in the | |
944 | // userAccountControl attribute ( see | |
945 | // http://support.microsoft.com/kb/305144 ) | |
946 | $sr = ldap_read($ldapconnection, $userdn, '(objectClass=*)', | |
947 | array('userAccountControl')); | |
948 | $info = auth_ldap_get_entries($ldapconnection, $sr); | |
949 | $newinfo['userAccountControl'] = $info[0]['userAccountControl'][0] | |
950 | | AUTH_AD_ACCOUNTDISABLE; | |
951 | break; | |
b9ddb2d5 | 952 | default: |
771dc7b2 | 953 | print_error('user_disablenotsupportusertype', 'auth'); |
139ebfdb | 954 | } |
b9ddb2d5 | 955 | $result = ldap_modify($ldapconnection, $userdn, $newinfo); |
eee34307 | 956 | $this->ldap_close(); |
b9ddb2d5 | 957 | return $result; |
139ebfdb | 958 | }*/ |
b9ddb2d5 | 959 | |
139ebfdb | 960 | /** |
b9ddb2d5 | 961 | * Returns true if user should be coursecreator. |
962 | * | |
6bc1e5d5 | 963 | * @param mixed $username username (without system magic quotes) |
b9ddb2d5 | 964 | * @return boolean result |
965 | */ | |
6bc1e5d5 | 966 | function iscreator($username) { |
139ebfdb | 967 | if (empty($this->config->creators) or empty($this->config->memberattribute)) { |
6bc1e5d5 | 968 | return null; |
b9ddb2d5 | 969 | } |
139ebfdb | 970 | |
971 | $textlib = textlib_get_instance(); | |
972 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); | |
973 | ||
6bc1e5d5 | 974 | return (boolean)$this->ldap_isgroupmember($extusername, $this->config->creators); |
b9ddb2d5 | 975 | } |
976 | ||
139ebfdb | 977 | /** |
b9ddb2d5 | 978 | * Called when the user record is updated. |
139ebfdb | 979 | * Modifies user in external database. It takes olduser (before changes) and newuser (after changes) |
b9ddb2d5 | 980 | * conpares information saved modified information to external db. |
981 | * | |
139ebfdb | 982 | * @param mixed $olduser Userobject before modifications (without system magic quotes) |
983 | * @param mixed $newuser Userobject new modified userobject (without system magic quotes) | |
b9ddb2d5 | 984 | * @return boolean result |
985 | * | |
986 | */ | |
987 | function user_update($olduser, $newuser) { | |
988 | ||
139ebfdb | 989 | global $USER; |
990 | ||
991 | if (isset($olduser->username) and isset($newuser->username) and $olduser->username != $newuser->username) { | |
992 | error_log("ERROR:User renaming not allowed in LDAP"); | |
993 | return false; | |
994 | } | |
995 | ||
6bc1e5d5 | 996 | if (isset($olduser->auth) and $olduser->auth != 'ldap') { |
139ebfdb | 997 | return true; // just change auth and skip update |
998 | } | |
999 | ||
1e3eee5f | 1000 | $attrmap = $this->ldap_attributes(); |
1001 | ||
1002 | // Before doing anything else, make sure really need to update anything | |
1003 | // in the external LDAP server. | |
1004 | $update_external = false; | |
1005 | foreach ($attrmap as $key => $ldapkeys) { | |
1006 | if (!empty($this->config->{'field_updateremote_'.$key})) { | |
1007 | $update_external = true; | |
1008 | break; | |
1009 | } | |
1010 | } | |
1011 | if (!$update_external) { | |
1012 | return true; | |
1013 | } | |
1014 | ||
139ebfdb | 1015 | $textlib = textlib_get_instance(); |
1016 | $extoldusername = $textlib->convert($olduser->username, 'utf-8', $this->config->ldapencoding); | |
b9ddb2d5 | 1017 | |
1018 | $ldapconnection = $this->ldap_connect(); | |
139ebfdb | 1019 | |
b9ddb2d5 | 1020 | $search_attribs = array(); |
1021 | ||
b9ddb2d5 | 1022 | foreach ($attrmap as $key => $values) { |
1023 | if (!is_array($values)) { | |
1024 | $values = array($values); | |
1025 | } | |
1026 | foreach ($values as $value) { | |
1027 | if (!in_array($value, $search_attribs)) { | |
1028 | array_push($search_attribs, $value); | |
1029 | } | |
139ebfdb | 1030 | } |
b9ddb2d5 | 1031 | } |
1032 | ||
139ebfdb | 1033 | $user_dn = $this->ldap_find_userdn($ldapconnection, $extoldusername); |
b9ddb2d5 | 1034 | |
1035 | $user_info_result = ldap_read($ldapconnection, $user_dn, | |
1036 | $this->config->objectclass, $search_attribs); | |
1037 | ||
1038 | if ($user_info_result) { | |
1039 | ||
1040 | $user_entry = $this->ldap_get_entries($ldapconnection, $user_info_result); | |
139ebfdb | 1041 | if (empty($user_entry)) { |
1e3eee5f | 1042 | $error = 'ldap: Could not find user while updating externally. '. |
1043 | 'Details follow: search base: \''.$user_dn.'\'; search filter: \''. | |
1044 | $this->config->objectclass.'\'; search attributes: '; | |
1045 | foreach ($search_attribs as $attrib) { | |
1046 | $error .= $attrib.' '; | |
1047 | } | |
1048 | error_log($error); | |
139ebfdb | 1049 | return false; // old user not found! |
1050 | } else if (count($user_entry) > 1) { | |
1e3eee5f | 1051 | error_log('ldap: Strange! More than one user record found in ldap. Only using the first one.'); |
139ebfdb | 1052 | return false; |
b9ddb2d5 | 1053 | } |
1054 | $user_entry = $user_entry[0]; | |
1055 | ||
1056 | //error_log(var_export($user_entry) . 'fpp' ); | |
b9ddb2d5 | 1057 | |
139ebfdb | 1058 | foreach ($attrmap as $key => $ldapkeys) { |
b9ddb2d5 | 1059 | // only process if the moodle field ($key) has changed and we |
1060 | // are set to update LDAP with it | |
139ebfdb | 1061 | if (isset($olduser->$key) and isset($newuser->$key) |
1062 | and $olduser->$key !== $newuser->$key | |
1063 | and !empty($this->config->{'field_updateremote_'. $key})) { | |
1064 | // for ldap values that could be in more than one | |
1065 | // ldap key, we will do our best to match | |
b9ddb2d5 | 1066 | // where they came from |
1067 | $ambiguous = true; | |
1068 | $changed = false; | |
1069 | if (!is_array($ldapkeys)) { | |
1070 | $ldapkeys = array($ldapkeys); | |
1071 | } | |
1072 | if (count($ldapkeys) < 2) { | |
1073 | $ambiguous = false; | |
1074 | } | |
139ebfdb | 1075 | |
1076 | $nuvalue = $textlib->convert($newuser->$key, 'utf-8', $this->config->ldapencoding); | |
15f80fd8 | 1077 | empty($nuvalue) ? $nuvalue = array() : $nuvalue; |
139ebfdb | 1078 | $ouvalue = $textlib->convert($olduser->$key, 'utf-8', $this->config->ldapencoding); |
1079 | ||
b9ddb2d5 | 1080 | foreach ($ldapkeys as $ldapkey) { |
139ebfdb | 1081 | $ldapkey = $ldapkey; |
b9ddb2d5 | 1082 | $ldapvalue = $user_entry[$ldapkey][0]; |
1083 | if (!$ambiguous) { | |
1084 | // skip update if the values already match | |
139ebfdb | 1085 | if ($nuvalue !== $ldapvalue) { |
1086 | //this might fail due to schema validation | |
1087 | if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) { | |
1088 | continue; | |
1089 | } else { | |
1090 | error_log('Error updating LDAP record. Error code: ' | |
1091 | . ldap_errno($ldapconnection) . '; Error string : ' | |
1092 | . ldap_err2str(ldap_errno($ldapconnection)) | |
1093 | . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'"); | |
1094 | continue; | |
1095 | } | |
b9ddb2d5 | 1096 | } |
139ebfdb | 1097 | } else { |
b9ddb2d5 | 1098 | // ambiguous |
1099 | // value empty before in Moodle (and LDAP) - use 1st ldap candidate field | |
1100 | // no need to guess | |
139ebfdb | 1101 | if ($ouvalue === '') { // value empty before - use 1st ldap candidate |
1102 | //this might fail due to schema validation | |
1103 | if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) { | |
b9ddb2d5 | 1104 | $changed = true; |
139ebfdb | 1105 | continue; |
1106 | } else { | |
1107 | error_log('Error updating LDAP record. Error code: ' | |
1108 | . ldap_errno($ldapconnection) . '; Error string : ' | |
1109 | . ldap_err2str(ldap_errno($ldapconnection)) | |
1110 | . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'"); | |
1111 | continue; | |
b9ddb2d5 | 1112 | } |
1113 | } | |
1114 | ||
139ebfdb | 1115 | // we found which ldap key to update! |
1116 | if ($ouvalue !== '' and $ouvalue === $ldapvalue ) { | |
1117 | //this might fail due to schema validation | |
1118 | if (@ldap_modify($ldapconnection, $user_dn, array($ldapkey => $nuvalue))) { | |
b9ddb2d5 | 1119 | $changed = true; |
139ebfdb | 1120 | continue; |
1121 | } else { | |
1122 | error_log('Error updating LDAP record. Error code: ' | |
b9ddb2d5 | 1123 | . ldap_errno($ldapconnection) . '; Error string : ' |
139ebfdb | 1124 | . ldap_err2str(ldap_errno($ldapconnection)) |
1125 | . "\nKey ($key) - old moodle value: '$ouvalue' new value: '$nuvalue'"); | |
1126 | continue; | |
b9ddb2d5 | 1127 | } |
1128 | } | |
1129 | } | |
1130 | } | |
139ebfdb | 1131 | |
b9ddb2d5 | 1132 | if ($ambiguous and !$changed) { |
139ebfdb | 1133 | error_log("Failed to update LDAP with ambiguous field $key". |
1134 | " old moodle value: '" . $ouvalue . | |
1135 | "' new value '" . $nuvalue ); | |
b9ddb2d5 | 1136 | } |
1137 | } | |
1138 | } | |
139ebfdb | 1139 | } else { |
b9ddb2d5 | 1140 | error_log("ERROR:No user found in LDAP"); |
eee34307 | 1141 | $this->ldap_close(); |
b9ddb2d5 | 1142 | return false; |
1143 | } | |
1144 | ||
eee34307 | 1145 | $this->ldap_close(); |
139ebfdb | 1146 | |
b9ddb2d5 | 1147 | return true; |
1148 | ||
1149 | } | |
1150 | ||
fb5c7739 | 1151 | /** |
b9ddb2d5 | 1152 | * changes userpassword in external db |
1153 | * | |
1154 | * called when the user password is updated. | |
1155 | * changes userpassword in external db | |
1156 | * | |
ae040d4b | 1157 | * @param object $user User table object |
1158 | * @param string $newpassword Plaintext password | |
b9ddb2d5 | 1159 | * @return boolean result |
1160 | * | |
1161 | */ | |
b9ddb2d5 | 1162 | function user_update_password($user, $newpassword) { |
1163 | /// called when the user password is updated -- it assumes it is called by an admin | |
1164 | /// or that you've otherwise checked the user's credentials | |
1165 | /// IMPORTANT: $newpassword must be cleartext, not crypted/md5'ed | |
1166 | ||
139ebfdb | 1167 | global $USER; |
b9ddb2d5 | 1168 | $result = false; |
1169 | $username = $user->username; | |
139ebfdb | 1170 | |
1171 | $textlib = textlib_get_instance(); | |
ae040d4b | 1172 | $extusername = $textlib->convert($username, 'utf-8', $this->config->ldapencoding); |
1173 | $extpassword = $textlib->convert($newpassword, 'utf-8', $this->config->ldapencoding); | |
139ebfdb | 1174 | |
344514fc | 1175 | switch ($this->config->passtype) { |
1176 | case 'md5': | |
1177 | $extpassword = '{MD5}' . base64_encode(pack('H*', md5($extpassword))); | |
1178 | break; | |
1179 | case 'sha1': | |
1180 | $extpassword = '{SHA}' . base64_encode(pack('H*', sha1($extpassword))); | |
1181 | break; | |
1182 | case 'plaintext': | |
1183 | default: | |
1184 | break; // plaintext | |
1185 | } | |
1186 | ||
b9ddb2d5 | 1187 | $ldapconnection = $this->ldap_connect(); |
1188 | ||
139ebfdb | 1189 | $user_dn = $this->ldap_find_userdn($ldapconnection, $extusername); |
1190 | ||
b9ddb2d5 | 1191 | if (!$user_dn) { |
ae040d4b | 1192 | error_log('LDAP Error in user_update_password(). No DN for: ' . $user->username); |
b9ddb2d5 | 1193 | return false; |
1194 | } | |
1195 | ||
1196 | switch ($this->config->user_type) { | |
1197 | case 'edir': | |
1198 | //Change password | |
139ebfdb | 1199 | $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword)); |
b9ddb2d5 | 1200 | if (!$result) { |
1201 | error_log('LDAP Error in user_update_password(). Error code: ' | |
1202 | . ldap_errno($ldapconnection) . '; Error string : ' | |
1203 | . ldap_err2str(ldap_errno($ldapconnection))); | |
1204 | } | |
1205 | //Update password expiration time, grace logins count | |
1206 | $search_attribs = array($this->config->expireattr, 'passwordExpirationInterval','loginGraceLimit' ); | |
cfcb7a17 | 1207 | $sr = ldap_read($ldapconnection, $user_dn, '(objectClass=*)', $search_attribs); |
b9ddb2d5 | 1208 | if ($sr) { |
1209 | $info=$this->ldap_get_entries($ldapconnection, $sr); | |
1210 | $newattrs = array(); | |
1211 | if (!empty($info[0][$this->config->expireattr][0])) { | |
1212 | //Set expiration time only if passwordExpirationInterval is defined | |
1213 | if (!empty($info[0]['passwordExpirationInterval'][0])) { | |
139ebfdb | 1214 | $expirationtime = time() + $info[0]['passwordExpirationInterval'][0]; |
b9ddb2d5 | 1215 | $ldapexpirationtime = $this->ldap_unix2expirationtime($expirationtime); |
1216 | $newattrs['passwordExpirationTime'] = $ldapexpirationtime; | |
139ebfdb | 1217 | } |
b9ddb2d5 | 1218 | |
1219 | //set gracelogin count | |
1220 | if (!empty($info[0]['loginGraceLimit'][0])) { | |
139ebfdb | 1221 | $newattrs['loginGraceRemaining']= $info[0]['loginGraceLimit'][0]; |
b9ddb2d5 | 1222 | } |
139ebfdb | 1223 | |
b9ddb2d5 | 1224 | //Store attribute changes to ldap |
1225 | $result = ldap_modify($ldapconnection, $user_dn, $newattrs); | |
1226 | if (!$result) { | |
1227 | error_log('LDAP Error in user_update_password() when modifying expirationtime and/or gracelogins. Error code: ' | |
1228 | . ldap_errno($ldapconnection) . '; Error string : ' | |
1229 | . ldap_err2str(ldap_errno($ldapconnection))); | |
1230 | } | |
1231 | } | |
1232 | } | |
1233 | else { | |
1234 | error_log('LDAP Error in user_update_password() when reading password expiration time. Error code: ' | |
1235 | . ldap_errno($ldapconnection) . '; Error string : ' | |
1236 | . ldap_err2str(ldap_errno($ldapconnection))); | |
d0e84e1b | 1237 | } |
1238 | break; | |
1239 | ||
1240 | case 'ad': | |
1241 | // Passwords in Active Directory must be encoded as Unicode | |
1242 | // strings (UCS-2 Little Endian format) and surrounded with | |
1243 | // double quotes. See http://support.microsoft.com/?kbid=269190 | |
1244 | if (!function_exists('mb_convert_encoding')) { | |
1245 | error_log ('You need the mbstring extension to change passwords in Active Directory'); | |
1246 | return false; | |
1247 | } | |
1248 | $extpassword = mb_convert_encoding('"'.$extpassword.'"', "UCS-2LE", $this->config->ldapencoding); | |
1249 | $result = ldap_modify($ldapconnection, $user_dn, array('unicodePwd' => $extpassword)); | |
1250 | if (!$result) { | |
e295df44 | 1251 | error_log('LDAP Error in user_update_password(). Error code: ' |
d0e84e1b | 1252 | . ldap_errno($ldapconnection) . '; Error string : ' |
1253 | . ldap_err2str(ldap_errno($ldapconnection))); | |
139ebfdb | 1254 | } |
b9ddb2d5 | 1255 | break; |
139ebfdb | 1256 | |
b9ddb2d5 | 1257 | default: |
1258 | $usedconnection = &$ldapconnection; | |
1259 | // send ldap the password in cleartext, it will md5 it itself | |
139ebfdb | 1260 | $result = ldap_modify($ldapconnection, $user_dn, array('userPassword' => $extpassword)); |
b9ddb2d5 | 1261 | if (!$result) { |
139ebfdb | 1262 | error_log('LDAP Error in user_update_password(). Error code: ' |
b9ddb2d5 | 1263 | . ldap_errno($ldapconnection) . '; Error string : ' |
1264 | . ldap_err2str(ldap_errno($ldapconnection))); | |
1265 | } | |
139ebfdb | 1266 | |
b9ddb2d5 | 1267 | } |
1268 | ||
eee34307 | 1269 | $this->ldap_close(); |
b9ddb2d5 | 1270 | return $result; |
1271 | } | |
1272 | ||
1273 | //PRIVATE FUNCTIONS starts | |
1274 | //private functions are named as ldap_* | |
1275 | ||
1276 | /** | |
1277 | * returns predefined usertypes | |
1278 | * | |
1279 | * @return array of predefined usertypes | |
1280 | */ | |
b9ddb2d5 | 1281 | function ldap_suppported_usertypes() { |
139ebfdb | 1282 | $types = array(); |
b9ddb2d5 | 1283 | $types['edir']='Novell Edirectory'; |
1284 | $types['rfc2307']='posixAccount (rfc2307)'; | |
1285 | $types['rfc2307bis']='posixAccount (rfc2307bis)'; | |
1286 | $types['samba']='sambaSamAccount (v.3.0.7)'; | |
139ebfdb | 1287 | $types['ad']='MS ActiveDirectory'; |
1288 | $types['default']=get_string('default'); | |
b9ddb2d5 | 1289 | return $types; |
139ebfdb | 1290 | } |
1291 | ||
b9ddb2d5 | 1292 | |
b9ddb2d5 | 1293 | /** |
139ebfdb | 1294 | * Initializes needed variables for ldap-module |
b9ddb2d5 | 1295 | * |
1296 | * Uses names defined in ldap_supported_usertypes. | |
1297 | * $default is first defined as: | |
1298 | * $default['pseudoname'] = array( | |
1299 | * 'typename1' => 'value', | |
1300 | * 'typename2' => 'value' | |
1301 | * .... | |
1302 | * ); | |
1303 | * | |
1304 | * @return array of default values | |
1305 | */ | |
1306 | function ldap_getdefaults() { | |
1307 | $default['objectclass'] = array( | |
1308 | 'edir' => 'User', | |
139ebfdb | 1309 | 'rfc2307' => 'posixAccount', |
1310 | 'rfc2307bis' => 'posixAccount', | |
b9ddb2d5 | 1311 | 'samba' => 'sambaSamAccount', |
1312 | 'ad' => 'user', | |
1313 | 'default' => '*' | |
1314 | ); | |
1315 | $default['user_attribute'] = array( | |
1316 | 'edir' => 'cn', | |
1317 | 'rfc2307' => 'uid', | |
1318 | 'rfc2307bis' => 'uid', | |
1319 | 'samba' => 'uid', | |
1320 | 'ad' => 'cn', | |
1321 | 'default' => 'cn' | |
1322 | ); | |
1323 | $default['memberattribute'] = array( | |
1324 | 'edir' => 'member', | |
1325 | 'rfc2307' => 'member', | |
1326 | 'rfc2307bis' => 'member', | |
1327 | 'samba' => 'member', | |
139ebfdb | 1328 | 'ad' => 'member', |
b9ddb2d5 | 1329 | 'default' => 'member' |
1330 | ); | |
1331 | $default['memberattribute_isdn'] = array( | |
1332 | 'edir' => '1', | |
1333 | 'rfc2307' => '0', | |
1334 | 'rfc2307bis' => '1', | |
1335 | 'samba' => '0', //is this right? | |
1336 | 'ad' => '1', | |
1337 | 'default' => '0' | |
1338 | ); | |
1339 | $default['expireattr'] = array ( | |
1340 | 'edir' => 'passwordExpirationTime', | |
1341 | 'rfc2307' => 'shadowExpire', | |
1342 | 'rfc2307bis' => 'shadowExpire', | |
1343 | 'samba' => '', //No support yet | |
f0a729bf | 1344 | 'ad' => 'pwdLastSet', |
b9ddb2d5 | 1345 | 'default' => '' |
1346 | ); | |
139ebfdb | 1347 | return $default; |
b9ddb2d5 | 1348 | } |
1349 | ||
1350 | /** | |
1351 | * return binaryfields of selected usertype | |
1352 | * | |
1353 | * | |
1354 | * @return array | |
1355 | */ | |
1356 | function ldap_getbinaryfields () { | |
b9ddb2d5 | 1357 | $binaryfields = array ( |
1358 | 'edir' => array('guid'), | |
139ebfdb | 1359 | 'rfc2307' => array(), |
1360 | 'rfc2307bis' => array(), | |
b9ddb2d5 | 1361 | 'samba' => array(), |
1362 | 'ad' => array(), | |
139ebfdb | 1363 | 'default' => array() |
b9ddb2d5 | 1364 | ); |
1365 | if (!empty($this->config->user_type)) { | |
139ebfdb | 1366 | return $binaryfields[$this->config->user_type]; |
b9ddb2d5 | 1367 | } |
1368 | else { | |
1369 | return $binaryfields['default']; | |
139ebfdb | 1370 | } |
b9ddb2d5 | 1371 | } |
1372 | ||
1373 | function ldap_isbinary ($field) { | |
139ebfdb | 1374 | if (empty($field)) { |
1375 | return false; | |
b9ddb2d5 | 1376 | } |
139ebfdb | 1377 | return array_search($field, $this->ldap_getbinaryfields()); |
b9ddb2d5 | 1378 | } |
1379 | ||
1380 | /** | |
1381 | * take expirationtime and return it as unixseconds | |
139ebfdb | 1382 | * |
b9ddb2d5 | 1383 | * takes expriration timestamp as readed from ldap |
1384 | * returns it as unix seconds | |
139ebfdb | 1385 | * depends on $this->config->user_type variable |
b9ddb2d5 | 1386 | * |
1387 | * @param mixed time Time stamp readed from ldap as it is. | |
bffe39c6 | 1388 | * @param string $ldapconnection Just needed for Active Directory. |
1389 | * @param string $user_dn User distinguished name for the user we are checking password expiration (just needed for Active Directory). | |
b9ddb2d5 | 1390 | * @return timestamp |
1391 | */ | |
bffe39c6 | 1392 | function ldap_expirationtime2unix ($time, $ldapconnection, $user_dn) { |
b9ddb2d5 | 1393 | $result = false; |
1394 | switch ($this->config->user_type) { | |
1395 | case 'edir': | |
1396 | $yr=substr($time,0,4); | |
1397 | $mo=substr($time,4,2); | |
1398 | $dt=substr($time,6,2); | |
1399 | $hr=substr($time,8,2); | |
1400 | $min=substr($time,10,2); | |
1401 | $sec=substr($time,12,2); | |
139ebfdb | 1402 | $result = mktime($hr,$min,$sec,$mo,$dt,$yr); |
b9ddb2d5 | 1403 | break; |
9347082d | 1404 | case 'rfc2307': |
1405 | case 'rfc2307bis': | |
b9ddb2d5 | 1406 | $result = $time * DAYSECS; //The shadowExpire contains the number of DAYS between 01/01/1970 and the actual expiration date |
1407 | break; | |
bffe39c6 | 1408 | case 'ad': |
1409 | $result = $this->ldap_get_ad_pwdexpire($time, $ldapconnection, $user_dn); | |
1410 | break; | |
139ebfdb | 1411 | default: |
2b06294b | 1412 | print_error('auth_ldap_usertypeundefined', 'auth_ldap'); |
b9ddb2d5 | 1413 | } |
1414 | return $result; | |
1415 | } | |
1416 | ||
1417 | /** | |
1418 | * takes unixtime and return it formated for storing in ldap | |
1419 | * | |
1420 | * @param integer unix time stamp | |
1421 | */ | |
1422 | function ldap_unix2expirationtime($time) { | |
b9ddb2d5 | 1423 | $result = false; |
1424 | switch ($this->config->user_type) { | |
1425 | case 'edir': | |
139ebfdb | 1426 | $result=date('YmdHis', $time).'Z'; |
b9ddb2d5 | 1427 | break; |
9347082d | 1428 | case 'rfc2307': |
1429 | case 'rfc2307bis': | |
b9ddb2d5 | 1430 | $result = $time ; //Already in correct format |
1431 | break; | |
139ebfdb | 1432 | default: |
2b06294b | 1433 | print_error('auth_ldap_usertypeundefined2', 'auth_ldap'); |
139ebfdb | 1434 | } |
b9ddb2d5 | 1435 | return $result; |
1436 | ||
1437 | } | |
1438 | ||
139ebfdb | 1439 | /** |
b9ddb2d5 | 1440 | * checks if user belong to specific group(s) |
6626b9ab | 1441 | * or is in a subtree. |
b9ddb2d5 | 1442 | * |
6626b9ab | 1443 | * Returns true if user belongs group in grupdns string OR |
1444 | * if the DN of the user is in a subtree pf the DN provided | |
1445 | * as "group" | |
b9ddb2d5 | 1446 | * |
1447 | * @param mixed $username username | |
1448 | * @param mixed $groupdns string of group dn separated by ; | |
1449 | * | |
1450 | */ | |
139ebfdb | 1451 | function ldap_isgroupmember($extusername='', $groupdns='') { |
b9ddb2d5 | 1452 | // Takes username and groupdn(s) , separated by ; |
1453 | // Returns true if user is member of any given groups | |
1454 | ||
b9ddb2d5 | 1455 | $ldapconnection = $this->ldap_connect(); |
139ebfdb | 1456 | |
cd874e21 | 1457 | if (empty($extusername) or empty($groupdns)) { |
1458 | return false; | |
b9ddb2d5 | 1459 | } |
1460 | ||
1461 | if ($this->config->memberattribute_isdn) { | |
cd874e21 | 1462 | $memberuser = $this->ldap_find_userdn($ldapconnection, $extusername); |
1463 | } else { | |
1464 | $memberuser = $extusername; | |
b9ddb2d5 | 1465 | } |
cd874e21 | 1466 | |
1467 | if (empty($memberuser)) { | |
1468 | return false; | |
b9ddb2d5 | 1469 | } |
1470 | ||
1471 | $groups = explode(";",$groupdns); | |
139ebfdb | 1472 | |
cd874e21 | 1473 | $result = false; |
b9ddb2d5 | 1474 | foreach ($groups as $group) { |
1475 | $group = trim($group); | |
1476 | if (empty($group)) { | |
1477 | continue; | |
1478 | } | |
6626b9ab | 1479 | |
1480 | // check cheaply if the user's DN sits in a subtree | |
1481 | // of the "group" DN provided. Granted, this isn't | |
1482 | // a proper LDAP group, but it's a popular usage. | |
1483 | if (strpos(strrev($memberuser), strrev($group))===0) { | |
1484 | $result = true; | |
1485 | break; | |
1486 | } | |
1487 | ||
b9ddb2d5 | 1488 | //echo "Checking group $group for member $username\n"; |
cd874e21 | 1489 | $search = ldap_read($ldapconnection, $group, '('.$this->config->memberattribute.'='.$this->filter_addslashes($memberuser).')', array($this->config->memberattribute)); |
1490 | if (!empty($search) and ldap_count_entries($ldapconnection, $search)) { | |
1491 | $info = $this->ldap_get_entries($ldapconnection, $search); | |
139ebfdb | 1492 | |
b9ddb2d5 | 1493 | if (count($info) > 0 ) { |
1494 | // user is member of group | |
1495 | $result = true; | |
1496 | break; | |
1497 | } | |
cd874e21 | 1498 | } |
b9ddb2d5 | 1499 | } |
b9ddb2d5 | 1500 | |
1501 | return $result; | |
1502 | ||
1503 | } | |
1504 | ||
1505 | /** | |
1506 | * connects to ldap server | |
1507 | * | |
1508 | * Tries connect to specified ldap servers. | |
1509 | * Returns connection result or error. | |
1510 | * | |
1511 | * @return connection result | |
1512 | */ | |
1513 | function ldap_connect($binddn='',$bindpwd='') { | |
eee34307 | 1514 | // Cache ldap connections (they are expensive to set up |
5117d598 | 1515 | // and can drain the TCP/IP ressources on the server if we |
eee34307 | 1516 | // are syncing a lot of users (as we try to open a new connection |
1517 | // to get the user details). This is the least invasive way | |
1518 | // to reuse existing connections without greater code surgery. | |
1519 | if(!empty($this->ldapconnection)) { | |
1520 | $this->ldapconns++; | |
1521 | return $this->ldapconnection; | |
1522 | } | |
1523 | ||
b9ddb2d5 | 1524 | //Select bind password, With empty values use |
1525 | //ldap_bind_* variables or anonymous bind if ldap_bind_* are empty | |
1526 | if ($binddn == '' and $bindpwd == '') { | |
1527 | if (!empty($this->config->bind_dn)) { | |
1528 | $binddn = $this->config->bind_dn; | |
1529 | } | |
1530 | if (!empty($this->config->bind_pw)) { | |
1531 | $bindpwd = $this->config->bind_pw; | |
1532 | } | |
1533 | } | |
139ebfdb | 1534 | |
b9ddb2d5 | 1535 | $urls = explode(";",$this->config->host_url); |
139ebfdb | 1536 | |
b9ddb2d5 | 1537 | foreach ($urls as $server) { |
1538 | $server = trim($server); | |
1539 | if (empty($server)) { | |
1540 | continue; | |
1541 | } | |
1542 | ||
1543 | $connresult = ldap_connect($server); | |
1544 | //ldap_connect returns ALWAYS true | |
139ebfdb | 1545 | |
8dcf8888 I |
1546 | if (!empty($this->config->ldap_version)) { |
1547 | ldap_set_option($connresult, LDAP_OPT_PROTOCOL_VERSION, $this->config->ldap_version); | |
b9ddb2d5 | 1548 | } |
1549 | ||
f6c80923 | 1550 | // Fix MDL-10921 |
1551 | if ($this->config->user_type == 'ad') { | |
1552 | ldap_set_option($connresult, LDAP_OPT_REFERRALS, 0); | |
1553 | } | |
1554 | ||
b9ddb2d5 | 1555 | if (!empty($binddn)) { |
1556 | //bind with search-user | |
139ebfdb | 1557 | //$debuginfo .= 'Using bind user'.$binddn.'and password:'.$bindpwd; |
b9ddb2d5 | 1558 | $bindresult=ldap_bind($connresult, $binddn,$bindpwd); |
1559 | } | |
1560 | else { | |
139ebfdb | 1561 | //bind anonymously |
b9ddb2d5 | 1562 | $bindresult=@ldap_bind($connresult); |
139ebfdb | 1563 | } |
1564 | ||
b9ddb2d5 | 1565 | if (!empty($this->config->opt_deref)) { |
1566 | ldap_set_option($connresult, LDAP_OPT_DEREF, $this->config->opt_deref); | |
1567 | } | |
1568 | ||
1569 | if ($bindresult) { | |
4454447d PS |
1570 | // Set the connection counter so we can call PHP's ldap_close() |
1571 | // when we call $this->ldap_close() for the last 'open' connection. | |
5117d598 | 1572 | $this->ldapconns = 1; |
eee34307 | 1573 | $this->ldapconnection = $connresult; |
b9ddb2d5 | 1574 | return $connresult; |
1575 | } | |
139ebfdb | 1576 | |
b9ddb2d5 | 1577 | $debuginfo .= "<br/>Server: '$server' <br/> Connection: '$connresult'<br/> Bind result: '$bindresult'</br>"; |
1578 | } | |
1579 | ||
1580 | //If any of servers are alive we have already returned connection | |
2b06294b | 1581 | print_error('auth_ldap_noconnect_all','auth_ldap','', $debuginfo); |
b9ddb2d5 | 1582 | return false; |
1583 | } | |
1584 | ||
eee34307 | 1585 | /** |
1586 | * disconnects from a ldap server | |
1587 | * | |
1588 | */ | |
1589 | function ldap_close() { | |
1590 | $this->ldapconns--; | |
1591 | if($this->ldapconns == 0) { | |
1592 | @ldap_close($this->ldapconnection); | |
1593 | unset($this->ldapconnection); | |
1594 | } | |
1595 | } | |
1596 | ||
b9ddb2d5 | 1597 | /** |
1598 | * retuns dn of username | |
1599 | * | |
1600 | * Search specified contexts for username and return user dn | |
1601 | * like: cn=username,ou=suborg,o=org | |
1602 | * | |
1603 | * @param mixed $ldapconnection $ldapconnection result | |
139ebfdb | 1604 | * @param mixed $username username (external encoding no slashes) |
b9ddb2d5 | 1605 | * |
1606 | */ | |
1607 | ||
139ebfdb | 1608 | function ldap_find_userdn ($ldapconnection, $extusername) { |
b9ddb2d5 | 1609 | |
1610 | //default return value | |
1611 | $ldap_user_dn = FALSE; | |
1612 | ||
1613 | //get all contexts and look for first matching user | |
1614 | $ldap_contexts = explode(";",$this->config->contexts); | |
139ebfdb | 1615 | |
b9ddb2d5 | 1616 | if (!empty($this->config->create_context)) { |
1617 | array_push($ldap_contexts, $this->config->create_context); | |
1618 | } | |
139ebfdb | 1619 | |
b9ddb2d5 | 1620 | foreach ($ldap_contexts as $context) { |
1621 | ||
1622 | $context = trim($context); | |
1623 | if (empty($context)) { | |
1624 | continue; | |
1625 | } | |
1626 | ||
1627 | if ($this->config->search_sub) { | |
1628 | //use ldap_search to find first user from subtree | |
139ebfdb | 1629 | $ldap_result = ldap_search($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute)); |
b9ddb2d5 | 1630 | |
1631 | } | |
1632 | else { | |
1633 | //search only in this context | |
139ebfdb | 1634 | $ldap_result = ldap_list($ldapconnection, $context, "(".$this->config->user_attribute."=".$this->filter_addslashes($extusername).")",array($this->config->user_attribute)); |
b9ddb2d5 | 1635 | } |
139ebfdb | 1636 | |
b9ddb2d5 | 1637 | $entry = ldap_first_entry($ldapconnection,$ldap_result); |
1638 | ||
1639 | if ($entry) { | |
1640 | $ldap_user_dn = ldap_get_dn($ldapconnection, $entry); | |
1641 | break ; | |
1642 | } | |
1643 | } | |
1644 | ||
1645 | return $ldap_user_dn; | |
1646 | } | |
1647 | ||
1648 | /** | |
1649 | * retuns user attribute mappings between moodle and ldap | |
1650 | * | |
1651 | * @return array | |
1652 | */ | |
1653 | ||
1654 | function ldap_attributes () { | |
b9ddb2d5 | 1655 | $moodleattributes = array(); |
4105caff | 1656 | foreach ($this->userfields as $field) { |
b9ddb2d5 | 1657 | if (!empty($this->config->{"field_map_$field"})) { |
1658 | $moodleattributes[$field] = $this->config->{"field_map_$field"}; | |
1659 | if (preg_match('/,/',$moodleattributes[$field])) { | |
1660 | $moodleattributes[$field] = explode(',', $moodleattributes[$field]); // split ? | |
1661 | } | |
1662 | } | |
1663 | } | |
5261baf1 | 1664 | $moodleattributes['username'] = $this->config->user_attribute; |
b9ddb2d5 | 1665 | return $moodleattributes; |
1666 | } | |
1667 | ||
1668 | /** | |
1669 | * return all usernames from ldap | |
1670 | * | |
1671 | * @return array | |
1672 | */ | |
1673 | ||
1674 | function ldap_get_userlist($filter="*") { | |
1675 | /// returns all users from ldap servers | |
b9ddb2d5 | 1676 | $fresult = array(); |
1677 | ||
1678 | $ldapconnection = $this->ldap_connect(); | |
1679 | ||
1680 | if ($filter=="*") { | |
cfcb7a17 | 1681 | $filter = '(&('.$this->config->user_attribute.'=*)'.$this->config->objectclass.')'; |
b9ddb2d5 | 1682 | } |
1683 | ||
1684 | $contexts = explode(";",$this->config->contexts); | |
139ebfdb | 1685 | |
b9ddb2d5 | 1686 | if (!empty($this->config->create_context)) { |
1687 | array_push($contexts, $this->config->create_context); | |
1688 | } | |
1689 | ||
1690 | foreach ($contexts as $context) { | |
1691 | ||
1692 | $context = trim($context); | |
1693 | if (empty($context)) { | |
1694 | continue; | |
1695 | } | |
1696 | ||
1697 | if ($this->config->search_sub) { | |
1698 | //use ldap_search to find first user from subtree | |
1699 | $ldap_result = ldap_search($ldapconnection, $context,$filter,array($this->config->user_attribute)); | |
1700 | } | |
1701 | else { | |
1702 | //search only in this context | |
1703 | $ldap_result = ldap_list($ldapconnection, $context, | |
1704 | $filter, | |
1705 | array($this->config->user_attribute)); | |
1706 | } | |
139ebfdb | 1707 | |
b9ddb2d5 | 1708 | $users = $this->ldap_get_entries($ldapconnection, $ldap_result); |
1709 | ||
1710 | //add found users to list | |
1711 | for ($i=0;$i<count($users);$i++) { | |
1712 | array_push($fresult, ($users[$i][$this->config->user_attribute][0]) ); | |
1713 | } | |
1714 | } | |
139ebfdb | 1715 | |
b9ddb2d5 | 1716 | return $fresult; |
1717 | } | |
1718 | ||
1719 | /** | |
1720 | * return entries from ldap | |
1721 | * | |
1722 | * Returns values like ldap_get_entries but is | |
1723 | * binary compatible and return all attributes as array | |
1724 | * | |
1725 | * @return array ldap-entries | |
1726 | */ | |
139ebfdb | 1727 | |
b9ddb2d5 | 1728 | function ldap_get_entries($conn, $searchresult) { |
1729 | //Returns values like ldap_get_entries but is | |
1730 | //binary compatible | |
1731 | $i=0; | |
1732 | $fresult=array(); | |
1733 | $entry = ldap_first_entry($conn, $searchresult); | |
1734 | do { | |
1735 | $attributes = @ldap_get_attributes($conn, $entry); | |
1736 | for ($j=0; $j<$attributes['count']; $j++) { | |
1737 | $values = ldap_get_values_len($conn, $entry,$attributes[$j]); | |
1738 | if (is_array($values)) { | |
1739 | $fresult[$i][$attributes[$j]] = $values; | |
1740 | } | |
1741 | else { | |
1742 | $fresult[$i][$attributes[$j]] = array($values); | |
1743 | } | |
139ebfdb | 1744 | } |
1745 | $i++; | |
b9ddb2d5 | 1746 | } |
1747 | while ($entry = @ldap_next_entry($conn, $entry)); | |
1748 | //were done | |
1749 | return ($fresult); | |
1750 | } | |
1751 | ||
edb5da83 PS |
1752 | function prevent_local_passwords() { |
1753 | return !empty($this->config->preventpassindb); | |
1754 | } | |
1755 | ||
b9ddb2d5 | 1756 | /** |
1757 | * Returns true if this authentication plugin is 'internal'. | |
1758 | * | |
139ebfdb | 1759 | * @return bool |
b9ddb2d5 | 1760 | */ |
1761 | function is_internal() { | |
1762 | return false; | |
1763 | } | |
1764 | ||
1765 | /** | |
1766 | * Returns true if this authentication plugin can change the user's | |
1767 | * password. | |
1768 | * | |
139ebfdb | 1769 | * @return bool |
b9ddb2d5 | 1770 | */ |
1771 | function can_change_password() { | |
430759a5 | 1772 | return !empty($this->config->stdchangepassword) or !empty($this->config->changepasswordurl); |
b9ddb2d5 | 1773 | } |
139ebfdb | 1774 | |
b9ddb2d5 | 1775 | /** |
430759a5 | 1776 | * Returns the URL for changing the user's pw, or empty if the default can |
b9ddb2d5 | 1777 | * be used. |
1778 | * | |
139ebfdb | 1779 | * @return string url |
b9ddb2d5 | 1780 | */ |
1781 | function change_password_url() { | |
139ebfdb | 1782 | if (empty($this->config->stdchangepassword)) { |
1783 | return $this->config->changepasswordurl; | |
1784 | } else { | |
430759a5 | 1785 | return ''; |
139ebfdb | 1786 | } |
b9ddb2d5 | 1787 | } |
139ebfdb | 1788 | |
1e8713ea | 1789 | /** |
1790 | * Will get called before the login page is shown, if NTLM SSO | |
1791 | * is enabled, and the user is in the right network, we'll redirect | |
1792 | * to the magic NTLM page for SSO... | |
1793 | * | |
1794 | */ | |
1795 | function loginpage_hook() { | |
4194d321 | 1796 | global $CFG, $SESSION; |
265edb48 | 1797 | |
1798 | // HTTPS is potentially required | |
1799 | httpsrequired(); | |
5117d598 | 1800 | |
4194d321 | 1801 | if (($_SERVER['REQUEST_METHOD'] === 'GET' // Only on initial GET of loginpage |
1802 | || ($_SERVER['REQUEST_METHOD'] === 'POST' | |
1803 | && (get_referer() != strip_querystring(qualified_me())))) | |
1804 | // Or when POSTed from another place | |
1805 | // See MDL-14071 | |
1806 | && !empty($this->config->ntlmsso_enabled) // SSO enabled | |
1807 | && !empty($this->config->ntlmsso_subnet) // have a subnet to test for | |
1808 | && empty($_GET['authldap_skipntlmsso']) // haven't failed it yet | |
1809 | && (isguestuser() || !isloggedin()) // guestuser or not-logged-in users | |
b8aa76c1 | 1810 | && address_in_subnet(getremoteaddr(), $this->config->ntlmsso_subnet)) { |
4194d321 | 1811 | |
1812 | // First, let's remember where we were trying to get to before we got here | |
1813 | if (empty($SESSION->wantsurl)) { | |
1814 | $SESSION->wantsurl = (array_key_exists('HTTP_REFERER', $_SERVER) && | |
1815 | $_SERVER['HTTP_REFERER'] != $CFG->wwwroot && | |
1816 | $_SERVER['HTTP_REFERER'] != $CFG->wwwroot.'/' && | |
1817 | $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/' && | |
1818 | $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/index.php') | |
1819 | ? $_SERVER['HTTP_REFERER'] : NULL; | |
1820 | } | |
02c7f3d9 | 1821 | |
4194d321 | 1822 | // Now start the whole NTLM machinery. |
16ceeb64 | 1823 | if(!empty($this->config->ntlmsso_ie_fastpath)) { |
1824 | // Shortcut for IE browsers: skip the attempt page at all | |
1825 | if(check_browser_version('MSIE')) { | |
1826 | $sesskey = sesskey(); | |
1827 | redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_magic.php?sesskey='.$sesskey); | |
1828 | } else { | |
1829 | redirect($CFG->httpswwwroot.'/login/index.php?authldap_skipntlmsso=1'); | |
1830 | } | |
1831 | } else { | |
1832 | redirect($CFG->wwwroot.'/auth/ldap/ntlmsso_attempt.php'); | |
1833 | } | |
4194d321 | 1834 | } |
5117d598 | 1835 | |
4194d321 | 1836 | // No NTLM SSO, Use the normal login page instead. |
1837 | ||
1838 | // If $SESSION->wantsurl is empty and we have a 'Referer:' header, the login | |
1839 | // page insists on redirecting us to that page after user validation. If | |
1840 | // we clicked on the redirect link at the ntlmsso_finish.php page instead | |
1841 | // of waiting for the redirection to happen, then we have a 'Referer:' header | |
1842 | // we don't want to use at all. As we can't get rid of it, just point | |
1843 | // $SESSION->wantsurl to $CFG->wwwroot (after all, we came from there). | |
1844 | if (empty($SESSION->wantsurl) | |
1845 | && (get_referer() == $CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php')) { | |
1846 | ||
1847 | $SESSION->wantsurl = $CFG->wwwroot; | |
1e8713ea | 1848 | } |
1849 | } | |
1850 | ||
1851 | /** | |
1852 | * To be called from a page running under NTLM's | |
5117d598 | 1853 | * "Integrated Windows Authentication". |
1e8713ea | 1854 | * |
5117d598 | 1855 | * If successful, it will set a special "cookie" (not an HTTP cookie!) |
065e2cc0 | 1856 | * in cache_flags under the "auth/ldap/ntlmsess" "plugin" and return true. |
1e8713ea | 1857 | * The "cookie" will be picked up by ntlmsso_finish() to complete the |
1858 | * process. | |
1859 | * | |
1860 | * On failure it will return false for the caller to display an appropriate | |
decd8016 | 1861 | * error message (probably saying that Integrated Windows Auth isn't enabled!) |
1e8713ea | 1862 | * |
5117d598 | 1863 | * NOTE that this code will execute under the OS user credentials, |
1e8713ea | 1864 | * so we MUST avoid dealing with files -- such as session files. |
6800d78e | 1865 | * (The caller should define('NO_MOODLE_COOKIES', true) before including config.php) |
1e8713ea | 1866 | * |
1867 | */ | |
decd8016 | 1868 | function ntlmsso_magic($sesskey) { |
1869 | if (isset($_SERVER['REMOTE_USER']) && !empty($_SERVER['REMOTE_USER'])) { | |
4194d321 | 1870 | |
1871 | // HTTP __headers__ seem to be sent in ISO-8859-1 encoding | |
1872 | // (according to my reading of RFC-1945, RFC-2616 and RFC-2617 and | |
1873 | // my local tests), so we need to convert the REMOTE_USER value | |
1874 | // (i.e., what we got from the HTTP WWW-Authenticate header) into UTF-8 | |
1875 | $textlib = textlib_get_instance(); | |
1876 | $username = $textlib->convert($_SERVER['REMOTE_USER'], 'iso-8859-1', 'utf-8'); | |
1877 | ||
decd8016 | 1878 | $username = substr(strrchr($username, '\\'), 1); //strip domain info |
355bd271 | 1879 | $username = moodle_strtolower($username); //compatibility hack |
065e2cc0 | 1880 | set_cache_flag('auth/ldap/ntlmsess', $sesskey, $username, AUTH_NTLMTIMEOUT); |
1881 | return true; | |
decd8016 | 1882 | } |
1883 | return false; | |
1e8713ea | 1884 | } |
1885 | ||
1886 | /** | |
5117d598 | 1887 | * Find the session set by ntlmsso_magic(), validate it and |
decd8016 | 1888 | * call authenticate_user_login() to authenticate the user through |
1889 | * the auth machinery. | |
5117d598 | 1890 | * |
decd8016 | 1891 | * It is complemented by a similar check in user_login(). |
5117d598 PS |
1892 | * |
1893 | * If it succeeds, it never returns. | |
decd8016 | 1894 | * |
1e8713ea | 1895 | */ |
1896 | function ntlmsso_finish() { | |
4025cf80 | 1897 | global $CFG, $USER, $SESSION; |
02c7f3d9 | 1898 | |
065e2cc0 | 1899 | $key = sesskey(); |
a44240fa | 1900 | $cf = get_cache_flags('auth/ldap/ntlmsess'); |
58eada35 | 1901 | if (!isset($cf[$key]) || $cf[$key] === '') { |
065e2cc0 | 1902 | return false; |
1903 | } | |
1904 | $username = $cf[$key]; | |
1905 | // Here we want to trigger the whole authentication machinery | |
1906 | // to make sure no step is bypassed... | |
1907 | $user = authenticate_user_login($username, $key); | |
1908 | if ($user) { | |
1909 | add_to_log(SITEID, 'user', 'login', "view.php?id=$USER->id&course=".SITEID, | |
1910 | $user->id, 0, $user->id); | |
b7b64ff2 | 1911 | complete_user_login($user); |
065e2cc0 | 1912 | |
1913 | // Cleanup the key to prevent reuse... | |
1914 | // and to allow re-logins with normal credentials | |
1915 | unset_cache_flag('auth/ldap/ntlmsess', $key); | |
1916 | ||
1917 | /// Redirection | |
1918 | if (user_not_fully_set_up($USER)) { | |
1919 | $urltogo = $CFG->wwwroot.'/user/edit.php'; | |
1920 | // We don't delete $SESSION->wantsurl yet, so we get there later | |
1921 | } else if (isset($SESSION->wantsurl) and (strpos($SESSION->wantsurl, $CFG->wwwroot) === 0)) { | |
1922 | $urltogo = $SESSION->wantsurl; /// Because it's an address in this site | |
1923 | unset($SESSION->wantsurl); | |
1924 | } else { | |
1925 | // no wantsurl stored or external - go to homepage | |
1926 | $urltogo = $CFG->wwwroot.'/'; | |
1927 | unset($SESSION->wantsurl); | |
decd8016 | 1928 | } |
065e2cc0 | 1929 | redirect($urltogo); |
decd8016 | 1930 | } |
065e2cc0 | 1931 | // Should never reach here. |
decd8016 | 1932 | return false; |
5117d598 | 1933 | } |
1e8713ea | 1934 | |
6bc1e5d5 | 1935 | /** |
1936 | * Sync roles for this user | |
1937 | * | |
1938 | * @param $user object user object (without system magic quotes) | |
1939 | */ | |
1940 | function sync_roles($user) { | |
1941 | $iscreator = $this->iscreator($user->username); | |
1942 | if ($iscreator === null) { | |
1943 | return; //nothing to sync - creators not configured | |
1944 | } | |
1945 | ||
4f0c2d00 | 1946 | if ($roles = get_archetype_roles('coursecreator')) { |
6bc1e5d5 | 1947 | $creatorrole = array_shift($roles); // We can only use one, let's use the first one |
1948 | $systemcontext = get_context_instance(CONTEXT_SYSTEM); | |
1949 | ||
1950 | if ($iscreator) { // Following calls will not create duplicates | |
df997f84 | 1951 | role_assign($creatorrole->id, $user->id, $systemcontext->id, 'auth_ldap'); |
6bc1e5d5 | 1952 | } else { |
1953 | //unassign only if previously assigned by this plugin! | |
df997f84 | 1954 | role_unassign($creatorrole->id, $user->id, $systemcontext->id, 'auth_ldap'); |
6bc1e5d5 | 1955 | } |
1956 | } | |
1957 | } | |
1958 | ||
b9ddb2d5 | 1959 | /** |
1960 | * Prints a form for configuring this authentication plugin. | |
1961 | * | |
1962 | * This function is called from admin/auth.php, and outputs a full page with | |
1963 | * a form for configuring this plugin. | |
1964 | * | |
1965 | * @param array $page An object containing all the data for this page. | |
1966 | */ | |
139ebfdb | 1967 | function config_form($config, $err, $user_fields) { |
95cb3955 I |
1968 | global $OUTPUT; |
1969 | ||
139ebfdb | 1970 | include 'config.html'; |
b9ddb2d5 | 1971 | } |
1972 | ||
1973 | /** | |
1974 | * Processes and stores configuration data for this authentication plugin. | |
1975 | */ | |
1976 | function process_config($config) { | |
1977 | // set to defaults if undefined | |
139ebfdb | 1978 | if (!isset($config->host_url)) |
b9ddb2d5 | 1979 | { $config->host_url = ''; } |
139ebfdb | 1980 | if (empty($config->ldapencoding)) |
1981 | { $config->ldapencoding = 'utf-8'; } | |
1982 | if (!isset($config->contexts)) | |
b9ddb2d5 | 1983 | { $config->contexts = ''; } |
139ebfdb | 1984 | if (!isset($config->user_type)) |
1985 | { $config->user_type = 'default'; } | |
1986 | if (!isset($config->user_attribute)) | |
b9ddb2d5 | 1987 | { $config->user_attribute = ''; } |
139ebfdb | 1988 | if (!isset($config->search_sub)) |
b9ddb2d5 | 1989 | { $config->search_sub = ''; } |
139ebfdb | 1990 | if (!isset($config->opt_deref)) |
b9ddb2d5 | 1991 | { $config->opt_deref = ''; } |
139ebfdb | 1992 | if (!isset($config->preventpassindb)) |
1993 | { $config->preventpassindb = 0; } | |
1994 | if (!isset($config->bind_dn)) | |
b9ddb2d5 | 1995 | {$config->bind_dn = ''; } |
139ebfdb | 1996 | if (!isset($config->bind_pw)) |
b9ddb2d5 | 1997 | {$config->bind_pw = ''; } |
8dcf8888 I |
1998 | if (!isset($config->ldap_version)) |
1999 | {$config->ldap_version = '2'; } | |
139ebfdb | 2000 | if (!isset($config->objectclass)) |
b9ddb2d5 | 2001 | {$config->objectclass = ''; } |
139ebfdb | 2002 | if (!isset($config->memberattribute)) |
b9ddb2d5 | 2003 | {$config->memberattribute = ''; } |
cd874e21 | 2004 | if (!isset($config->memberattribute_isdn)) |
2005 | {$config->memberattribute_isdn = ''; } | |
139ebfdb | 2006 | if (!isset($config->creators)) |
b9ddb2d5 | 2007 | {$config->creators = ''; } |
139ebfdb | 2008 | if (!isset($config->create_context)) |
b9ddb2d5 | 2009 | {$config->create_context = ''; } |
139ebfdb | 2010 | if (!isset($config->expiration)) |
b9ddb2d5 | 2011 | {$config->expiration = ''; } |
139ebfdb | 2012 | if (!isset($config->expiration_warning)) |
b9ddb2d5 | 2013 | {$config->expiration_warning = '10'; } |
139ebfdb | 2014 | if (!isset($config->expireattr)) |
b9ddb2d5 | 2015 | {$config->expireattr = ''; } |
139ebfdb | 2016 | if (!isset($config->gracelogins)) |
b9ddb2d5 | 2017 | {$config->gracelogins = ''; } |
139ebfdb | 2018 | if (!isset($config->graceattr)) |
b9ddb2d5 | 2019 | {$config->graceattr = ''; } |
139ebfdb | 2020 | if (!isset($config->auth_user_create)) |
b9ddb2d5 | 2021 | {$config->auth_user_create = ''; } |
139ebfdb | 2022 | if (!isset($config->forcechangepassword)) |
2023 | {$config->forcechangepassword = 0; } | |
b9ddb2d5 | 2024 | if (!isset($config->stdchangepassword)) |
8dd009f3 | 2025 | {$config->stdchangepassword = 0; } |
344514fc | 2026 | if (!isset($config->passtype)) |
2027 | {$config->passtype = 'plaintext'; } | |
b9ddb2d5 | 2028 | if (!isset($config->changepasswordurl)) |
2029 | {$config->changepasswordurl = ''; } | |
139ebfdb | 2030 | if (!isset($config->removeuser)) |
6f87ef52 | 2031 | {$config->removeuser = AUTH_REMOVEUSER_KEEP; } |
b95b46a8 | 2032 | if (!isset($config->ntlmsso_enabled)) |
2033 | {$config->ntlmsso_enabled = 0; } | |
2034 | if (!isset($config->ntlmsso_subnet)) | |
2035 | {$config->ntlmsso_subnet = ''; } | |
16ceeb64 | 2036 | if (!isset($config->ntlmsso_ie_fastpath)) |
2037 | {$config->ntlmsso_ie_fastpath = 0; } | |
b9ddb2d5 | 2038 | |
2039 | // save settings | |
2040 | set_config('host_url', $config->host_url, 'auth/ldap'); | |
139ebfdb | 2041 | set_config('ldapencoding', $config->ldapencoding, 'auth/ldap'); |
2042 | set_config('host_url', $config->host_url, 'auth/ldap'); | |
b9ddb2d5 | 2043 | set_config('contexts', $config->contexts, 'auth/ldap'); |
2044 | set_config('user_type', $config->user_type, 'auth/ldap'); | |
2045 | set_config('user_attribute', $config->user_attribute, 'auth/ldap'); | |
2046 | set_config('search_sub', $config->search_sub, 'auth/ldap'); | |
2047 | set_config('opt_deref', $config->opt_deref, 'auth/ldap'); | |
2048 | set_config('preventpassindb', $config->preventpassindb, 'auth/ldap'); | |
2049 | set_config('bind_dn', $config->bind_dn, 'auth/ldap'); | |
2050 | set_config('bind_pw', $config->bind_pw, 'auth/ldap'); | |
8dcf8888 | 2051 | set_config('ldap_version', $config->ldap_version, 'auth/ldap'); |
cfcb7a17 | 2052 | set_config('objectclass', trim($config->objectclass), 'auth/ldap'); |
b9ddb2d5 | 2053 | set_config('memberattribute', $config->memberattribute, 'auth/ldap'); |
cd874e21 | 2054 | set_config('memberattribute_isdn', $config->memberattribute_isdn, 'auth/ldap'); |
b9ddb2d5 | 2055 | set_config('creators', $config->creators, 'auth/ldap'); |
2056 | set_config('create_context', $config->create_context, 'auth/ldap'); | |
2057 | set_config('expiration', $config->expiration, 'auth/ldap'); | |
2058 | set_config('expiration_warning', $config->expiration_warning, 'auth/ldap'); | |
2059 | set_config('expireattr', $config->expireattr, 'auth/ldap'); | |
2060 | set_config('gracelogins', $config->gracelogins, 'auth/ldap'); | |
2061 | set_config('graceattr', $config->graceattr, 'auth/ldap'); | |
2062 | set_config('auth_user_create', $config->auth_user_create, 'auth/ldap'); | |
2063 | set_config('forcechangepassword', $config->forcechangepassword, 'auth/ldap'); | |
2064 | set_config('stdchangepassword', $config->stdchangepassword, 'auth/ldap'); | |
344514fc | 2065 | set_config('passtype', $config->passtype, 'auth/ldap'); |
b9ddb2d5 | 2066 | set_config('changepasswordurl', $config->changepasswordurl, 'auth/ldap'); |
139ebfdb | 2067 | set_config('removeuser', $config->removeuser, 'auth/ldap'); |
b95b46a8 | 2068 | set_config('ntlmsso_enabled', (int)$config->ntlmsso_enabled, 'auth/ldap'); |
2069 | set_config('ntlmsso_subnet', $config->ntlmsso_subnet, 'auth/ldap'); | |
16ceeb64 | 2070 | set_config('ntlmsso_ie_fastpath', (int)$config->ntlmsso_ie_fastpath, 'auth/ldap'); |
b9ddb2d5 | 2071 | |
2072 | return true; | |
2073 | } | |
2074 | ||
139ebfdb | 2075 | /** |
2076 | * Quote control characters in texts used in ldap filters - see rfc2254.txt | |
2077 | * | |
2078 | * @param string | |
2079 | */ | |
2080 | function filter_addslashes($text) { | |
2081 | $text = str_replace('\\', '\\5c', $text); | |
2082 | $text = str_replace(array('*', '(', ')', "\0"), | |
2083 | array('\\2a', '\\28', '\\29', '\\00'), $text); | |
2084 | return $text; | |
2085 | } | |
2086 | ||
2087 | /** | |
2088 | * Quote control characters in quoted "texts" used in ldap | |
2089 | * | |
2090 | * @param string | |
2091 | */ | |
2092 | function ldap_addslashes($text) { | |
2093 | $text = str_replace('\\', '\\\\', $text); | |
2094 | $text = str_replace(array('"', "\0"), | |
2095 | array('\\"', '\\00'), $text); | |
2096 | return $text; | |
2097 | } | |
bffe39c6 | 2098 | |
2099 | /** | |
2100 | * Get password expiration time for a given user from Active Directory | |
2101 | * | |
2102 | * @param string $pwdlastset The time last time we changed the password. | |
2103 | * @param resource $lcapconn The open LDAP connection. | |
2104 | * @param string $user_dn The distinguished name of the user we are checking. | |
2105 | * | |
2106 | * @return string $unixtime | |
2107 | */ | |
2108 | function ldap_get_ad_pwdexpire($pwdlastset, $ldapconn, $user_dn){ | |
2109 | define ('ROOTDSE', ''); | |
2110 | // UF_DONT_EXPIRE_PASSWD value taken from MSDN directly | |
2111 | define ('UF_DONT_EXPIRE_PASSWD', 0x00010000); | |
e295df44 | 2112 | |
bffe39c6 | 2113 | global $CFG; |
2114 | ||
2115 | if (!function_exists('bcsub')) { | |
2116 | error_log ('You need the BCMath extension to use grace logins with Active Directory'); | |
2117 | return 0; | |
2118 | } | |
2119 | ||
2120 | // If UF_DONT_EXPIRE_PASSWD flag is set in user's | |
2121 | // userAccountControl attribute, the password doesn't expire. | |
cfcb7a17 | 2122 | $sr = ldap_read($ldapconn, $user_dn, '(objectClass=*)', |
bffe39c6 | 2123 | array('userAccountControl')); |
2124 | if (!$sr) { | |
2125 | error_log("ldap: error getting userAccountControl for $user_dn"); | |
2126 | // don't expire password, as we are not sure it has to be | |
2127 | // expired or not. | |
2128 | return 0; | |
2129 | } | |
e295df44 | 2130 | |
bffe39c6 | 2131 | $info = $this->ldap_get_entries($ldapconn, $sr); |
2132 | $useraccountcontrol = $info[0]['userAccountControl'][0]; | |
2133 | if ($useraccountcontrol & UF_DONT_EXPIRE_PASSWD) { | |
2134 | // password doesn't expire. | |
2135 | return 0; | |
2136 | } | |
e295df44 | 2137 | |
bffe39c6 | 2138 | // If pwdLastSet is zero, the user must change his/her password now |
2139 | // (unless UF_DONT_EXPIRE_PASSWD flag is set, but we already | |
2140 | // tested this above) | |
2141 | if ($pwdlastset === '0') { | |
2142 | // password has expired | |
2143 | return -1; | |
2144 | } | |
e295df44 | 2145 | |
bffe39c6 | 2146 | // ---------------------------------------------------------------- |
2147 | // Password expiration time in Active Directory is the composition of | |
2148 | // two values: | |
2149 | // | |
2150 | // - User's pwdLastSet attribute, that stores the last time | |
2151 | // the password was changed. | |
2152 | // | |
2153 | // - Domain's maxPwdAge attribute, that sets how long | |
2154 | // passwords last in this domain. | |
2155 | // | |
2156 | // We already have the first value (passed in as a parameter). We | |
2157 | // need to get the second one. As we don't know the domain DN, we | |
2158 | // have to query rootDSE's defaultNamingContext attribute to get | |
2159 | // it. Then we have to query that DN's maxPwdAge attribute to get | |
2160 | // the real value. | |
2161 | // | |
2162 | // Once we have both values, we just need to combine them. But MS | |
2163 | // chose to use a different base and unit for time measurements. | |
2164 | // So we need to convert the values to Unix timestamps (see | |
2165 | // details below). | |
2166 | // ---------------------------------------------------------------- | |
e295df44 | 2167 | |
cfcb7a17 | 2168 | $sr = ldap_read($ldapconn, ROOTDSE, '(objectClass=*)', |
bffe39c6 | 2169 | array('defaultNamingContext')); |
2170 | if (!$sr) { | |
2171 | error_log("ldap: error querying rootDSE for Active Directory"); | |
2172 | return 0; | |
2173 | } | |
e295df44 | 2174 | |
bffe39c6 | 2175 | $info = $this->ldap_get_entries($ldapconn, $sr); |
2176 | $domaindn = $info[0]['defaultNamingContext'][0]; | |
e295df44 | 2177 | |
cfcb7a17 | 2178 | $sr = ldap_read ($ldapconn, $domaindn, '(objectClass=*)', |
bffe39c6 | 2179 | array('maxPwdAge')); |
2180 | $info = $this->ldap_get_entries($ldapconn, $sr); | |
2181 | $maxpwdage = $info[0]['maxPwdAge'][0]; | |
2182 | ||
2183 | // ---------------------------------------------------------------- | |
2184 | // MSDN says that "pwdLastSet contains the number of 100 nanosecond | |
2185 | // intervals since January 1, 1601 (UTC), stored in a 64 bit integer". | |
2186 | // | |
2187 | // According to Perl's Date::Manip, the number of seconds between | |
2188 | // this date and Unix epoch is 11644473600. So we have to | |
2189 | // substract this value to calculate a Unix time, once we have | |
2190 | // scaled pwdLastSet to seconds. This is the script used to | |
2191 | // calculate the value shown above: | |
2192 | // | |
2193 | // #!/usr/bin/perl -w | |
2194 | // | |
2195 | // use Date::Manip; | |
2196 | // | |
2197 | // $date1 = ParseDate ("160101010000 UTC"); | |
2198 | // $date2 = ParseDate ("197001010000 UTC"); | |
2199 | // $delta = DateCalc($date1, $date2, \$err); | |
2200 | // $secs = Delta_Format($delta, 0, "%st"); | |
2201 | // print "$secs \n"; | |
2202 | // | |
2203 | // MSDN also says that "maxPwdAge is stored as a large integer that | |
2204 | // represents the number of 100 nanosecond intervals from the time | |
2205 | // the password was set before the password expires." We also need | |
2206 | // to scale this to seconds. Bear in mind that this value is stored | |
2207 | // as a _negative_ quantity (at least in my AD domain). | |
2208 | // | |
2209 | // As a last remark, if the low 32 bits of maxPwdAge are equal to 0, | |
2210 | // the maximum password age in the domain is set to 0, which means | |
e295df44 | 2211 | // passwords do not expire (see |
bffe39c6 | 2212 | // http://msdn2.microsoft.com/en-us/library/ms974598.aspx) |
2213 | // | |
2214 | // As the quantities involved are too big for PHP integers, we | |
2215 | // need to use BCMath functions to work with arbitrary precision | |
2216 | // numbers. | |
2217 | // ---------------------------------------------------------------- | |
e295df44 | 2218 | |
bffe39c6 | 2219 | |
2220 | // If the low order 32 bits are 0, then passwords do not expire in | |
2221 | // the domain. Just do '$maxpwdage mod 2^32' and check the result | |
2222 | // (2^32 = 4294967296) | |
2223 | if (bcmod ($maxpwdage, 4294967296) === '0') { | |
2224 | return 0; | |
2225 | } | |
2226 | ||
2227 | // Add up pwdLastSet and maxPwdAge to get password expiration | |
2228 | // time, in MS time units. Remember maxPwdAge is stored as a | |
2229 | // _negative_ quantity, so we need to substract it in fact. | |
2230 | $pwdexpire = bcsub ($pwdlastset, $maxpwdage); | |
e295df44 | 2231 | |
bffe39c6 | 2232 | // Scale the result to convert it to Unix time units and return |
2233 | // that value. | |
2234 | return bcsub( bcdiv($pwdexpire, '10000000'), '11644473600'); | |
2235 | } | |
2236 | ||
b9ddb2d5 | 2237 | } |
2238 | ||
5117d598 | 2239 |