source: npl/webapps/mediawiki/LdapAuthentication.php @ 26ffad7

Last change on this file since 26ffad7 was c5c522c, checked in by Edwin Eefting <edwin@datux.nl>, 8 years ago

initial commit, transferred from cleaned syn3 svn tree

  • Property mode set to 100644
File size: 47.7 KB
Line 
1<?php
2# Copyright (C) 2004 Ryan Lane <http://www.mediawiki.org/wiki/User:Ryan_lane>
3#
4# This program is free software; you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation; either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17# http://www.gnu.org/copyleft/gpl.html
18
19/**
20 * LdapAuthentication plugin.
21 *
22 * Password authentication, and Smartcard Authentication support are currently
23 * available. All forms of authentication, current and future, should support
24 * group, and attribute based restrictions; preference pulling; and group
25 * syncronization. All forms of authentication should have basic support for
26 * adding users, changing passwords, and updating preferences in LDAP.
27 *
28 * Password authentication has a number of configurations, including straight binds,
29 * proxy based authentication, and anonymous-search based authentication.
30 *
31 * @package MediaWiki
32 */
33
34#
35# LdapAuthentication.php
36#
37# Info available at http://meta.wikimedia.org/wiki/LDAP_Authentication
38# and at http://meta.wikimedia.org/wiki/LDAP_Authentication_Configuration_Examples
39# and at http://meta.wikimedia.org/wiki/LDAP_Smartcard_Authentication_Configuration_Examples
40#
41# Support is available at http://meta.wikimedia.org/wiki/Talk:LDAP_Authentication
42#
43# Version 1.1d / 12/04/2006
44#
45
46require_once( 'AuthPlugin.php' );
47
48class LdapAuthenticationPlugin extends AuthPlugin {
49        var $email, $lang, $realname, $nickname, $SearchType;
50        var $LDAPUsername;
51        var $userLDAPGroups, $foundUserLDAPGroups;
52        var $allLDAPGroups;
53
54        function LdapAuthenticationPlugin() {
55        }
56
57        /**
58         * Check whether there exists a user account with the given name.
59         * The name will be normalized to MediaWiki's requirements, so
60         * you might need to munge it (for instance, for lowercase initial
61         * letters).
62         *
63         * @param string $username
64         * @return bool
65         * @access public
66         */
67        function userExists( $username ) {
68                global $wgLDAPAddLDAPUsers;
69
70                $this->printDebug("Entering userExists",1);
71
72                //If we can't add LDAP users, we don't really need to check
73                //if the user exists, the authenticate method will do this for
74                //us. This will decrease hits to the LDAP server.
75                //We do however, need to use this if we are using smartcard authentication.
76                if ( (!isset($wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) && !$this->useSmartcardAuth()) {
77                        return true;
78                }
79
80                $ldapconn = $this->connect();
81                if ($ldapconn) {
82                        $this->printDebug("Successfully connected",1);
83                        $searchstring = $this->getSearchString($ldapconn,$username);
84
85                        //If we are using smartcard authentication, and we got
86                        //anything back, then the user exists.
87                        if ($this->useSmartcardAuth() && $searchstring != '') {
88                                //getSearchString is going to bind, but will not unbind
89                                //Let's clean up
90                                @ldap_unbind();
91                                return true;
92                        }
93
94                        //Search for the entry.
95                        $entry = @ldap_read($ldapconn, $searchstring, "objectclass=*");
96
97                        //getSearchString is going to bind, but will not unbind
98                        //Let's clean up
99                        @ldap_unbind();
100                        if (!$entry) {
101                                $this->printDebug("Did not find a matching user in LDAP",1);
102                                //user wasn't found
103                                return false;
104                        } else {
105                                $this->printDebug("Found a matching user in LDAP",1);
106                                return true;
107                        }
108                } else {
109                        $this->printDebug("Failed to connect",1);
110                        return false;
111                }
112               
113        }
114
115        /**
116         * Connect to LDAP
117         *
118         * @return resource
119         * @access private
120         */
121        function connect() {
122                global $wgLDAPServerNames;
123                global $wgLDAPEncryptionType;
124
125                $this->printDebug("Entering Connect",1);
126
127                //If the user didn't set an encryption type, we default to tls
128                if ( isset($wgLDAPEncryptionType[$_SESSION['wsDomain']]) ) {
129                        $encryptionType = $wgLDAPEncryptionType[$_SESSION['wsDomain']];
130                } else {
131                        $encryptionType = "tls";
132                }
133
134                //Set the server string depending on whether we use ssl or not
135                switch($encryptionType) {
136                        case "ssl":
137                                $this->printDebug("Using SSL",2);
138                                $serverpre = "ldaps://";
139                                break;
140                        default:
141                                $this->printDebug("Using TLS or not using encryption.",2);
142                                $serverpre = "ldap://";
143                }
144
145                //Make a space seperated list of server strings with the ldap:// or ldaps://
146                //string added.
147                $servers = "";
148                $tmpservers = $wgLDAPServerNames[$_SESSION['wsDomain']];
149                $tok = strtok($tmpservers, " ");
150                while ($tok) {
151                        $servers = $servers . " " . $serverpre . $tok;
152                        $tok = strtok(" ");
153                }
154                $servers = rtrim($servers);
155
156                $this->printDebug("Using servers: $servers",2);
157
158                //Connect and set options
159                $ldapconn = @ldap_connect( $servers );
160                ldap_set_option( $ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
161                ldap_set_option( $ldapconn, LDAP_OPT_REFERRALS, 0);
162
163                //TLS needs to be started after the connection is made
164                if ( $encryptionType == "tls" ) {
165                        $this->printDebug("Using TLS",2);
166                        if ( !ldap_start_tls( $ldapconn ) ) {
167                                $this->printDebug("Failed to start TLS.",2);
168                                return;
169                        }
170                }
171
172                return $ldapconn;
173        }
174
175        /**
176         * Check if a username+password pair is a valid login, or if the username
177         * is allowed access to the wiki.
178         * The name will be normalized to MediaWiki's requirements, so
179         * you might need to munge it (for instance, for lowercase initial
180         * letters).
181         *
182         * @param string $username
183         * @param string $password
184         * @return bool
185         * @access public
186         */
187        function authenticate( $username, $password='' ) {
188                global $wgLDAPRetrievePrefs;
189                global $wgLDAPGroupDN, $wgLDAPRequiredGroups;
190                global $wgLDAPGroupUseFullDN, $wgLDAPGroupUseRetrievedUsername;
191                global $wgLDAPUseLDAPGroups;
192                global $wgLDAPRequireAuthAttribute, $wgLDAPAuthAttribute;
193                global $wgLDAPSSLUsername;
194                global $wgLDAPLowerCaseUsername;
195                global $wgLDAPSearchStrings;
196
197                $this->printDebug("Entering authenticate",1);
198
199                //We don't handle local authentication
200                if ( 'local' == $_SESSION['wsDomain'] ) {
201                        $this->printDebug("User is using a local domain",2);
202                        return false;
203                }
204
205                //If the user is using smartcard authentication, we need to ensure
206                //that he/she isn't trying to fool us by sending a username other
207                //than the one the web server got from the smartcard.
208                if ( $this->useSmartcardAuth() && $wgLDAPSSLUsername != $username ) {
209                        $this->printDebug("The username provided doesn't match the username on the smartcard. The user is probably trying to log in to the smartcard domain with password authentication. Denying access.",2);
210                        return false;
211                }
212
213                //We need to ensure that if we require a password, that it is
214                //not blank. We don't allow blank passwords, so we are being
215                //tricked if someone is supplying one when using password auth.
216                //Smartcard authentication uses a pin, and does not require
217                //a password to be given; a blank password here is wanted.
218                if ( '' == $password && !$this->useSmartcardAuth() ) {
219                        $this->printDebug("User used a blank password",1);
220                        return false;
221                }
222
223                $ldapconn = $this->connect();
224                if ( $ldapconn ) {
225                        $this->printDebug("Connected successfully",1);
226
227                        //Mediawiki munges the username before authenticate is called,
228                        //this can mess with authentication, group pulling/restriction,
229                        //preference pulling, etc. Let's allow the user to use
230                        //a lowercased username.
231                        if ( isset($wgLDAPLowerCaseUsername[$_SESSION['wsDomain']]) && $wgLDAPLowerCaseUsername[$_SESSION['wsDomain']] ) {
232                                $username = strtolower($username);
233                                $this->printDebug("Lowercasing the username: $username",1);
234                        }
235
236                        $userdn = $this->getSearchString($ldapconn, $username);
237
238                        //It is possible that getSearchString will return an
239                        //empty string; if this happens, the bind will ALWAYS
240                        //return true, and will let anyone in!
241                        if ('' == $userdn) {
242                                $this->printDebug("User DN is blank",1);
243                                // Lets clean up.
244                                @ldap_unbind();
245                                return false;
246                        }
247
248                        //If we are using password authentication, we need to bind as the
249                        //user to make sure the password is correct.
250                        if ( !$this->useSmartcardAuth() ) {
251                                $this->printDebug("Binding as the user",1);
252
253                                //Let's see if the user can authenticate.
254                                $bind = $this->bindAs($ldapconn, $userdn, $password);
255                                if (!$bind) {
256                                        // Lets clean up.
257                                        @ldap_unbind();
258                                        return false;
259                                }
260                                $this->printDebug("Binded successfully",1);
261
262                                if ( isset( $wgLDAPSearchStrings[$_SESSION['wsDomain']] ) ) {
263                                        $ss = $wgLDAPSearchStrings[$_SESSION['wsDomain']];
264                                        if ( strstr( $ss, "@" ) || strstr( $ss, '\\' ) ) {
265                                                //We are most likely configured using USER-NAME@DOMAIN, or
266                                                //DOMAIN\\USER-NAME.
267                                                //Get the user's full DN so we can search for groups and such.
268                                                $userdn = $this->getUserDN($ldapconn, $username);
269                                                $this->printDebug("Pulled the user's DN: $userdn",1);
270                                        }
271                                }
272
273                                if ( (isset($wgLDAPRequireAuthAttribute[$_SESSION['wsDomain']]) && $wgLDAPRequireAuthAttribute[$_SESSION['wsDomain']]) ) {
274                                        $this->printDebug("Checking for auth attributes",1);
275                                        $filter = "(" . $wgLDAPAuthAttribute[$_SESSION['wsDomain']] . ")";
276                                        $attributes = array("dn");
277                                        $entry = ldap_read($ldapconn, $userdn, $filter, $attributes);
278                                        $info = ldap_get_entries($ldapconn, $entry);
279                                        if ($info["count"] < 1) {
280                                                $this->printDebug("Failed auth attribute check",1);
281                                                // Lets clean up.
282                                                @ldap_unbind();
283                                                return false;
284                                        }
285                                }
286                        }
287
288                        //Old style groups, non-nestable and fairly limited on group type (full DN
289                        //versus username). DEPRECATED
290                        if ($wgLDAPGroupDN) {
291                                $this->printDebug("Checking for (old style) group membership",1);
292                                if (!$this->isMemberOfLdapGroup($ldapconn, $userdn, $wgLDAPGroupDN)) {
293                                        $this->printDebug("Failed (old style) group membership check",1);
294
295                                        //No point in going on if the user isn't in the required group
296                                        // Lets clean up.
297                                        @ldap_unbind();
298                                        return false;
299                                }
300                        }
301
302                        //New style group checking
303                        if ( isset($wgLDAPRequiredGroups[$_SESSION['wsDomain']]) ) {
304                                $this->printDebug("Checking for (new style) group membership",1);
305
306                                if ( isset($wgLDAPGroupUseFullDN[$_SESSION['wsDomain']]) && $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) {
307                                        $inGroup = $this->isMemberOfRequiredLdapGroup($ldapconn, $userdn);
308                                } else {
309                                        if ( (isset($wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']]) && $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']])
310                                                && $this->LDAPUsername != '' ) {
311                                                $this->printDebug("Using the username retrieved from the user's entry.",1);
312                                                $inGroup = $this->isMemberOfRequiredLdapGroup($ldapconn, $this->LDAPUsername);
313                                        } else {
314                                                $inGroup = $this->isMemberOfRequiredLdapGroup($ldapconn, $username);
315                                        }
316                                }
317
318                                if (!$inGroup) {
319                                        // Lets clean up.
320                                        @ldap_unbind();
321                                        return false;
322                                }
323
324                        }
325
326                        //Synch LDAP groups with MediaWiki groups
327                        if ( isset($wgLDAPUseLDAPGroups[$_SESSION['wsDomain']]) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) {
328                                $this->printDebug("Retrieving LDAP group membership",1);
329
330                                //Let's get the user's LDAP groups
331                                if ( isset($wgLDAPGroupUseFullDN[$_SESSION['wsDomain']]) && $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) {
332                                        $this->userLDAPGroups = $this->getUserGroups($ldapconn, $userdn, true);
333                                } else {
334                                        if ( (isset($wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']]) && $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']])
335                                                && $this->LDAPUsername != '' ) {
336                                                $this->userLDAPGroups = $this->getUserGroups($ldapconn, $this->LDAPUsername, true);
337                                        } else {
338                                                $this->userLDAPGroups = $this->getUserGroups($ldapconn, $username, true);
339                                        }
340                                }
341
342                                //If the user doesn't have any groups there is no need to waste another search.
343                                if ( $this->foundUserLDAPGroups ) {
344                                        $this->allLDAPGroups = $this->getAllGroups($ldapconn, true);
345                                }
346                        }
347
348                        //Retrieve preferences
349                        if ( isset($wgLDAPRetrievePrefs[$_SESSION['wsDomain']]) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) {
350                                $this->printDebug("Retrieving preferences",1);
351
352                                $entry = @ldap_read($ldapconn, $userdn, "objectclass=*");
353                                $info = @ldap_get_entries($ldapconn, $entry);
354                                $this->email = $info[0]["mail"][0];
355                                $this->lang = $info[0]["preferredlanguage"][0];
356                                $this->nickname = $info[0]["displayname"][0];
357                                $this->realname = $info[0]["cn"][0];
358
359                                $this->printDebug("Retrieved: $this->email, $this->lang, $this->nickname, $this->realname",2);
360                        }
361
362                        // Lets clean up.
363                        @ldap_unbind();
364                } else {
365                        $this->printDebug("Failed to connect",1);
366                        return false;
367                }
368                $this->printDebug("Authentication passed",1);
369                //We made it this far; the user authenticated and didn't fail any checks, so he/she gets in.
370                return true;
371        }
372
373        /**
374         * Modify options in the login template.
375         *
376         * @param UserLoginTemplate $template
377         * @access public
378         */
379        function modifyUITemplate( &$template ) {
380                global $wgLDAPDomainNames, $wgLDAPUseLocal;
381                global $wgLDAPAddLDAPUsers;
382                global $wgLDAPUseSmartcardAuth, $wgLDAPSmartcardDomain;
383
384                $this->printDebug("Entering modifyUITemplate",1);
385
386                if ( !isset($wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) {
387                        $template->set( 'create', false );
388                }
389
390                $template->set( 'usedomain', true );
391                $template->set( 'useemail', false );
392
393                $tempDomArr = $wgLDAPDomainNames;
394                if ( $wgLDAPUseLocal ) {
395                        $this->printDebug("Allowing the local domain, adding it to the list.",1);
396                        array_push( $tempDomArr, 'local' );
397                }
398
399                if ( $wgLDAPUseSmartcardAuth ) {
400                        $this->printDebug("Allowing smartcard login, removing the domain from the list.",1);
401                        //There is no reason for people to log in directly to the wiki if the are using a
402                        //smartcard. If they try to, they are probably up to something fishy.
403                        unset( $tempDomArr[array_search($wgLDAPSmartcardDomain, $tempDomArr)] );
404                }
405
406                $template->set( 'domainnames', $tempDomArr );
407        }
408
409        /**
410         * Return true if the wiki should create a new local account automatically
411         * when asked to login a user who doesn't exist locally but does in the
412         * external auth database.
413         *
414         * This is just a question, and shouldn't perform any actions.
415         *
416         * @return bool
417         * @access public
418         */
419        function autoCreate() {
420                global $wgLDAPDisableAutoCreate;
421
422                if ( isset($wgLDAPDisableAutoCreate[$_SESSION['wsDomain']]) && $wgLDAPDisableAutoCreate[$_SESSION['wsDomain']] ) {
423                        return false;
424                } else {
425                        return true;
426                }
427        }
428
429        /**
430         * Set the given password in LDAP.
431         * Return true if successful.
432         *
433         * @param User $user
434         * @param string $password
435         * @return bool
436         * @access public
437         */
438        function setPassword( $user, &$password ) {
439                global $wgLDAPUpdateLDAP, $wgLDAPWriterDN, $wgLDAPWriterPassword;
440
441                $this->printDebug("Entering setPassword",1);
442
443                if ($_SESSION['wsDomain'] == 'local') {
444                        $this->printDebug("User is using a local domain",1);
445
446                        //We don't set local passwords, but we don't want the wiki
447                        //to send the user a failure.           
448                        return true;
449                } else if ( !isset($wgLDAPUpdateLDAP[$_SESSION['wsDomain']]) || !$wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) {
450                        $this->printDebug("Wiki is set to not allow updates",1);
451
452                        //We aren't allowing the user to change his/her own password
453                        return false;
454                }
455
456                if (!isset($wgLDAPWriterDN[$_SESSION['wsDomain']])) {
457                        $this->printDebug("Wiki doesn't have wgLDAPWriterDN set",1);
458
459                        //We can't change a user's password without an account that is
460                        //allowed to do it.
461                        return false;
462                }
463
464                $pass = $this->getPasswordHash($password);
465
466                $ldapconn = $this->connect();
467                if ($ldapconn) {
468                        $this->printDebug("Connected successfully",1);
469                        $userdn = $this->getSearchString($ldapconn, $user->getName());
470
471                        $this->printDebug("Binding as the writerDN",1);
472                        $bind = $this->bindAs( $ldapconn, $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
473                        if (!$bind) {
474                                return false;
475                        }
476
477                        $values["userpassword"] = $pass;
478
479                        //Blank out the password in the database. We don't want to save
480                        //domain credentials for security reasons.
481                        $password = '';
482
483                        $success = ldap_modify($ldapconn, $userdn, $values);
484
485                        //Let's clean up
486                        @ldap_unbind();
487                        if ($success) {
488                                $this->printDebug("Successfully modified the user's password",1);
489                                return true;
490                        } else {
491                                $this->printDebug("Failed to modify the user's password",1);
492                                return false;
493                        }
494                } else {
495                        return false;
496                }
497        }
498
499        /**
500         * Update user information in LDAP
501         * Return true if successful.
502         *
503         * @param User $user
504         * @return bool
505         * @access public
506         */     
507        function updateExternalDB( $user ) {
508                global $wgLDAPUpdateLDAP;
509                global $wgLDAPWriterDN, $wgLDAPWriterPassword;
510
511                $this->printDebug("Entering updateExternalDB",1);
512
513                if ( (!isset($wgLDAPUpdateLDAP[$_SESSION['wsDomain']]) || !$wgLDAPUpdateLDAP[$_SESSION['wsDomain']]) ||
514                        $_SESSION['wsDomain'] == 'local') {
515                        $this->printDebug("Either the user is using a local domain, or the wiki isn't allowing updates",1);
516
517                        //We don't handle local preferences, but we don't want the
518                        //wiki to return an error.
519                        return true;
520                }
521
522                if (!isset($wgLDAPWriterDN[$_SESSION['wsDomain']])) {
523                        $this->printDebug("The wiki doesn't have wgLDAPWriterDN set",1);
524
525                        //We can't modify LDAP preferences if we don't have a user
526                        //capable of editing LDAP attributes.
527                        return false;
528                }
529
530                $this->email = $user->getEmail();
531                $this->realname = $user->getRealName();
532                $this->nickname = $user->getOption('nickname');
533                $this->language = $user->getOption('language');
534
535                $ldapconn = $this->connect();
536                if ($ldapconn) {
537                        $this->printDebug("Connected successfully",1);
538                        $userdn = $this->getSearchString($ldapconn, $user->getName());
539
540                        $this->printDebug("Binding as the writerDN",1);
541                        $bind = $this->bindAs( $ldapconn, $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
542                        if (!$bind) {
543                                return false;
544                        }
545
546                        if ('' != $this->email) { $values["mail"] = $this->email; }
547                        if ('' != $this->nickname) { $values["displayname"] = $this->nickname; }
548                        if ('' != $this->realname) { $values["cn"] = $this->realname; }
549                        if ('' != $this->language) { $values["preferredlanguage"] = $this->language; }
550
551                        if (0 != sizeof($values) && ldap_modify($ldapconn, $userdn, $values)) {
552                                $this->printDebug("Successfully modified the user's attributes",1);
553                                @ldap_unbind();
554                                return true;
555                        } else {
556                                $this->printDebug("Failed to modify the user's attributes",1);
557                                @ldap_unbind();
558                                return false;
559                        }
560                } else {
561                        $this->printDebug("Failed to Connect",1);
562                        return false;
563                }
564        }
565
566        /**
567         * Can the wiki create accounts in LDAP?
568         * Return true if yes.
569         *
570         * @return bool
571         * @access public
572         */     
573        function canCreateAccounts() {
574                global $wgLDAPAddLDAPUsers;
575
576                if ( isset($wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) && $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) {
577                        return true;
578                } else {
579                        return false;
580                }
581        }
582
583        /**
584         * Can the wiki change passwords in LDAP?
585         * Return true if yes.
586         *
587         * @return bool
588         * @access public
589         */     
590        function allowPasswordChange() {
591                global $wgLDAPUpdateLDAP, $wgLDAPMailPassword;
592
593                if ( isset($wgLDAPUpdateLDAP[$_SESSION['wsDomain']]) ) {
594                        $updateLDAP = $wgLDAPUpdateLDAP[$_SESSION['wsDomain']];
595                } else {
596                        $updateLDAP = false;
597                }
598                if ( isset($wgLDAPMailPassword[$_SESSION['wsDomain']]) ) {
599                        $mailPassword = $wgLDAPMailPassword[$_SESSION['wsDomain']];
600                } else {
601                        $mailPassword = false;
602                }
603
604                if ( $updateLDAP || $mailPassword ) {
605                        return true;
606                } else {
607                        return false;
608                }
609        }
610
611        /**
612         * Add a user to LDAP.
613         * Return true if successful.
614         *
615         * @param User $user
616         * @param string $password
617         * @return bool
618         * @access public
619         */
620        function addUser( $user, $password ) {
621                global $wgLDAPAddLDAPUsers, $wgLDAPWriterDN, $wgLDAPWriterPassword;
622                global $wgLDAPSearchAttributes;
623                global $wgLDAPWriteLocation;
624                global $wgLDAPRequiredGroups, $wgLDAPGroupDN;
625                global $wgLDAPRequireAuthAttribute, $wgLDAPAuthAttribute;
626
627                $this->printDebug("Entering addUser",1);
628
629                if ( (!isset($wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) ||
630                        'local' == $_SESSION['wsDomain'] ) {
631                        $this->printDebug("Either the user is using a local domain, or the wiki isn't allowing users to be added to LDAP",1);
632
633                        //Tell the wiki not to return an error.
634                        return true;
635                }
636
637                if ($wgLDAPRequiredGroups || $wgLDAPGroupDN) {
638                        $this->printDebug("The wiki is requiring users to be in specific groups, and cannot add users as this would be a security hole.",1);
639                        //It is possible that later we can add users into
640                        //groups, but since we don't support it, we don't want
641                        //to open holes!
642                        return false;
643                }
644
645                if (!isset($wgLDAPWriterDN[$_SESSION['wsDomain']])) {
646                        $this->printDebug("The wiki doesn't have wgLDAPWriterDN set",1);
647
648                        //We can't add users without an LDAP account capable of doing so.
649                        return false;
650                }
651
652                $this->email = $user->getEmail();
653                $this->realname = $user->getRealName();
654                $username = $user->getName();
655
656                $pass = $this->getPasswordHash($password);
657
658                $ldapconn = $this->connect();
659                if ($ldapconn) {
660                        $this->printDebug("Successfully connected",1);
661                        $userdn = $this->getSearchString($ldapconn, $username);
662                        if ('' == $userdn) {
663                                $this->printDebug("userdn is blank, attempting to use wgLDAPWriteLocation",1);
664                                if (isset($wgLDAPWriteLocation[$_SESSION['wsDomain']])) {
665                                        $this->printDebug("wgLDAPWriteLocation is set, using that",1);
666                                        $userdn = $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" .
667                                                $username . $wgLDAPWriteLocation[$_SESSION['wsDomain']];
668                                } else {
669                                        $this->printDebug("wgLDAPWriteLocation is not set, failing",1);
670                                        //getSearchString will bind, but will not unbind
671                                        @ldap_unbind();
672                                        return false;
673                                }
674                        }
675
676                        $this->printDebug("Binding as the writerDN",1);
677                        $bind = $this->bindAs( $ldapconn, $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
678                        if (!$bind) {
679                                return false;
680                        }
681
682                        //Set up LDAP attributes
683                        $values["uid"] = $username;
684                        $values["sn"] = $username;
685                        if ('' != $this->email) { $values["mail"] = $this->email; }
686                        if ('' != $this->realname) {$values["cn"] = $this->realname; }
687                                else { $values["cn"] = $username; }
688                        $values["userpassword"] = $pass;
689                        $values["objectclass"] = "inetorgperson";
690
691                        if ($wgLDAPRequireAuthAttribute) {
692                                $values[$wgLDAPAuthAttribute[$_SESSION['wsDomain']]] = "true";
693                        }
694
695                        if (@ldap_add($ldapconn, $userdn, $values)) {
696                                $this->printDebug("Successfully added user",1);
697                                @ldap_unbind();
698                                return true;
699                        } else {
700                                $this->printDebug("Failed to add user",1);
701                                @ldap_unbind();
702                                return false;
703                        }
704                } else {
705                        return false;
706                }
707        }
708
709        /**
710         * Set the domain this plugin is supposed to use when authenticating.
711         *
712         * @param string $domain
713         * @access public       
714         */
715        function setDomain( $domain ) {
716                $this->printDebug("Setting domain as: $domain",1);
717                $_SESSION['wsDomain'] = $domain;
718        }
719
720        /**
721         * Check to see if the specific domain is a valid domain.
722         * Return true if the domain is valid.
723         *
724         * @param string $domain
725         * @return bool
726         * @access public
727         */
728        function validDomain( $domain ) {
729                global $wgLDAPDomainNames, $wgLDAPUseLocal;
730
731                $this->printDebug("Entering validDomain",1);
732
733                if (in_array($domain, $wgLDAPDomainNames) || ($wgLDAPUseLocal && 'local' == $domain)) {
734                        $this->printDebug("User is using a valid domain.",1);
735                        return true;
736                } else {
737                        $this->printDebug("User is not using a valid domain.",1);
738                        return false;
739                }
740        }
741
742        /**
743         * When a user logs in, update user with information from LDAP.
744         *
745         * @param User $user
746         * @access public
747         */
748        function updateUser( &$user ) {
749                global $wgLDAPRetrievePrefs;
750                global $wgLDAPUseLDAPGroups;
751
752                $this->printDebug("Entering updateUser",1);
753
754                $saveSettings = false;
755
756                //If we aren't pulling preferences, we don't want to accidentally
757                //overwrite anything.
758                if ( isset($wgLDAPRetrievePrefs[$_SESSION['wsDomain']]) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) {
759                        $this->printDebug("Setting user preferences.",1);
760
761                        if ('' != $this->lang) {
762                                $user->setOption('language',$this->lang);
763                        }
764                        if ('' != $this->nickname) {
765                                $user->setOption('nickname',$this->nickname);
766                        }
767                        if ('' != $this->realname) {
768                                $user->setRealName($this->realname);
769                        }
770                        if ('' != $this->email) {
771                                $user->setEmail($this->email);
772                        }
773
774                        $saveSettings = true;
775                }
776
777                if ( isset($wgLDAPUseLDAPGroups[$_SESSION['wsDomain']]) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) {
778                        $this->setGroups($user);
779                        $saveSettings = true;
780                }
781
782                if ( $saveSettings ) {
783                        $this->printDebug("Saving user settings.",1);
784                        $user->saveSettings();
785                }
786        }
787
788        /**
789         * Return true to prevent logins that don't authenticate here from being
790         * checked against the local database's password fields.
791         *
792         * This is just a question, and shouldn't perform any actions.
793         *
794         * @return bool
795         * @access public
796         */
797        function strict() {
798                global $wgLDAPUseLocal, $wgLDAPMailPassword;
799
800                $this->printDebug("Entering strict.",1);
801
802                if ($wgLDAPUseLocal || $wgLDAPMailPassword) {
803                        $this->printDebug("Returning false in strict().",1);
804                        return false;
805                } else {
806                        $this->printDebug("Returning true in strict().",1);
807                        return true;
808                }
809        }
810
811        /**
812         * When creating a user account, initialize user with information from LDAP.
813         *
814         * @param User $user
815         * @access public
816         */
817        function initUser( &$user ) {
818                global $wgLDAPUseLDAPGroups;
819
820                $this->printDebug("Entering initUser",1);
821
822                if ('local' == $_SESSION['wsDomain']) {
823                        $this->printDebug("User is using a local domain",1);
824                        return;
825                }
826
827                //We are creating an LDAP user, it is very important that we do
828                //NOT set a local password because it could compromise the
829                //security of our domain.
830                $user->mPassword = '';
831
832                if ( isset($wgLDAPRetrievePrefs[$_SESSION['wsDomain']]) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) {
833                        if ('' != $this->lang) {
834                                $user->setOption('language',$this->lang);
835                        }
836                        if ('' != $this->nickname) {
837                                $user->setOption('nickname',$this->nickname);
838                        }
839                        if ('' != $this->realname) {
840                                $user->setRealName($this->realname);
841                        }
842                        if ('' != $this->email) {
843                                $user->setEmail($this->email);
844                        }
845                }
846
847                if ( isset($wgLDAPUseLDAPGroups[$_SESSION['wsDomain']]) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) {
848                        $this->setGroups($user);
849                }
850
851                $user->saveSettings();
852        }
853
854        /**
855         * Munge the username to always have a form of uppercase for the first letter,
856         * and lowercase for the rest of the letters.
857         *
858         * @param string $username
859         * @return string
860         * @access public
861         */
862        function getCanonicalName( $username ) {
863                $this->printDebug("Entering getCanonicalName",1);
864
865                if ( $username != '' ) {
866                        $this->printDebug("Username isn't empty.",1);
867
868                        //We want to use the username returned by LDAP
869                        //if it exists
870                        if ( $this->LDAPUsername != '' ) {
871                                $this->printDebug("Using LDAPUsername.",1);
872                                $username = $this->LDAPUsername;
873                        }
874
875                        //Change username to lowercase so that multiple user accounts
876                        //won't be created for the same user.
877                        $username = strtolower($username);
878
879                        //The wiki considers an all lowercase name to be invalid; need to
880                        //uppercase the first letter
881                        $username[0] = strtoupper($username[0]);
882                }
883                $this->printDebug("Munged username: $username",1);
884                return $username;
885        }
886
887        /**
888         * Returns the username pulled from LDAP when getSearchString() was called.
889         *
890         * @return string
891         * @access public
892         */
893        function getLDAPUsername() {
894                return $this->LDAPUsername;
895        }
896
897        /**
898         * Configures the authentication plugin for use with auto-authentication
899         * plugins.
900         *
901         * @access public
902         */
903        function autoAuthSetup() {
904                global $wgLDAPUseSmartcardAuth;
905                global $wgLDAPSmartcardDomain;
906
907                $wgLDAPUseSmartcardAuth = true;
908                $this->setDomain($wgLDAPSmartcardDomain);
909        }
910
911        /**
912         * Gets the searchstring for a user based upon settings for the domain.
913         * Returns a full DN for a user.
914         *
915         * @param resource $ldapconn
916         * @param string $username
917         * @return string
918         * @access private
919         */
920        function getSearchString($ldapconn, $username) {
921                global $wgLDAPSearchStrings;
922                global $wgLDAPProxyAgent, $wgLDAPProxyAgentPassword;
923
924                $this->printDebug("Entering getSearchString",1);
925
926                if (isset($wgLDAPSearchStrings[$_SESSION['wsDomain']])) {
927                        //This is a straight bind
928                        $this->printDebug("Doing a straight bind",1);
929
930                        $tmpuserdn = $wgLDAPSearchStrings[$_SESSION['wsDomain']];
931                        $userdn = str_replace("USER-NAME",$username,$tmpuserdn);
932                } else {
933                        //This is a proxy bind, or an anonymous bind with a search
934                        if (isset($wgLDAPProxyAgent[$_SESSION['wsDomain']])) {
935                                //This is a proxy bind
936                                $this->printDebug("Doing a proxy bind",1);
937                                $bind = $this->bindAs( $ldapconn, $wgLDAPProxyAgent[$_SESSION['wsDomain']], $wgLDAPProxyAgentPassword[$_SESSION['wsDomain']] );
938                        } else {
939                                //This is an anonymous bind
940                                $this->printDebug("Doing an anonymous bind",1);
941                                $bind = $this->bindAs( $ldapconn );
942                        }
943       
944                        if (!$bind) {
945                                $this->printDebug("Failed to bind",1);
946                                return '';
947                        }
948
949                        $userdn = $this->getUserDN($ldapconn, $username);
950                }
951                $this->printDebug("userdn is: $userdn",2);
952                return $userdn;
953        }
954
955        /**
956         * Gets the DN of a user based upon settings for the domain.
957         * This function will set $this->LDAPUsername
958         * You must bind to the server before calling this.
959         *
960         * @param resource $ldapconn
961         * @param string $username
962         * @return string
963         * @access private
964         */
965        function getUserDN($ldapconn, $username) {
966                global $wgLDAPSearchAttributes;
967                global $wgLDAPRequireAuthAttribute, $wgLDAPAuthAttribute;
968                global $wgLDAPBaseDNs;
969
970                $this->printDebug("Entering getUserDN",1);
971
972                //we need to do a subbase search for the entry
973
974                //Smartcard auth needs to check LDAP for required attributes.
975                if ( (isset($wgLDAPRequireAuthAttribute[$_SESSION['wsDomain']]) && $wgLDAPRequireAuthAttribute[$_SESSION['wsDomain']])
976                        && $this->useSmartcardAuth() ) {
977                        $auth_filter = "(" . $wgLDAPAuthAttribute[$_SESSION['wsDomain']] . ")";
978                        $srch_filter = "(" . $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" . $this->getLdapEscapedString($username) . ")";
979                        $filter = "(&" . $srch_filter . $auth_filter . ")";
980                        $this->printDebug("Created an auth attribute filter: $filter",2);
981                } else {
982                        $filter = "(" . $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" . $this->getLdapEscapedString($username) . ")";
983                        $this->printDebug("Created a regular filter: $filter",2);
984                }
985
986                $attributes = array("*");
987                $base = $wgLDAPBaseDNs[$_SESSION['wsDomain']];
988
989                $this->printDebug("Using base: $base",2);
990
991                $entry = @ldap_search($ldapconn, $base, $filter, $attributes);
992                if (!$entry) {
993                        $this->printDebug("Couldn't find an entry",1);
994                        return '';
995                }
996
997                $info = @ldap_get_entries($ldapconn, $entry);
998
999                //This is a pretty useful thing to have for both smartcard authentication,
1000                //group checking, and pulling preferences.
1001                wfRunHooks('SetUsernameAttributeFromLDAP',array(&$this->LDAPUsername, $info));
1002                if (!is_string($this->LDAPUsername)) {
1003                        $this->printDebug("Fetched username is not a string (check your hook code...).",1);
1004                        $this->LDAPUsername = '';
1005                }
1006
1007                $userdn = $info[0]["dn"];
1008                return $userdn;
1009        }
1010
1011        //DEPRECATED
1012        function isMemberOfLdapGroup( $ldapconn, $userDN, $groupDN ) {
1013                $this->printDebug("Entering isMemberOfLdapGroup (DEPRECATED)",1);
1014
1015                //we need to do a subbase search for the entry
1016                $filter = "(member=" . $this->getLdapEscapedString($userDN) . ")";
1017                $info = ldap_get_entries( $ldapconn, @ldap_search($ldapconn, $groupDN, $filter) );
1018                return ( $info["count"] >= 1 );
1019        }
1020
1021        /**
1022         * Determines whether a user is a member of a group, or a nested group.
1023         *
1024         * @param resource $ldapconn
1025         * @param string $userDN
1026         * @return bool
1027         * @access private
1028         */
1029        function isMemberOfRequiredLdapGroup( $ldapconn, $userDN ) {
1030                global $wgLDAPRequiredGroups;
1031                global $wgLDAPGroupSearchNestedGroups;
1032
1033                $this->printDebug("Entering isMemberOfRequiredLdapGroup",1);
1034
1035                $reqgroups = $wgLDAPRequiredGroups[$_SESSION['wsDomain']];
1036                for ( $i = 0; $i < count($reqgroups); $i++ ) {
1037                        $reqgroups[$i] = strtolower( $reqgroups[$i] );
1038                }
1039
1040                $searchnested = $wgLDAPGroupSearchNestedGroups[$_SESSION['wsDomain']];
1041
1042                $this->printDebug("Required groups:" . implode(",",$reqgroups) . "",1);
1043
1044                $groups = $this->getUserGroups($ldapconn, $userDN);
1045
1046                if ( !$this->foundUserLDAPGroups ) {
1047                        //User isn't in any groups, so he/she obviously can't be in
1048                        //a required one
1049                        $this->printDebug("Couldn't find the user in any groups (1).",1);
1050
1051                        return false;
1052                } else {
1053                        //User is in groups, let's see if a required group is one of them
1054                        foreach ($groups as $group) {
1055                                if ( in_array( $group, $reqgroups ) ) {
1056                                        $this->printDebug("Found user in a group.",1);
1057                                        return true;
1058                                }
1059                        }
1060
1061                        //We didn't find the user in the group, lets check nested groups
1062                        if ( $searchnested ) {
1063                                //No reason to go on if we aren't allowing nested group
1064                                //searches
1065                                if ( $this->searchNestedGroups($ldapconn, $groups) ) {
1066                                        return true;
1067                                }
1068                        }
1069
1070                        $this->printDebug("Couldn't find the user in any groups (2).",1);
1071
1072                        return false;
1073                }
1074        }
1075
1076        /**
1077         * Helper function for isMemberOfRequiredLdapGroup.
1078         * $checkedgroups is used for tail recursion and shouldn't be provided
1079         * when called externally.
1080         *
1081         * @param resource $ldapconn
1082         * @param string $userDN
1083         * @param array $checkedgroups
1084         * @return bool
1085         * @access private
1086         */
1087        function searchNestedGroups( $ldapconn, $groups, $checkedgroups = array() ) {
1088                global $wgLDAPRequiredGroups;
1089
1090                $this->printDebug("Entering searchNestedGroups",1);
1091
1092                //base case, no more groups left to check
1093                if (!$groups) {
1094                        $this->printDebug("Couldn't find user in any nested groups.",1);
1095                        return false;
1096                }
1097
1098                $this->printDebug("Checking groups:" . implode(",",$groups) . "",2);
1099
1100                $reqgroups = $wgLDAPRequiredGroups[$_SESSION['wsDomain']];
1101                for ( $i = 0; $i < count($reqgroups); $i++ ) {
1102                        $reqgroups[$i] = strtolower( $reqgroups[$i] );
1103                }
1104
1105                $groupstocheck = array();
1106                foreach ( $groups as $group ) {
1107                        $returnedgroups = $this->getUserGroups($ldapconn, $group);
1108                        foreach ($returnedgroups as $checkme) {
1109                                $this->printDebug("Checking membership for: $checkme",2);
1110                                if ( in_array( $checkme, $checkedgroups ) ) {
1111                                        //We already checked this, move on
1112                                        continue;
1113                                } else if ( in_array( $checkme, $reqgroups ) ) {
1114                                        $this->printDebug("Found user in a nested group.",1);
1115                                        //Woohoo
1116                                        return true;
1117                                } else {
1118                                        //We'll need to check this group's members now
1119                                        array_push( $groupstocheck, $checkme );
1120                                }
1121                        }
1122                }
1123
1124                $checkedgroups = array_unique(array_merge($groups, $checkedgroups));
1125
1126                //Mmmmmm. Tail recursion. Tasty.
1127                if ( $this->searchNestedGroups($ldapconn, $groupstocheck, $checkedgroups) ) {
1128                        return true;
1129                } else {
1130                        return false;
1131                }
1132        }
1133
1134        /**
1135         * Helper function for isMemberOfRequiredLdapGroup and searchNestedGroups
1136         * Sets $this->foundUserLDAPGroups
1137         *
1138         * @param resource $ldapconn
1139         * @param string $dn
1140         * @return array
1141         * @access private
1142         */
1143        function getUserGroups( $ldapconn, $dn, $getShortnames = false ) {
1144                $this->printDebug("Entering getUserGroups",1);
1145
1146                //Let's return the saved groups if they are available
1147                if ( $getShortnames ) {
1148                        if ( isset($this->userLDAPShortnameGroupCache) ) {
1149                                return $this->userLDAPShortnameGroupCache;
1150                        }
1151                } else {
1152                        if ( isset($this->userLDAPGroupCache) ) {
1153                                return $this->userLDAPGroupCache;
1154                        }
1155                }
1156
1157                //We haven't done a search yet, lets do it now
1158                list($groups, $shortnamegroups) = $this->getGroups( $ldapconn, $dn );
1159
1160                //Save the groups for next time we are called
1161                $this->userLDAPGroupCache = $groups;
1162                $this->userLDAPShortnameGroupCache = $shortnamegroups;
1163
1164                //We only need to check one of the two arrays, as they should be
1165                //identical from a member standpoint.
1166                if (count($groups) == 0) {
1167                        $this->foundUserLDAPGroups = false;
1168                } else {
1169                        $this->foundUserLDAPGroups = true;
1170                }
1171
1172                if ( $getShortnames ) {
1173                        return $shortnamegroups;
1174                } else {
1175                        return $groups;
1176                }
1177        }
1178
1179        /**
1180         * Helper function for retrieving all LDAP groups
1181         * Sets $this->foundAllLDAPGroups
1182         *
1183         * @param resource $ldapconn
1184         * @param string $dn
1185         * @return array
1186         * @access private
1187         */
1188        function getAllGroups( $ldapconn, $getShortnames = false ) {
1189                $this->printDebug("Entering getAllGroups",1);
1190
1191                //Let's return the saved groups if they are available
1192                if ( $getShortnames ) {
1193                        if ( isset($this->allLDAPShortnameGroupCache) ) {
1194                                return $this->allLDAPShortnameGroupCache;
1195                        }
1196                } else {
1197                        if ( isset($this->allLDAPGroupCache) ) {
1198                                return $this->allLDAPGroupCache;
1199                        }
1200                }
1201
1202                //We haven't done a search yet, lets do it now
1203                list($groups, $shortnamegroups) = $this->getGroups( $ldapconn, '*' );
1204
1205                //Save the groups for next time we are called
1206                $this->allLDAPGroupCache = $groups;
1207                $this->allLDAPShortnameGroupCache = $shortnamegroups;
1208
1209                //We only need to check one of the two arrays, as they should be
1210                //identical from a member standpoint.
1211                if (count($groups) == 0) {
1212                        $this->foundAllLDAPGroups = false;
1213                } else {
1214                        $this->foundAllLDAPGroups = true;
1215                }
1216
1217                if ( $getShortnames ) {
1218                        return $shortnamegroups;
1219                } else {
1220                        return $groups;
1221                }
1222        }
1223
1224        /**
1225         * Helper function for getUserGroups and getAllGroups. You shouldn't
1226         * call this directly.
1227         *
1228         * @param resource $ldapconn
1229         * @param string $dn
1230         * @return array
1231         * @access private
1232         */
1233        function getGroups( $ldapconn, $dn ) {
1234                global $wgLDAPBaseDNs;
1235                global $wgLDAPGroupObjectclass, $wgLDAPGroupAttribute, $wgLDAPGroupNameAttribute;
1236                global $wgLDAPProxyAgent, $wgLDAPProxyAgentPassword;
1237
1238                $this->printDebug("Entering getGroups",1);
1239
1240                $base = $wgLDAPBaseDNs[$_SESSION['wsDomain']];
1241                $objectclass = $wgLDAPGroupObjectclass[$_SESSION['wsDomain']];
1242                $attribute = $wgLDAPGroupAttribute[$_SESSION['wsDomain']];
1243                $nameattribute = $wgLDAPGroupNameAttribute[$_SESSION['wsDomain']];
1244
1245                //Search for the groups this user is in
1246                $filter = "(&($attribute=" . $this->getLdapEscapedString($dn) . ")(objectclass=$objectclass))";
1247
1248                $this->printDebug("Search string: $filter",2);
1249
1250                if ( isset($wgLDAPProxyAgent[$_SESSION['wsDomain']]) ) {
1251                        //We'll try to bind as the proxyagent as the proxyagent should normally have more
1252                        //rights than the user. If the proxyagent fails to bind, we will still be able
1253                        //to search as the normal user (which is why we don't return on fail).
1254                        $this->printDebug("Binding as the proxyagentDN",1);
1255                        $bind = $this->bindAs($ldapconn, $wgLDAPProxyAgent[$_SESSION['wsDomain']], $wgLDAPProxyAgentPassword[$_SESSION['wsDomain']]);
1256                }
1257
1258                $info = @ldap_search($ldapconn, $base, $filter);
1259                if ( !$info ) {
1260                        $this->printDebug("No entries returned from search.",2);
1261                        //Return an array with two empty arrays so that other functions
1262                        //don't error out.
1263                        return array( array(), array() );
1264                }
1265
1266                $entries = @ldap_get_entries($ldapconn,$info);
1267
1268                //We need to shift because the first entry will be a count
1269                array_shift($entries);
1270
1271                //Let's get a list of both full dn groups and shortname groups
1272                $groups = array();
1273                $shortnamegroups = array();
1274                foreach ($entries as $entry) {
1275                        $mem = strtolower($entry['dn']);
1276                        $shortnamemem = strtolower($entry[$nameattribute][0]);
1277
1278                        array_push($groups,$mem);
1279                        array_push($shortnamegroups,$shortnamemem);
1280                }
1281
1282                $both_groups = array();
1283                array_push($both_groups, $groups);
1284                array_push($both_groups, $shortnamegroups);
1285
1286                $this->printDebug("Returned groups:" . implode(",",$groups) . "",2);
1287                $this->printDebug("Returned groups:" . implode(",",$shortnamegroups) . "",2);
1288
1289                return $both_groups;
1290        }
1291
1292        /**
1293         * Returns true if this group is in the list of the currently authenticated
1294         * user's groups, else false.
1295         *
1296         * @param string $group
1297         * @return bool
1298         * @access private
1299         */
1300        function hasLDAPGroup( $group ) {
1301                $this->printDebug("Entering hasLDAPGroup",1);
1302
1303                return in_array( strtolower( $group ), $this->userLDAPGroups );
1304        }
1305
1306        /**
1307         * Returns true if an LDAP group with this name exists, else false.
1308         *
1309         * @param string $group
1310         * @return bool
1311         * @access private
1312         */
1313        function isLDAPGroup( $group ) {
1314                $this->printDebug("Entering isLDAPGroup",1);
1315
1316                return in_array( strtolower( $group ), $this->allLDAPGroups );
1317        }
1318
1319        /**
1320         * Helper function for updateUser() and initUser(). Adds users into MediaWiki security groups
1321         * based upon groups retreived from LDAP.
1322         *
1323         * @param User $user
1324         * @access private
1325         */
1326        function setGroups( &$user ) {
1327                $this->printDebug("Pulling groups from LDAP.",1);
1328
1329                # add groups permissions
1330                $localAvailGrps = $user->getAllGroups();
1331                $localUserGrps = $user->getEffectiveGroups();
1332
1333                $this->printDebug("Available groups are: " . implode(",",$localAvailGrps) . "",1);
1334                $this->printDebug("Effective groups are: " . implode(",",$localUserGrps) . "",1);
1335
1336                # note: $localUserGrps does not need to be updated with $cGroup added,
1337                #       as $localAvailGrps contains $cGroup only once.
1338                foreach ($localAvailGrps as $cGroup) {
1339                        # did we once add the user to the group?
1340                        if (in_array($cGroup,$localUserGrps)) {
1341                                $this->printDebug("Checking to see if we need to remove user from: $cGroup",1);
1342                                if ((!$this->hasLDAPGroup($cGroup)) && ($this->isLDAPGroup($cGroup))) {
1343                                        $this->printDebug("Removing user from: $cGroup",1);
1344                                        # the ldap group overrides the local group
1345                                        # so as the user is currently not a member of the ldap group, he shall be removed from the local group
1346                                        $user->removeGroup($cGroup);
1347                                }
1348                        } else { # no, but maybe the user has recently been added to the ldap group?
1349                                $this->printDebug("Checking to see if user is in: $cGroup",1);
1350                                if ($this->hasLDAPGroup($cGroup)) {
1351                                        $this->printDebug("Adding user to: $cGroup",1);
1352                                        # so use the addGroup function
1353                                        $user->addGroup($cGroup);
1354                                        # completedfor $cGroup.
1355                                }
1356                        }
1357                }
1358        }
1359
1360        /**
1361         * Returns a password that is created via the configured hash settings.
1362         *
1363         * @param string $password
1364         * @return string
1365         * @access private
1366         */
1367        function getPasswordHash( $password ) {
1368                global $wgLDAPPasswordHash;
1369
1370                $this->printDebug("Entering getPasswordHash",1);
1371
1372                if (isset($wgLDAPPasswordHash[$_SESSION['wsDomain']])) {
1373                        $hashtouse = $wgLDAPPasswordHash[$_SESSION['wsDomain']];
1374                } else {
1375                        $hashtouse = '';
1376                }
1377                //Set the password hashing based upon admin preference
1378                switch ($hashtouse) {
1379                        case 'crypt':
1380                                $pass = '{CRYPT}' . crypt($password);
1381                                break;
1382                        case 'clear':
1383                                $pass = $password;
1384                                break;
1385                        default:
1386                                $pwd_md5 = base64_encode(pack('H*',sha1($password)));
1387                                $pass = "{SHA}".$pwd_md5;
1388                                break;
1389                }
1390                $this->printDebug("Password is $pass",2);
1391                return $pass;
1392        }
1393
1394        /**
1395         * Prints debugging information. $debugText is what you want to print, $debugVal
1396         * is the level at which you want to print the information.
1397         *
1398         * @param string $debugText
1399         * @param string $debugVal
1400         * @access private
1401         */
1402        function printDebug( $debugText, $debugVal ) {
1403                global $wgLDAPDebug;
1404
1405                if ($wgLDAPDebug > $debugVal) {
1406                        echo $debugText . "<br>";
1407                }
1408        }
1409
1410        /**
1411         * Binds as $userdn with $password. This can be called with only the ldap
1412         * connection resource for an anonymous bind.
1413         *
1414         * @param resourse $ldapconn
1415         * @param string $userdn
1416         * @param string $password
1417         * @return bool
1418         * @access private
1419         */
1420        function bindAs( $ldapconn, $userdn=null, $password=null ) {
1421                //Let's see if the user can authenticate.
1422                if ($userdn == null || $password == null) {
1423                        $bind = @ldap_bind($ldapconn);
1424                } else {
1425                        $bind = @ldap_bind($ldapconn, $userdn, $password);
1426                }
1427                if (!$bind) {
1428                        $this->printDebug("Failed to bind as $userdn",1);
1429                        $this->printDebug("with password: $password",3);
1430                        return false;
1431                }
1432                return true;
1433        }
1434
1435        /**
1436         * Returns true if smartcard authentication is allowed, and the user is
1437         * authenticating using the smartcard domain.
1438         *
1439         * @return bool
1440         * @access private
1441         */
1442        function useSmartcardAuth() {
1443                global $wgLDAPUseSmartcardAuth, $wgLDAPSmartcardDomain;
1444
1445                return $wgLDAPUseSmartcardAuth && $_SESSION['wsDomain'] == $wgLDAPSmartcardDomain;
1446        }
1447
1448        /**
1449         * Returns a string which has the chars *, (, ), \ & NUL escaped to LDAP compliant
1450         * syntax as per RFC 2254
1451         * Thanks and credit to Iain Colledge for the research and function.
1452         *
1453         * @param string $string
1454         * @return string
1455         * @access private
1456         */
1457        function getLdapEscapedString ($string) {
1458                // Make the string LDAP compliant by escaping *, (, ) , \ & NUL
1459                return str_replace(array("*","(",")","\\","\x00"),array("\\2a","\\28","\\29","\\5c","\\00"),$string);
1460        }
1461
1462}
1463
1464/**
1465 * Add extension information to Special:Version
1466 */
1467$wgExtensionCredits['other'][] = array(
1468        'name' => 'LDAP Authentication Plugin',
1469        'version' => '1.1e',
1470        'author' => 'Ryan Lane',
1471        'description' => 'LDAP Authentication plugin with support for multiple LDAP authentication methods',
1472        'url' => 'http://meta.wikimedia.org/wiki/LDAP_Authentication'
1473        );
1474
1475// The following was derived from the SSL Authentication plugin
1476// http://www.mediawiki.org/wiki/SSL_authentication
1477
1478/**
1479 * Sets up the SSL authentication piece of the LDAP plugin.
1480 *
1481 * @access public
1482 */
1483function AutoAuthSetup() {
1484        global $wgLDAPSSLUsername;
1485        global $wgHooks;
1486        global $wgAuth;
1487        global $wgLDAPAutoAuthMethod;
1488
1489        $wgAuth = new LdapAuthenticationPlugin();
1490
1491        $wgAuth->printDebug("Entering AutoAuthSetup.",1);
1492
1493        //We may add quite a few different auto authenticate methods in the
1494        //future, let's make it easy to support.
1495        switch($wgLDAPAutoAuthMethod) {
1496                case "smartcard":
1497                        $wgAuth->printDebug("Allowing smartcard authentication.",1);
1498                        $wgAuth->printDebug("wgLDAPSSLUsername = $wgLDAPSSLUsername",2);
1499
1500                        if($wgLDAPSSLUsername != null) {
1501                                $wgAuth->printDebug("wgLDAPSSLUsername is not null, adding hooks.",1);
1502                                $wgHooks['AutoAuthenticate'][] = 'SSLAuth'; /* Hook for magical authN */
1503                                $wgHooks['PersonalUrls'][] = 'NoLogout'; /* Disallow logout link */
1504                        }
1505                        break;
1506                default:
1507                        $wgAuth->printDebug("Not using any AutoAuthentication methods .",1);
1508        }
1509}
1510
1511/* No logout link in MW */
1512function NoLogout(&$personal_urls, $title) {
1513        $personal_urls['logout'] = null;
1514}
1515
1516/**
1517 * Does the SSL authentication piece of the LDAP plugin.
1518 *
1519 * @access public
1520 */
1521function SSLAuth(&$user) {
1522        global $wgLDAPSSLUsername;
1523        global $wgUser;
1524        global $wgAuth;
1525
1526        $wgAuth->printDebug("Entering SSLAuth.",1);
1527
1528        //Give us a user, see if we're around
1529        $tmpuser = User::LoadFromSession();
1530
1531        //They already with us?  If so, quit this function.
1532        if($tmpuser->isLoggedIn()) {
1533                $wgAuth->printDebug("User is already logged in.",1);
1534                return;
1535        }
1536
1537        //Let regular authentication plugins configure themselves for auto
1538        //authentication chaining
1539        $wgAuth->autoAuthSetup();
1540
1541        //The user hasn't already been authenticated, let's check them
1542        $wgAuth->printDebug("User is not logged in, we need to authenticate",1);
1543        $authenticated = $wgAuth->authenticate($wgLDAPSSLUsername);
1544        if (!$authenticated) {
1545                //If the user doesn't exist in LDAP, there isn't much reason to
1546                //go any further.
1547                $wgAuth->printDebug("User wasn't found in LDAP, exiting.",1);
1548                return;
1549        }
1550
1551        //We need the username that MediaWiki will always use, *not* the one we
1552        //get from LDAP.
1553        $mungedUsername = $wgAuth->getCanonicalName($wgLDAPSSLUsername);
1554
1555        $wgAuth->printDebug("User exists in LDAP; finding the user by name in MediaWiki.",1);
1556
1557        //Is the user already in the database?
1558        $tmpuser = User::newFromName($mungedUsername);
1559
1560        if ( $tmpuser == null ) {
1561                $wgAuth->printDebug("Username is not a valid MediaWiki username.",1);
1562                return;
1563        }
1564
1565        //If exists, log them in
1566        if($tmpuser->getID() != 0)
1567        {
1568                $wgAuth->printDebug("User exists in local database, logging in.",1);
1569                $wgUser = &$tmpuser;
1570                $wgAuth->updateUser($wgUser);
1571                $wgUser->setCookies();
1572                $wgUser->setupSession();
1573                return;
1574        }
1575        $wgAuth->printDebug("User does not exist in local database; creating.",1);
1576
1577        //Require SpecialUserlogin so that we can get a loginForm
1578        require_once('SpecialUserlogin.php');
1579
1580        //This section contains a silly hack for MW
1581        global $wgLang;
1582        global $wgContLang;
1583        global $wgRequest;
1584        if(!isset($wgLang))
1585        {
1586                $wgLang = $wgContLang;
1587                $wgLangUnset = true;
1588        }
1589
1590        $wgAuth->printDebug("Creating LoginForm.",1);
1591
1592        //This creates our form that'll let us create a new user in the database
1593        $lf = new LoginForm($wgRequest);
1594
1595        //The user we'll be creating...
1596        $wgUser = &$tmpuser;
1597        $wgUser->setName($wgContLang->ucfirst($mungedUsername));
1598
1599        $wgAuth->printDebug("Creating User.",1);
1600
1601        //Create the user
1602        $lf->initUser($wgUser);
1603
1604        //Initialize the user
1605        $wgUser->setupSession();
1606        $wgUser->setCookies();
1607}
1608?>
Note: See TracBrowser for help on using the repository browser.