Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 1071 |
|
0.00% |
0 / 58 |
CRAP | |
0.00% |
0 / 1 |
LdapAuthenticationPlugin | |
0.00% |
0 / 1071 |
|
0.00% |
0 / 58 |
98282 | |
0.00% |
0 / 1 |
getInstance | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
ldap_connect | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_bind | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_unbind | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
ldap_modify | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_add | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_delete | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_search | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
ldap_read | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
ldap_list | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
ldap_get_entries | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_count_entries | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
ldap_errno | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getConf | |
0.00% |
0 / 134 |
|
0.00% |
0 / 1 |
2162 | |||
setOrDefault | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setOrDefaultPrivate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
userExists | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
userExistsReal | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
42 | |||
connect | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
156 | |||
authenticate | |
0.00% |
0 / 65 |
|
0.00% |
0 / 1 |
342 | |||
markAuthFailed | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
domainList | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
autoCreate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
setPassword | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
56 | |||
updateExternalDB | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
306 | |||
canCreateAccounts | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
allowPasswordChange | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 | |||
allowSetLocalPassword | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addUser | |
0.00% |
0 / 91 |
|
0.00% |
0 / 1 |
650 | |||
setDomain | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getDomain | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
72 | |||
validDomain | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
updateUser | |
0.00% |
0 / 30 |
|
0.00% |
0 / 1 |
90 | |||
initUser | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
30 | |||
strict | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
getCanonicalName | |
0.00% |
0 / 40 |
|
0.00% |
0 / 1 |
72 | |||
autoAuthSetup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSearchString | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
getUserDN | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
56 | |||
getUserInfo | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
getUserInfoStateless | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
6 | |||
getPreferences | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
90 | |||
checkGroups | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
getGroups | |
0.00% |
0 / 53 |
|
0.00% |
0 / 1 |
380 | |||
searchNestedGroups | |
0.00% |
0 / 24 |
|
0.00% |
0 / 1 |
56 | |||
searchGroups | |
0.00% |
0 / 63 |
|
0.00% |
0 / 1 |
240 | |||
hasLDAPGroup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
isLDAPGroup | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
setGroups | |
0.00% |
0 / 37 |
|
0.00% |
0 / 1 |
110 | |||
getPasswordHash | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
printDebug | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
bindAs | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
unbind | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
useAutoAuth | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLdapEscapedString | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getBaseDN | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
42 | |||
loadDomain | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
12 | |||
saveDomain | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | /** |
3 | * Copyright (C) 2004 Ryan Lane <https://www.mediawiki.org/wiki/User:Ryan_lane> |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | */ |
20 | |
21 | use MediaWiki\Context\RequestContext; |
22 | use MediaWiki\Extension\LdapAuthentication\Hooks\HookRunner; |
23 | use MediaWiki\Extension\LdapAuthentication\LdapAuthenticationException; |
24 | use MediaWiki\MediaWikiServices; |
25 | use MediaWiki\User\User; |
26 | use Wikimedia\AtEase\AtEase; |
27 | |
28 | class LdapAuthenticationPlugin { |
29 | |
30 | /** @var self|null */ |
31 | private static $instance = null; |
32 | |
33 | /** @var resource|null ldap connection resource */ |
34 | public $ldapconn; |
35 | |
36 | /** @var string|null */ |
37 | public $email; |
38 | /** @var string|null */ |
39 | public $lang; |
40 | /** @var string|null */ |
41 | public $realname; |
42 | /** @var string|null */ |
43 | public $nickname; |
44 | /** @var string|null */ |
45 | public $externalid; |
46 | |
47 | /** @var string username pulled from ldap */ |
48 | public $LDAPUsername = ''; |
49 | |
50 | /** @var string userdn pulled from ldap */ |
51 | public $userdn = ''; |
52 | |
53 | /** @var string[][] groups pulled from ldap */ |
54 | public $userLDAPGroups = []; |
55 | /** @var string[][] groups pulled from ldap */ |
56 | public $allLDAPGroups = []; |
57 | |
58 | /** @var bool|null to test for failed auth */ |
59 | public $authFailed; |
60 | |
61 | /** @var bool|null to test for fetched user info */ |
62 | public $fetchedUserInfo; |
63 | |
64 | /** @var array|null the user's entry and all attributes */ |
65 | public $userInfo; |
66 | |
67 | /** @var string|null the user we are currently bound as */ |
68 | public $boundAs; |
69 | |
70 | /** |
71 | * Fetch the singleton instance of LdapAuthenticationPlugin |
72 | * @return self |
73 | */ |
74 | public static function getInstance() { |
75 | if ( self::$instance === null ) { |
76 | self::$instance = new LdapAuthenticationPlugin; |
77 | } |
78 | return self::$instance; |
79 | } |
80 | |
81 | /** |
82 | * Wrapper for ldap_connect |
83 | * @param string|null $hostname |
84 | * @param int $port |
85 | * @return resource|false |
86 | */ |
87 | public static function ldap_connect( $hostname = null, $port = 389 ) { |
88 | AtEase::suppressWarnings(); |
89 | $ret = ldap_connect( $hostname, $port ); |
90 | AtEase::restoreWarnings(); |
91 | return $ret; |
92 | } |
93 | |
94 | /** |
95 | * Wrapper for ldap_bind |
96 | * @param resource $ldapconn |
97 | * @param string|null $dn |
98 | * @param string|null $password |
99 | * @return bool |
100 | */ |
101 | public static function ldap_bind( $ldapconn, $dn = null, $password = null ) { |
102 | AtEase::suppressWarnings(); |
103 | $ret = ldap_bind( $ldapconn, $dn, $password ); |
104 | AtEase::restoreWarnings(); |
105 | return $ret; |
106 | } |
107 | |
108 | /** |
109 | * Wrapper for ldap_unbind |
110 | * @param resource $ldapconn |
111 | * @return bool |
112 | */ |
113 | public static function ldap_unbind( $ldapconn ) { |
114 | if ( $ldapconn ) { |
115 | AtEase::suppressWarnings(); |
116 | $ret = ldap_unbind( $ldapconn ); |
117 | AtEase::restoreWarnings(); |
118 | } else { |
119 | $ret = false; |
120 | } |
121 | return $ret; |
122 | } |
123 | |
124 | /** |
125 | * Wrapper for ldap_modify |
126 | * @param resource $ldapconn |
127 | * @param string $dn |
128 | * @param array $entry |
129 | * @return bool |
130 | */ |
131 | public static function ldap_modify( $ldapconn, $dn, $entry ) { |
132 | AtEase::suppressWarnings(); |
133 | $ret = ldap_modify( $ldapconn, $dn, $entry ); |
134 | AtEase::restoreWarnings(); |
135 | return $ret; |
136 | } |
137 | |
138 | /** |
139 | * Wrapper for ldap_add |
140 | * @param resource $ldapconn |
141 | * @param string $dn |
142 | * @param array $entry |
143 | * @return bool |
144 | */ |
145 | public static function ldap_add( $ldapconn, $dn, $entry ) { |
146 | AtEase::suppressWarnings(); |
147 | $ret = ldap_add( $ldapconn, $dn, $entry ); |
148 | AtEase::restoreWarnings(); |
149 | return $ret; |
150 | } |
151 | |
152 | /** |
153 | * Wrapper for ldap_delete |
154 | * @param resource $ldapconn |
155 | * @param string $dn |
156 | * @return bool |
157 | */ |
158 | public static function ldap_delete( $ldapconn, $dn ) { |
159 | AtEase::suppressWarnings(); |
160 | $ret = ldap_delete( $ldapconn, $dn ); |
161 | AtEase::restoreWarnings(); |
162 | return $ret; |
163 | } |
164 | |
165 | /** |
166 | * Wrapper for ldap_search |
167 | * @param resource $ldapconn |
168 | * @param string $basedn |
169 | * @param string $filter |
170 | * @param array|null $attributes |
171 | * @param int|null $attrsonly |
172 | * @param int|null $sizelimit |
173 | * @param int|null $timelimit |
174 | * @param int|null $deref |
175 | * @return resource |
176 | */ |
177 | public static function ldap_search( |
178 | $ldapconn, |
179 | $basedn, |
180 | $filter, |
181 | $attributes = [], |
182 | $attrsonly = null, |
183 | $sizelimit = null, |
184 | $timelimit = null, |
185 | $deref = null |
186 | ) { |
187 | AtEase::suppressWarnings(); |
188 | $ret = ldap_search( |
189 | $ldapconn, |
190 | $basedn, |
191 | $filter, |
192 | $attributes, |
193 | $attrsonly, |
194 | $sizelimit, |
195 | $timelimit, |
196 | $deref |
197 | ); |
198 | AtEase::restoreWarnings(); |
199 | return $ret; |
200 | } |
201 | |
202 | /** |
203 | * Wrapper for ldap_read |
204 | * @param resource $ldapconn |
205 | * @param string $basedn |
206 | * @param string $filter |
207 | * @param array|null $attributes |
208 | * @param int|null $attrsonly |
209 | * @param int|null $sizelimit |
210 | * @param int|null $timelimit |
211 | * @param int|null $deref |
212 | * @return resource |
213 | */ |
214 | public static function ldap_read( |
215 | $ldapconn, |
216 | $basedn, |
217 | $filter, |
218 | $attributes = [], |
219 | $attrsonly = null, |
220 | $sizelimit = null, |
221 | $timelimit = null, |
222 | $deref = null |
223 | ) { |
224 | AtEase::suppressWarnings(); |
225 | $ret = ldap_read( |
226 | $ldapconn, |
227 | $basedn, |
228 | $filter, |
229 | $attributes, |
230 | $attrsonly, |
231 | $sizelimit, |
232 | $timelimit, |
233 | $deref |
234 | ); |
235 | AtEase::restoreWarnings(); |
236 | return $ret; |
237 | } |
238 | |
239 | /** |
240 | * Wrapper for ldap_list |
241 | * @param resource $ldapconn |
242 | * @param string $basedn |
243 | * @param string $filter |
244 | * @param array|null $attributes |
245 | * @param int|null $attrsonly |
246 | * @param int|null $sizelimit |
247 | * @param int|null $timelimit |
248 | * @param int|null $deref |
249 | * @return resource |
250 | */ |
251 | public static function ldap_list( |
252 | $ldapconn, |
253 | $basedn, |
254 | $filter, |
255 | $attributes = [], |
256 | $attrsonly = null, |
257 | $sizelimit = null, |
258 | $timelimit = null, |
259 | $deref = null |
260 | ) { |
261 | AtEase::suppressWarnings(); |
262 | $ret = ldap_list( |
263 | $ldapconn, |
264 | $basedn, |
265 | $filter, |
266 | $attributes, |
267 | $attrsonly, |
268 | $sizelimit, |
269 | $timelimit, |
270 | $deref |
271 | ); |
272 | AtEase::restoreWarnings(); |
273 | return $ret; |
274 | } |
275 | |
276 | /** |
277 | * Wrapper for ldap_get_entries |
278 | * @param resource $ldapconn |
279 | * @param resource $resultid |
280 | * @return array |
281 | * @phan-return array<int|string,int|array<int|string,string|int|array<int|string,int|string>>> |
282 | */ |
283 | public static function ldap_get_entries( $ldapconn, $resultid ) { |
284 | AtEase::suppressWarnings(); |
285 | $ret = ldap_get_entries( $ldapconn, $resultid ); |
286 | AtEase::restoreWarnings(); |
287 | return $ret; |
288 | } |
289 | |
290 | /** |
291 | * Wrapper for ldap_count_entries |
292 | * @param resource $ldapconn |
293 | * @param resource $resultid |
294 | * @return int |
295 | */ |
296 | public static function ldap_count_entries( $ldapconn, $resultid ) { |
297 | AtEase::suppressWarnings(); |
298 | $ret = ldap_count_entries( $ldapconn, $resultid ); |
299 | AtEase::restoreWarnings(); |
300 | return $ret; |
301 | } |
302 | |
303 | /** |
304 | * Wrapper for ldap_errno |
305 | * @param resource $ldapconn |
306 | * @return int |
307 | */ |
308 | public static function ldap_errno( $ldapconn ) { |
309 | AtEase::suppressWarnings(); |
310 | $ret = ldap_errno( $ldapconn ); |
311 | AtEase::restoreWarnings(); |
312 | return $ret; |
313 | } |
314 | |
315 | /** |
316 | * Get configuration defined by admin, or return default value |
317 | * |
318 | * @param string $preference |
319 | * @param string $domain |
320 | * @return mixed |
321 | */ |
322 | public function getConf( $preference, $domain = '' ) { |
323 | # Global preferences |
324 | switch ( $preference ) { |
325 | case 'DomainNames': |
326 | global $wgLDAPDomainNames; |
327 | return $wgLDAPDomainNames; |
328 | case 'UseLocal': |
329 | global $wgLDAPUseLocal; |
330 | return $wgLDAPUseLocal; |
331 | case 'AutoAuthUsername': |
332 | global $wgLDAPAutoAuthUsername; |
333 | return $wgLDAPAutoAuthUsername; |
334 | case 'AutoAuthDomain': |
335 | global $wgLDAPAutoAuthDomain; |
336 | return $wgLDAPAutoAuthDomain; |
337 | case 'LockOnBlock': |
338 | global $wgLDAPLockOnBlock; |
339 | return $wgLDAPLockOnBlock; |
340 | case 'LDAPLockPasswordPolicy': |
341 | global $wgLDAPLockPasswordPolicy; |
342 | return $wgLDAPLockPasswordPolicy; |
343 | } |
344 | |
345 | # Domain specific preferences |
346 | if ( !$domain ) { |
347 | $domain = $this->getDomain(); |
348 | } |
349 | switch ( $preference ) { |
350 | case 'ServerNames': |
351 | global $wgLDAPServerNames; |
352 | return self::setOrDefault( $wgLDAPServerNames, $domain ); |
353 | case 'EncryptionType': |
354 | global $wgLDAPEncryptionType; |
355 | return self::setOrDefault( $wgLDAPEncryptionType, $domain, 'tls' ); |
356 | case 'Options': |
357 | global $wgLDAPOptions; |
358 | return self::setOrDefault( $wgLDAPOptions, $domain, [] ); |
359 | case 'Port': |
360 | global $wgLDAPPort; |
361 | if ( isset( $wgLDAPPort[$domain] ) ) { |
362 | $this->printDebug( "Using non-standard port: " . $wgLDAPPort[$domain], SENSITIVE ); |
363 | return (string)$wgLDAPPort[$domain]; |
364 | } |
365 | |
366 | if ( $this->getConf( 'EncryptionType' ) == 'ssl' ) { |
367 | return "636"; |
368 | } |
369 | |
370 | return "389"; |
371 | case 'SearchString': |
372 | global $wgLDAPSearchStrings; |
373 | return self::setOrDefault( $wgLDAPSearchStrings, $domain ); |
374 | case 'ProxyAgent': |
375 | global $wgLDAPProxyAgent; |
376 | return self::setOrDefault( $wgLDAPProxyAgent, $domain ); |
377 | case 'ProxyAgentPassword': |
378 | global $wgLDAPProxyAgentPassword; |
379 | return self::setOrDefaultPrivate( $wgLDAPProxyAgentPassword, $domain ); |
380 | case 'SearchAttribute': |
381 | global $wgLDAPSearchAttributes; |
382 | return self::setOrDefault( $wgLDAPSearchAttributes, $domain ); |
383 | case 'BaseDN': |
384 | global $wgLDAPBaseDNs; |
385 | return self::setOrDefault( $wgLDAPBaseDNs, $domain ); |
386 | case 'GroupBaseDN': |
387 | global $wgLDAPGroupBaseDNs; |
388 | return self::setOrDefault( $wgLDAPGroupBaseDNs, $domain ); |
389 | case 'UserBaseDN': |
390 | global $wgLDAPUserBaseDNs; |
391 | return self::setOrDefault( $wgLDAPUserBaseDNs, $domain ); |
392 | case 'WriterDN': |
393 | global $wgLDAPWriterDN; |
394 | return self::setOrDefault( $wgLDAPWriterDN, $domain ); |
395 | case 'WriterPassword': |
396 | global $wgLDAPWriterPassword; |
397 | return self::setOrDefaultPrivate( $wgLDAPWriterPassword, $domain ); |
398 | case 'WriteLocation': |
399 | global $wgLDAPWriteLocation; |
400 | return self::setOrDefault( $wgLDAPWriteLocation, $domain ); |
401 | case 'AddLDAPUsers': |
402 | global $wgLDAPAddLDAPUsers; |
403 | return self::setOrDefault( $wgLDAPAddLDAPUsers, $domain, false ); |
404 | case 'UpdateLDAP': |
405 | global $wgLDAPUpdateLDAP; |
406 | return self::setOrDefault( $wgLDAPUpdateLDAP, $domain, false ); |
407 | case 'PasswordHash': |
408 | global $wgLDAPPasswordHash; |
409 | return self::setOrDefaultPrivate( $wgLDAPPasswordHash, $domain, 'clear' ); |
410 | case 'MailPassword': |
411 | global $wgLDAPMailPassword; |
412 | return self::setOrDefaultPrivate( $wgLDAPMailPassword, $domain, false ); |
413 | case 'Preferences': |
414 | global $wgLDAPPreferences; |
415 | return self::setOrDefault( $wgLDAPPreferences, $domain, [] ); |
416 | case 'DisableAutoCreate': |
417 | global $wgLDAPDisableAutoCreate; |
418 | return self::setOrDefault( $wgLDAPDisableAutoCreate, $domain, false ); |
419 | case 'GroupUseFullDN': |
420 | global $wgLDAPGroupUseFullDN; |
421 | return self::setOrDefault( $wgLDAPGroupUseFullDN, $domain, false ); |
422 | case 'LowerCaseUsername': |
423 | global $wgLDAPLowerCaseUsername; |
424 | // Default set to true for backwards compatibility with |
425 | // versions < 2.0a |
426 | return self::setOrDefault( $wgLDAPLowerCaseUsername, $domain, true ); |
427 | case 'GroupUseRetrievedUsername': |
428 | global $wgLDAPGroupUseRetrievedUsername; |
429 | return self::setOrDefault( $wgLDAPGroupUseRetrievedUsername, $domain, false ); |
430 | case 'GroupObjectclass': |
431 | global $wgLDAPGroupObjectclass; |
432 | return self::setOrDefault( $wgLDAPGroupObjectclass, $domain ); |
433 | case 'GroupAttribute': |
434 | global $wgLDAPGroupAttribute; |
435 | return self::setOrDefault( $wgLDAPGroupAttribute, $domain ); |
436 | case 'GroupNameAttribute': |
437 | global $wgLDAPGroupNameAttribute; |
438 | return self::setOrDefault( $wgLDAPGroupNameAttribute, $domain ); |
439 | case 'GroupsUseMemberOf': |
440 | global $wgLDAPGroupsUseMemberOf; |
441 | return self::setOrDefault( $wgLDAPGroupsUseMemberOf, $domain, false ); |
442 | case 'UseLDAPGroups': |
443 | global $wgLDAPUseLDAPGroups; |
444 | return self::setOrDefault( $wgLDAPUseLDAPGroups, $domain, false ); |
445 | case 'LocallyManagedGroups': |
446 | global $wgLDAPLocallyManagedGroups; |
447 | return self::setOrDefault( $wgLDAPLocallyManagedGroups, $domain, [] ); |
448 | case 'GroupsPrevail': |
449 | global $wgLDAPGroupsPrevail; |
450 | return self::setOrDefault( $wgLDAPGroupsPrevail, $domain, false ); |
451 | case 'RequiredGroups': |
452 | global $wgLDAPRequiredGroups; |
453 | return self::setOrDefault( $wgLDAPRequiredGroups, $domain, [] ); |
454 | case 'ExcludedGroups': |
455 | global $wgLDAPExcludedGroups; |
456 | return self::setOrDefault( $wgLDAPExcludedGroups, $domain, [] ); |
457 | case 'GroupSearchNestedGroups': |
458 | global $wgLDAPGroupSearchNestedGroups; |
459 | return self::setOrDefault( $wgLDAPGroupSearchNestedGroups, $domain, false ); |
460 | case 'AuthAttribute': |
461 | global $wgLDAPAuthAttribute; |
462 | return self::setOrDefault( $wgLDAPAuthAttribute, $domain ); |
463 | case 'ActiveDirectory': |
464 | global $wgLDAPActiveDirectory; |
465 | return self::setOrDefault( $wgLDAPActiveDirectory, $domain, false ); |
466 | case 'GroupSearchPosixPrimaryGroup': |
467 | global $wgLDAPGroupSearchPosixPrimaryGroup; |
468 | return self::setOrDefault( $wgLDAPGroupSearchPosixPrimaryGroup, $domain, false ); |
469 | } |
470 | return ''; |
471 | } |
472 | |
473 | /** |
474 | * Returns the item from $array at index $key if it is set, |
475 | * else, it returns $default |
476 | * |
477 | * @param array $array |
478 | * @param string $key |
479 | * @param mixed $default |
480 | * @return mixed |
481 | */ |
482 | private static function setOrDefault( $array, $key, $default = '' ) { |
483 | return $array[$key] ?? $default; |
484 | } |
485 | |
486 | /** |
487 | * Returns the item from $array at index $key if it is set, |
488 | * else, it returns $default |
489 | * |
490 | * Use for sensitive data |
491 | * |
492 | * @param array $array |
493 | * @param string $key |
494 | * @param mixed $default |
495 | * @return mixed |
496 | */ |
497 | private static function setOrDefaultPrivate( $array, $key, $default = '' ) { |
498 | return $array[$key] ?? $default; |
499 | } |
500 | |
501 | /** |
502 | * Check whether there exists a user account with the given name. |
503 | * The name will be normalized to MediaWiki's requirements, so |
504 | * you might need to munge it (for instance, for lowercase initial |
505 | * letters). |
506 | * |
507 | * @param string $username |
508 | * @return bool |
509 | */ |
510 | public function userExists( $username ) { |
511 | $this->printDebug( "Entering userExists", NONSENSITIVE ); |
512 | |
513 | // If we can't add LDAP users, we don't really need to check |
514 | // if the user exists, the authenticate method will do this for |
515 | // us. This will decrease hits to the LDAP server. |
516 | // We do however, need to use this if we are using auto authentication. |
517 | if ( !$this->getConf( 'AddLDAPUsers' ) && !$this->useAutoAuth() ) { |
518 | return true; |
519 | } |
520 | |
521 | return $this->userExistsReal( $username ); |
522 | } |
523 | |
524 | /** |
525 | * Like self::userExists, but always does the check |
526 | * @see self::userExists() |
527 | * @param string $username |
528 | * @return bool |
529 | */ |
530 | public function userExistsReal( $username ) { |
531 | $this->printDebug( "Entering userExistsReal", NONSENSITIVE ); |
532 | |
533 | $ret = false; |
534 | if ( $this->connect() ) { |
535 | $searchstring = $this->getSearchString( $username ); |
536 | |
537 | if ( $searchstring == '' ) { |
538 | // It is possible that getSearchString will return an |
539 | // empty string, which means "no user". |
540 | } elseif ( $this->useAutoAuth() ) { |
541 | // If we are using auto authentication, and we got |
542 | // anything back, then the user exists. |
543 | $ret = true; |
544 | } else { |
545 | // Search for the entry. |
546 | $entry = self::ldap_read( |
547 | $this->ldapconn, $searchstring, "objectclass=*" |
548 | ); |
549 | if ( $entry && |
550 | self::ldap_count_entries( $this->ldapconn, $entry ) > 0 |
551 | ) { |
552 | $this->printDebug( "Found a matching user in LDAP", NONSENSITIVE ); |
553 | $ret = true; |
554 | } else { |
555 | $this->printDebug( "Did not find a matching user in LDAP", NONSENSITIVE ); |
556 | } |
557 | } |
558 | // getSearchString is going to bind, but will not unbind |
559 | $this->unbind(); |
560 | } |
561 | return $ret; |
562 | } |
563 | |
564 | /** |
565 | * Connect to LDAP |
566 | * @param string $domain |
567 | * @return true |
568 | */ |
569 | public function connect( $domain = '' ): bool { |
570 | $this->printDebug( "Entering Connect", NONSENSITIVE ); |
571 | |
572 | if ( !function_exists( 'ldap_connect' ) ) { |
573 | throw new LdapAuthenticationException( "Missing PHP LDAP support" ); |
574 | } |
575 | |
576 | // Set the server string depending on whether we use ssl or not |
577 | $encryptionType = $this->getConf( 'EncryptionType', $domain ); |
578 | switch ( $encryptionType ) { |
579 | case "ldapi": |
580 | $this->printDebug( "Using ldapi", SENSITIVE ); |
581 | $serverpre = "ldapi://"; |
582 | break; |
583 | case "ssl": |
584 | $this->printDebug( "Using SSL", SENSITIVE ); |
585 | $serverpre = "ldaps://"; |
586 | break; |
587 | default: |
588 | $this->printDebug( "Using TLS or not using encryption.", SENSITIVE ); |
589 | $serverpre = "ldap://"; |
590 | } |
591 | |
592 | // Make a space separated list of server strings with the connection type |
593 | // string added. |
594 | $servers = ""; |
595 | $tmpservers = $this->getConf( 'ServerNames', $domain ); |
596 | $tok = strtok( $tmpservers, " " ); |
597 | while ( $tok ) { |
598 | $servers = $servers . " " . $serverpre . $tok . ":" . $this->getConf( 'Port', $domain ); |
599 | $tok = strtok( " " ); |
600 | } |
601 | $servers = trim( $servers ); |
602 | if ( !$servers ) { |
603 | throw new LdapAuthenticationException( 'No servers configured' ); |
604 | } |
605 | |
606 | $this->printDebug( "Using servers: $servers", SENSITIVE ); |
607 | |
608 | // Connect and set options |
609 | $this->ldapconn = self::ldap_connect( $servers ); |
610 | if ( !$this->ldapconn ) { |
611 | throw new LdapAuthenticationException( 'Failed to connect to the LDAP server' ); |
612 | } |
613 | |
614 | ldap_set_option( $this->ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3 ); |
615 | ldap_set_option( $this->ldapconn, LDAP_OPT_REFERRALS, 0 ); |
616 | |
617 | foreach ( $this->getConf( 'Options' ) as $key => $value ) { |
618 | if ( !ldap_set_option( $this->ldapconn, constant( $key ), $value ) ) { |
619 | $this->printDebug( |
620 | "Can't set option to LDAP! Option code and value: " . $key . "=" . $value, 1 |
621 | ); |
622 | } |
623 | } |
624 | |
625 | // TLS needs to be started after the connection resource is available |
626 | if ( $encryptionType == "tls" ) { |
627 | $this->printDebug( "Using TLS", SENSITIVE ); |
628 | if ( !ldap_start_tls( $this->ldapconn ) ) { |
629 | throw new LdapAuthenticationException( 'Failed to enable TLS on the LDAP connection' ); |
630 | } |
631 | } |
632 | $this->printDebug( "PHP's LDAP connect method returned true (note, this does not imply " . |
633 | "it connected to the server).", NONSENSITIVE ); |
634 | |
635 | // TODO: this method currently just throws exceptions if this fails, so |
636 | // we should be able to remove this return value |
637 | return true; |
638 | } |
639 | |
640 | /** |
641 | * Check if a username+password pair is a valid login, or if the username |
642 | * is allowed access to the wiki. |
643 | * The name will be normalized to MediaWiki's requirements, so |
644 | * you might need to munge it (for instance, for lowercase initial |
645 | * letters). |
646 | * |
647 | * @param string $username |
648 | * @param string $password |
649 | * @return bool |
650 | */ |
651 | public function authenticate( $username, $password = '' ) { |
652 | $this->printDebug( "Entering authenticate for username $username", NONSENSITIVE ); |
653 | |
654 | // We don't handle local authentication |
655 | if ( $this->getDomain() == 'local' ) { |
656 | $this->printDebug( "User is using a local domain", SENSITIVE ); |
657 | return false; |
658 | } |
659 | |
660 | // MediaWiki munges the username before authenticate is called, |
661 | // this can mess with authentication, group pulling/restriction, |
662 | // preference pulling, etc. Let's allow the admin to use |
663 | // a lowercased username if needed. |
664 | if ( $this->getConf( 'LowerCaseUsername' ) ) { |
665 | $username = strtolower( $username ); |
666 | } |
667 | |
668 | // If the user is using auto authentication, we need to ensure |
669 | // that he/she isn't trying to fool us by sending a username other |
670 | // than the one the web server got from the auto-authentication method. |
671 | if ( $this->useAutoAuth() && $this->getConf( 'AutoAuthUsername' ) != $username ) { |
672 | $this->printDebug( "The username provided ($username) doesn't match the username " . |
673 | "provided by the webserver (" . $this->getConf( 'AutoAuthUsername' ) . "). " . |
674 | "The user is probably trying to log in to the auto-authentication domain with " . |
675 | "password authentication via the wiki. Denying access.", SENSITIVE ); |
676 | return false; |
677 | } |
678 | |
679 | // We need to ensure that if we require a password, that it is |
680 | // not blank. We don't allow blank passwords, so we are being |
681 | // tricked if someone is supplying one when using password auth. |
682 | // auto-authentication is handled by the webserver; a blank password |
683 | // here is wanted. |
684 | if ( $password == '' && !$this->useAutoAuth() ) { |
685 | $this->printDebug( "User used a blank password", NONSENSITIVE ); |
686 | return false; |
687 | } |
688 | |
689 | if ( $this->connect() ) { |
690 | $this->userdn = $this->getSearchString( $username ); |
691 | |
692 | // It is possible that getSearchString will return an |
693 | // empty string; if this happens, the bind will ALWAYS |
694 | // return true, and will let anyone in! |
695 | if ( $this->userdn == '' ) { |
696 | $this->printDebug( "User DN is blank", NONSENSITIVE ); |
697 | $this->unbind(); |
698 | $this->markAuthFailed(); |
699 | return false; |
700 | } |
701 | |
702 | // If we are using password authentication, we need to bind as the |
703 | // user to make sure the password is correct. |
704 | if ( !$this->useAutoAuth() ) { |
705 | $this->printDebug( "Binding as the user", NONSENSITIVE ); |
706 | $bind = $this->bindAs( $this->userdn, $password ); |
707 | if ( !$bind ) { |
708 | $this->markAuthFailed(); |
709 | return false; |
710 | } |
711 | $result = true; |
712 | ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) ) |
713 | ->onChainAuth( $username, $password, $result ); |
714 | if ( !$result ) { |
715 | return false; |
716 | } |
717 | |
718 | $this->printDebug( "Bound successfully", NONSENSITIVE ); |
719 | |
720 | $ss = $this->getConf( 'SearchString' ); |
721 | if ( $ss ) { |
722 | if ( strstr( $ss, "@" ) || strstr( $ss, '\\' ) ) { |
723 | // We are most likely configured using USER-NAME@DOMAIN, or |
724 | // DOMAIN\\USER-NAME. |
725 | // Get the user's full DN so we can search for groups and such. |
726 | $this->userdn = $this->getUserDN( $username ); |
727 | $this->printDebug( "Fetched UserDN: $this->userdn", NONSENSITIVE ); |
728 | } else { |
729 | // Now that we are bound, we can pull the user's info. |
730 | $this->getUserInfo(); |
731 | } |
732 | } |
733 | } |
734 | |
735 | // Ensure the user's entry has the required auth attribute |
736 | $aa = $this->getConf( 'AuthAttribute' ); |
737 | if ( $aa ) { |
738 | $this->printDebug( "Checking for auth attributes: $aa", NONSENSITIVE ); |
739 | $filter = "(" . $aa . ")"; |
740 | $attributes = [ "dn" ]; |
741 | $entry = self::ldap_read( |
742 | $this->ldapconn, $this->userdn, $filter, $attributes |
743 | ); |
744 | $info = self::ldap_get_entries( $this->ldapconn, $entry ); |
745 | if ( $info["count"] < 1 ) { |
746 | $this->printDebug( "Failed auth attribute check", NONSENSITIVE ); |
747 | $this->unbind(); |
748 | $this->markAuthFailed(); |
749 | return false; |
750 | } |
751 | } |
752 | |
753 | $this->getGroups( $username ); |
754 | |
755 | if ( !$this->checkGroups() ) { |
756 | $this->unbind(); |
757 | $this->markAuthFailed(); |
758 | return false; |
759 | } |
760 | |
761 | $this->getPreferences(); |
762 | $this->unbind(); |
763 | } else { |
764 | $this->markAuthFailed(); |
765 | return false; |
766 | } |
767 | $this->printDebug( "Authentication passed", NONSENSITIVE ); |
768 | |
769 | // We made it this far; the user authenticated and didn't fail any checks, so he/she gets in |
770 | return true; |
771 | } |
772 | |
773 | public function markAuthFailed() { |
774 | $this->authFailed = true; |
775 | } |
776 | |
777 | /** |
778 | * @return array |
779 | */ |
780 | public function domainList() { |
781 | $tempDomArr = $this->getConf( 'DomainNames' ); |
782 | if ( $this->getConf( 'UseLocal' ) ) { |
783 | $this->printDebug( "Allowing the local domain, adding it to the list.", NONSENSITIVE ); |
784 | array_push( $tempDomArr, 'local' ); |
785 | } |
786 | |
787 | if ( $this->getConf( 'AutoAuthDomain' ) ) { |
788 | $this->printDebug( |
789 | "Allowing auto-authentication login, removing the domain from the list.", |
790 | NONSENSITIVE |
791 | ); |
792 | // There is no reason for people to log in directly to the wiki if the are using an |
793 | // auto-authentication domain. If they try to, they are probably up to something fishy. |
794 | unset( $tempDomArr[array_search( $this->getConf( 'AutoAuthDomain' ), $tempDomArr )] ); |
795 | } |
796 | |
797 | $domains = []; |
798 | foreach ( $tempDomArr as $tempDom ) { |
799 | $domains["$tempDom"] = $tempDom; |
800 | } |
801 | return $domains; |
802 | } |
803 | |
804 | /** |
805 | * Return true if the wiki should create a new local account automatically |
806 | * when asked to login a user who doesn't exist locally but does in the |
807 | * external auth database. |
808 | * |
809 | * This is just a question, and shouldn't perform any actions. |
810 | * |
811 | * @return bool |
812 | */ |
813 | public function autoCreate() { |
814 | return !$this->getConf( 'DisableAutoCreate' ); |
815 | } |
816 | |
817 | /** |
818 | * Set the given password in LDAP. |
819 | * Return true if successful. |
820 | * |
821 | * @param User $user |
822 | * @param string|null $password |
823 | * @return bool |
824 | */ |
825 | public function setPassword( $user, $password ) { |
826 | $this->printDebug( "Entering setPassword", NONSENSITIVE ); |
827 | |
828 | if ( $this->getDomain() == 'local' ) { |
829 | $this->printDebug( "User is using a local domain", NONSENSITIVE ); |
830 | |
831 | // We don't set local passwords, but we don't want the wiki |
832 | // to send the user a failure. |
833 | return true; |
834 | } |
835 | if ( !$this->getConf( 'UpdateLDAP' ) ) { |
836 | $this->printDebug( "Wiki is set to not allow updates", NONSENSITIVE ); |
837 | |
838 | // We aren't allowing the user to change his/her own password |
839 | return false; |
840 | } |
841 | |
842 | $writer = $this->getConf( 'WriterDN' ); |
843 | if ( !$writer ) { |
844 | $this->printDebug( "Wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); |
845 | |
846 | // We can't change a user's password without an account that is |
847 | // allowed to do it. |
848 | return false; |
849 | } |
850 | $pass = $this->getPasswordHash( $password ); |
851 | |
852 | if ( $this->connect() ) { |
853 | $this->userdn = $this->getSearchString( $user->getName() ); |
854 | $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); |
855 | $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); |
856 | if ( !$bind ) { |
857 | return false; |
858 | } |
859 | $values = [ 'userpassword' => $pass ]; |
860 | |
861 | // Blank out the password in the database. We don't want to save |
862 | // domain credentials for security reasons. |
863 | // This doesn't do anything. $password isn't by reference |
864 | $password = ''; |
865 | |
866 | $success = self::ldap_modify( |
867 | $this->ldapconn, $this->userdn, $values |
868 | ); |
869 | $this->unbind(); |
870 | if ( $success ) { |
871 | $this->printDebug( "Successfully modified the user's password", NONSENSITIVE ); |
872 | return true; |
873 | } |
874 | $this->printDebug( "Failed to modify the user's password", NONSENSITIVE ); |
875 | } |
876 | return false; |
877 | } |
878 | |
879 | /** |
880 | * Update user information in LDAP |
881 | * Return true if successful. |
882 | * |
883 | * @param User $user |
884 | * @return bool |
885 | */ |
886 | public function updateExternalDB( $user ) { |
887 | $this->printDebug( "Entering updateExternalDB", NONSENSITIVE ); |
888 | if ( !$this->getConf( 'UpdateLDAP' ) || $this->getDomain() == 'local' ) { |
889 | $this->printDebug( |
890 | "Either the user is using a local domain, or the wiki isn't allowing updates", |
891 | NONSENSITIVE |
892 | ); |
893 | // We don't handle local preferences, but we don't want the |
894 | // wiki to return an error. |
895 | return true; |
896 | } |
897 | |
898 | $writer = $this->getConf( 'WriterDN' ); |
899 | if ( !$writer ) { |
900 | $this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); |
901 | // We can't modify LDAP preferences if we don't have a user |
902 | // capable of editing LDAP attributes. |
903 | return false; |
904 | } |
905 | |
906 | $this->email = $user->getEmail(); |
907 | $this->realname = $user->getRealName(); |
908 | $userOptionsLookup = MediaWikiServices::getInstance()->getUserOptionsLookup(); |
909 | $this->nickname = $userOptionsLookup->getOption( $user, 'nickname' ); |
910 | $this->lang = $userOptionsLookup->getOption( $user, 'language' ); |
911 | if ( $this->connect() ) { |
912 | $this->userdn = $this->getSearchString( $user->getName() ); |
913 | $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); |
914 | $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); |
915 | if ( !$bind ) { |
916 | return false; |
917 | } |
918 | |
919 | $values = []; |
920 | $prefs = $this->getConf( 'Preferences' ); |
921 | foreach ( array_keys( $prefs ) as $key ) { |
922 | $attr = strtolower( $prefs[$key] ); |
923 | switch ( $key ) { |
924 | case "email": |
925 | if ( is_string( $this->email ) ) { |
926 | $values[$attr] = $this->email; |
927 | } |
928 | break; |
929 | case "nickname": |
930 | if ( is_string( $this->nickname ) ) { |
931 | $values[$attr] = $this->nickname; |
932 | } |
933 | break; |
934 | case "realname": |
935 | if ( is_string( $this->realname ) ) { |
936 | $values[$attr] = $this->realname; |
937 | } |
938 | break; |
939 | case "language": |
940 | if ( is_string( $this->lang ) ) { |
941 | $values[$attr] = $this->lang; |
942 | } |
943 | break; |
944 | } |
945 | } |
946 | |
947 | if ( count( $values ) && |
948 | self::ldap_modify( $this->ldapconn, $this->userdn, $values ) |
949 | ) { |
950 | // We changed the user, we need to invalidate the memcache key |
951 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
952 | $key = $cache->makeKey( 'ldapauthentication-userinfo', $this->userdn ); |
953 | $cache->delete( $key ); |
954 | |
955 | $this->printDebug( "Successfully modified the user's attributes", NONSENSITIVE ); |
956 | $this->unbind(); |
957 | return true; |
958 | } |
959 | $this->printDebug( "Failed to modify the user's attributes", NONSENSITIVE ); |
960 | $this->unbind(); |
961 | } |
962 | return false; |
963 | } |
964 | |
965 | /** |
966 | * Can the wiki create accounts in LDAP? |
967 | * Return true if yes. |
968 | * |
969 | * @return bool |
970 | */ |
971 | public function canCreateAccounts() { |
972 | return $this->getConf( 'AddLDAPUsers' ); |
973 | } |
974 | |
975 | /** |
976 | * Can the wiki change passwords in LDAP, or can the user |
977 | * change passwords locally? |
978 | * Return true if yes. |
979 | * |
980 | * @return bool |
981 | */ |
982 | public function allowPasswordChange() { |
983 | $this->printDebug( "Entering allowPasswordChange", NONSENSITIVE ); |
984 | |
985 | // Local domains need to be able to change passwords |
986 | return ( $this->getConf( 'UseLocal' ) && $this->getDomain() == 'local' ) |
987 | || $this->getConf( 'UpdateLDAP' ) |
988 | || $this->getConf( 'MailPassword' ); |
989 | } |
990 | |
991 | /** |
992 | * Disallow MediaWiki from setting local passwords in the database, |
993 | * unless UseLocal is true. Warning: if you set $wgLDAPUseLocal, |
994 | * it will cause MediaWiki to leak LDAP passwords into the local database. |
995 | * @return bool |
996 | */ |
997 | public function allowSetLocalPassword() { |
998 | return $this->getConf( 'UseLocal' ); |
999 | } |
1000 | |
1001 | /** |
1002 | * Add a user to LDAP. |
1003 | * Return true if successful. |
1004 | * |
1005 | * @param User $user |
1006 | * @param string $password |
1007 | * @param string $email |
1008 | * @param string $realname |
1009 | * @return bool |
1010 | */ |
1011 | public function addUser( $user, $password, $email = '', $realname = '' ) { |
1012 | $this->printDebug( "Entering addUser", NONSENSITIVE ); |
1013 | |
1014 | if ( !$this->getConf( 'AddLDAPUsers' ) || $this->getDomain() == 'local' ) { |
1015 | $this->printDebug( "Either the user is using a local domain, or the wiki isn't " . |
1016 | "allowing users to be added to LDAP", NONSENSITIVE ); |
1017 | |
1018 | // Tell the wiki not to return an error. |
1019 | return true; |
1020 | } |
1021 | if ( $this->getConf( 'RequiredGroups' ) ) { |
1022 | $this->printDebug( "The wiki is requiring users to be in specific groups, and cannot " . |
1023 | "add users as this would be a security hole.", NONSENSITIVE ); |
1024 | // It is possible that later we can add users into |
1025 | // groups, but since we don't support it, we don't want |
1026 | // to open holes! |
1027 | return false; |
1028 | } |
1029 | |
1030 | $writer = $this->getConf( 'WriterDN' ); |
1031 | if ( !$writer ) { |
1032 | $this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); |
1033 | |
1034 | // We can't add users without an LDAP account capable of doing so. |
1035 | return false; |
1036 | } |
1037 | |
1038 | $this->email = $user->getEmail(); |
1039 | $this->realname = $user->getRealName(); |
1040 | $username = $user->getName(); |
1041 | if ( $this->getConf( 'LowerCaseUsername' ) ) { |
1042 | $username = strtolower( $username ); |
1043 | } |
1044 | $pass = $this->getPasswordHash( $password ); |
1045 | if ( $this->connect() ) { |
1046 | $writeloc = $this->getConf( 'WriteLocation' ); |
1047 | $this->userdn = $this->getSearchString( $username ); |
1048 | if ( $this->userdn == '' ) { |
1049 | $this->printDebug( |
1050 | "userdn is blank, attempting to use wgLDAPWriteLocation", NONSENSITIVE |
1051 | ); |
1052 | if ( $writeloc ) { |
1053 | $this->printDebug( "wgLDAPWriteLocation is set, using that", NONSENSITIVE ); |
1054 | $this->userdn = $this->getConf( 'SearchAttribute' ) . "=" . |
1055 | $username . "," . $writeloc; |
1056 | } else { |
1057 | $this->printDebug( "wgLDAPWriteLocation is not set, failing", NONSENSITIVE ); |
1058 | // getSearchString will bind, but will not unbind |
1059 | $this->unbind(); |
1060 | return false; |
1061 | } |
1062 | } |
1063 | |
1064 | $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); |
1065 | $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); |
1066 | if ( !$bind ) { |
1067 | $this->printDebug( "Failed to bind as the writerDN; add failed", NONSENSITIVE ); |
1068 | return false; |
1069 | } |
1070 | |
1071 | // Set up LDAP objectclasses and attributes |
1072 | // TODO: make objectclasses and attributes configurable |
1073 | $values = [ 'uid' => $username ]; |
1074 | // sn is required for objectclass inetorgperson |
1075 | $values["sn"] = $username; |
1076 | $prefs = $this->getConf( 'Preferences' ); |
1077 | foreach ( array_keys( $prefs ) as $key ) { |
1078 | $attr = strtolower( $prefs[$key] ); |
1079 | switch ( $key ) { |
1080 | case "email": |
1081 | if ( is_string( $this->email ) ) { |
1082 | $values[$attr] = $this->email; |
1083 | } |
1084 | break; |
1085 | case "nickname": |
1086 | if ( is_string( $this->nickname ) ) { |
1087 | $values[$attr] = $this->nickname; |
1088 | } |
1089 | break; |
1090 | case "realname": |
1091 | if ( is_string( $this->realname ) ) { |
1092 | $values[$attr] = $this->realname; |
1093 | } |
1094 | break; |
1095 | case "language": |
1096 | if ( is_string( $this->lang ) ) { |
1097 | $values[$attr] = $this->lang; |
1098 | } |
1099 | break; |
1100 | } |
1101 | } |
1102 | if ( !array_key_exists( "cn", $values ) ) { |
1103 | $values["cn"] = $username; |
1104 | } |
1105 | $values["userpassword"] = $pass; |
1106 | $values["objectclass"] = [ "inetorgperson" ]; |
1107 | |
1108 | $result = true; |
1109 | # Let other extensions modify the user object before creation |
1110 | $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ); |
1111 | $hookRunner->onLDAPSetCreationValues( |
1112 | $this, $username, $values, $writeloc, $this->userdn, $result ); |
1113 | if ( !$result ) { |
1114 | $this->printDebug( |
1115 | "Failed to add user because LDAPSetCreationValues returned false", NONSENSITIVE |
1116 | ); |
1117 | $this->unbind(); |
1118 | return false; |
1119 | } |
1120 | |
1121 | $this->printDebug( "Adding user", NONSENSITIVE ); |
1122 | if ( self::ldap_add( $this->ldapconn, $this->userdn, $values ) ) { |
1123 | $this->printDebug( "Successfully added user", NONSENSITIVE ); |
1124 | $this->unbind(); |
1125 | return true; |
1126 | } |
1127 | $errno = self::ldap_errno( $this->ldapconn ); |
1128 | # Constraint violation, let's allow other plugins a chance to retry |
1129 | if ( $errno === 19 ) { |
1130 | $result = false; |
1131 | $hookRunner->onLDAPRetrySetCreationValues( |
1132 | $this, $username, $values, $writeloc, $this->userdn, $result ); |
1133 | if ( $result && |
1134 | self::ldap_add( $this->ldapconn, $this->userdn, $values ) |
1135 | ) { |
1136 | $this->printDebug( "Successfully added user", NONSENSITIVE ); |
1137 | $this->unbind(); |
1138 | return true; |
1139 | } |
1140 | } |
1141 | $this->printDebug( "Failed to add user (errno $errno)", NONSENSITIVE ); |
1142 | $this->unbind(); |
1143 | } |
1144 | return false; |
1145 | } |
1146 | |
1147 | /** |
1148 | * Set the domain this plugin is supposed to use when authenticating. |
1149 | * |
1150 | * @param string $domain |
1151 | */ |
1152 | public function setDomain( $domain ) { |
1153 | $this->printDebug( "Setting domain as: $domain", NONSENSITIVE ); |
1154 | $_SESSION['wsDomain'] = $domain; |
1155 | } |
1156 | |
1157 | /** |
1158 | * Get the user's domain |
1159 | * |
1160 | * @return string |
1161 | */ |
1162 | public function getDomain() { |
1163 | $this->printDebug( "Entering getDomain", NONSENSITIVE ); |
1164 | |
1165 | # If there's only a single domain set, there's no reason |
1166 | # to bother with sessions, tokens, etc.. This works around |
1167 | # a number of bugs caused by supporting multiple domains. |
1168 | # The bugs will still exist when using multiple domains, |
1169 | # though. |
1170 | $domainNames = $this->getConf( 'DomainNames' ); |
1171 | if ( ( count( $domainNames ) === 1 ) && !$this->getConf( 'UseLocal' ) ) { |
1172 | return $domainNames[0]; |
1173 | } |
1174 | # First check if we already have a valid domain set |
1175 | if ( isset( $_SESSION['wsDomain'] ) && $_SESSION['wsDomain'] != 'invaliddomain' ) { |
1176 | $this->printDebug( "Pulling domain from session.", NONSENSITIVE ); |
1177 | return $_SESSION['wsDomain']; |
1178 | } |
1179 | # If the session domain isn't set, the user may have been logged |
1180 | # in with a token, check the user options. |
1181 | $user = RequestContext::getMain()->getUser(); |
1182 | if ( $user->isRegistered() && $user->getToken( false ) ) { |
1183 | $this->printDebug( "Pulling domain from user options.", NONSENSITIVE ); |
1184 | $domain = self::loadDomain( $user ); |
1185 | if ( $domain ) { |
1186 | return $domain; |
1187 | } |
1188 | } |
1189 | # The user must be using an invalid domain |
1190 | $this->printDebug( "No domain found, returning invaliddomain", NONSENSITIVE ); |
1191 | return 'invaliddomain'; |
1192 | } |
1193 | |
1194 | /** |
1195 | * Check to see if the specific domain is a valid domain. |
1196 | * Return true if the domain is valid. |
1197 | * |
1198 | * @param string $domain |
1199 | * @return bool |
1200 | */ |
1201 | public function validDomain( $domain ) { |
1202 | $this->printDebug( "Entering validDomain", NONSENSITIVE ); |
1203 | if ( in_array( $domain, $this->getConf( 'DomainNames' ) ) || |
1204 | ( $this->getConf( 'UseLocal' ) && $domain == 'local' ) |
1205 | ) { |
1206 | $this->printDebug( "User is using a valid domain ($domain).", NONSENSITIVE ); |
1207 | return true; |
1208 | } |
1209 | $this->printDebug( "User is not using a valid domain ($domain).", NONSENSITIVE ); |
1210 | return false; |
1211 | } |
1212 | |
1213 | /** |
1214 | * When a user logs in, update user with information from LDAP. |
1215 | * |
1216 | * @param User &$user |
1217 | * TODO: fix the setExternalID stuff |
1218 | */ |
1219 | public function updateUser( &$user ) { |
1220 | $this->printDebug( "Entering updateUser", NONSENSITIVE ); |
1221 | if ( $this->authFailed ) { |
1222 | $this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE ); |
1223 | return; |
1224 | } |
1225 | |
1226 | $services = MediaWikiServices::getInstance(); |
1227 | if ( $this->getConf( 'Preferences' ) ) { |
1228 | $this->printDebug( "Setting user preferences.", NONSENSITIVE ); |
1229 | $userOptionsManager = $services->getUserOptionsManager(); |
1230 | if ( is_string( $this->lang ) ) { |
1231 | $this->printDebug( "Setting language.", NONSENSITIVE ); |
1232 | $userOptionsManager->setOption( $user, 'language', $this->lang ); |
1233 | } |
1234 | if ( is_string( $this->nickname ) ) { |
1235 | $this->printDebug( "Setting nickname.", NONSENSITIVE ); |
1236 | $userOptionsManager->setOption( $user, 'nickname', $this->nickname ); |
1237 | } |
1238 | if ( is_string( $this->realname ) ) { |
1239 | $this->printDebug( "Setting realname.", NONSENSITIVE ); |
1240 | $user->setRealName( $this->realname ); |
1241 | } |
1242 | if ( is_string( $this->email ) ) { |
1243 | $this->printDebug( "Setting email.", NONSENSITIVE ); |
1244 | $user->setEmail( $this->email ); |
1245 | $user->confirmEmail(); |
1246 | } |
1247 | } |
1248 | |
1249 | if ( $this->getConf( 'UseLDAPGroups' ) ) { |
1250 | $this->printDebug( "Setting user groups.", NONSENSITIVE ); |
1251 | $this->setGroups( $user ); |
1252 | } |
1253 | |
1254 | # We must set a user option if we want token based logins to work |
1255 | if ( $user->getToken( false ) ) { |
1256 | $this->printDebug( "User has a token, setting domain in user options.", NONSENSITIVE ); |
1257 | self::saveDomain( $user, $_SESSION['wsDomain'] ); |
1258 | } |
1259 | |
1260 | # Let other extensions update the user |
1261 | ( new HookRunner( $services->getHookContainer() ) )->onLDAPUpdateUser( $user ); |
1262 | |
1263 | $this->printDebug( "Saving user settings.", NONSENSITIVE ); |
1264 | $user->saveSettings(); |
1265 | } |
1266 | |
1267 | /** |
1268 | * When creating a user account, initialize user with information from LDAP. |
1269 | * TODO: fix setExternalID stuff |
1270 | * |
1271 | * @param User &$user |
1272 | * @param bool $autocreate |
1273 | */ |
1274 | public function initUser( &$user, $autocreate = false ) { |
1275 | $this->printDebug( "Entering initUser", NONSENSITIVE ); |
1276 | |
1277 | if ( $this->authFailed ) { |
1278 | $this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE ); |
1279 | return; |
1280 | } |
1281 | if ( $this->getDomain() == 'local' ) { |
1282 | $this->printDebug( "User is using a local domain", NONSENSITIVE ); |
1283 | return; |
1284 | } |
1285 | |
1286 | if ( $autocreate && !$this->userExists( $user->mName ) ) { |
1287 | // Generate a random password for the account under the assumption |
1288 | // that either the caller will be setting a password immediately |
1289 | // after using User::changeAuthenticationData or that other |
1290 | // password recovery means will be used if the account is meant |
1291 | // for interactive use. |
1292 | $pwreq = MediaWiki\Auth\TemporaryPasswordAuthenticationRequest::newRandom(); |
1293 | $this->addUser( $user, $pwreq->password ); |
1294 | } |
1295 | |
1296 | // The update user function does everything else we need done. |
1297 | $this->updateUser( $user ); |
1298 | |
1299 | // updateUser() won't necessarily save the user's settings |
1300 | $user->saveSettings(); |
1301 | } |
1302 | |
1303 | /** |
1304 | * Return true to prevent logins that don't authenticate here from being |
1305 | * checked against the local database's password fields. |
1306 | * |
1307 | * This is just a question, and shouldn't perform any actions. |
1308 | * |
1309 | * @return bool |
1310 | */ |
1311 | public function strict() { |
1312 | $this->printDebug( "Entering strict.", NONSENSITIVE ); |
1313 | |
1314 | if ( $this->getConf( 'UseLocal' ) || $this->getConf( 'MailPassword' ) ) { |
1315 | $this->printDebug( "Returning false in strict().", NONSENSITIVE ); |
1316 | return false; |
1317 | } |
1318 | $this->printDebug( "Returning true in strict().", NONSENSITIVE ); |
1319 | return true; |
1320 | } |
1321 | |
1322 | /** |
1323 | * Munge the username based on a scheme (lowercase, by default), by search attribute |
1324 | * otherwise. |
1325 | * |
1326 | * @param string $username |
1327 | * @return string |
1328 | */ |
1329 | public function getCanonicalName( $username ) { |
1330 | $this->printDebug( "Entering getCanonicalName", NONSENSITIVE ); |
1331 | $userNameUtils = MediaWikiServices::getInstance()->getUserNameUtils(); |
1332 | if ( $userNameUtils->isIP( $username ) ) { |
1333 | $this->printDebug( "Username is an IP, not munging.", NONSENSITIVE ); |
1334 | return $username; |
1335 | } |
1336 | $canonicalname = $username; |
1337 | if ( $username != '' ) { |
1338 | $this->printDebug( "Username is: $username", NONSENSITIVE ); |
1339 | if ( $this->getConf( 'LowerCaseUsername' ) ) { |
1340 | $canonicalname = ucfirst( strtolower( $canonicalname ) ); |
1341 | } else { |
1342 | # Fetch username, so that we can possibly use it. |
1343 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
1344 | $userInfo = $cache->getWithSetCallback( |
1345 | $cache->makeKey( 'ldapauthentication-canonicalname', $username ), |
1346 | $cache::TTL_DAY, |
1347 | function () use ( $username ) { |
1348 | $canonicalname = $username; |
1349 | |
1350 | if ( $this->validDomain( $this->getDomain() ) && $this->connect() ) { |
1351 | // Try to pull the username from LDAP. In the case of straight binds, |
1352 | // try to fetch the username by search before bind. |
1353 | $this->userdn = $this->getUserDN( $username, true ); |
1354 | $hookSetUsername = $this->LDAPUsername; |
1355 | ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) ) |
1356 | ->onSetUsernameAttributeFromLDAP( $hookSetUsername, $this->userInfo ); |
1357 | if ( is_string( $hookSetUsername ) ) { |
1358 | $this->printDebug( |
1359 | "Username munged by hook: $hookSetUsername", NONSENSITIVE |
1360 | ); |
1361 | $this->LDAPUsername = $hookSetUsername; |
1362 | } else { |
1363 | $this->printDebug( |
1364 | "Fetched username is not a string (check your hook code...). " . |
1365 | "This message can be safely ignored if you do not have the " . |
1366 | "SetUsernameAttributeFromLDAP hook defined.", NONSENSITIVE |
1367 | ); |
1368 | } |
1369 | } |
1370 | |
1371 | // We want to use the username returned by LDAP if it exists |
1372 | if ( $this->LDAPUsername != '' ) { |
1373 | $canonicalname = ucfirst( $this->LDAPUsername ); |
1374 | $this->printDebug( "Using LDAPUsername: $canonicalname", NONSENSITIVE ); |
1375 | } |
1376 | |
1377 | return [ 'username' => $username, 'canonicalname' => $canonicalname ]; |
1378 | } |
1379 | ); |
1380 | |
1381 | $canonicalname = $userInfo['canonicalname']; |
1382 | } |
1383 | } |
1384 | $this->printDebug( "Munged username: $canonicalname", NONSENSITIVE ); |
1385 | return $canonicalname; |
1386 | } |
1387 | |
1388 | /** |
1389 | * Configures the authentication plugin for use with auto-authentication |
1390 | * plugins. |
1391 | */ |
1392 | public function autoAuthSetup() { |
1393 | $this->setDomain( $this->getConf( 'AutoAuthDomain' ) ); |
1394 | } |
1395 | |
1396 | /** |
1397 | * Gets the searchstring for a user based upon settings for the domain. |
1398 | * Returns a full DN for a user. |
1399 | * |
1400 | * @param string $username |
1401 | * @return string |
1402 | */ |
1403 | private function getSearchString( $username ) { |
1404 | $this->printDebug( "Entering getSearchString", NONSENSITIVE ); |
1405 | $ss = $this->getConf( 'SearchString' ); |
1406 | if ( $ss ) { |
1407 | // This is a straight bind |
1408 | $this->printDebug( "Doing a straight bind", NONSENSITIVE ); |
1409 | $userdn = str_replace( "USER-NAME", $username, $ss ); |
1410 | } else { |
1411 | $userdn = $this->getUserDN( $username, true ); |
1412 | } |
1413 | $this->printDebug( "userdn is: $userdn", SENSITIVE ); |
1414 | return $userdn; |
1415 | } |
1416 | |
1417 | /** |
1418 | * Gets the DN of a user based upon settings for the domain. |
1419 | * This function will set $this->LDAPUsername |
1420 | * |
1421 | * @param string $username |
1422 | * @param bool $bind |
1423 | * @param string $searchattr |
1424 | * @return string |
1425 | */ |
1426 | public function getUserDN( $username, $bind = false, $searchattr = '' ) { |
1427 | $this->printDebug( "Entering getUserDN", NONSENSITIVE ); |
1428 | if ( $bind ) { |
1429 | // This is a proxy bind, or an anonymous bind with a search |
1430 | $proxyagent = $this->getConf( 'ProxyAgent' ); |
1431 | if ( $proxyagent ) { |
1432 | // This is a proxy bind |
1433 | $this->printDebug( "Doing a proxy bind", NONSENSITIVE ); |
1434 | $bind = $this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) ); |
1435 | } else { |
1436 | // This is an anonymous bind |
1437 | $this->printDebug( "Doing an anonymous bind", NONSENSITIVE ); |
1438 | $bind = $this->bindAs(); |
1439 | } |
1440 | if ( !$bind ) { |
1441 | $this->printDebug( "Failed to bind", NONSENSITIVE ); |
1442 | $this->fetchedUserInfo = false; |
1443 | $this->userInfo = null; |
1444 | return ''; |
1445 | } |
1446 | } |
1447 | |
1448 | if ( !$searchattr ) { |
1449 | $searchattr = $this->getConf( 'SearchAttribute' ); |
1450 | } |
1451 | // we need to do a subbase search for the entry |
1452 | $filter = "(" . $searchattr . "=" . $this->getLdapEscapedString( $username ) . ")"; |
1453 | $this->printDebug( "Created a regular filter: $filter", SENSITIVE ); |
1454 | |
1455 | // We explicitly put memberof here because it's an operational attribute in some servers. |
1456 | $attributes = [ "*", "memberof" ]; |
1457 | $base = $this->getBaseDN( USERDN ); |
1458 | $this->printDebug( "Using base: $base", SENSITIVE ); |
1459 | $entry = self::ldap_search( |
1460 | $this->ldapconn, $base, $filter, $attributes |
1461 | ); |
1462 | if ( self::ldap_count_entries( $this->ldapconn, $entry ) == 0 ) { |
1463 | $this->printDebug( "Couldn't find an entry", NONSENSITIVE ); |
1464 | $this->fetchedUserInfo = false; |
1465 | $this->userInfo = null; |
1466 | return ''; |
1467 | } |
1468 | $this->userInfo = self::ldap_get_entries( $this->ldapconn, $entry ); |
1469 | $this->fetchedUserInfo = true; |
1470 | if ( isset( $this->userInfo[0][$searchattr] ) ) { |
1471 | $username = $this->userInfo[0][$searchattr][0]; |
1472 | $this->printDebug( |
1473 | "Setting the LDAPUsername based on fetched wgLDAPSearchAttributes: $username", |
1474 | NONSENSITIVE |
1475 | ); |
1476 | $this->LDAPUsername = $username; |
1477 | } |
1478 | return $this->userInfo[0]["dn"]; |
1479 | } |
1480 | |
1481 | /** |
1482 | * Load the current user's entry |
1483 | * |
1484 | * @return bool |
1485 | */ |
1486 | public function getUserInfo() { |
1487 | // Don't fetch the same data more than once |
1488 | if ( $this->fetchedUserInfo ) { |
1489 | return true; |
1490 | } |
1491 | $userInfo = $this->getUserInfoStateless( $this->userdn ); |
1492 | if ( $userInfo === null ) { |
1493 | $this->fetchedUserInfo = false; |
1494 | } else { |
1495 | $this->fetchedUserInfo = true; |
1496 | $this->userInfo = $userInfo; |
1497 | } |
1498 | return $this->fetchedUserInfo; |
1499 | } |
1500 | |
1501 | /** |
1502 | * @param string $userdn |
1503 | * @return array|null |
1504 | * @phan-return ?array<int|string,int|array<int|string,string|int|array<int|string,int|string>>> |
1505 | */ |
1506 | public function getUserInfoStateless( $userdn ) { |
1507 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
1508 | |
1509 | return $cache->getWithSetCallback( |
1510 | $cache->makeKey( 'ldapauthentication-userinfo', $userdn ), |
1511 | $cache::TTL_DAY, |
1512 | function ( $oldValue, &$ttl ) use ( $userdn, $cache ) { |
1513 | $entry = self::ldap_read( |
1514 | $this->ldapconn, |
1515 | $userdn, |
1516 | "objectclass=*", |
1517 | [ '*', 'memberof' ] |
1518 | ); |
1519 | $userInfo = self::ldap_get_entries( $this->ldapconn, $entry ); |
1520 | if ( $userInfo["count"] < 1 ) { |
1521 | $ttl = $cache::TTL_UNCACHEABLE; |
1522 | |
1523 | return null; |
1524 | } |
1525 | |
1526 | return $userInfo; |
1527 | } |
1528 | ); |
1529 | } |
1530 | |
1531 | /** |
1532 | * Retrieve user preferences from LDAP |
1533 | */ |
1534 | private function getPreferences() { |
1535 | $this->printDebug( "Entering getPreferences", NONSENSITIVE ); |
1536 | |
1537 | // Retrieve preferences |
1538 | $prefs = $this->getConf( 'Preferences' ); |
1539 | if ( !$prefs ) { |
1540 | return; |
1541 | } |
1542 | if ( !$this->getUserInfo() ) { |
1543 | $this->printDebug( |
1544 | "Failed to get preferences, the user's entry wasn't found.", NONSENSITIVE |
1545 | ); |
1546 | return; |
1547 | } |
1548 | $this->printDebug( "Retrieving preferences", NONSENSITIVE ); |
1549 | foreach ( array_keys( $prefs ) as $key ) { |
1550 | $attr = strtolower( $prefs[$key] ); |
1551 | if ( !isset( $this->userInfo[0][$attr] ) ) { |
1552 | continue; |
1553 | } |
1554 | $value = $this->userInfo[0][$attr][0]; |
1555 | switch ( $key ) { |
1556 | case "email": |
1557 | $this->email = $value; |
1558 | $this->printDebug( |
1559 | "Retrieved email ($this->email) using attribute ($prefs[$key])", |
1560 | NONSENSITIVE |
1561 | ); |
1562 | break; |
1563 | case "language": |
1564 | $this->lang = $value; |
1565 | $this->printDebug( |
1566 | "Retrieved language ($this->lang) using attribute ($prefs[$key])", |
1567 | NONSENSITIVE |
1568 | ); |
1569 | break; |
1570 | case "nickname": |
1571 | $this->nickname = $value; |
1572 | $this->printDebug( |
1573 | "Retrieved nickname ($this->nickname) using attribute ($prefs[$key])", |
1574 | NONSENSITIVE |
1575 | ); |
1576 | break; |
1577 | case "realname": |
1578 | $this->realname = $value; |
1579 | $this->printDebug( |
1580 | "Retrieved realname ($this->realname) using attribute ($prefs[$key])", |
1581 | NONSENSITIVE |
1582 | ); |
1583 | break; |
1584 | } |
1585 | } |
1586 | } |
1587 | |
1588 | /** |
1589 | * Checks to see whether a user is in a required group. |
1590 | * |
1591 | * @return bool |
1592 | */ |
1593 | private function checkGroups() { |
1594 | $this->printDebug( "Entering checkGroups", NONSENSITIVE ); |
1595 | |
1596 | $excgroups = $this->getConf( 'ExcludedGroups' ); |
1597 | if ( $excgroups ) { |
1598 | $this->printDebug( "Checking for excluded group membership", NONSENSITIVE ); |
1599 | |
1600 | $excgroups = array_map( 'strtolower', $excgroups ); |
1601 | |
1602 | $this->printDebug( "Excluded groups:", NONSENSITIVE, $excgroups ); |
1603 | |
1604 | foreach ( $this->userLDAPGroups["dn"] as $group ) { |
1605 | $this->printDebug( "Checking against: $group", NONSENSITIVE ); |
1606 | if ( in_array( $group, $excgroups ) ) { |
1607 | $this->printDebug( "Found user in an excluded group.", NONSENSITIVE ); |
1608 | return false; |
1609 | } |
1610 | } |
1611 | } |
1612 | |
1613 | $reqgroups = $this->getConf( 'RequiredGroups' ); |
1614 | if ( $reqgroups ) { |
1615 | $this->printDebug( "Checking for (new style) group membership", NONSENSITIVE ); |
1616 | |
1617 | $reqgroups = array_map( 'strtolower', $reqgroups ); |
1618 | |
1619 | $this->printDebug( "Required groups:", NONSENSITIVE, $reqgroups ); |
1620 | |
1621 | foreach ( $this->userLDAPGroups["dn"] as $group ) { |
1622 | $this->printDebug( "Checking against: $group", NONSENSITIVE ); |
1623 | if ( in_array( $group, $reqgroups ) ) { |
1624 | $this->printDebug( "Found user in a group.", NONSENSITIVE ); |
1625 | return true; |
1626 | } |
1627 | } |
1628 | |
1629 | $this->printDebug( "Couldn't find the user in any groups.", NONSENSITIVE ); |
1630 | return false; |
1631 | } |
1632 | |
1633 | // Ensure we return true if we aren't checking groups. |
1634 | return true; |
1635 | } |
1636 | |
1637 | /** |
1638 | * Function to get the user's groups. |
1639 | * @param string $username |
1640 | */ |
1641 | protected function getGroups( $username ) { |
1642 | $this->printDebug( "Entering getGroups", NONSENSITIVE ); |
1643 | |
1644 | // Ensure userLDAPGroups is set, no matter what |
1645 | $this->userLDAPGroups = [ "dn" => [], "short" => [] ]; |
1646 | |
1647 | // Find groups |
1648 | if ( $this->getConf( 'RequiredGroups' ) || $this->getConf( 'UseLDAPGroups' ) ) { |
1649 | $this->printDebug( "Retrieving LDAP group membership", NONSENSITIVE ); |
1650 | |
1651 | // Let's figure out what we should be searching for |
1652 | if ( $this->getConf( 'GroupUseFullDN' ) ) { |
1653 | $usertopass = $this->userdn; |
1654 | } else { |
1655 | if ( $this->getConf( 'GroupUseRetrievedUsername' ) && $this->LDAPUsername != '' ) { |
1656 | $usertopass = $this->LDAPUsername; |
1657 | } else { |
1658 | $usertopass = $username; |
1659 | } |
1660 | } |
1661 | |
1662 | if ( $this->getConf( 'GroupsUseMemberOf' ) ) { |
1663 | $this->printDebug( "Using memberOf", NONSENSITIVE ); |
1664 | if ( !$this->getUserInfo() ) { |
1665 | $this->printDebug( "Couldn't get the user's entry.", NONSENSITIVE ); |
1666 | } elseif ( isset( $this->userInfo[0]["memberof"] ) ) { |
1667 | # The first entry is always a count |
1668 | $memberOfMembers = $this->userInfo[0]["memberof"]; |
1669 | array_shift( $memberOfMembers ); |
1670 | $groups = [ "dn" => [], "short" => [] ]; |
1671 | |
1672 | foreach ( $memberOfMembers as $mem ) { |
1673 | array_push( $groups["dn"], strtolower( $mem ) ); |
1674 | |
1675 | // Get short name of group |
1676 | $memAttrs = explode( ',', strtolower( $mem ) ); |
1677 | if ( isset( $memAttrs[0] ) ) { |
1678 | $memAttrs = explode( '=', $memAttrs[0] ); |
1679 | if ( isset( $memAttrs[0] ) ) { |
1680 | array_push( $groups["short"], strtolower( $memAttrs[1] ) ); |
1681 | } |
1682 | } |
1683 | } |
1684 | $this->printDebug( "Got the following groups:", SENSITIVE, $groups["dn"] ); |
1685 | |
1686 | $this->userLDAPGroups = $groups; |
1687 | } else { |
1688 | $this->printDebug( "memberOf attribute isn't set", NONSENSITIVE ); |
1689 | } |
1690 | } else { |
1691 | $this->printDebug( "Searching for the groups", NONSENSITIVE ); |
1692 | $this->userLDAPGroups = $this->searchGroups( $usertopass ); |
1693 | if ( $this->getConf( 'GroupSearchNestedGroups' ) ) { |
1694 | $this->userLDAPGroups = $this->searchNestedGroups( $this->userLDAPGroups ); |
1695 | $this->printDebug( |
1696 | "Got the following nested groups:", SENSITIVE, $this->userLDAPGroups["dn"] |
1697 | ); |
1698 | } |
1699 | } |
1700 | |
1701 | if ( $this->getConf( 'GroupSearchPosixPrimaryGroup' ) ) { |
1702 | if ( !$this->getUserInfo() ) { |
1703 | $this->printDebug( "Couldn't get the user's entry.", NONSENSITIVE ); |
1704 | } elseif ( isset( $this->userInfo[0]["gidnumber"] ) ) { |
1705 | $base = $this->getBaseDN( GROUPDN ); |
1706 | $objectclass = $this->getConf( 'GroupObjectclass' ); |
1707 | $filter = "(&(objectClass={$objectclass})" . |
1708 | "(gidNumber={$this->userInfo[0]['gidnumber'][0]}))"; |
1709 | $info = self::ldap_search( $this->ldapconn, $base, $filter ); |
1710 | $entries = self::ldap_get_entries( $this->ldapconn, $info ); |
1711 | if ( empty( $entries[0] ) ) { |
1712 | $this->printDebug( "Couldn't get the user's primary group.", NONSENSITIVE ); |
1713 | } else { |
1714 | $primary_group_dn = strtolower( $entries[0]["dn"] ); |
1715 | $this->printDebug( "Got the user's primary group:" . $primary_group_dn, SENSITIVE ); |
1716 | $this->userLDAPGroups["dn"][] = $primary_group_dn; |
1717 | $nameattribute = strtolower( $this->getConf( 'GroupNameAttribute' ) ); |
1718 | $this->userLDAPGroups["short"][] = $entries[0][$nameattribute][0]; |
1719 | } |
1720 | } |
1721 | } |
1722 | |
1723 | // Only find all groups if the user has any groups; otherwise, we are |
1724 | // just wasting a search. |
1725 | if ( $this->getConf( 'GroupsPrevail' ) && count( $this->userLDAPGroups ) != 0 ) { |
1726 | $this->allLDAPGroups = $this->searchGroups( '*' ); |
1727 | } |
1728 | } |
1729 | } |
1730 | |
1731 | /** |
1732 | * Function to return an array of nested groups when given a group or list of groups. |
1733 | * $searchedgroups is used for tail recursion and shouldn't be provided |
1734 | * when called externally. |
1735 | * |
1736 | * @param array $groups |
1737 | * @param array $searchedgroups |
1738 | * @return array |
1739 | */ |
1740 | private function searchNestedGroups( $groups, $searchedgroups = [ "dn" => [], "short" => [] ] ) { |
1741 | $this->printDebug( "Entering searchNestedGroups", NONSENSITIVE ); |
1742 | |
1743 | // base case, no more groups left to check |
1744 | if ( count( $groups["dn"] ) == 0 ) { |
1745 | $this->printDebug( "No more groups to search.", NONSENSITIVE ); |
1746 | return $searchedgroups; |
1747 | } |
1748 | |
1749 | $this->printDebug( "Searching groups:", SENSITIVE, $groups["dn"] ); |
1750 | $groupstosearch = [ "short" => [], "dn" => [] ]; |
1751 | foreach ( $groups["dn"] as $group ) { |
1752 | $returnedgroups = $this->searchGroups( $group ); |
1753 | $this->printDebug( |
1754 | "Group $group is in the following groups:", SENSITIVE, $returnedgroups["dn"] |
1755 | ); |
1756 | foreach ( $returnedgroups["dn"] as $searchme ) { |
1757 | if ( in_array( $searchme, $searchedgroups["dn"] ) ) { |
1758 | // We already searched this, move on |
1759 | continue; |
1760 | } |
1761 | |
1762 | // We'll need to search this group's members now |
1763 | $this->printDebug( "Adding $searchme to the list of groups (1)", SENSITIVE ); |
1764 | $groupstosearch["dn"][] = $searchme; |
1765 | } |
1766 | foreach ( $returnedgroups["short"] as $searchme ) { |
1767 | if ( in_array( $searchme, $searchedgroups["short"] ) ) { |
1768 | // We already searched this, move on |
1769 | continue; |
1770 | } |
1771 | |
1772 | $this->printDebug( "Adding $searchme to the list of groups (2)", SENSITIVE ); |
1773 | // We'll need to search this group's members now |
1774 | $groupstosearch["short"][] = $searchme; |
1775 | } |
1776 | } |
1777 | $searchedgroups = array_merge_recursive( $groups, $searchedgroups ); |
1778 | |
1779 | return $this->searchNestedGroups( $groupstosearch, $searchedgroups ); |
1780 | } |
1781 | |
1782 | /** |
1783 | * Search groups for the supplied DN |
1784 | * |
1785 | * @param string $dn |
1786 | * @return array |
1787 | */ |
1788 | private function searchGroups( $dn ) { |
1789 | $this->printDebug( "Entering searchGroups", NONSENSITIVE ); |
1790 | |
1791 | $base = $this->getBaseDN( GROUPDN ); |
1792 | $objectclass = $this->getConf( 'GroupObjectclass' ); |
1793 | $attribute = $this->getConf( 'GroupAttribute' ); |
1794 | $nameattribute = $this->getConf( 'GroupNameAttribute' ); |
1795 | |
1796 | // We actually want to search for * not \2a, ensure we don't escape * |
1797 | $value = $dn; |
1798 | if ( $value != "*" ) { |
1799 | $value = $this->getLdapEscapedString( $value ); |
1800 | } |
1801 | |
1802 | $proxyagent = $this->getConf( 'ProxyAgent' ); |
1803 | if ( $proxyagent ) { |
1804 | // We'll try to bind as the proxyagent as the proxyagent should normally have more |
1805 | // rights than the user. If the proxyagent fails to bind, we will still be able |
1806 | // to search as the normal user (which is why we don't return on fail). |
1807 | $this->printDebug( "Binding as the proxyagent", NONSENSITIVE ); |
1808 | $this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) ); |
1809 | } |
1810 | |
1811 | $groups = [ "short" => [], "dn" => [] ]; |
1812 | |
1813 | // AD does not include the primary group in the list of groups, we have to find it ourselves |
1814 | if ( $dn != "*" && $this->getConf( 'ActiveDirectory' ) ) { |
1815 | $PGfilter = "(&(distinguishedName=$value)(objectclass=user))"; |
1816 | $this->printDebug( "User Filter: $PGfilter", SENSITIVE ); |
1817 | $PGinfo = self::ldap_search( $this->ldapconn, $base, $PGfilter ); |
1818 | $PGentries = self::ldap_get_entries( $this->ldapconn, $PGinfo ); |
1819 | if ( !empty( $PGentries[0] ) ) { |
1820 | $Usid = $PGentries[0]['objectsid'][0]; |
1821 | $PGrid = $PGentries[0]['primarygroupid'][0]; |
1822 | $PGsid = bin2hex( $Usid ); |
1823 | $PGSID = []; |
1824 | for ( $i = 0; $i < 56; $i += 2 ) { |
1825 | $PGSID[] = substr( $PGsid, $i, 2 ); |
1826 | } |
1827 | $dPGrid = dechex( $PGrid ); |
1828 | $dPGrid = str_pad( $dPGrid, 8, '0', STR_PAD_LEFT ); |
1829 | $PGRID = []; |
1830 | for ( $i = 0; $i < 8; $i += 2 ) { |
1831 | array_push( $PGRID, substr( $dPGrid, $i, 2 ) ); |
1832 | } |
1833 | for ( $i = 24; $i < 28; $i++ ) { |
1834 | $PGSID[$i] = array_pop( $PGRID ); |
1835 | } |
1836 | $PGsid_string = ''; |
1837 | foreach ( $PGSID as $PGsid_bit ) { |
1838 | $PGsid_string .= "\\" . $PGsid_bit; |
1839 | } |
1840 | $PGfilter = "(&(objectSid=$PGsid_string)(objectclass=$objectclass))"; |
1841 | $this->printDebug( "Primary Group Filter: $PGfilter", SENSITIVE ); |
1842 | $info = self::ldap_search( $this->ldapconn, $base, $PGfilter ); |
1843 | $PGentries = self::ldap_get_entries( $this->ldapconn, $info ); |
1844 | array_shift( $PGentries ); |
1845 | $dnMember = strtolower( $PGentries[0]['dn'] ); |
1846 | $groups["dn"][] = $dnMember; |
1847 | // Get short name of group |
1848 | $memAttrs = explode( ',', strtolower( $dnMember ) ); |
1849 | if ( isset( $memAttrs[0] ) ) { |
1850 | $memAttrs = explode( '=', $memAttrs[0] ); |
1851 | if ( isset( $memAttrs[1] ) ) { |
1852 | $groups["short"][] = strtolower( $memAttrs[1] ); |
1853 | } |
1854 | } |
1855 | |
1856 | } |
1857 | } |
1858 | |
1859 | $filter = "(&($attribute=$value)(objectclass=$objectclass))"; |
1860 | $this->printDebug( "Search string: $filter", SENSITIVE ); |
1861 | $info = self::ldap_search( $this->ldapconn, $base, $filter ); |
1862 | if ( !$info ) { |
1863 | $this->printDebug( "No entries returned from search.", SENSITIVE ); |
1864 | // Return an array so that other functions |
1865 | // don't error out. |
1866 | return [ "short" => [], "dn" => [] ]; |
1867 | } |
1868 | |
1869 | $entries = self::ldap_get_entries( $this->ldapconn, $info ); |
1870 | if ( $entries ) { |
1871 | // We need to shift because the first entry will be a count |
1872 | array_shift( $entries ); |
1873 | // Let's get a list of both full dn groups and shortname groups |
1874 | foreach ( $entries as $entry ) { |
1875 | $shortMember = strtolower( $entry[$nameattribute][0] ); |
1876 | $dnMember = strtolower( $entry['dn'] ); |
1877 | $groups["short"][] = $shortMember; |
1878 | $groups["dn"][] = $dnMember; |
1879 | } |
1880 | } |
1881 | |
1882 | $this->printDebug( "Returned groups:", SENSITIVE, $groups["dn"] ); |
1883 | return $groups; |
1884 | } |
1885 | |
1886 | /** |
1887 | * Returns true if this group is in the list of the currently authenticated |
1888 | * user's groups, else false. |
1889 | * |
1890 | * @param string $group |
1891 | * @return bool |
1892 | */ |
1893 | private function hasLDAPGroup( $group ) { |
1894 | $this->printDebug( "Entering hasLDAPGroup", NONSENSITIVE ); |
1895 | return in_array( strtolower( $group ), $this->userLDAPGroups["short"] ); |
1896 | } |
1897 | |
1898 | /** |
1899 | * Returns true if an LDAP group with this name exists, else false. |
1900 | * |
1901 | * @param string $group |
1902 | * @return bool |
1903 | */ |
1904 | private function isLDAPGroup( $group ) { |
1905 | $this->printDebug( "Entering isLDAPGroup", NONSENSITIVE ); |
1906 | return in_array( strtolower( $group ), $this->allLDAPGroups["short"] ); |
1907 | } |
1908 | |
1909 | /** |
1910 | * Helper function for updateUser() and initUser(). Adds users into MediaWiki security groups |
1911 | * based upon groups retrieved from LDAP. |
1912 | * |
1913 | * @param User &$user |
1914 | */ |
1915 | private function setGroups( &$user ) { |
1916 | global $wgGroupPermissions; |
1917 | $userGroupManager = MediaWikiServices::getInstance()->getUserGroupManager(); |
1918 | |
1919 | // TODO: this is *really* ugly code. clean it up! |
1920 | $this->printDebug( "Entering setGroups.", NONSENSITIVE ); |
1921 | |
1922 | # Add ldap groups as local groups |
1923 | if ( $this->getConf( 'GroupsPrevail' ) ) { |
1924 | $this->printDebug( |
1925 | "Adding all groups to wgGroupPermissions: ", SENSITIVE, $this->allLDAPGroups |
1926 | ); |
1927 | |
1928 | foreach ( $this->allLDAPGroups["short"] as $ldapgroup ) { |
1929 | if ( !array_key_exists( $ldapgroup, $wgGroupPermissions ) ) { |
1930 | $wgGroupPermissions[$ldapgroup] = []; |
1931 | } |
1932 | } |
1933 | } |
1934 | |
1935 | # add groups permissions |
1936 | $localAvailGrps = $userGroupManager->listAllGroups(); |
1937 | $localUserGrps = $userGroupManager->getUserEffectiveGroups( $user ); |
1938 | $defaultLocallyManagedGrps = [ 'bot', 'sysop', 'bureaucrat' ]; |
1939 | $locallyManagedGrps = $this->getConf( 'LocallyManagedGroups' ); |
1940 | if ( $locallyManagedGrps ) { |
1941 | $locallyManagedGrps = array_unique( |
1942 | array_merge( $defaultLocallyManagedGrps, $locallyManagedGrps ) ); |
1943 | $this->printDebug( "Locally managed groups: ", SENSITIVE, $locallyManagedGrps ); |
1944 | } else { |
1945 | $locallyManagedGrps = $defaultLocallyManagedGrps; |
1946 | $this->printDebug( |
1947 | "Locally managed groups is unset, using defaults: ", SENSITIVE, $locallyManagedGrps |
1948 | ); |
1949 | } |
1950 | |
1951 | $this->printDebug( "Available groups are: ", NONSENSITIVE, $localAvailGrps ); |
1952 | $this->printDebug( "Effective groups are: ", NONSENSITIVE, $localUserGrps ); |
1953 | # note: $localUserGrps does not need to be updated with $cGroup added, |
1954 | # as $localAvailGrps contains $cGroup only once. |
1955 | foreach ( $localAvailGrps as $cGroup ) { |
1956 | # did we once add the user to the group? |
1957 | if ( in_array( $cGroup, $localUserGrps ) ) { |
1958 | $this->printDebug( |
1959 | "Checking to see if we need to remove user from: $cGroup", NONSENSITIVE |
1960 | ); |
1961 | if ( !$this->hasLDAPGroup( $cGroup ) && |
1962 | !in_array( $cGroup, $locallyManagedGrps ) |
1963 | ) { |
1964 | $this->printDebug( "Removing user from: $cGroup", NONSENSITIVE ); |
1965 | # the ldap group overrides the local group |
1966 | # so as the user is currently not a member of the ldap group, |
1967 | # he shall be removed from the local group |
1968 | $userGroupManager->removeUserFromGroup( $user, $cGroup ); |
1969 | } |
1970 | } else { |
1971 | # no, but maybe the user has recently been added to the ldap group? |
1972 | $this->printDebug( "Checking to see if user is in: $cGroup", NONSENSITIVE ); |
1973 | if ( $this->hasLDAPGroup( $cGroup ) ) { |
1974 | $this->printDebug( "Adding user to: $cGroup", NONSENSITIVE ); |
1975 | $userGroupManager->addUserToGroup( $user, $cGroup ); |
1976 | } |
1977 | } |
1978 | } |
1979 | } |
1980 | |
1981 | /** |
1982 | * Returns a password that is created via the configured hash settings. |
1983 | * |
1984 | * @param string $password |
1985 | * @return string |
1986 | */ |
1987 | private function getPasswordHash( $password ) { |
1988 | $this->printDebug( "Entering getPasswordHash", NONSENSITIVE ); |
1989 | |
1990 | // Set the password hashing based upon admin preference |
1991 | switch ( $this->getConf( 'PasswordHash' ) ) { |
1992 | case 'crypt': |
1993 | // @phan-suppress-next-line PhanParamTooFewInternal FIXME Second arg is optional but emit E_NOTICE |
1994 | $pass = '{CRYPT}' . crypt( $password ); |
1995 | break; |
1996 | case 'clear': |
1997 | $pass = $password; |
1998 | break; |
1999 | default: |
2000 | $pwd_sha = base64_encode( pack( 'H*', sha1( $password ) ) ); |
2001 | $pass = "{SHA}" . $pwd_sha; |
2002 | break; |
2003 | } |
2004 | |
2005 | return $pass; |
2006 | } |
2007 | |
2008 | /** |
2009 | * Prints debugging information. $debugText is what you want to print, $debugVal |
2010 | * is the level at which you want to print the information. |
2011 | * |
2012 | * @param string $debugText |
2013 | * @param int $debugVal One of NONSENSITIVE, SENSITIVE or HIGHLYSENSITIVE |
2014 | * @param array|null $debugArr |
2015 | */ |
2016 | public function printDebug( $debugText, $debugVal, $debugArr = null ) { |
2017 | if ( !function_exists( 'wfDebugLog' ) ) { |
2018 | return; |
2019 | } |
2020 | |
2021 | global $wgLDAPDebug; |
2022 | |
2023 | if ( $wgLDAPDebug >= $debugVal ) { |
2024 | if ( $debugArr !== null ) { |
2025 | $debugText = $debugText . " " . implode( "::", $debugArr ); |
2026 | } |
2027 | /* Update second parameter when bumping versions */ |
2028 | wfDebugLog( 'ldap', '2.2.0' . ' ' . $debugText, false ); |
2029 | } |
2030 | } |
2031 | |
2032 | /** |
2033 | * Binds as $userdn with $password. This can be called with only the ldap |
2034 | * connection resource for an anonymous bind. |
2035 | * |
2036 | * @param string|null $userdn |
2037 | * @param string|null $password |
2038 | * @return bool |
2039 | */ |
2040 | public function bindAs( $userdn = null, $password = null ) { |
2041 | // Let's see if the user can authenticate. |
2042 | if ( $userdn == null || $password == null ) { |
2043 | $bind = self::ldap_bind( $this->ldapconn ); |
2044 | } else { |
2045 | $bind = self::ldap_bind( $this->ldapconn, $userdn, $password ); |
2046 | } |
2047 | if ( !$bind ) { |
2048 | $this->printDebug( "Failed to bind as $userdn", NONSENSITIVE ); |
2049 | return false; |
2050 | } |
2051 | $this->boundAs = $userdn; |
2052 | return true; |
2053 | } |
2054 | |
2055 | /** |
2056 | * Unbind and destroy the current LDAP connection. |
2057 | * @return void |
2058 | */ |
2059 | public function unbind() { |
2060 | self::ldap_unbind( $this->ldapconn ); |
2061 | // ldap_unbind marks the connection resource as unusable, so discard |
2062 | // it so that we know to recreate it when needed in the future. |
2063 | $this->ldapconn = null; |
2064 | $this->boundAs = null; |
2065 | } |
2066 | |
2067 | /** |
2068 | * Returns true if auto-authentication is allowed, and the user is |
2069 | * authenticating using the auto-authentication domain. |
2070 | * |
2071 | * @return bool |
2072 | */ |
2073 | private function useAutoAuth() { |
2074 | return $this->getDomain() == $this->getConf( 'AutoAuthDomain' ); |
2075 | } |
2076 | |
2077 | /** |
2078 | * Returns a string which has the chars *, (, ), \ & NUL escaped to LDAP compliant |
2079 | * syntax as per RFC 2254 |
2080 | * Thanks and credit to Iain Colledge for the research and function. |
2081 | * |
2082 | * @param string $string |
2083 | * @return string |
2084 | */ |
2085 | public function getLdapEscapedString( $string ) { |
2086 | // Make the string LDAP compliant by escaping *, (, ) , \ & NUL |
2087 | return str_replace( |
2088 | [ "\\", "(", ")", "*", "\x00" ], |
2089 | [ "\\5c", "\\28", "\\29", "\\2a", "\\00" ], |
2090 | $string |
2091 | ); |
2092 | } |
2093 | |
2094 | /** |
2095 | * Returns a basedn by the type of entry we are searching for. |
2096 | * |
2097 | * @param int $type |
2098 | * @return string |
2099 | */ |
2100 | public function getBaseDN( $type ) { |
2101 | $this->printDebug( "Entering getBaseDN", NONSENSITIVE ); |
2102 | |
2103 | $ret = ''; |
2104 | switch ( $type ) { |
2105 | case USERDN: |
2106 | $ret = $this->getConf( 'UserBaseDN' ); |
2107 | break; |
2108 | case GROUPDN: |
2109 | $ret = $this->getConf( 'GroupBaseDN' ); |
2110 | break; |
2111 | case DEFAULTDN: |
2112 | $ret = $this->getConf( 'BaseDN' ); |
2113 | if ( $ret ) { |
2114 | return $ret; |
2115 | } |
2116 | |
2117 | $this->printDebug( "basedn is not set.", NONSENSITIVE ); |
2118 | return ''; |
2119 | } |
2120 | |
2121 | if ( $ret == '' ) { |
2122 | $this->printDebug( |
2123 | "basedn is not set for this type of entry, trying to get the default basedn.", |
2124 | NONSENSITIVE |
2125 | ); |
2126 | // We will never reach here if $type is self::DEFAULTDN, so to avoid code |
2127 | // code duplication, we'll get the default by re-calling the function. |
2128 | return $this->getBaseDN( DEFAULTDN ); |
2129 | } |
2130 | |
2131 | $this->printDebug( "basedn is $ret", NONSENSITIVE ); |
2132 | return $ret; |
2133 | } |
2134 | |
2135 | /** |
2136 | * @param User $user |
2137 | * @return string|null |
2138 | */ |
2139 | public static function loadDomain( $user ) { |
2140 | $user_id = $user->getId(); |
2141 | if ( $user_id != 0 ) { |
2142 | $row = MediaWikiServices::getInstance() |
2143 | ->getConnectionProvider() |
2144 | ->getReplicaDatabase() |
2145 | ->newSelectQueryBuilder() |
2146 | ->select( 'domain' ) |
2147 | ->from( 'ldap_domains' ) |
2148 | ->where( [ 'user_id' => $user_id ] ) |
2149 | ->caller( __METHOD__ ) |
2150 | ->fetchRow(); |
2151 | |
2152 | if ( $row ) { |
2153 | return $row->domain; |
2154 | } |
2155 | } |
2156 | |
2157 | return null; |
2158 | } |
2159 | |
2160 | /** |
2161 | * @param User $user |
2162 | * @param string $domain |
2163 | */ |
2164 | public static function saveDomain( $user, $domain ) { |
2165 | $user_id = $user->getId(); |
2166 | if ( $user_id != 0 ) { |
2167 | $olddomain = self::loadDomain( $user ); |
2168 | if ( $olddomain ) { |
2169 | // Check we really need to update domain. |
2170 | // Otherwise we can receive an error when logging in with |
2171 | // $wgReadOnly. |
2172 | if ( $olddomain != $domain ) { |
2173 | MediaWikiServices::getInstance() |
2174 | ->getConnectionProvider() |
2175 | ->getPrimaryDatabase() |
2176 | ->newUpdateQueryBuilder() |
2177 | ->update( 'ldap_domains' ) |
2178 | ->set( [ 'domain' => $domain ] ) |
2179 | ->where( [ 'user_id' => $user_id ] ) |
2180 | ->caller( __METHOD__ ) |
2181 | ->execute(); |
2182 | } |
2183 | } else { |
2184 | MediaWikiServices::getInstance() |
2185 | ->getConnectionProvider() |
2186 | ->getPrimaryDatabase() |
2187 | ->newInsertQueryBuilder() |
2188 | ->insertInto( 'ldap_domains' ) |
2189 | ->row( [ |
2190 | 'domain' => $domain, |
2191 | 'user_id' => $user_id |
2192 | ] ) |
2193 | ->caller( __METHOD__ ) |
2194 | ->execute(); |
2195 | } |
2196 | } |
2197 | } |
2198 | |
2199 | } |