/[drupal]/contributions/modules/ldap_integration/ldapauth.module
ViewVC logotype

Contents of /contributions/modules/ldap_integration/ldapauth.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.46 - (show annotations) (download) (as text)
Tue Oct 27 14:29:16 2009 UTC (4 weeks, 2 days ago) by miglius
Branch: MAIN
CVS Tags: DRUPAL-6--1-0-BETA2, HEAD
Changes since 1.45: +32 -24 lines
File MIME type: text/x-php
ldap_integration: fixed database table indexes, #612956 by lkeller
1 <?php
2 // $Id: ldapauth.module,v 1.45 2009/07/28 14:03:05 miglius Exp $
3
4 /**
5 * @file
6 * ldapauth provides authentication against ldap server.
7 */
8
9 //////////////////////////////////////////////////////////////////////////////
10
11 define('LDAPAUTH_AUTH_MIXED', 0);
12 define('LDAPAUTH_AUTH_EXCLUSIVED', 1);
13 define('LDAPAUTH_CONFLICT_LOG', 0);
14 define('LDAPAUTH_CONFLICT_RESOLVE', 1);
15 define('LDAPAUTH_EMAIL_FIELD_NO', 0);
16 define('LDAPAUTH_EMAIL_FIELD_REMOVE', 1);
17 define('LDAPAUTH_EMAIL_FIELD_DISABLE', 2);
18 define('LDAPAUTH_PROFILE', 'LDAP authentication');
19 define('LDAPAUTH_PROFILE_WEIGHT', 4);
20
21 define('LDAPAUTH_LOGIN_PROCESS', variable_get('ldapauth_login_process', LDAPAUTH_AUTH_MIXED));
22 define('LDAPAUTH_LOGIN_CONFLICT', variable_get('ldapauth_login_conflict', LDAPAUTH_CONFLICT_LOG));
23 define('LDAPAUTH_FORGET_PASSWORDS', variable_get('ldapauth_forget_passwords', TRUE));
24 define('LDAPAUTH_SYNC_PASSWORDS', variable_get('ldapauth_sync_passwords', FALSE));
25 define('LDAPAUTH_DISABLE_PASS_CHANGE', variable_get('ldapauth_disable_pass_change', FALSE));
26 define('LDAPAUTH_ALTER_EMAIL_FIELD', variable_get('ldapauth_alter_email_field', LDAPAUTH_EMAIL_FIELD_NO));
27 define('LDAPAUTH_DEFAULT_USER_ATTR', variable_get('ldapauth_default_user_attr', 'uid'));
28 define('LDAPAUTH_DEFAULT_MAIL_ATTR', variable_get('ldapauth_default_mail_attr', 'mail'));
29
30 //////////////////////////////////////////////////////////////////////////////
31 // Core API hooks
32
33 /**
34 * Implements hook_init().
35 */
36 function ldapauth_init() {
37 require_once(drupal_get_path('module', 'ldapauth') .'/includes/LDAPInterface.inc');
38 }
39
40 /**
41 * Implementation of hook_help().
42 */
43 function ldapauth_help($path, $arg) {
44 switch ($path) {
45 case 'admin/settings/ldapauth':
46 return '<p>'. t('A system wide settings will affect all configured LDAP servers.') .'</p>';
47 }
48 }
49
50 /**
51 * Implements hook_menu().
52 */
53 function ldapauth_menu() {
54 return array(
55 'admin/settings/ldap' => array(
56 'title' => 'LDAP',
57 'description' => 'Configure LDAP integration settings.',
58 'page callback' => 'ldapauth_admin_menu_block_page',
59 'access arguments' => array('administer ldap modules'),
60 'file' => 'ldapauth.admin.inc',
61 ),
62 'admin/settings/ldap/ldapauth' => array(
63 'title' => 'Authentication',
64 'description' => 'Configure LDAP authentication settings.',
65 'page callback' => 'drupal_get_form',
66 'page arguments' => array('ldapauth_admin_settings'),
67 'access arguments' => array('administer ldap modules'),
68 'file' => 'ldapauth.admin.inc',
69 ),
70 'admin/settings/ldap/ldapauth/configure' => array(
71 'title' => 'Settings',
72 'type' => MENU_DEFAULT_LOCAL_TASK,
73 ),
74 'admin/settings/ldap/ldapauth/list' => array(
75 'title' => 'List',
76 'page callback' => 'drupal_get_form',
77 'page arguments' => array('ldapauth_admin_list'),
78 'type' => MENU_LOCAL_TASK,
79 'weight' => 1,
80 'access arguments' => array('administer ldap modules'),
81 'file' => 'ldapauth.admin.inc',
82 ),
83 'admin/settings/ldap/ldapauth/add' => array(
84 'title' => 'Add Server',
85 'page callback' => 'drupal_get_form',
86 'page arguments' => array('ldapauth_admin_form', 4),
87 'type' => MENU_LOCAL_TASK,
88 'weight' => 2,
89 'access arguments' => array('administer ldap modules'),
90 'file' => 'ldapauth.admin.inc',
91 ),
92 'admin/settings/ldap/ldapauth/edit' => array(
93 'title' => 'Configure LDAP Server',
94 'page callback' => 'drupal_get_form',
95 'page arguments' => array('ldapauth_admin_form', 4, 5),
96 'type' => MENU_CALLBACK,
97 'access arguments' => array('administer ldap modules'),
98 'file' => 'ldapauth.admin.inc',
99 ),
100 'admin/settings/ldap/ldapauth/edit/%/test' => array(
101 'title' => 'Test LDAP Server',
102 'page callback' => '_ldapauth_ajax_test',
103 'page arguments' => array(5),
104 'type' => MENU_CALLBACK,
105 'access arguments' => array('administer ldap modules'),
106 'file' => 'ldapauth.admin.inc',
107 ),
108 'admin/settings/ldap/ldapauth/delete' => array(
109 'title' => 'Delete LDAP Server',
110 'page callback' => 'drupal_get_form',
111 'page arguments' => array('ldapauth_admin_delete', 5),
112 'type' => MENU_CALLBACK,
113 'access arguments' => array('administer ldap modules'),
114 'file' => 'ldapauth.admin.inc',
115 ),
116 'admin/settings/ldap/ldapauth/activate' => array(
117 'title' => 'Activate LDAP Source',
118 'page callback' => 'drupal_get_form',
119 'page arguments' => array('ldapauth_admin_activate'),
120 'access arguments' => array('administer ldap modules'),
121 'type' => MENU_CALLBACK,
122 'file' => 'ldapauth.admin.inc',
123 ),
124 'admin/settings/ldap/ldapauth/deactivate' => array(
125 'title' => 'De-activate LDAP Source',
126 'page callback' => 'drupal_get_form',
127 'page arguments' => array('ldapauth_admin_deactivate'),
128 'access arguments' => array('administer ldap modules'),
129 'type' => MENU_CALLBACK,
130 'file' => 'ldapauth.admin.inc',
131 ),
132 );
133 }
134
135 /**
136 * Implements hook_theme().
137 */
138 function ldapauth_theme() {
139 return array(
140 'ldapauth_admin_list' => array(
141 'arguments' => array('form' => NULL),
142 'file' => 'ldapauth.theme.inc'
143 ),
144 );
145 }
146
147 /**
148 * Implements hook_user().
149 */
150 function ldapauth_user($op, &$edit, &$account, $category = NULL) {
151 switch ($op) {
152 case 'update':
153 if ($category == 'account') {
154
155 // If authentication is being done in "LDAP only" mode, passwords
156 // should not be written to the database, or users would be able
157 // to log in even after removing their LDAP entry.
158 if (isset($account->ldap_authentified) && (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_EXCLUSIVED || !LDAPAUTH_SYNC_PASSWORDS))
159 $edit['pass'] = NULL;
160 }
161
162 if (LDAPAUTH_ALTER_EMAIL_FIELD == LDAPAUTH_EMAIL_FIELD_REMOVE)
163 unset($edit['mail']);
164 break;
165 case 'view':
166 if (user_access('administer users') && isset($account->ldap_authentified) && $account->ldap_dn) {
167 $row = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE sid = %d", $account->ldap_config));
168 $account->content[t(LDAPAUTH_PROFILE)] = array(
169 '#type' => 'user_profile_category',
170 '#title' => t(LDAPAUTH_PROFILE),
171 '#attributes' => array('class' => 'ldapauth-entry'),
172 '#weight' => LDAPAUTH_PROFILE_WEIGHT,
173 'ldap_server' => array('#type' => 'user_profile_item', '#title' => t('LDAP server'), '#value' => l($row->name, 'admin/settings/ldap/ldapauth/edit/'. $row->sid), '#weight' => 0),
174 'ldap_dn' => array('#type' => 'user_profile_item', '#title' => t('LDAP dn'), '#value' => $account->ldap_dn, '#weight' => 1),
175 );
176 }
177 break;
178 }
179 }
180
181 /**
182 * Implementation of hook_menu_alter().
183 */
184 function ldapauth_menu_alter(&$callbacks) {
185 if (variable_get('ldapauth_disable_pass_change', FALSE))
186 unset($callbacks['user/password']);
187 }
188
189 /**
190 * Implements hook_perm().
191 */
192 function ldapauth_perm() {
193 return array('administer ldap modules');
194 }
195
196 /**
197 * Implements hook_form_alter().
198 */
199 function ldapauth_form_alter(&$form, $form_state, $form_id) {
200 global $user;
201
202 // Replace the drupal authenticate function is it's used as validation.
203 if (isset($form['#validate']) && is_array($form['#validate']) && ($key = array_search('user_login_authenticate_validate', $form['#validate'])))
204 $form['#validate'][$key] = 'ldapauth_login_authenticate_validate';
205
206 switch ($form_id) {
207 case 'user_login_block':
208 if (LDAPAUTH_DISABLE_PASS_CHANGE)
209 unset($form['links']);
210 break;
211 case 'user_profile_form':
212 $account = $form["_account"]["#value"];
213 if ($user->uid != 1 && isset($account->ldap_authentified)) {
214 if (LDAPAUTH_DISABLE_PASS_CHANGE)
215 unset($form['account']['pass']);
216
217 switch (LDAPAUTH_ALTER_EMAIL_FIELD) {
218 case LDAPAUTH_EMAIL_FIELD_REMOVE :
219 $form['account']['mail']['#type'] = 'hidden';
220 $form['account']['mail']['#attributes']['READONLY'] = 'READONLY';
221 break;
222 case LDAPAUTH_EMAIL_FIELD_DISABLE :
223 $form['account']['mail']['#attributes']['READONLY'] = 'READONLY';
224 break;
225 }
226
227 // Remove fieldset if empty.
228 if (isset($form['account']) && !isset($form['account']['pass']) && $form['account']['mail']['#type'] == 'hidden' && count(array_filter($form['account'], create_function('$a', 'return is_array($a) ? TRUE : FALSE;'))) == 1) {
229 $form['mail'] = $form['account']['mail'];
230 unset($form['account']);
231 }
232 }
233 break;
234 }
235 }
236
237 /**
238 * Implements hook_cron().
239 */
240 function ldapauth_cron() {
241 cache_clear_all(NULL, 'cache_filter');
242 }
243
244 /**
245 * Implements hook_exit().
246 */
247 function ldapauth_exit() {
248 // We delete the login info here, instead of just not storing it at
249 // _ldapauth_auth(), so at least ldapgroups can use it at login time.
250 if (LDAPAUTH_FORGET_PASSWORDS && isset($_SESSION['ldap_login'])) {
251 unset($_SESSION['ldap_login']);
252 }
253 }
254
255 //////////////////////////////////////////////////////////////////////////////
256 // Login process functions
257
258 /**
259 * Main user validation function.
260 *
261 * If successful, sets the global $user object.
262 */
263 function ldapauth_login_authenticate_validate($form, &$form_state) {
264 ldapauth_authenticate($form_state['values']);
265 }
266
267 /**
268 * Main user authentication function.
269 *
270 * If successful, sets the global $user object.
271 */
272 function ldapauth_authenticate($form_values = array()) {
273 global $user, $_ldapauth_ldap;
274
275 $name = $form_values['name'];
276 $pass = trim($form_values['pass']);
277
278 // The user_login_name_validate() is not called if the user is being authenticated
279 // from the httpauth or services modules, therefore call it here.
280 $form_state['values'] = $form_values;
281 user_login_name_validate(NULL, $form_state);
282
283 // (Design decision) uid=1 (admin user) must always authenticate to local database
284 // this user is critical for all drupal admin and upgrade operations so it is best
285 // left with drupal's native authentication.
286 $result = db_query("SELECT uid FROM {users} WHERE name = '%s' AND uid = '1'", $name);
287 if ($account = db_fetch_object($result)) {
288 user_authenticate($form_values);
289 return;
290 }
291
292 if (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_MIXED) {
293 // Authenticate local users first.
294 $result = db_query("SELECT name, data FROM {users} WHERE name='%s'", $name);
295 if ($row = db_fetch_array($result)) {
296 $data = unserialize($row['data']);
297 if (!isset($data['ldap_authentified']) || $data['ldap_authentified'] == 0) {
298 // A local user with same name exists - authenticate that user.
299 if (user_authenticate($form_values)) {
300 // Nullify global ldap resource for good measure.
301 unset($_ldapauth_ldap);
302 return;
303 }
304 }
305 }
306 }
307
308 $account = user_load(array('name' => $name, 'status' => 1));
309 if ($account && drupal_is_denied('mail', $account->mail)) {
310 form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $account->name)));
311 }
312
313 // If there is any validations errors, we do not query LDAP.
314 if (form_get_errors())
315 return;
316
317 // Authenticate LDAP user.
318 if (!($dn = _ldapauth_auth($name, $pass)))
319 return;
320
321 if (!$account) {
322 // Register this new user.
323 if ($ldap_user = _ldapauth_user_lookup($name)) {
324 // If mail attribute is missing, set the name as mail.
325 $init = $mail = key_exists(($_ldapauth_ldap->getOption('mail_attr') ? $_ldapauth_ldap->getOption('mail_attr') : LDAPAUTH_DEFAULT_MAIL_ATTR), $ldap_user) ? $ldap_user[$_ldapauth_ldap->getOption('mail_attr')][0] : $name;
326
327 // Check if the e-mail is not denied.
328 if (drupal_is_denied('mail', $mail)) {
329 form_set_error('name', t('The name %name is registered using a reserved e-mail address and therefore could not be logged in.', array('%name' => $name)));
330 return;
331 }
332
333 // Generate a random drupal password. LDAP password will be used anyways.
334 $pass_new = (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_EXCLUSIVED || !LDAPAUTH_SYNC_PASSWORDS) ? user_password(20) : $pass;
335
336 $userinfo = array('name' => $name, 'pass' => $pass_new, 'mail' => $mail, 'init' => $init, 'status' => 1, 'authname_ldapauth' => $name, 'ldap_authentified' => TRUE, 'ldap_dn' => $ldap_user['dn'], 'ldap_config' => $_ldapauth_ldap->getOption('sid'));
337 $user = user_save('', $userinfo);
338 watchdog('ldapauth', 'New external user %name created from the LDAP server %server.', array('%name' => $name, '%server' => $_ldapauth_ldap->getOption('name')), WATCHDOG_NOTICE, l(t('edit'), 'user/'. $user->uid .'/edit'));
339 }
340 }
341 else {
342 // Login existing user.
343 $data = array(
344 'ldap_dn' => $dn,
345 'ldap_config' => $_ldapauth_ldap->getOption('sid'),
346 );
347
348 if (!isset($account->ldap_authentified)) {
349 // LDAP and local user conflict.
350 if (LDAPAUTH_LOGIN_CONFLICT == LDAPAUTH_CONFLICT_LOG) {
351 watchdog('ldapauth', 'LDAP user with DN %dn has a naming conflict with a local drupal user %name', array('%dn' => $dn, '%name' => $account->name), WATCHDOG_ERROR);
352 drupal_set_message(t('Another user already exists in the system with the same login name. You should contact the system administrator in order to solve this conflict.'), 'error');
353 return;
354 }
355 else {
356 $data['ldap_authentified'] = TRUE;
357 $data['authname_ldapauth'] = $name;
358 }
359 }
360
361 // Successfull login.
362 // Save the new login data.
363 if (LDAPAUTH_LOGIN_PROCESS == LDAPAUTH_AUTH_MIXED && LDAPAUTH_SYNC_PASSWORDS)
364 $data['pass'] = $pass;
365 $user = user_save($account, $data);
366 }
367
368 // Save user's authentication data to the session.
369 $_SESSION['ldap_login']['dn'] = $dn;
370 $_SESSION['ldap_login']['pass'] = $pass;
371
372 user_authenticate_finalize($form_values);
373 return $user;
374 }
375
376 /**
377 * Authenticate the user against LDAP server.
378 *
379 * @param $name
380 * A username.
381 * @param $pass
382 * A password.
383 *
384 * @return
385 * User's LDAP dn success, FALSE otherwise.
386 */
387 function _ldapauth_auth($name, $pass) {
388 global $_ldapauth_ldap;
389
390 // Don't allow empty passwords because they cause problems on some setups.
391 // http://drupal.org/node/87831
392 if (empty($pass))
393 return FALSE;
394
395 // Cycle through LDAP configurations. First one to succeed wins.
396 $result = db_query("SELECT sid FROM {ldapauth} WHERE status = 1 ORDER BY weight");
397 while ($row = db_fetch_object($result)) {
398
399 // Initialize LDAP.
400 if (!_ldapauth_init($row->sid))
401 return FALSE;
402
403 // Look up the user in LDAP.
404 if (!($ldap = _ldapauth_user_lookup($name)) || !isset($ldap['dn']))
405 continue;
406
407 // Filter users based on their LDAP data.
408 if (($code = _ldapauth_ldap_info($row->sid, 'filter_php')) && !eval($code))
409 continue;
410
411 // Try to authenticate.
412 if (!$_ldapauth_ldap->connect($ldap['dn'], $pass))
413 continue;
414
415 return $ldap['dn'];
416 }
417 return FALSE;
418 }
419
420 /**
421 * Queries LDAP server for the user.
422 *
423 * @param $name
424 * A login name.
425 *
426 * @return
427 * An array with user's LDAP data or NULL if not found.
428 */
429 function _ldapauth_user_lookup($name) {
430 global $_ldapauth_ldap;
431
432 if (!$_ldapauth_ldap)
433 return;
434
435 // Transform login name.
436 $login_name = ($code = _ldapauth_ldap_info($_ldapauth_ldap->getOption('sid'), 'login_php')) ? eval($code) : $name;
437
438 // If there is no bindn and bindpw - the connect will be an anonymous connect.
439 $_ldapauth_ldap->connect($_ldapauth_ldap->getOption('binddn'), $_ldapauth_ldap->getOption('bindpw'));
440 foreach (explode("\r\n", $_ldapauth_ldap->getOption('basedn')) as $base_dn) {
441 if (empty($base_dn))
442 continue;
443
444 $name_attr = $_ldapauth_ldap->getOption('user_attr') ? $_ldapauth_ldap->getOption('user_attr') : LDAPAUTH_DEFAULT_USER_ATTR;
445 $filter = $name_attr .'='. $login_name;
446 $result = $_ldapauth_ldap->search($base_dn, $filter);
447 if (!$result)
448 continue;
449
450 $num_matches = $result['count'];
451 // Must find exactly one user for authentication to.
452 if ($num_matches != 1) {
453 watchdog('ldapauth', "Error: %num_matches users found with $%filter under %base_dn.", array('%num_matches' => $num_matches, '%filter' => $filter, '%base_dn' => $base_dn), WATCHDOG_ERROR);
454 continue;
455 }
456 $match = $result[0];
457
458 // These lines serve to fix the attribute name in case a
459 // naughty server (i.e.: MS Active Directory) is messing the
460 // characters' case.
461 // This was contributed by Dan "Gribnif" Wilga, and described
462 // here: http://drupal.org/node/87833
463 if (!isset($match[$name_attr][0])) {
464 $name_attr = drupal_strtolower($name_attr);
465 if (!isset($match[$name_attr][0]))
466 continue;
467 }
468 // Finally, we must filter out results with spaces added before
469 // or after, which are considered OK by LDAP but are no good for us
470 // We allow lettercase independence, as requested by Marc Galera
471 // on http://drupal.org/node/97728
472 //
473 // Some setups have multiple $name_attr per entry, as pointed out by
474 // Clarence "sparr" Risher on http://drupal.org/node/102008, so we
475 // loop through all possible options.
476 foreach ($match[$name_attr] as $value) {
477 if (drupal_strtolower(trim($value)) == drupal_strtolower($login_name))
478 return $match;
479 }
480 }
481 }
482
483 //////////////////////////////////////////////////////////////////////////////
484 // Auxiliary functions
485
486 /**
487 * Initiates the LDAPInterfase class.
488 *
489 * @param $sid
490 * An ID of the LDAP server configuration or user object.
491 *
492 * @return
493 */
494 function _ldapauth_init($sid) {
495 global $_ldapauth_ldap;
496
497 if (!($sid = is_object($sid) ? (isset($sid->ldap_config) ? $sid->ldap_config : NULL) : $sid))
498 return;
499
500 static $servers = array();
501 if (!isset($servers[$sid]))
502 $servers[$sid] = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE status = 1 AND sid = %d", $sid));
503
504 if ($servers[$sid]) {
505 $_ldapauth_ldap = new LDAPInterface();
506 $_ldapauth_ldap->setOption('sid', $sid);
507 $_ldapauth_ldap->setOption('name', $servers[$sid]->name);
508 $_ldapauth_ldap->setOption('server', $servers[$sid]->server);
509 $_ldapauth_ldap->setOption('port', $servers[$sid]->port);
510 $_ldapauth_ldap->setOption('tls', $servers[$sid]->tls);
511 $_ldapauth_ldap->setOption('encrypted', $servers[$sid]->encrypted);
512 $_ldapauth_ldap->setOption('basedn', $servers[$sid]->basedn);
513 $_ldapauth_ldap->setOption('user_attr', $servers[$sid]->user_attr);
514 $_ldapauth_ldap->setOption('mail_attr', $servers[$sid]->mail_attr);
515 $_ldapauth_ldap->setOption('binddn', $servers[$sid]->binddn);
516 $_ldapauth_ldap->setOption('bindpw', $servers[$sid]->bindpw);
517 return $_ldapauth_ldap;
518 }
519 return FALSE;
520 }
521
522 /**
523 * Retrieve the saved ldapgroups saved setting.
524 *
525 * @param $sid
526 * A server ID or user object.
527 * @param $req
528 * An attribute name.
529 *
530 * @return
531 * The attribute value.
532 */
533 function _ldapauth_ldap_info($sid, $req) {
534 if (!($sid = is_object($sid) ? (isset($sid->ldap_config) ? $sid->ldap_config : NULL) : $sid))
535 return;
536
537 static $servers = array();
538 if (!isset($servers[$sid]))
539 $servers[$sid] = db_fetch_object(db_query("SELECT * FROM {ldapauth} WHERE sid = %d", $sid));
540
541 switch ($req) {
542 case 'login_php':
543 return $servers[$sid]->login_php;
544 case 'filter_php':
545 return $servers[$sid]->filter_php;
546 }
547 }
548

  ViewVC Help
Powered by ViewVC 1.1.2