2
# Copyright (C) 2004 Ryan Lane <http://www.mediawiki.org/wiki/User:Ryan_lane>
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.
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.
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
20
* LdapAuthentication plugin.
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.
28
* Password authentication has a number of configurations, including straight binds,
29
* proxy based authentication, and anonymous-search based authentication.
35
# LdapAuthentication.php
37
# Info available at http://www.mediawiki.org/wiki/Extension:LDAP_Authentication
38
# and at http://www.mediawiki.org/wiki/Extension:LDAP_Authentication/Configuration_Examples
39
# and at http://www.mediawiki.org/wiki/Extension:LDAP_Authentication/Smartcard_Configuration_Examples
41
# Support is available at http://www.mediawiki.org/wiki/Extension_talk:LDAP_Authentication
45
* Add extension information to Special:Version
47
$wgExtensionCredits['other'][] = array(
48
'name' => 'LDAP Authentication Plugin',
49
'version' => '1.2b (alpha)',
50
'author' => 'Ryan Lane',
51
'description' => 'LDAP Authentication plugin with support for multiple LDAP authentication methods',
52
'url' => 'http://www.mediawiki.org/wiki/Extension:LDAP_Authentication',
55
//constants for search base
58
define("DEFAULTDN", 2);
60
//constants for error reporting
61
define("NONSENSITIVE", 1);
62
define("SENSITIVE", 2);
63
define("HIGHLYSENSITIVE", 3);
65
class LdapAuthenticationPlugin extends AuthPlugin {
67
//ldap connection resource
71
var $email, $lang, $realname, $nickname, $externalid;
73
//username pulled from ldap
76
//userdn pulled from ldap
79
//groups pulled from ldap
83
//boolean to test for failed auth
86
//boolean to test for fetched user info
89
//the user's entry and all attributes
92
function LdapAuthenticationPlugin() {
96
* Check whether there exists a user account with the given name.
97
* The name will be normalized to MediaWiki's requirements, so
98
* you might need to munge it (for instance, for lowercase initial
101
* @param string $username
105
function userExists( $username ) {
106
global $wgLDAPAddLDAPUsers;
108
$this->printDebug( "Entering userExists", NONSENSITIVE );
110
//If we can't add LDAP users, we don't really need to check
111
//if the user exists, the authenticate method will do this for
112
//us. This will decrease hits to the LDAP server.
113
//We do however, need to use this if we are using auto authentication.
114
if ( ( !isset( $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']]) && !$this->useAutoAuth() ) {
118
$this->ldapconn = $this->connect();
119
if ( $this->ldapconn ) {
120
$this->printDebug( "Successfully connected", NONSENSITIVE );
122
$searchstring = $this->getSearchString( $this->ldapconn, $username );
124
//If we are using auto authentication, and we got
125
//anything back, then the user exists.
126
if ( $this->useAutoAuth() && $searchstring != '' ) {
127
//getSearchString is going to bind, but will not unbind
133
//Search for the entry.
134
$entry = @ldap_read( $this->ldapconn, $searchstring, "objectclass=*" );
136
//getSearchString is going to bind, but will not unbind
140
$this->printDebug( "Did not find a matching user in LDAP", NONSENSITIVE );
143
$this->printDebug( "Found a matching user in LDAP", NONSENSITIVE );
147
$this->printDebug( "Failed to connect", NONSENSITIVE );
159
global $wgLDAPServerNames;
160
global $wgLDAPEncryptionType;
161
global $wgLDAPOptions;
163
$this->printDebug( "Entering Connect", NONSENSITIVE );
165
if ( !function_exists( 'ldap_connect' ) ) {
166
$this->printDebug( "It looks like you are issing LDAP support; please ensure you have either compiled LDAP support in, or have enabled the module. If the authentication is working for you, the plugin isn't properly detecting the LDAP module, and you can safely ignore this message.", NONSENSITIVE );
170
//If the admin didn't set an encryption type, we default to tls
171
if ( isset( $wgLDAPEncryptionType[$_SESSION['wsDomain']] ) ) {
172
$encryptionType = $wgLDAPEncryptionType[$_SESSION['wsDomain']];
174
$encryptionType = "tls";
177
//Set the server string depending on whether we use ssl or not
178
switch( $encryptionType ) {
180
# this is a really dirty place to put this,
181
# but it is easy and avoids another config option.
182
$this->printDebug( "Using ldapi", SENSITIVE );
183
$serverpre = "ldapi://";
186
$this->printDebug( "Using SSL", SENSITIVE );
187
$serverpre = "ldaps://";
190
$this->printDebug( "Using TLS or not using encryption.", SENSITIVE );
191
$serverpre = "ldap://";
194
//Make a space separated list of server strings with the ldap:// or ldaps://
197
$tmpservers = $wgLDAPServerNames[$_SESSION['wsDomain']];
198
$tok = strtok( $tmpservers, " " );
200
$servers = $servers . " " . $serverpre . $tok;
201
$tok = strtok( " " );
203
$servers = rtrim($servers);
205
$this->printDebug( "Using servers: $servers", SENSITIVE );
207
//Connect and set options
208
$this->ldapconn = @ldap_connect( $servers );
209
ldap_set_option( $this->ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3);
210
ldap_set_option( $this->ldapconn, LDAP_OPT_REFERRALS, 0);
212
if ( isset( $wgLDAPOptions[$_SESSION['wsDomain']] ) ) {
213
$options = $wgLDAPOptions[$_SESSION['wsDomain']];
214
foreach ( $options as $key => $value ) {
215
if ( !ldap_set_option( $this->ldapconn, constant( $key ), $value ) ) {
216
$this->printDebug( "Can't set option to LDAP! Option code and value: " . $key . "=" . $value, 1 );
221
//TLS needs to be started after the connection is made
222
if ( $encryptionType == "tls" ) {
223
$this->printDebug( "Using TLS", SENSITIVE );
224
if ( !ldap_start_tls( $this->ldapconn ) ) {
225
$this->printDebug( "Failed to start TLS.", SENSITIVE );
232
* Check if a username+password pair is a valid login, or if the username
233
* is allowed access to the wiki.
234
* The name will be normalized to MediaWiki's requirements, so
235
* you might need to munge it (for instance, for lowercase initial
238
* @param string $username
239
* @param string $password
243
function authenticate( $username, $password='' ) {
244
global $wgLDAPAuthAttribute;
245
global $wgLDAPAutoAuthUsername;
246
global $wgLDAPLowerCaseUsername;
247
global $wgLDAPSearchStrings;
249
$this->printDebug( "Entering authenticate", NONSENSITIVE );
251
//We don't handle local authentication
252
if ( 'local' == $_SESSION['wsDomain'] ) {
253
$this->printDebug( "User is using a local domain", SENSITIVE );
257
//If the user is using auto authentication, we need to ensure
258
//that he/she isn't trying to fool us by sending a username other
259
//than the one the web server got from the auto-authentication method.
260
if ( $this->useAutoAuth() && $wgLDAPAutoAuthUsername != $username ) {
261
$this->printDebug( "The username provided ($username) doesn't match the username provided by the webserver ($wgLDAPAutoAuthUsername). The user is probably trying to log in to the auto-authentication domain with password authentication via the wiki. Denying access.", SENSITIVE );
265
//We need to ensure that if we require a password, that it is
266
//not blank. We don't allow blank passwords, so we are being
267
//tricked if someone is supplying one when using password auth.
268
//Smartcard authentication uses a pin, and does not require
269
//a password to be given; a blank password here is wanted.
270
if ( '' == $password && !$this->useAutoAuth() ) {
271
$this->printDebug( "User used a blank password", NONSENSITIVE );
276
if ( $this->ldapconn ) {
277
$this->printDebug( "Connected successfully", NONSENSITIVE );
279
//Mediawiki munges the username before authenticate is called,
280
//this can mess with authentication, group pulling/restriction,
281
//preference pulling, etc. Let's allow the admin to use
282
//a lowercased username if needed.
283
if ( isset( $wgLDAPLowerCaseUsername[$_SESSION['wsDomain']] ) && $wgLDAPLowerCaseUsername[$_SESSION['wsDomain']] ) {
284
$this->printDebug( "Lowercasing the username: $username", NONSENSITIVE );
285
$username = strtolower( $username );
288
$this->userdn = $this->getSearchString( $username );
290
//It is possible that getSearchString will return an
291
//empty string; if this happens, the bind will ALWAYS
292
//return true, and will let anyone in!
293
if ( '' == $this->userdn ) {
294
$this->printDebug( "User DN is blank", NONSENSITIVE );
296
$this->markAuthFailed();
300
//If we are using password authentication, we need to bind as the
301
//user to make sure the password is correct.
302
if ( !$this->useAutoAuth() ) {
303
$this->printDebug( "Binding as the user", NONSENSITIVE );
305
//Let's see if the user can authenticate.
306
$bind = $this->bindAs( $this->userdn, $password );
308
$this->markAuthFailed();
312
$this->printDebug( "Bound successfully", NONSENSITIVE );
314
if ( isset( $wgLDAPSearchStrings[$_SESSION['wsDomain']] ) ) {
315
$ss = $wgLDAPSearchStrings[$_SESSION['wsDomain']];
316
if ( strstr( $ss, "@" ) || strstr( $ss, '\\' ) ) {
317
//We are most likely configured using USER-NAME@DOMAIN, or
319
//Get the user's full DN so we can search for groups and such.
320
$this->userdn = $this->getUserDN( $username );
321
$this->printDebug( "Pulled the user's DN: $this->userdn", NONSENSITIVE );
325
if ( isset( $wgLDAPAuthAttribute[$_SESSION['wsDomain']] ) ) {
327
$this->printDebug( "Checking for auth attributes", NONSENSITIVE );
329
$filter = "(" . $wgLDAPAuthAttribute[$_SESSION['wsDomain']] . ")";
330
$attributes = array( "dn" );
332
$entry = ldap_read( $this->ldapconn, $this->userdn, $filter, $attributes );
333
$info = ldap_get_entries( $this->ldapconn, $entry );
335
if ( $info["count"] < 1 ) {
336
$this->printDebug( "Failed auth attribute check", NONSENSITIVE );
338
$this->markAuthFailed();
344
$this->getGroups( $username );
346
if ( !$this->checkGroups( $username ) ) {
348
$this->markAuthFailed();
352
$this->getPreferences();
354
if ( !$this->synchUsername( $username ) ) {
356
$this->markAuthFailed();
362
$this->printDebug( "Failed to connect", NONSENSITIVE );
363
$this->markAuthFailed();
366
$this->printDebug( "Authentication passed", NONSENSITIVE );
368
//We made it this far; the user authenticated and didn't fail any checks, so he/she gets in.
372
function markAuthFailed() {
373
$this->authFailed = true;
377
* Modify options in the login template.
379
* @param UserLoginTemplate $template
382
function modifyUITemplate( &$template ) {
383
global $wgLDAPDomainNames, $wgLDAPUseLocal;
384
global $wgLDAPAddLDAPUsers;
385
global $wgLDAPAutoAuthDomain;
387
$this->printDebug( "Entering modifyUITemplate", NONSENSITIVE );
389
if ( !isset( $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) {
390
$template->set( 'create', false );
393
$template->set( 'usedomain', true );
394
$template->set( 'useemail', false );
396
$tempDomArr = $wgLDAPDomainNames;
397
if ( $wgLDAPUseLocal ) {
398
$this->printDebug( "Allowing the local domain, adding it to the list.", NONSENSITIVE );
399
array_push( $tempDomArr, 'local' );
402
if ( isset( $wgLDAPAutoAuthDomain ) ) {
403
$this->printDebug( "Allowing auto-authentication login, removing the domain from the list.", NONSENSITIVE );
405
//There is no reason for people to log in directly to the wiki if the are using an
406
//auto-authentication domain. If they try to, they are probably up to something fishy.
407
unset( $tempDomArr[array_search( $wgLDAPAutoAuthDomain, $tempDomArr )] );
410
$template->set( 'domainnames', $tempDomArr );
414
* Return true if the wiki should create a new local account automatically
415
* when asked to login a user who doesn't exist locally but does in the
416
* external auth database.
418
* This is just a question, and shouldn't perform any actions.
423
function autoCreate() {
424
global $wgLDAPDisableAutoCreate;
426
if ( isset( $wgLDAPDisableAutoCreate[$_SESSION['wsDomain']] ) && $wgLDAPDisableAutoCreate[$_SESSION['wsDomain']] ) {
434
* Set the given password in LDAP.
435
* Return true if successful.
438
* @param string $password
442
function setPassword( $user, &$password ) {
443
global $wgLDAPUpdateLDAP, $wgLDAPWriterDN, $wgLDAPWriterPassword;
445
$this->printDebug( "Entering setPassword", NONSENSITIVE );
447
if ( $_SESSION['wsDomain'] == 'local' ) {
448
$this->printDebug( "User is using a local domain", NONSENSITIVE );
450
//We don't set local passwords, but we don't want the wiki
451
//to send the user a failure.
453
} else if ( !isset( $wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) || !$wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) {
454
$this->printDebug( "Wiki is set to not allow updates", NONSENSITIVE );
456
//We aren't allowing the user to change his/her own password
460
if ( !isset( $wgLDAPWriterDN[$_SESSION['wsDomain']] ) ) {
461
$this->printDebug( "Wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
463
//We can't change a user's password without an account that is
468
$pass = $this->getPasswordHash( $password );
471
if ( $this->ldapconn ) {
472
$this->printDebug( "Connected successfully", NONSENSITIVE );
473
$this->userdn = $this->getSearchString( $user->getName() );
475
$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
476
$bind = $this->bindAs( $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
481
$values["userpassword"] = $pass;
483
//Blank out the password in the database. We don't want to save
484
//domain credentials for security reasons.
487
$success = @ldap_modify( $this->ldapconn, $this->userdn, $values );
492
$this->printDebug( "Successfully modified the user's password", NONSENSITIVE );
495
$this->printDebug( "Failed to modify the user's password", NONSENSITIVE );
499
$this->printDebug( "Failed to connect", NONSENSITIVE );
505
* Update user information in LDAP
506
* Return true if successful.
512
function updateExternalDB( $user ) {
513
global $wgLDAPUpdateLDAP;
514
global $wgLDAPWriterDN, $wgLDAPWriterPassword;
516
$this->printDebug( "Entering updateExternalDB", NONSENSITIVE );
518
if ( ( !isset( $wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) || !$wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) ||
519
$_SESSION['wsDomain'] == 'local' ) {
520
$this->printDebug( "Either the user is using a local domain, or the wiki isn't allowing updates", NONSENSITIVE );
522
//We don't handle local preferences, but we don't want the
523
//wiki to return an error.
527
if ( !isset( $wgLDAPWriterDN[$_SESSION['wsDomain']] ) ) {
528
$this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
530
//We can't modify LDAP preferences if we don't have a user
531
//capable of editing LDAP attributes.
535
$this->email = $user->getEmail();
536
$this->realname = $user->getRealName();
537
$this->nickname = $user->getOption( 'nickname' );
538
$this->language = $user->getOption( 'language' );
541
if ( $this->ldapconn ) {
542
$this->printDebug( "Connected successfully", NONSENSITIVE );
543
$this->userdn = $this->getSearchString( $user->getName() );
545
$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
546
$bind = $this->bindAs( $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
551
if ( '' != $this->email ) { $values["mail"] = $this->email; }
552
if ( '' != $this->nickname ) { $values["displayname"] = $this->nickname; }
553
if ( '' != $this->realname ) { $values["cn"] = $this->realname; }
554
if ( '' != $this->language ) { $values["preferredlanguage"] = $this->language; }
556
if ( 0 != sizeof( $values ) && @ldap_modify( $this->ldapconn, $this->userdn, $values ) ) {
557
$this->printDebug( "Successfully modified the user's attributes", NONSENSITIVE );
561
$this->printDebug( "Failed to modify the user's attributes", NONSENSITIVE );
566
$this->printDebug( "Failed to Connect", NONSENSITIVE );
572
* Can the wiki create accounts in LDAP?
573
* Return true if yes.
578
function canCreateAccounts() {
579
global $wgLDAPAddLDAPUsers;
581
if ( isset( $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) && $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) {
589
* Can the wiki change passwords in LDAP, or can the user
590
* change passwords locally?
591
* Return true if yes.
596
function allowPasswordChange() {
597
global $wgLDAPUpdateLDAP, $wgLDAPMailPassword;
598
global $wgLDAPUseLocal;
600
$this->printDebug( "Entering allowPasswordChange", NONSENSITIVE );
604
// Local domains need to be able to change passwords
605
if ( (isset($wgLDAPUseLocal) && $wgLDAPUseLocal) && 'local' == $_SESSION['wsDomain'] ) {
609
if ( isset( $wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) && $wgLDAPUpdateLDAP[$_SESSION['wsDomain']] ) {
613
if ( isset( $wgLDAPMailPassword[$_SESSION['wsDomain']] ) && $wgLDAPMailPassword[$_SESSION['wsDomain']] ) {
621
* Add a user to LDAP.
622
* Return true if successful.
625
* @param string $password
629
function addUser( $user, $password ) {
630
global $wgLDAPAddLDAPUsers, $wgLDAPWriterDN, $wgLDAPWriterPassword;
631
global $wgLDAPSearchAttributes;
632
global $wgLDAPWriteLocation;
633
global $wgLDAPRequiredGroups, $wgLDAPGroupDN;
634
global $wgLDAPAuthAttribute;
636
$this->printDebug( "Entering addUser", NONSENSITIVE );
638
if ( ( !isset( $wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) || !$wgLDAPAddLDAPUsers[$_SESSION['wsDomain']] ) ||
639
'local' == $_SESSION['wsDomain'] ) {
640
$this->printDebug( "Either the user is using a local domain, or the wiki isn't allowing users to be added to LDAP", NONSENSITIVE );
642
//Tell the wiki not to return an error.
646
if ( $wgLDAPRequiredGroups || $wgLDAPGroupDN ) {
647
$this->printDebug( "The wiki is requiring users to be in specific groups, and cannot add users as this would be a security hole.", NONSENSITIVE );
648
//It is possible that later we can add users into
649
//groups, but since we don't support it, we don't want
654
if ( !isset( $wgLDAPWriterDN[$_SESSION['wsDomain']] ) ) {
655
$this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE );
657
//We can't add users without an LDAP account capable of doing so.
661
$this->email = $user->getEmail();
662
$this->realname = $user->getRealName();
663
$username = $user->getName();
665
$pass = $this->getPasswordHash( $password );
668
if ( $this->ldapconn ) {
669
$this->printDebug( "Successfully connected", NONSENSITIVE );
671
$this->userdn = $this->getSearchString( $username );
672
if ( '' == $this->userdn ) {
673
$this->printDebug( "$this->userdn is blank, attempting to use wgLDAPWriteLocation", NONSENSITIVE );
674
if ( isset( $wgLDAPWriteLocation[$_SESSION['wsDomain']] ) ) {
675
$this->printDebug( "wgLDAPWriteLocation is set, using that", NONSENSITIVE );
676
$this->userdn = $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" .
677
$username . "," . $wgLDAPWriteLocation[$_SESSION['wsDomain']];
679
$this->printDebug( "wgLDAPWriteLocation is not set, failing", NONSENSITIVE );
680
//getSearchString will bind, but will not unbind
686
$this->printDebug( "Binding as the writerDN", NONSENSITIVE );
688
$bind = $this->bindAs( $wgLDAPWriterDN[$_SESSION['wsDomain']], $wgLDAPWriterPassword[$_SESSION['wsDomain']] );
690
$this->printDebug( "Failed to bind as the writerDN; add failed", NONSENSITIVE );
694
//Set up LDAP attributes
695
$values["uid"] = $username;
696
//sn is required for objectclass inetorgperson
697
$values["sn"] = $username;
698
if ( '' != $this->email ) { $values["mail"] = $this->email; }
699
if ( '' != $this->realname ) {$values["cn"] = $this->realname; }
700
else { $values["cn"] = $username; }
701
$values["userpassword"] = $pass;
702
$values["objectclass"] = "inetorgperson";
704
if ( isset ( $wgLDAPAuthAttribute[$_SESSION['wsDomain']] ) ) {
705
$values[$wgLDAPAuthAttribute[$_SESSION['wsDomain']]] = "true";
708
$this->printDebug( "Adding user", NONSENSITIVE );
709
if ( @ldap_add( $this->ldapconn, $this->userdn, $values ) ) {
710
$this->printDebug( "Successfully added user", NONSENSITIVE );
714
$this->printDebug( "Failed to add user", NONSENSITIVE );
719
$this->printDebug( "Failed to connect; add failed", NONSENSITIVE );
725
* Set the domain this plugin is supposed to use when authenticating.
727
* @param string $domain
730
function setDomain( $domain ) {
731
$this->printDebug( "Setting domain as: $domain", NONSENSITIVE );
732
$_SESSION['wsDomain'] = $domain;
736
* Check to see if the specific domain is a valid domain.
737
* Return true if the domain is valid.
739
* @param string $domain
743
function validDomain( $domain ) {
744
global $wgLDAPDomainNames, $wgLDAPUseLocal;
746
$this->printDebug( "Entering validDomain", NONSENSITIVE );
748
if ( in_array( $domain, $wgLDAPDomainNames ) || ( $wgLDAPUseLocal && 'local' == $domain ) ) {
749
$this->printDebug( "User is using a valid domain.", NONSENSITIVE );
752
$this->printDebug( "User is not using a valid domain.", NONSENSITIVE );
758
* When a user logs in, update user with information from LDAP.
762
* TODO: fix the setExternalID stuff
764
function updateUser( &$user ) {
765
global $wgLDAPRetrievePrefs, $wgLDAPPreferences;
766
global $wgLDAPUseLDAPGroups;
767
global $wgLDAPUniqueBlockLogin, $wgLDAPUniqueRenameUser;
769
$this->printDebug( "Entering updateUser", NONSENSITIVE );
771
if ($this->authFailed) {
772
$this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE );
776
$saveSettings = false;
778
//If we aren't pulling preferences, we don't want to accidentally
779
//overwrite anything.
780
if ( ( isset( $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] )
781
|| isset( $wgLDAPPreferences[$_SESSION['wsDomain']] ) ) {
782
$this->printDebug( "Setting user preferences.", NONSENSITIVE );
784
if ( '' != $this->lang ) {
785
$this->printDebug( "Setting language.", NONSENSITIVE );
786
$user->setOption( 'language', $this->lang );
788
if ( '' != $this->nickname ) {
789
$this->printDebug( "Setting nickname.", NONSENSITIVE );
790
$user->setOption( 'nickname', $this->nickname );
792
if ( '' != $this->realname ) {
793
$this->printDebug( "Setting realname.", NONSENSITIVE );
794
$user->setRealName( $this->realname );
796
if ( '' != $this->email ) {
797
$this->printDebug( "Setting email.", NONSENSITIVE );
798
$user->setEmail( $this->email );
799
$user->confirmEmail();
801
if ( ( isset( $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] ) && $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] )
802
|| ( isset( $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] ) && $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] ) ) {
804
if ( '' != $this->externalid ) {
805
$user->setExternalID( $this->externalid );
809
$saveSettings = true;
812
if ( isset( $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) {
813
$this->printDebug( "Setting user groups.", NONSENSITIVE );
814
$this->setGroups( $user );
816
$saveSettings = true;
819
if ( $saveSettings ) {
820
$this->printDebug( "Saving user settings.", NONSENSITIVE );
821
$user->saveSettings();
826
* When creating a user account, initialize user with information from LDAP.
830
* TODO: fix setExternalID stuff
832
function initUser( &$user ) {
833
global $wgLDAPUseLDAPGroups;
835
$this->printDebug( "Entering initUser", NONSENSITIVE );
837
if ($this->authFailed) {
838
$this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE );
842
if ( 'local' == $_SESSION['wsDomain'] ) {
843
$this->printDebug( "User is using a local domain", NONSENSITIVE );
847
//We are creating an LDAP user, it is very important that we do
848
//NOT set a local password because it could compromise the
849
//security of our domain.
850
$user->mPassword = '';
852
//The update user function does everything else we need done.
853
$this->updateUser($user);
855
//updateUser() won't definately save the user's settings
856
$user->saveSettings();
860
* Return true to prevent logins that don't authenticate here from being
861
* checked against the local database's password fields.
863
* This is just a question, and shouldn't perform any actions.
869
global $wgLDAPUseLocal, $wgLDAPMailPassword;
871
$this->printDebug( "Entering strict.", NONSENSITIVE );
873
if ( $wgLDAPUseLocal || $wgLDAPMailPassword ) {
874
$this->printDebug( "Returning false in strict().", NONSENSITIVE );
877
$this->printDebug( "Returning true in strict().", NONSENSITIVE );
883
* Munge the username to always have a form of uppercase for the first letter,
884
* and lowercase for the rest of the letters.
886
* @param string $username
890
function getCanonicalName( $username ) {
891
global $wgLDAPUseLocal;
892
$this->printDebug( "Entering getCanonicalName", NONSENSITIVE );
894
if ( $username != '' ) {
895
$this->printDebug( "Username isn't empty.", NONSENSITIVE );
897
//We want to use the username returned by LDAP
899
if ( $this->LDAPUsername != '' ) {
900
$this->printDebug( "Using LDAPUsername.", NONSENSITIVE );
901
$username = $this->LDAPUsername;
904
if ( isset($_SESSION['wsDomain']) && 'local' != $_SESSION['wsDomain']) {
905
//Change username to lowercase so that multiple user accounts
906
//won't be created for the same user.
907
//But don't do it for the local domain!
908
$username = strtolower( $username );
911
//The wiki considers an all lowercase name to be invalid; need to
912
//uppercase the first letter
913
$username[0] = strtoupper( $username[0] );
916
$this->printDebug( "Munged username: $username", NONSENSITIVE );
922
* Configures the authentication plugin for use with auto-authentication
927
function autoAuthSetup() {
928
global $wgLDAPAutoAuthDomain;
930
$this->setDomain( $wgLDAPAutoAuthDomain );
934
* Gets the searchstring for a user based upon settings for the domain.
935
* Returns a full DN for a user.
937
* @param string $username
941
function getSearchString( $username ) {
942
global $wgLDAPSearchStrings;
943
global $wgLDAPProxyAgent, $wgLDAPProxyAgentPassword;
945
$this->printDebug( "Entering getSearchString", NONSENSITIVE );
947
if ( isset( $wgLDAPSearchStrings[$_SESSION['wsDomain']] ) ) {
948
//This is a straight bind
949
$this->printDebug( "Doing a straight bind", NONSENSITIVE );
951
$tmpuserdn = $wgLDAPSearchStrings[$_SESSION['wsDomain']];
952
$userdn = str_replace( "USER-NAME", $username, $tmpuserdn );
954
//This is a proxy bind, or an anonymous bind with a search
955
if ( isset( $wgLDAPProxyAgent[$_SESSION['wsDomain']] ) ) {
956
//This is a proxy bind
957
$this->printDebug( "Doing a proxy bind", NONSENSITIVE );
958
$bind = $this->bindAs( $wgLDAPProxyAgent[$_SESSION['wsDomain']], $wgLDAPProxyAgentPassword[$_SESSION['wsDomain']] );
960
//This is an anonymous bind
961
$this->printDebug( "Doing an anonymous bind", NONSENSITIVE );
962
$bind = $this->bindAs();
966
$this->printDebug( "Failed to bind", NONSENSITIVE );
970
$userdn = $this->getUserDN( $username );
972
$this->printDebug( "userdn is: $userdn", SENSITIVE );
977
* Gets the DN of a user based upon settings for the domain.
978
* This function will set $this->LDAPUsername
979
* You must bind to the server before calling this.
981
* @param string $username
985
function getUserDN( $username ) {
986
global $wgLDAPSearchAttributes;
987
global $wgLDAPAuthAttribute;
989
$this->printDebug("Entering getUserDN", NONSENSITIVE);
991
//we need to do a subbase search for the entry
993
//Auto auth needs to check LDAP for required attributes.
994
if ( ( isset( $wgLDAPAuthAttribute[$_SESSION['wsDomain']] ) )
995
&& $this->useAutoAuth() ) {
996
$auth_filter = "(" . $wgLDAPAuthAttribute[$_SESSION['wsDomain']] . ")";
997
$srch_filter = "(" . $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" . $this->getLdapEscapedString( $username ) . ")";
998
$filter = "(&" . $srch_filter . $auth_filter . ")";
999
$this->printDebug( "Created an auth attribute filter: $filter", SENSITIVE );
1001
$filter = "(" . $wgLDAPSearchAttributes[$_SESSION['wsDomain']] . "=" . $this->getLdapEscapedString( $username ) . ")";
1002
$this->printDebug( "Created a regular filter: $filter", SENSITIVE );
1005
$attributes = array( "*" );
1006
$base = $this->getBaseDN( USERDN );
1008
$this->printDebug( "Using base: $base", SENSITIVE );
1010
$entry = @ldap_search( $this->ldapconn, $base, $filter, $attributes );
1012
$this->printDebug( "Couldn't find an entry", NONSENSITIVE );
1013
$this->fetchedUserInfo = false;
1017
$this->userInfo = @ldap_get_entries( $this->ldapconn, $entry );
1018
$this->fetchedUserInfo = true;
1020
//This is a pretty useful thing to have for auto authentication,
1021
//group checking, and pulling preferences.
1022
wfRunHooks( 'SetUsernameAttributeFromLDAP', array( &$this->LDAPUsername, $this->userInfo ) );
1023
if ( !is_string( $this->LDAPUsername ) ) {
1024
$this->printDebug( "Fetched username is not a string (check your hook code...). This message can be safely ignored if you do not have the SetUsernameAttributeFromLDAP hook defined.", NONSENSITIVE );
1025
$this->LDAPUsername = '';
1028
$userdn = $this->userInfo[0]["dn"];
1032
function getUserInfo() {
1033
//Don't fetch the same data more than once
1034
if ( $this->fetchedUserInfo ) {
1035
return $this->userInfo;
1038
$entry = @ldap_read( $this->ldapconn, $this->userdn, "objectclass=*" );
1039
$userInfo = @ldap_get_entries( $this->ldapconn, $entry );
1040
if ( $userInfo["count"] < 1 ) {
1041
$this->fetchedUserInfo = false;
1044
$this->fetchedUserInfo = true;
1050
* Retrieve user preferences from LDAP
1052
* @param string $userDN
1055
function getPreferences() {
1056
global $wgLDAPPreferences;
1057
global $wgLDAPRetrievePrefs;
1059
$this->printDebug("Entering getPreferences", NONSENSITIVE);
1061
$this->userInfo = $this->getUserInfo();
1062
if ( is_null( $this->userInfo ) ) {
1063
$this->printDebug("Failed to get preferences", NONSENSITIVE);
1066
//Retrieve preferences
1067
if ( isset( $wgLDAPPreferences[$_SESSION['wsDomain']] ) ) {
1068
$this->printDebug( "Retrieving preferences", NONSENSITIVE );
1069
$prefs = $wgLDAPPreferences[$_SESSION['wsDomain']];
1070
foreach ( array_keys( $prefs ) as $key ) {
1073
if ( isset( $this->userInfo[0]["$prefs[$key]"] ) ) {
1074
$this->email = $this->userInfo[0]["$prefs[$key]"][0];
1075
$this->printDebug( "Retrieved email ($this->email) using attribute ($prefs[$key])", NONSENSITIVE );
1079
if ( isset( $this->userInfo[0]["$prefs[$key]"] ) ) {
1080
$this->lang = $this->userInfo[0][$prefs[$key]][0];
1081
$this->printDebug( "Retrieved language ($this->lang) using attribute ($prefs[$key])", NONSENSITIVE );
1085
if ( isset( $this->userInfo[0]["$prefs[$key]"] ) ) {
1086
$this->nickname = $this->userInfo[0]["$prefs[$key]"][0];
1087
$this->printDebug( "Retrieved nickname ($this->nickname) using attribute ($prefs[$key])", NONSENSITIVE );
1091
if ( isset( $this->userInfo[0]["$prefs[$key]"] ) ) {
1092
$this->realname = $this->userInfo[0]["$prefs[$key]"][0];
1093
$this->printDebug( "Retrieved realname ($this->realname) using attribute ($prefs[$key])", NONSENSITIVE );
1098
} else if ( isset( $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) && $wgLDAPRetrievePrefs[$_SESSION['wsDomain']] ) {
1099
//DEPRECATED. Kept for backwards compatibility.
1100
$this->printDebug( "Retrieving preferences", NONSENSITIVE );
1101
$this->printDebug( '$wgLDAPRetrievePrefs is a DEPRECATED option, please use $wgLDAPPreferences.', NONSENSITIVE );
1103
if (isset($this->userInfo[0]["mail"])) {
1104
$this->email = $this->userInfo[0]["mail"][0];
1106
if (isset($this->userInfo[0]["preferredlanguage"])) {
1107
$this->lang = $this->userInfo[0]["preferredlanguage"][0];
1109
if (isset($this->userInfo[0]["displayname"])) {
1110
$this->nickname = $this->userInfo[0]["displayname"][0];
1112
if (isset($this->userInfo[0]["cn"])) {
1113
$this->realname = $this->userInfo[0]["cn"][0];
1116
$this->printDebug( "Retrieved: $this->email, $this->lang, $this->nickname, $this->realname", SENSITIVE );
1120
function synchUsername( $username ) {
1121
global $wgLDAPUniqueBlockLogin, $wgLDAPUniqueRenameUser;
1122
global $wgLDAPUniqueAttribute;
1124
$this->printDebug("Entering synchUsername", NONSENSITIVE);
1126
$this->userInfo = $this->getUserInfo();
1127
if ( is_null( $this->userInfo ) ) {
1128
$this->printDebug("Failed to get preferences", NONSENSITIVE);
1131
// Are we blocking login/renaming users on unique external ID mismatches?
1133
// This needs to be fixed before use! This probably does not work correctly
1134
// with all options. It is probably a good idea to refactor the username stuff
1135
// in general (as it is currently somewhat of a kludge). Also, MediaWiki does
1136
// not currently have support for this.
1138
if ( ( isset( $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] ) && $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] )
1139
|| ( isset( $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] ) && $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] ) ) {
1141
$this->printDebug( "Checking for username change in LDAP.", SENSITIVE );
1143
//Get the user's unique attribute from LDAP
1144
if ( isset( $wgLDAPUniqueAttribute[$_SESSION['wsDomain']] ) ) {
1145
$ldapuniqueattr = $wgLDAPUniqueAttribute[$_SESSION['wsDomain']];
1146
$this->externalid = $this->info[0][$ldapuniqueattr][0];
1151
$this->printDebug( "Retrieved external id: $this->externalid", SENSITIVE );
1153
$retrievedusername = User::whoIsExternalID( "$this->externalid" );
1155
$this->printDebug( "Username (in MediaWiki database) of fetched external id: $retrievedusername", SENSITIVE );
1157
// See if the username returned from the database matches the username given
1158
if ( $retrievedusername != '' && ( $username != $retrievedusername ) ) {
1159
if ( isset( $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] )
1160
&& $wgLDAPUniqueBlockLogin[$_SESSION['wsDomain']] ) {
1162
$this->printDebug( "Usernames do not match, blocking login.", SENSITIVE );
1164
} else if ( isset( $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] )
1165
&& $wgLDAPUniqueRenameUser[$_SESSION['wsDomain']] ) {
1167
$this->printDebug( "Usernames do not match, renaming user in database.", SENSITIVE );
1170
if ( version_compare( $wgVersion, '1.7.0', '<' ) ) {
1171
$this->printDebug( "Renaming users is only supported in MediaWiki 1.7+, please upgrade.", SENSITIVE );
1172
$this->markAuthFailed();
1176
$olduser = User::newFromName( $retrievedusername );
1177
$uid = $olduser->idForName();
1179
// Ensure we don't require the same class twice
1180
if ( !class_exists( 'RenameuserSQL' ) ) {
1181
require( 'Renameuser/SpecialRenameuser_body.php' );
1184
// Make a new rename user object with: from, to, uid of from
1185
$rename = new RenameuserSQL( $retrievedusername, $username, $uid );
1188
// For the time being we can't just allow the user to log in
1189
// as MediaWiki will try to create the user account after we
1190
// do a rename. If we don't return false, the user will get
1196
$this->printDebug( "Usernames matched or the user doesn't exist in the database yet.", SENSITIVE );
1203
* Checks to see whether a user is in a required group.
1205
* @param string $username
1209
function checkGroups( $username ) {
1210
global $wgLDAPGroupDN;
1211
global $wgLDAPRequiredGroups, $wgLDAPExcludedGroups;
1213
$this->printDebug("Entering checkGroups", NONSENSITIVE);
1215
//Old style groups, non-nestable and fairly limited on group type (full DN
1216
//versus username). DEPRECATED
1217
if ( $wgLDAPGroupDN ) {
1218
$this->printDebug( "Checking for (old style) group membership", NONSENSITIVE );
1219
//we need to do a subbase search for the entry
1220
$filter = "(member=" . $this->getLdapEscapedString( $this->userdn ) . ")";
1221
$info = @ldap_get_entries( $this->ldapconn, @ldap_search( $this->ldapconn, $wgLDAPGroupDN, $filter ) );
1223
return ( $info["count"] >= 1 );
1226
if ( isset( $wgLDAPExcludedGroups[$_SESSION['wsDomain']] ) ) {
1227
$this->printDebug( "Checking for excluded group membership", NONSENSITIVE );
1228
$excgroups = $wgLDAPExcludedGroups[$_SESSION['wsDomain']];
1229
for ( $i = 0; $i < count( $excgroups ); $i++ ) {
1230
$excgroups[$i] = strtolower( $excgroups[$i] );
1233
$this->printDebug( "Excluded groups:", NONSENSITIVE, $excgroups );
1235
foreach ( $this->userLDAPGroups["dn"] as $group ) {
1236
$this->printDebug( "Checking against: $group", NONSENSITIVE );
1237
if ( in_array( $group, $excgroups ) ) {
1238
$this->printDebug( "Found user in an excluded group.", NONSENSITIVE );
1244
//New style group checking
1245
if ( isset( $wgLDAPRequiredGroups[$_SESSION['wsDomain']] ) ) {
1246
$this->printDebug( "Checking for (new style) group membership", NONSENSITIVE );
1247
$reqgroups = $wgLDAPRequiredGroups[$_SESSION['wsDomain']];
1248
for ( $i = 0; $i < count( $reqgroups ); $i++ ) {
1249
$reqgroups[$i] = strtolower( $reqgroups[$i] );
1252
$this->printDebug( "Required groups:", NONSENSITIVE, $reqgroups );
1254
foreach ( $this->userLDAPGroups["dn"] as $group ) {
1255
$this->printDebug( "Checking against: $group", NONSENSITIVE );
1256
if ( in_array( $group, $reqgroups ) ) {
1257
$this->printDebug( "Found user in a group.", NONSENSITIVE );
1262
$this->printDebug("Couldn't find the user in any groups.", NONSENSITIVE );
1266
// Ensure we return true if we aren't checking groups.
1271
* Function to get the user's groups.
1275
function getGroups( $username ) {
1276
global $wgLDAPRequiredGroups, $wgLDAPUseLDAPGroups;
1277
global $wgLDAPGroupUseFullDN, $wgLDAPGroupUseRetrievedUsername;
1278
global $wgLDAPGroupSearchNestedGroups;
1279
global $wgLDAPGroupsPrevail;
1280
global $wgLDAPGroupsUseMemberOf;
1282
$this->printDebug("Entering getGroups", NONSENSITIVE);
1285
if ( isset( $wgLDAPRequiredGroups[$_SESSION['wsDomain']] ) || ( isset( $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) && $wgLDAPUseLDAPGroups[$_SESSION['wsDomain']] ) ) {
1286
$this->printDebug( "Retrieving LDAP group membership", NONSENSITIVE );
1288
//Let's figure out what we should be searching for
1289
if ( isset( $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) && $wgLDAPGroupUseFullDN[$_SESSION['wsDomain']] ) {
1290
$usertopass = $this->userdn;
1292
if ( ( isset( $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']] ) && $wgLDAPGroupUseRetrievedUsername[$_SESSION['wsDomain']] )
1293
&& $this->LDAPUsername != '' ) {
1295
$usertopass = $this->LDAPUsername;
1297
$usertopass = $username;
1301
if ( isset( $wgLDAPGroupsUseMemberOf[$_SESSION['wsDomain']] ) && $wgLDAPGroupsUseMemberOf[$_SESSION['wsDomain']] ) {
1302
$this->printDebug( "Using memberOf", NONSENSITIVE );
1303
$this->userInfo = $this->getUserInfo();
1304
if ( is_null( $this->userInfo ) ) {
1305
$this->printDebug("Failed to get memberOf attribute", NONSENSITIVE);
1307
if ( isset( $this->userInfo[0]["memberof"] ) ) {
1308
# The first entry is always a count
1309
$memberOfMembers = $this->userInfo[0]["memberof"];
1310
array_shift( $memberOfMembers );
1311
$groups = array( "dn"=> array(), "short"=>array() );
1312
foreach( $memberOfMembers as $mem ) {
1313
array_push( $groups["dn"], strtolower( $mem ) );
1315
$this->userLDAPGroups = $groups;
1318
$this->printDebug( "Searching for the groups", NONSENSITIVE );
1319
$this->userLDAPGroups = $this->searchGroups( $usertopass );
1321
if ( isset( $wgLDAPGroupSearchNestedGroups[$_SESSION['wsDomain']] ) && $wgLDAPGroupSearchNestedGroups[$_SESSION['wsDomain']] ) {
1322
$this->userLDAPGroups = $this->searchNestedGroups( $this->userLDAPGroups );
1323
$this->printDebug( "Got the following nested groups:", SENSITIVE, $this->userLDAPGroups["dn"] );
1327
//Only find all groups if the user has any groups; otherwise, we are
1328
//just wasting a search.
1329
if ( ( isset( $wgLDAPGroupsPrevail[$_SESSION['wsDomain']] ) && $wgLDAPGroupsPrevail[$_SESSION['wsDomain']] ) && count( $this->userLDAPGroups ) != 0 ) {
1330
$this->allLDAPGroups = $this->searchGroups( '*' );
1336
* Function to return an array of nested groups when given a group or list of groups.
1337
* $searchedgroups is used for tail recursion and shouldn't be provided
1338
* when called externally.
1340
* @param string $userDN
1341
* @param array $searchedgroups
1345
function searchNestedGroups( $groups, $searchedgroups = array( "dn" => Array(), "short" => Array() ) ) {
1346
$this->printDebug( "Entering searchNestedGroups", NONSENSITIVE );
1348
//base case, no more groups left to check
1349
if ( count( $groups["dn"] ) == 0 ) {
1350
$this->printDebug( "No more groups to search.", NONSENSITIVE );
1351
return $searchedgroups;
1354
$this->printDebug( "Searching groups:", SENSITIVE, $groups["dn"] );
1356
$groupstosearch = array( "short"=>array(), "dn"=>array() );
1357
foreach ( $groups["dn"] as $group ) {
1358
$returnedgroups = $this->searchGroups( $group );
1359
$this->printDebug( "Group $group is in the following groups:", SENSITIVE, $returnedgroups["dn"] );
1360
foreach ( $returnedgroups["dn"] as $searchme ) {
1361
if ( in_array( $searchme, $searchedgroups["dn"] ) ) {
1362
//We already searched this, move on
1365
//We'll need to search this group's members now
1366
$this->printDebug( "Adding $searchme to the list of groups (1)", SENSITIVE );
1367
$groupstosearch["dn"][] = $searchme;
1370
foreach ( $returnedgroups["short"] as $searchme ) {
1371
if ( in_array( $searchme, $searchedgroups["short"] ) ) {
1372
//We already searched this, move on
1375
$this->printDebug( "Adding $searchme to the list of groups (2)", SENSITIVE );
1376
//We'll need to search this group's members now
1377
$groupstosearch["short"][] = $searchme;
1382
$searchedgroups = array_merge_recursive( $groups, $searchedgroups );
1384
//Mmmmmm. Tail recursion. Tasty.
1385
return $this->searchNestedGroups( $groupstosearch, $searchedgroups );
1389
* Search groups for the supplied DN
1395
function searchGroups( $dn ) {
1396
global $wgLDAPGroupObjectclass, $wgLDAPGroupAttribute, $wgLDAPGroupNameAttribute;
1397
global $wgLDAPProxyAgent, $wgLDAPProxyAgentPassword;
1400
$this->printDebug( "Entering searchGroups", NONSENSITIVE );
1402
$base = $this->getBaseDN( GROUPDN );
1404
$objectclass = $wgLDAPGroupObjectclass[$_SESSION['wsDomain']];
1405
$attribute = $wgLDAPGroupAttribute[$_SESSION['wsDomain']];
1406
$nameattribute = $wgLDAPGroupNameAttribute[$_SESSION['wsDomain']];
1408
// We actually want to search for * not \2a
1410
if ( $value != "*" )
1411
$value = $this->getLdapEscapedString( $value );
1413
$filter = "(&($attribute=$value)(objectclass=$objectclass))";
1415
$this->printDebug( "Search string: $filter", SENSITIVE );
1417
if ( isset( $wgLDAPProxyAgent[$_SESSION['wsDomain']] ) ) {
1418
//We'll try to bind as the proxyagent as the proxyagent should normally have more
1419
//rights than the user. If the proxyagent fails to bind, we will still be able
1420
//to search as the normal user (which is why we don't return on fail).
1421
$this->printDebug( "Binding as the proxyagent", NONSENSITIVE );
1422
$bind = $this->bindAs( $wgLDAPProxyAgent[$_SESSION['wsDomain']], $wgLDAPProxyAgentPassword[$_SESSION['wsDomain']] );
1425
$info = @ldap_search( $this->ldapconn, $base, $filter );
1426
#if ( $info["count"] < 1 ) {
1428
$this->printDebug( "No entries returned from search.", SENSITIVE );
1430
//Return an array so that other functions
1432
return array( "short"=>array(), "dn"=>array() );
1435
$entries = @ldap_get_entries( $this->ldapconn, $info );
1437
//We need to shift because the first entry will be a count
1438
array_shift( $entries );
1440
//Let's get a list of both full dn groups and shortname groups
1441
$groups = array( "short"=>array(), "dn"=>array() );
1442
foreach ( $entries as $entry ) {
1443
$shortMember = strtolower( $entry[$nameattribute][0] );
1444
$dnMember = strtolower( $entry['dn'] );
1445
$groups["short"][] = $shortMember;
1446
$groups["dn"][] = $dnMember;
1449
$this->printDebug( "Returned groups:", SENSITIVE, $groups["dn"] );
1455
* Returns true if this group is in the list of the currently authenticated
1456
* user's groups, else false.
1458
* @param string $group
1462
function hasLDAPGroup( $group ) {
1463
$this->printDebug( "Entering hasLDAPGroup", NONSENSITIVE );
1465
return in_array( strtolower( $group ), $this->userLDAPGroups["short"] );
1469
* Returns true if an LDAP group with this name exists, else false.
1471
* @param string $group
1475
function isLDAPGroup( $group ) {
1476
$this->printDebug( "Entering isLDAPGroup", NONSENSITIVE );
1478
return in_array( strtolower( $group ), $this->allLDAPGroups["short"] );
1482
* Helper function for updateUser() and initUser(). Adds users into MediaWiki security groups
1483
* based upon groups retreived from LDAP.
1488
function setGroups( &$user ) {
1489
global $wgLDAPGroupsPrevail, $wgGroupPermissions;
1490
global $wgLDAPLocallyManagedGroups;
1492
//TODO: this is *really* ugly code. clean it up!
1494
$this->printDebug( "Entering setGroups.", NONSENSITIVE );
1496
# add groups permissions
1497
$localAvailGrps = $user->getAllGroups();
1498
$localUserGrps = $user->getEffectiveGroups();
1500
$defaultLocallyManagedGrps = array( 'bot', 'sysop', 'bureaucrat' );
1502
if ( isset( $wgLDAPLocallyManagedGroups[$_SESSION['wsDomain']] ) ) {
1503
$locallyManagedGrps = $wgLDAPLocallyManagedGroups[$_SESSION['wsDomain']];
1504
$locallyManagedGrps = array_unique( array_merge( $defaultLocallyManagedGrps, $locallyManagedGrps ) );
1505
$this->printDebug( "Locally managed groups: ", SENSITIVE, $locallyManagedGrps );
1507
$locallyManagedGrps = $defaultLocallyManagedGrps;
1508
$this->printDebug( "Locally managed groups is unset, using defaults: ", SENSITIVE, $locallyManagedGrps );
1512
# Add ldap groups as local groups
1513
if ( isset( $wgLDAPGroupsPrevail[$_SESSION['wsDomain']] ) && $wgLDAPGroupsPrevail[$_SESSION['wsDomain']] ) {
1514
$this->printDebug( "Adding all groups to wgGroupPermissions: ", SENSITIVE, $this->allLDAPGroups );
1515
foreach ( $this->allLDAPGroups["short"] as $ldapgroup )
1516
if ( !array_key_exists( $ldapgroup, $wgGroupPermissions ) )
1517
$wgGroupPermissions[$ldapgroup] = array();
1520
$this->printDebug( "Available groups are: ", NONSENSITIVE, $localAvailGrps );
1521
$this->printDebug( "Effective groups are: ", NONSENSITIVE, $localUserGrps );
1523
# note: $localUserGrps does not need to be updated with $cGroup added,
1524
# as $localAvailGrps contains $cGroup only once.
1525
foreach ( $localAvailGrps as $cGroup ) {
1526
# did we once add the user to the group?
1527
if ( in_array( $cGroup,$localUserGrps ) ) {
1528
$this->printDebug( "Checking to see if we need to remove user from: $cGroup", NONSENSITIVE );
1529
if ( ( !$this->hasLDAPGroup( $cGroup ) ) && ( !in_array( $cGroup, $locallyManagedGrps ) ) ) {
1530
$this->printDebug( "Removing user from: $cGroup", NONSENSITIVE );
1531
# the ldap group overrides the local group
1532
# so as the user is currently not a member of the ldap group, he shall be removed from the local group
1533
$user->removeGroup( $cGroup );
1535
} else { # no, but maybe the user has recently been added to the ldap group?
1536
$this->printDebug( "Checking to see if user is in: $cGroup", NONSENSITIVE );
1537
if ( $this->hasLDAPGroup( $cGroup ) ) {
1538
$this->printDebug( "Adding user to: $cGroup", NONSENSITIVE );
1539
$user->addGroup( $cGroup );
1546
* Returns a password that is created via the configured hash settings.
1548
* @param string $password
1552
function getPasswordHash( $password ) {
1553
global $wgLDAPPasswordHash;
1555
$this->printDebug( "Entering getPasswordHash", NONSENSITIVE );
1557
if ( isset( $wgLDAPPasswordHash[$_SESSION['wsDomain']] ) ) {
1558
$hashtouse = $wgLDAPPasswordHash[$_SESSION['wsDomain']];
1563
//Set the password hashing based upon admin preference
1564
switch ( $hashtouse ) {
1566
$pass = '{CRYPT}' . crypt( $password );
1572
$pwd_sha = base64_encode( pack( 'H*',sha1( $password ) ) );
1573
$pass = "{SHA}".$pwd_sha;
1577
$this->printDebug( "Password is $pass", HIGHLYSENSITIVE );
1582
* Prints debugging information. $debugText is what you want to print, $debugVal
1583
* is the level at which you want to print the information.
1585
* @param string $debugText
1586
* @param string $debugVal
1589
function printDebug( $debugText, $debugVal, $debugArr = null ) {
1590
global $wgLDAPDebug;
1592
if ( isset( $debugArr ) ) {
1593
if ( $wgLDAPDebug > $debugVal ) {
1594
$text = $debugText . " " . implode( "::", $debugArr );
1595
wfDebugLog( 'ldap', $text, false );
1598
if ( $wgLDAPDebug > $debugVal ) {
1599
wfDebugLog( 'ldap', $debugText, false );
1605
* Binds as $userdn with $password. This can be called with only the ldap
1606
* connection resource for an anonymous bind.
1608
* @param string $userdn
1609
* @param string $password
1613
function bindAs( $userdn=null, $password=null ) {
1614
//Let's see if the user can authenticate.
1615
if ( $userdn == null || $password == null ) {
1616
$bind = @ldap_bind( $this->ldapconn );
1618
$bind = @ldap_bind( $this->ldapconn, $userdn, $password );
1621
$this->printDebug( "Failed to bind as $userdn", NONSENSITIVE );
1622
$this->printDebug( "with password: $password", HIGHLYSENSITIVE );
1629
* Returns true if auto-authentication is allowed, and the user is
1630
* authenticating using the auto-authentication domain.
1635
function useAutoAuth() {
1636
global $wgLDAPAutoAuthDomain;
1638
$this->printDebug( "", NONSENSITIVE );
1639
return isset( $wgLDAPAutoAuthDomain ) && $_SESSION['wsDomain'] == $wgLDAPAutoAuthDomain;
1643
* Returns a string which has the chars *, (, ), \ & NUL escaped to LDAP compliant
1644
* syntax as per RFC 2254
1645
* Thanks and credit to Iain Colledge for the research and function.
1647
* @param string $string
1651
function getLdapEscapedString ( $string ) {
1652
// Make the string LDAP compliant by escaping *, (, ) , \ & NUL
1654
array( "*", "(", ")", "\\", "\x00" ), //replace this
1655
array( "\\2a", "\\28", "\\29", "\\5c", "\\00" ), //with this
1661
* Returns a basedn by the type of entry we are searching for.
1667
function getBaseDN ( $type ) {
1668
global $wgLDAPBaseDNs, $wgLDAPGroupBaseDNs, $wgLDAPUserBaseDNs;
1670
$this->printDebug( "Entering getBaseDN", NONSENSITIVE );
1675
if ( isset( $wgLDAPUserBaseDNs[$_SESSION['wsDomain']] ) ) {
1676
$ret = $wgLDAPUserBaseDNs[$_SESSION['wsDomain']];
1680
if ( isset( $wgLDAPGroupBaseDNs[$_SESSION['wsDomain']] ) ) {
1681
$ret = $wgLDAPGroupBaseDNs[$_SESSION['wsDomain']];
1685
if ( isset( $wgLDAPBaseDNs[$_SESSION['wsDomain']] ) ) {
1686
$ret = $wgLDAPBaseDNs[$_SESSION['wsDomain']];
1687
$this->printDebug( "basedn is $ret", NONSENSITIVE );
1690
$this->printDebug( "basedn is not set.", NONSENSITIVE );
1697
$this->printDebug( "basedn is not set for this type of entry, trying to get the default basedn.", NONSENSITIVE );
1698
// We will never reach here if $type is self::DEFAULTDN, so to avoid code
1699
// code duplication, we'll get the default by re-calling the function.
1700
return $this->getBaseDN( DEFAULTDN );
1702
$this->printDebug( "basedn is $ret", NONSENSITIVE );
1709
// The following was derived from the SSL Authentication plugin
1710
// http://www.mediawiki.org/wiki/SSL_authentication
1713
* Sets up the SSL authentication piece of the LDAP plugin.
1717
function AutoAuthSetup() {
1718
global $wgLDAPAutoAuthUsername;
1719
global $wgLDAPSSLUsername;
1720
global $wgLDAPAutoAuthDomain;
1721
global $wgLDAPSmartcardDomain;
1725
$wgAuth = new LdapAuthenticationPlugin();
1727
$wgAuth->printDebug( "Entering AutoAuthSetup.", NONSENSITIVE );
1729
//Set configuration options for backwards compatibility
1730
if ( isset( $wgLDAPSSLUsername ) ) {
1731
$wgAuth->printDebug( 'Setting $wgLDAPAutoAuthUsername to $wgLDAPSSLUsername; please change your configuration to fix this deprecated configuration variable.', NONSENSITIVE );
1732
$wgLDAPAutoAuthUsername = $wgLDAPSSLUsername;
1734
if ( isset( $wgLDAPSmartcardDomain ) ) {
1735
$wgAuth->printDebug( 'Setting $wgLDAPAutoAuthDomain to $wgLDAPSmartcardDomain; please change your configuration to fix this deprecated configuration variable.', NONSENSITIVE );
1736
$wgLDAPAutoAuthDomain = $wgLDAPSmartcardDomain;
1739
if( $wgLDAPAutoAuthUsername != null ) {
1740
$wgAuth->printDebug( "wgLDAPAutoAuthUsername is not null, adding hooks.", NONSENSITIVE );
1741
if ( version_compare( $wgVersion, '1.14.0', '<' ) ) {
1742
$wgHooks['UserLoadFromSession'][] = 'LdapAutoAuthentication::Authenticate';
1744
$wgHooks['UserLoadAfterLoadFromSession'][] = 'LdapAutoAuthentication::Authenticate';
1746
$wgHooks['PersonalUrls'][] = 'LdapAutoAuthentication::NoLogout'; /* Disallow logout link */