root/ldap-authentication/trunk/ldap-authentication.php

Revision 447, 17.4 kB (checked in by SamBauers, 1 year ago)

ldap-authentication: Added workaround for bbPress bug 670, tagged version 2.0.2

Line 
1 <?php
2 /*
3 Plugin Name: LDAP authentication
4 Plugin URI: http://bbpress.org/plugins/topic/26
5 Description: Allows users to authenticate against an LDAP service
6 Author: Sam Bauers
7 Version: 2.0.2
8 Author URI:
9
10 Version History:
11 1.0     : Initial Release
12 1.0.1     : Small non-critical fixes to ldap_remove_password_capability()
13 1.0.2    : Cookie hacking vulnerability fixed
14           Disabled password reseting function for LDAP users
15           Added option to disable automatic registration of LDAP users
16 1.0.3    : Added option to retrieve LDAP users email address on registration
17 1.0.4    : Added support for new admin menu structure introduced in build 740
18 2.0        : Moved most functions into a class
19           Amalgamated options into new serialized options
20           Fixed issues with enabling disabling features when using permalinks
21           Added support for bb_admin_add_submenu()
22 2.0.1    : Made PHP4 compatible
23 2.0.2    : Workaround for bbPress bug 670 - http://trac.bbpress.org/ticket/670
24 */
25
26
27 /**
28  * LDAP authentication for bbPress version 2.0.2
29  *
30  * ----------------------------------------------------------------------------------
31  *
32  * Copyright (C) 2007 Sam Bauers (sam@viveka.net.au)
33  *
34  * ----------------------------------------------------------------------------------
35  *
36  * LICENSE:
37  *
38  * This program is free software; you can redistribute it and/or modify
39  * it under the terms of the GNU General Public License as published by
40  * the Free Software Foundation; either version 2 of the License, or
41  * (at your option) any later version.
42  *
43  * This program is distributed in the hope that it will be useful,
44  * but WITHOUT ANY WARRANTY; without even the implied warranty of
45  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
46  * GNU General Public License for more details.
47  *
48  * You should have received a copy of the GNU General Public License
49  * along with this program; if not, write to the Free Software
50  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
51  *
52  * ----------------------------------------------------------------------------------
53  *
54  * PHP version 4 and 5
55  *
56  * ----------------------------------------------------------------------------------
57  *
58  * @author    Sam Bauers <sam@viveka.net.au>
59  * @copyright 2007 Sam Bauers
60  * @license   http://www.gnu.org/licenses/gpl.txt GNU General Public License v2
61  * @version   2.0.2
62  **/
63
64
65 /**
66  * Container class for LDAP authentication
67  *
68  * @author  Sam Bauers
69  * @version 1.0.1
70  **/
71 class LDAP_Authentication
72 {
73     /**
74      * The current version of the plugin
75      *
76      * @var string
77      **/
78     var $version = '2.0.1';
79     
80     
81     /**
82      * Whether the plugin is enabled
83      *
84      * @var boolean
85      **/
86     var $enabled;
87     
88     
89     /**
90      * An array of settings for the LDAP server connection
91      *
92      * @var array
93      **/
94     var $server;
95     
96     
97     /**
98      * Whether the plugin has enough settings to work
99      *
100      * @var boolean
101      **/
102     var $active = false;
103     
104     
105     /**
106      * Additional plugin options in an array
107      *
108      * @var array
109      **/
110     var $options;
111     
112     
113     /**
114      * Pulls out database settings
115      *
116      * @return void
117      * @author Sam Bauers
118      **/
119     function LDAP_Authentication()
120     {
121         // An integer set to 1 for enabled or 0 for disabled
122         $this->enabled = bb_get_option('ldap_authentication_enabled');
123         
124         $this->server = bb_get_option('ldap_authentication_server');
125         
126         if (isset($this->server['host']) && isset($this->server['domain'])) {
127             $this->active = true;
128         }
129         
130         $this->options = bb_get_option('ldap_authentication_options');
131     }
132     
133     
134     /**
135      * Returns whether the plugin is active or not
136      *
137      * @return boolean
138      * @author Sam Bauers
139      **/
140     function isActive()
141     {
142         if ($this->enabled && $this->active) {
143             return true;
144         } else {
145             return false;
146         }
147     }
148     
149     
150     /**
151      * Determines whether we are viewing the given page
152      *
153      * Mostly adapted from bb_get_location();
154      *
155      * @return boolean
156      * @author Sam Bauers
157      **/
158     function locationIs($page)
159     {
160         $names = array(
161             $_SERVER['PHP_SELF'],
162             $_SERVER['SCRIPT_FILENAME'],
163             $_SERVER['SCRIPT_NAME']
164         );
165         
166         foreach ($names as $name) {
167             if (false !== strpos($name, '.php')) {
168                 $file = $name;
169             }
170         }
171         
172         if (bb_find_filename($file) == $page) {
173             return true;
174         } else {
175             return false;
176         }
177     }
178     
179     
180     /**
181      * Disables standard registration
182      *
183      * @return void
184      * @author Sam Bauers
185      **/
186     function disableRegistration()
187     {
188         if ($this->isActive() && $this->options['disable_registration'] && $this->locationIs('register.php')) {
189             bb_die(__('Registration is disabled for this forum, please login using your LDAP username and password.'));
190         }
191     }
192     
193     
194     /**
195      * Disables password recovery for users who have LDAP passwords
196      *
197      * @return void
198      * @author Sam Bauers
199      **/
200     function disableLDAPpasswordRecovery()
201     {
202         if ($this->isActive() && $this->locationIs('bb-reset-password.php')) {
203             $user_login = user_sanitize($_POST['user_login']);
204             if (!empty($user_login)) {
205                 $user = bb_get_user_by_name($user_login);
206                 if (substr($user->user_pass, 0, 5) == '^LDAP') {
207                     bb_die(__('Password recovery is not possible for this account because it uses an LDAP username and password to login. To change your LDAP password, please contact your system administrator.'));
208                 }
209             }
210         }
211     }
212     
213     
214     /**
215      * Disables password editing for users who have LDAP passwords
216      *
217      * @return void
218      * @author Sam Bauers
219      **/
220     function disableLDAPpasswordEditing()
221     {
222         global $bb_current_user;
223         
224         if ($this->isActive() && ($this->locationIs('profile.php') || $this->locationIs('profile-edit.php'))) {
225             if (substr($bb_current_user->data->user_pass, 0, 5) == '^LDAP') {
226                 add_filter('bb_user_has_cap', array($this, 'removePasswordCapability'), 10, 2);
227             }
228         }
229     }
230     
231     
232     /**
233      * Removes the change password capability for the current user
234      *
235      * @return array
236      * @author Sam Bauers
237      **/
238     function removePasswordCapability($allcaps, $caps)
239     {
240         if ($caps[0] == 'change_password') {
241             unset($allcaps['change_password']);
242         }
243         
244         return $allcaps;
245     }
246     
247     
248     /**
249      * Replacement for bb_check_login
250      *
251      * @return object
252      * @author Sam Bauers
253      **/
254     function checkLogin($user, $pass, $already_md5 = false)
255     {
256         global $bbdb;
257         
258         $user = user_sanitize($user);
259         
260         if (!$already_md5) {
261             $user_exists = bb_user_exists($user);
262             if (!$user_exists) {
263                 // Check using LDAP
264                 if (!$this->options['disable_automatic_ldap_registration'] && $mail = $this->connectUser($user, $pass, true)) {
265                     // Create the new user in the local database
266                     if ($user_id = $this->newUser($user, $pass, $mail)) {
267                         return $bbdb->get_row("SELECT * FROM $bbdb->users WHERE `ID` = $user_id");
268                     } else {
269                         bb_die(__('Failed to add new LDAP user to local database.'));
270                     }
271                 } else {
272                     return false;
273                 }
274             } else {
275                 if (substr($user_exists->user_pass, 0, 5) == '^LDAP') {
276                     if ($this->connectUser($user, $pass)) {
277                         // Update their MD5 hash in case their password has changed
278                         $bbdb->query("UPDATE $bbdb->users SET user_pass = '^LDAP-" . md5($pass) . "' WHERE user_login = '$user'");
279                         
280                         // Get their record from the local database
281                         return $bbdb->get_row("SELECT * FROM $bbdb->users WHERE user_login = '$user' AND SUBSTRING(SUBSTRING_INDEX(user_pass, '---', 1), 1, 5) = '^LDAP'");
282                     } else {
283                         return false;
284                     }
285                 } else {
286                     $pass = user_sanitize(md5($pass));
287                     return $bbdb->get_row("SELECT * FROM $bbdb->users WHERE user_login = '$user' AND SUBSTRING_INDEX(user_pass, '---', 1) = '$pass'");
288                 }
289             }
290         } else {
291             return $bbdb->get_row("SELECT * FROM $bbdb->users WHERE user_login = '$user' AND MD5( user_pass ) = '$pass'");
292         }
293     }
294     
295     
296     /**
297      * Connects the user to the LDAP server
298      *
299      * @return mixed
300      * @author Sam Bauers
301      **/
302     function connectUser($user, $pass, $register = false)
303     {
304         if ($this->server['port']) {
305             $connection = ldap_connect($this->server['host'], $this->server['port']);
306         } else {
307             $connection = ldap_connect($this->server['host']);
308         }
309         
310         if ($this->server['options']) {
311             $options = explode('|', $this->server['options']);
312             foreach ($options as $option) {
313                 $optionParts = explode(':', $option);
314                 ldap_set_option($connection, $optionParts[0], $optionParts[1]);
315             }
316         }
317         
318         if ($this->server['tls']) {
319             // TLS requires ldap protocol version 3
320             ldap_set_option($connection, LDAP_OPT_PROTOCOL_VERSION, 3);
321             $tlsStart = ldap_start_tls($connection);
322         } else {
323             $tlsStart = true;
324         }
325         
326         if ($connection && $tlsStart) {
327             $uid = 'uid=' . stripslashes(addslashes($user));
328             $bindString = $uid . ',' . $this->server['domain'];
329             
330             $result = ldap_bind($connection, $bindString, stripslashes(addslashes($pass)));
331             
332             if ($result) {
333                 $mail = true;
334                 
335                 if ($register && $this->options['enable_email_retrieval_from_ldap']) {
336                     if ($search = ldap_search($connection, $this->server['domain'], '(' . $uid . ')', array('mail'))) {
337                         if ($entry = ldap_first_entry($connection, $search)) {
338                             $attributes = ldap_get_attributes($connection, $entry);
339                             $mail = $attributes['mail'][0];
340                         }
341                     }
342                 }
343                 
344                 ldap_unbind($result);
345                 ldap_close($connection);
346                 
347                 return $mail;
348             } else {
349                 ldap_close($connection);
350                 return FALSE;
351             }
352         } else {
353             bb_die(__('Could not connect to LDAP authentication service.'));
354         }
355     }
356     
357     
358     /**
359      * Creates a new user with an LDAP password
360      *
361      * @return integer
362      * @author Sam Bauers
363      **/
364     function newUser($user_login, $user_pass, $user_email)
365     {
366         global $bbdb;
367         global $bb_table_prefix;
368         
369         $now = bb_current_time('mysql');
370         $password = '^LDAP-' . md5($user_pass);
371         
372         if ($user_email !== true) {
373             $email = $user_email;
374         }
375         
376         $bbdb->query("INSERT INTO $bbdb->users (user_login, user_pass, user_email, user_registered) VALUES ('$user_login', '$password', '$email', '$now')");
377         $user_id = $bbdb->insert_id;
378         
379         bb_update_usermeta($user_id, $bb_table_prefix . 'capabilities', array('member' => true));
380         
381         do_action('ldap_authentication_new_user', $user_id);
382         
383         return $user_id;
384     }
385 } // END class LDAP_Authentication
386
387
388 // Initialise the class
389 $ldap_authentication = new LDAP_Authentication();
390
391
392 // If active, then add filters via API
393 if ($ldap_authentication->isActive()) {
394     add_action('bb_init', array($ldap_authentication, 'disableRegistration'));
395     add_action('bb_init', array($ldap_authentication, 'disableLDAPpasswordRecovery'));
396     add_action('bb_init', array($ldap_authentication, 'disableLDAPpasswordEditing'));
397     
398     
399     /**
400      * Alias that hooks into LDAP_Authentication class checkLogin function
401      *
402      * This is a pluggable function, so it must sit outside the class like this
403      *
404      * @return object
405      * @author Sam Bauers
406      **/
407     if (!function_exists('bb_check_login')) {
408         function bb_check_login($user, $pass, $already_md5 = false)
409         {
410             global $ldap_authentication;
411             return $ldap_authentication->checkLogin($user, $pass, $already_md5);
412         }
413     }
414 }
415
416
417 /**
418  * The admin pages below are handled outside of the class due to constraints
419  * in the architecture of the admin menu generation routine in bbPress
420  */
421
422
423 // Add filters for the admin area
424 add_action('bb_admin_menu_generator', 'ldap_authentication_admin_page_add');
425 add_action('bb_admin-header.php','ldap_authentication_admin_page_process');
426
427
428 /**
429  * Adds in an item to the $bb_admin_submenu array
430  *
431  * @return void
432  * @author Sam Bauers
433  **/
434 function ldap_authentication_admin_page_add() {
435     if (function_exists('bb_admin_add_submenu')) { // Build 794+
436         bb_admin_add_submenu(__('LDAP authentication'), 'use_keys', 'ldap_authentication_admin_page');
437     } else {
438         global $bb_submenu;
439         $submenu = array(__('LDAP authentication'), 'use_keys', 'ldap_authentication_admin_page');
440         if (isset($bb_submenu['plugins.php'])) { // Build 740-793
441             $bb_submenu['plugins.php'][] = $submenu;
442         } else { // Build 277-739
443             $bb_submenu['site.php'][] = $submenu;
444         }
445     }
446 }
447
448
449 /**
450  * Writes an admin page for the plugin
451  *
452  * @return string
453  * @author Sam Bauers
454  **/
455 function ldap_authentication_admin_page() {
456     $enabled = bb_get_option('ldap_authentication_enabled');
457     $options = bb_get_option('ldap_authentication_options');
458     $server = bb_get_option('ldap_authentication_server');
459     
460     if ($enabled) {
461         $enabled_checked = ' checked="checked"';
462     }
463     if ($options['enable_email_retrieval_from_ldap']) {
464         $enable_email_retrieval_from_ldap_checked = ' checked="checked"';
465     }
466     if ($options['disable_automatic_ldap_registration']) {
467         $disable_automatic_ldap_registration_checked = ' checked="checked"';
468     }
469     if ($options['disable_registration']) {
470         $disable_registration_checked = ' checked="checked"';
471     }
472     if ($server['tls']) {
473         $tls_checked = ' checked="checked"';
474     }
475 ?>
476     <h2>LDAP authentication</h2>
477     <h3>Enable</h3>
478     <form method="post">
479     <p>
480         Enable LDAP authentication here:
481     </p>
482     <p>
483         <input type="checkbox" name="ldap_authentication_enabled" value="1" tabindex="10"<?php echo $enabled_checked; ?> /> Enable LDAP authentication<br />
484         &nbsp;
485     </p>
486     <h3>Retrieve email address when LDAP users register</h3>
487     <p>
488         If checked, the LDAP registration process will attempt to retrieve the LDAP
489         users email address from the LDAP repository:
490     </p>
491     <p>
492         <input type="checkbox" name="ldap_authentication_enable_email_retrieval_from_ldap" value="1" tabindex="20"<?php echo $enable_email_retrieval_from_ldap_checked; ?> /> Retrieve email address when LDAP users register<br />
493         &nbsp;
494     </p>
495     <h3>Disable automatic registration of LDAP users</h3>
496     <p>
497         In normal use, LDAP users are registered in bbPress on their first successful
498         login. If automatic registration is disabled here, then LDAP users will have
499         to be added manually in the database, they cannot be created through the normal
500         registration process:
501     </p>
502     <p>
503         <input type="checkbox" name="ldap_authentication_disable_automatic_ldap_registration" value="1" tabindex="20"<?php echo $disable_automatic_ldap_registration_checked; ?> /> Disable automatic registration of LDAP users<br />
504         &nbsp;
505     </p>
506     <h3>Disable normal registration</h3>
507     <p>
508         This will disable the normal registration page. Non-LDAP users are still
509         allowed to login normally with this option activated:
510     </p>
511     <p>
512         <input type="checkbox" name="ldap_authentication_disable_registration" value="1" tabindex="30"<?php echo $disable_registration_checked; ?> /> Disable normal registration<br />
513         &nbsp;
514     </p>
515     <h3>Server settings</h3>
516     <p>
517         Specify LDAP server settings here:
518     </p>
519     <table>
520         <tr>
521             <th scope="row">Host:</th>
522             <td><input type="text" name="ldap_authentication_host" tabindex="40" value="<?php echo $server['host']; ?>" /> required</td>
523         </tr>
524         <tr>
525             <th scope="row">Port:</th>
526             <td><input type="text" name="ldap_authentication_port" tabindex="50" value="<?php echo $server['port']; ?>" /> defaults to 389</td>
527         </tr>
528         <tr>
529             <th scope="row">Domain:</th>
530             <td><input type="text" name="ldap_authentication_domain" tabindex="60" value="<?php echo $server['domain']; ?>" /> required</td>
531         </tr>
532         <tr>
533             <th scope="row">TLS:</th>
534             <td><input type="checkbox" name="ldap_authentication_tls" value="1" tabindex="70"<?php echo $tls_checked; ?> /> use TLS encryption to connect (sets LDAP protocol to version 3)</td>
535         </tr>
536         <tr>
537             <th scope="row">Options:</th>
538             <td><input type="text" name="ldap_authentication_options" tabindex="80" value="<?php echo $server['options']; ?>" /> e.g.: option1:value1|option2:value2|...</td>
539         </tr>
540     </table>
541     <p class="submit alignleft">
542         <input name="submit" type="submit" value="<?php _e('Update'); ?>" tabindex="90" />
543         <input type="hidden" name="action" value="ldap_authentication_update" />
544     </p>
545     </form>
546 <?php
547 }
548
549
550 /**
551  * Processes the admin page form
552  *
553  * @return void
554  * @author Sam Bauers
555  **/
556 function ldap_authentication_admin_page_process() {
557     if (isset($_POST['submit'])) {
558         if ('ldap_authentication_update' == $_POST['action']) {
559             // Enable LDAP
560             if ($_POST['ldap_authentication_enabled']) {
561                 bb_update_option('ldap_authentication_enabled', $_POST['ldap_authentication_enabled']);
562             } else {
563                 bb_delete_option('ldap_authentication_enabled');
564             }
565             
566             // Set an empty options array
567             $options = array();
568             
569             // Enable email retrieval from LDAP
570             if ($_POST['ldap_authentication_enable_email_retrieval_from_ldap']) {
571                 $options['enable_email_retrieval_from_ldap'] = $_POST['ldap_authentication_enable_email_retrieval_from_ldap'];
572             }
573             
574             // Disable automatic LDAP registration
575             if ($_POST['ldap_authentication_disable_automatic_ldap_registration']) {
576                 $options['disable_automatic_ldap_registration'] = $_POST['ldap_authentication_disable_automatic_ldap_registration'];
577             }
578             
579             // Disable normal registration
580             if ($_POST['ldap_authentication_disable_registration']) {
581                 $options['disable_registration'] = $_POST['ldap_authentication_disable_registration'];
582             }
583             
584             // Save or delete the options
585             if (count($options)) {
586                 bb_update_option('ldap_authentication_options', $options);
587             } else {
588                 bb_delete_option('ldap_authentication_options');
589             }
590             
591             // Set an empty server array
592             $server = array();
593             
594             // Host
595             if ($_POST['ldap_authentication_host']) {
596                 $server['host'] = $_POST['ldap_authentication_host'];
597             }
598             
599             // Port
600             if ($_POST['ldap_authentication_port']) {
601                 $server['port'] = $_POST['ldap_authentication_port'];
602             }
603             
604             // Domain
605             if ($_POST['ldap_authentication_domain']) {
606                 $server['domain'] = $_POST['ldap_authentication_domain'];
607             }
608             
609             // TLS
610             if ($_POST['ldap_authentication_tls']) {
611                 $server['tls'] = $_POST['ldap_authentication_tls'];
612             }
613             
614             // Host
615             if ($_POST['ldap_authentication_host']) {
616                 $server['options'] = $_POST['ldap_authentication_options'];
617