Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 243 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 1 |
CentralAuthPrimaryAuthenticationProvider | |
0.00% |
0 / 243 |
|
0.00% |
0 / 16 |
9312 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
getAuthenticationRequests | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
30 | |||
getPasswordAuthenticationRequest | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
beginPrimaryAuthentication | |
0.00% |
0 / 64 |
|
0.00% |
0 / 1 |
380 | |||
postAuthentication | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
72 | |||
testUserCanAuthenticate | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
56 | |||
testUserExists | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
12 | |||
providerAllowsAuthenticationDataChange | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
110 | |||
providerChangeAuthenticationData | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
72 | |||
accountCreationType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
testUserForCreation | |
0.00% |
0 / 28 |
|
0.00% |
0 / 1 |
182 | |||
getAntiSpoofAuthenticationRequest | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
testForAccountCreation | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
42 | |||
beginPrimaryAccountCreation | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
56 | |||
finishAccountCreation | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
autoCreatedAccount | |
0.00% |
0 / 27 |
|
0.00% |
0 / 1 |
30 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | * @ingroup Auth |
20 | */ |
21 | |
22 | namespace MediaWiki\Extension\CentralAuth; |
23 | |
24 | use IDBAccessObject; |
25 | use InvalidPassword; |
26 | use MediaWiki\Auth\AbstractPasswordPrimaryAuthenticationProvider; |
27 | use MediaWiki\Auth\AuthenticationRequest; |
28 | use MediaWiki\Auth\AuthenticationResponse; |
29 | use MediaWiki\Auth\AuthManager; |
30 | use MediaWiki\Auth\PasswordAuthenticationRequest; |
31 | use MediaWiki\Deferred\DeferredUpdates; |
32 | use MediaWiki\Extension\AntiSpoof\AntiSpoofAuthenticationRequest; |
33 | use MediaWiki\Extension\CentralAuth\GlobalRename\GlobalRenameRequestStore; |
34 | use MediaWiki\Extension\CentralAuth\User\CentralAuthAntiSpoofManager; |
35 | use MediaWiki\Extension\CentralAuth\User\CentralAuthUser; |
36 | use MediaWiki\User\User; |
37 | use MediaWiki\User\UserIdentityLookup; |
38 | use MediaWiki\User\UserNameUtils; |
39 | use MediaWiki\WikiMap\WikiMap; |
40 | use Psr\Log\NullLogger; |
41 | use RequestContext; |
42 | use StatusValue; |
43 | use Wikimedia\Rdbms\ReadOnlyMode; |
44 | |
45 | /** |
46 | * A primary authentication provider that uses the CentralAuth password. |
47 | */ |
48 | class CentralAuthPrimaryAuthenticationProvider |
49 | extends AbstractPasswordPrimaryAuthenticationProvider |
50 | { |
51 | /** @var string The internal ID of this provider. */ |
52 | public const ID = 'CentralAuthPrimaryAuthenticationProvider'; |
53 | |
54 | private ReadOnlyMode $readOnlyMode; |
55 | private UserIdentityLookup $userIdentityLookup; |
56 | private CentralAuthAntiSpoofManager $caAntiSpoofManager; |
57 | private CentralAuthDatabaseManager $databaseManager; |
58 | private CentralAuthUtilityService $utilityService; |
59 | private GlobalRenameRequestStore $globalRenameRequestStore; |
60 | |
61 | /** @var bool Whether to auto-migrate non-merged accounts on login */ |
62 | protected $autoMigrate = null; |
63 | |
64 | /** @var bool Whether to auto-migrate non-global accounts on login */ |
65 | protected $autoMigrateNonGlobalAccounts = null; |
66 | |
67 | /** @var bool Whether to check for spoofed user names */ |
68 | protected $antiSpoofAccounts = null; |
69 | |
70 | /** |
71 | * @param ReadOnlyMode $readOnlyMode |
72 | * @param UserIdentityLookup $userIdentityLookup |
73 | * @param UserNameUtils $userNameUtils |
74 | * @param CentralAuthAntiSpoofManager $caAntiSpoofManager |
75 | * @param CentralAuthDatabaseManager $databaseManager |
76 | * @param CentralAuthUtilityService $utilityService |
77 | * @param GlobalRenameRequestStore $globalRenameRequestStore |
78 | * @param array $params Settings. All are optional, defaulting to the |
79 | * similarly-named $wgCentralAuth* globals. |
80 | * - autoMigrate: If true, attempt to auto-migrate local accounts on other |
81 | * wikis when logging in. |
82 | * - autoMigrateNonGlobalAccounts: If true, attempt to auto-migrate |
83 | * non-global accounts on login. |
84 | * - antiSpoofAccounts: Whether to anti-spoof new accounts. Ignored if the |
85 | * AntiSpoof extension isn't installed or the extension is outdated. |
86 | */ |
87 | public function __construct( |
88 | ReadOnlyMode $readOnlyMode, |
89 | UserIdentityLookup $userIdentityLookup, |
90 | UserNameUtils $userNameUtils, |
91 | CentralAuthAntiSpoofManager $caAntiSpoofManager, |
92 | CentralAuthDatabaseManager $databaseManager, |
93 | CentralAuthUtilityService $utilityService, |
94 | GlobalRenameRequestStore $globalRenameRequestStore, |
95 | $params = [] |
96 | ) { |
97 | global $wgCentralAuthAutoMigrate, |
98 | $wgCentralAuthAutoMigrateNonGlobalAccounts, |
99 | $wgCentralAuthStrict, $wgAntiSpoofAccounts; |
100 | |
101 | $this->readOnlyMode = $readOnlyMode; |
102 | $this->userIdentityLookup = $userIdentityLookup; |
103 | // AbstractAuthenticationProvider::$userNameUtils is protected |
104 | // TODO this is probably unneeded since AbstractAuthenticationProvider::init |
105 | // will be called and that will set the $userNameUtils, no need to even inject |
106 | // into this class |
107 | $this->userNameUtils = $userNameUtils; |
108 | $this->caAntiSpoofManager = $caAntiSpoofManager; |
109 | $this->databaseManager = $databaseManager; |
110 | $this->utilityService = $utilityService; |
111 | $this->globalRenameRequestStore = $globalRenameRequestStore; |
112 | |
113 | $params += [ |
114 | 'autoMigrate' => $wgCentralAuthAutoMigrate, |
115 | 'autoMigrateNonGlobalAccounts' => $wgCentralAuthAutoMigrateNonGlobalAccounts, |
116 | 'antiSpoofAccounts' => $wgAntiSpoofAccounts, |
117 | 'authoritative' => $wgCentralAuthStrict, |
118 | ]; |
119 | |
120 | parent::__construct( $params ); |
121 | |
122 | $this->autoMigrate = (bool)$params['autoMigrate']; |
123 | $this->autoMigrateNonGlobalAccounts = (bool)$params['autoMigrateNonGlobalAccounts']; |
124 | $this->antiSpoofAccounts = (bool)$params['antiSpoofAccounts']; |
125 | } |
126 | |
127 | /** @inheritDoc */ |
128 | public function getAuthenticationRequests( $action, array $options ) { |
129 | $ret = parent::getAuthenticationRequests( $action, $options ); |
130 | |
131 | if ( $this->antiSpoofAccounts && $action === AuthManager::ACTION_CREATE ) { |
132 | $user = User::newFromName( $options['username'] ) ?: new User(); |
133 | if ( $user->isAllowed( 'override-antispoof' ) ) { |
134 | $ret[] = new AntiSpoofAuthenticationRequest(); |
135 | } |
136 | } |
137 | |
138 | return $ret; |
139 | } |
140 | |
141 | /** |
142 | * @param array $reqs |
143 | * @return PasswordAuthenticationRequest|null |
144 | */ |
145 | private static function getPasswordAuthenticationRequest( array $reqs ) { |
146 | return AuthenticationRequest::getRequestByClass( |
147 | $reqs, PasswordAuthenticationRequest::class |
148 | ); |
149 | } |
150 | |
151 | /** @inheritDoc */ |
152 | public function beginPrimaryAuthentication( array $reqs ) { |
153 | $req = self::getPasswordAuthenticationRequest( $reqs ); |
154 | if ( !$req ) { |
155 | return AuthenticationResponse::newAbstain(); |
156 | } |
157 | |
158 | if ( $req->username === null || $req->password === null ) { |
159 | return AuthenticationResponse::newAbstain(); |
160 | } |
161 | |
162 | $username = $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE ); |
163 | if ( $username === false ) { |
164 | return AuthenticationResponse::newAbstain(); |
165 | } |
166 | |
167 | $status = $this->checkPasswordValidity( $username, $req->password ); |
168 | if ( !$status->isOK() ) { |
169 | return $this->getFatalPasswordErrorResponse( $username, $status ); |
170 | } |
171 | |
172 | // First, check normal login |
173 | $centralUser = CentralAuthUser::getInstanceByName( $username ); |
174 | |
175 | $authenticateResult = $centralUser->authenticate( $req->password ); |
176 | |
177 | $pass = $authenticateResult === [ CentralAuthUser::AUTHENTICATE_OK ]; |
178 | |
179 | if ( in_array( CentralAuthUser::AUTHENTICATE_LOCKED, $authenticateResult ) ) { |
180 | if ( !in_array( CentralAuthUser::AUTHENTICATE_BAD_PASSWORD, $authenticateResult ) ) { |
181 | // Because the absence of "bad password" for any code that hooks and receives |
182 | // the returned AuthenticationResponse means either that the password |
183 | // was correct or that the password was not checked, provide "good password" |
184 | // which removes the two possible meanings of no "bad password". |
185 | $authenticateResult[] = CentralAuthUser::AUTHENTICATE_GOOD_PASSWORD; |
186 | } |
187 | return AuthenticationResponse::newFail( |
188 | wfMessage( 'centralauth-login-error-locked' ) |
189 | ->params( wfEscapeWikiText( $centralUser->getName() ) ), |
190 | $authenticateResult |
191 | ); |
192 | } |
193 | |
194 | // If we don't have a central account, see if all local accounts match |
195 | // the password and can be globalized. (bug T72392) |
196 | if ( !$centralUser->exists() ) { |
197 | $this->logger->debug( |
198 | 'no global account for "{username}"', [ 'username' => $username ] ); |
199 | // Confirm using DB_PRIMARY in case of replication lag |
200 | $latestCentralUser = CentralAuthUser::getPrimaryInstanceByName( $username ); |
201 | if ( $this->autoMigrateNonGlobalAccounts && !$latestCentralUser->exists() ) { |
202 | $ok = $latestCentralUser->storeAndMigrate( |
203 | [ $req->password ], |
204 | /* $sendToRC = */ true, |
205 | /* $safe = */ true, |
206 | /* $checkHome = */ true |
207 | ); |
208 | if ( $ok ) { |
209 | $this->logger->debug( |
210 | 'wgCentralAuthAutoMigrateNonGlobalAccounts successful in creating ' . |
211 | 'a global account for "{username}"', |
212 | [ 'username' => $username ] |
213 | ); |
214 | $this->setPasswordResetFlag( $username, $status ); |
215 | return AuthenticationResponse::newPass( $username ); |
216 | } |
217 | } |
218 | return $this->failResponse( $req ); |
219 | } |
220 | |
221 | if ( $pass && $this->autoMigrate ) { |
222 | // If the user passed in the global password, we can identify |
223 | // any remaining local accounts with a matching password |
224 | // and migrate them in transparently. |
225 | // That may or may not include the current wiki. |
226 | $this->logger->debug( 'attempting wgCentralAuthAutoMigrate for "{username}"', [ |
227 | 'username' => $username, |
228 | ] ); |
229 | if ( $centralUser->isAttached() ) { |
230 | // Defer any automatic migration for other wikis |
231 | DeferredUpdates::addCallableUpdate( static function () use ( $username, $req ) { |
232 | $latestCentralUser = CentralAuthUser::getPrimaryInstanceByName( $username ); |
233 | $latestCentralUser->attemptPasswordMigration( $req->password ); |
234 | } ); |
235 | } else { |
236 | // The next steps depend on whether a migration happens for this wiki. |
237 | // Update the $centralUser instance so the checks below reflect any migrations. |
238 | $centralUser = CentralAuthUser::getPrimaryInstanceByName( $username ); |
239 | $centralUser->attemptPasswordMigration( $req->password ); |
240 | } |
241 | } |
242 | |
243 | if ( !$centralUser->isAttached() ) { |
244 | $local = User::newFromName( $username ); |
245 | if ( $local && $local->getId() ) { |
246 | // An unattached local account; central authentication can't |
247 | // be used until this account has been transferred. |
248 | // $wgCentralAuthStrict will determine if local login is allowed. |
249 | $this->logger->debug( 'unattached account for "{username}"', [ |
250 | 'username' => $username, |
251 | ] ); |
252 | return $this->failResponse( $req ); |
253 | } |
254 | } |
255 | |
256 | if ( $pass ) { |
257 | $this->setPasswordResetFlag( $username, $status ); |
258 | return AuthenticationResponse::newPass( $username ); |
259 | } else { |
260 | // We know the central user is attached at this point, so never |
261 | // fall back to other password providers. |
262 | return AuthenticationResponse::newFail( wfMessage( 'wrongpassword' ) ); |
263 | } |
264 | } |
265 | |
266 | /** @inheritDoc */ |
267 | public function postAuthentication( $user, AuthenticationResponse $response ) { |
268 | if ( $response->status === AuthenticationResponse::PASS ) { |
269 | $centralUser = CentralAuthUser::getInstance( $user ); |
270 | if ( $centralUser->exists() && |
271 | $centralUser->isAttached() && |
272 | $centralUser->getEmail() != $user->getEmail() && |
273 | !$this->readOnlyMode->isReadOnly() |
274 | ) { |
275 | DeferredUpdates::addCallableUpdate( static function () use ( $user ) { |
276 | $centralUser = CentralAuthUser::getPrimaryInstance( $user ); |
277 | if ( !$centralUser->exists() || !$centralUser->isAttached() ) { |
278 | // something major changed? |
279 | return; |
280 | } |
281 | |
282 | $user->setEmail( $centralUser->getEmail() ); |
283 | // @TODO: avoid direct User object field access |
284 | $user->mEmailAuthenticated = $centralUser->getEmailAuthenticationTimestamp(); |
285 | $user->saveSettings(); |
286 | } ); |
287 | } |
288 | } |
289 | } |
290 | |
291 | /** @inheritDoc */ |
292 | public function testUserCanAuthenticate( $username ) { |
293 | $username = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_USABLE ); |
294 | if ( $username === false ) { |
295 | return false; |
296 | } |
297 | |
298 | // Note this omits the case where an unattached local user exists but |
299 | // will be globalized on login thanks to $this->autoMigrate or |
300 | // $this->autoMigrateNonGlobalAccounts. Both are impossible to really |
301 | // test here because they both need cleartext passwords to do their |
302 | // thing. If you have such accounts on your wiki, you should have |
303 | // LocalPasswordPrimaryAuthenticationProvider configured too which |
304 | // will return true for such users. |
305 | |
306 | $centralUser = CentralAuthUser::getInstanceByName( $username ); |
307 | $userIdentity = $this->userIdentityLookup->getUserIdentityByName( $username ); |
308 | return $centralUser && $centralUser->exists() && |
309 | ( $centralUser->isAttached() || !$userIdentity || !$userIdentity->isRegistered() ) && |
310 | !$centralUser->getPasswordObject() instanceof InvalidPassword; |
311 | } |
312 | |
313 | /** @inheritDoc */ |
314 | public function testUserExists( $username, $flags = IDBAccessObject::READ_NORMAL ) { |
315 | $username = $this->userNameUtils->getCanonical( $username, UserNameUtils::RIGOR_USABLE ); |
316 | if ( $username === false ) { |
317 | return false; |
318 | } |
319 | |
320 | $centralUser = CentralAuthUser::getInstanceByName( $username ); |
321 | return $centralUser && $centralUser->exists(); |
322 | } |
323 | |
324 | /** @inheritDoc */ |
325 | public function providerAllowsAuthenticationDataChange( |
326 | AuthenticationRequest $req, $checkData = true |
327 | ) { |
328 | if ( get_class( $req ) === PasswordAuthenticationRequest::class ) { |
329 | if ( !$checkData ) { |
330 | return StatusValue::newGood(); |
331 | } |
332 | |
333 | $username = $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE ); |
334 | if ( $username !== false ) { |
335 | $centralUser = CentralAuthUser::getInstanceByName( $username ); |
336 | $userIdentity = $this->userIdentityLookup |
337 | ->getUserIdentityByName( $username, IDBAccessObject::READ_LATEST ); |
338 | if ( $centralUser->exists() && |
339 | ( $centralUser->isAttached() || |
340 | !$userIdentity || !$userIdentity->isRegistered() ) |
341 | ) { |
342 | $sv = StatusValue::newGood(); |
343 | if ( $req->password !== null ) { |
344 | if ( $req->password !== $req->retype ) { |
345 | $sv->fatal( 'badretype' ); |
346 | } else { |
347 | $sv->merge( $this->checkPasswordValidity( $username, $req->password ) ); |
348 | } |
349 | } |
350 | return $sv; |
351 | } |
352 | } |
353 | } |
354 | |
355 | return StatusValue::newGood( 'ignored' ); |
356 | } |
357 | |
358 | /** @inheritDoc */ |
359 | public function providerChangeAuthenticationData( AuthenticationRequest $req ) { |
360 | $username = $req->username !== null |
361 | ? $this->userNameUtils->getCanonical( $req->username, UserNameUtils::RIGOR_USABLE ) |
362 | : false; |
363 | if ( $username === false ) { |
364 | return; |
365 | } |
366 | |
367 | if ( get_class( $req ) === PasswordAuthenticationRequest::class ) { |
368 | $centralUser = CentralAuthUser::getPrimaryInstanceByName( $username ); |
369 | $userIdentity = $this->userIdentityLookup->getUserIdentityByName( $username, IDBAccessObject::READ_LATEST ); |
370 | if ( $centralUser->exists() && |
371 | ( $centralUser->isAttached() || !$userIdentity || !$userIdentity->isRegistered() ) |
372 | ) { |
373 | $centralUser->setPassword( $req->password ); |
374 | } |
375 | } |
376 | } |
377 | |
378 | public function accountCreationType() { |
379 | return self::TYPE_CREATE; |
380 | } |
381 | |
382 | /** @inheritDoc */ |
383 | public function testUserForCreation( $user, $autocreate, array $options = [] ) { |
384 | global $wgCentralAuthEnableGlobalRenameRequest; |
385 | |
386 | $options += [ 'flags' => IDBAccessObject::READ_NORMAL ]; |
387 | |
388 | $status = parent::testUserForCreation( $user, $autocreate, $options ); |
389 | if ( !$status->isOK() ) { |
390 | return $status; |
391 | } |
392 | |
393 | $centralUser = ( $options['flags'] & IDBAccessObject::READ_LATEST ) == IDBAccessObject::READ_LATEST |
394 | ? CentralAuthUser::getPrimaryInstance( $user ) |
395 | : CentralAuthUser::getInstance( $user ); |
396 | |
397 | // Rename in progress? |
398 | if ( $centralUser->renameInProgressOn( WikiMap::getCurrentWikiId(), $options['flags'] ) ) { |
399 | $status->fatal( 'centralauth-rename-abortlogin', $user->getName() ); |
400 | return $status; |
401 | } |
402 | |
403 | if ( $autocreate !== $this->getUniqueId() ) { |
404 | // Prevent creation if the user exists centrally |
405 | if ( $centralUser->exists() && |
406 | $autocreate !== AuthManager::AUTOCREATE_SOURCE_SESSION |
407 | ) { |
408 | $status->fatal( 'centralauth-account-exists' ); |
409 | return $status; |
410 | } |
411 | |
412 | // Prevent creation if the user exists anywhere else we know about |
413 | if ( $centralUser->listUnattached() ) { |
414 | $status->fatal( 'centralauth-account-unattached-exists' ); |
415 | return $status; |
416 | } |
417 | |
418 | // Block account creation if name is a pending rename request |
419 | if ( $wgCentralAuthEnableGlobalRenameRequest && |
420 | $this->globalRenameRequestStore->nameHasPendingRequest( $user->getName() ) |
421 | ) { |
422 | $status->fatal( 'centralauth-account-rename-exists' ); |
423 | return $status; |
424 | } |
425 | } |
426 | |
427 | // Check CentralAuthAntiSpoof, if applicable. Assume the user will override if they can. |
428 | if ( $this->antiSpoofAccounts && empty( $options['creating'] ) && |
429 | !RequestContext::getMain()->getAuthority()->isAllowed( 'override-antispoof' ) |
430 | ) { |
431 | $status->merge( $this->caAntiSpoofManager->testNewAccount( |
432 | $user, new User, true, false, new NullLogger |
433 | ) ); |
434 | } |
435 | |
436 | return $status; |
437 | } |
438 | |
439 | /** |
440 | * @param array $reqs |
441 | * @return AntiSpoofAuthenticationRequest|null |
442 | */ |
443 | private static function getAntiSpoofAuthenticationRequest( array $reqs ) { |
444 | return AuthenticationRequest::getRequestByClass( |
445 | $reqs, |
446 | AntiSpoofAuthenticationRequest::class |
447 | ); |
448 | } |
449 | |
450 | /** @inheritDoc */ |
451 | public function testForAccountCreation( $user, $creator, array $reqs ) { |
452 | $req = self::getPasswordAuthenticationRequest( $reqs ); |
453 | |
454 | $ret = StatusValue::newGood(); |
455 | if ( $req && $req->username !== null && $req->password !== null ) { |
456 | if ( $req->password !== $req->retype ) { |
457 | $ret->fatal( 'badretype' ); |
458 | } else { |
459 | $ret->merge( |
460 | $this->checkPasswordValidity( $user->getName(), $req->password ) |
461 | ); |
462 | } |
463 | } |
464 | |
465 | // Check CentralAuthAntiSpoof, if applicable |
466 | $antiSpoofReq = self::getAntiSpoofAuthenticationRequest( $reqs ); |
467 | $ret->merge( $this->caAntiSpoofManager->testNewAccount( |
468 | $user, $creator, $this->antiSpoofAccounts, |
469 | $antiSpoofReq && $antiSpoofReq->ignoreAntiSpoof |
470 | ) ); |
471 | |
472 | return $ret; |
473 | } |
474 | |
475 | /** @inheritDoc */ |
476 | public function beginPrimaryAccountCreation( $user, $creator, array $reqs ) { |
477 | $req = self::getPasswordAuthenticationRequest( $reqs ); |
478 | if ( $req ) { |
479 | if ( $req->username !== null && $req->password !== null ) { |
480 | $centralUser = CentralAuthUser::getPrimaryInstance( $user ); |
481 | if ( $centralUser->exists() ) { |
482 | return AuthenticationResponse::newFail( |
483 | wfMessage( 'centralauth-account-exists' ) |
484 | ); |
485 | } |
486 | if ( $centralUser->listUnattached() ) { |
487 | // $this->testUserForCreation() will already have rejected it if necessary |
488 | return AuthenticationResponse::newAbstain(); |
489 | } |
490 | // Username is unused; set up as a global account |
491 | if ( !$centralUser->register( $req->password, $user->getEmail() ) ) { |
492 | // Wha? |
493 | return AuthenticationResponse::newFail( wfMessage( 'userexists' ) ); |
494 | } |
495 | return AuthenticationResponse::newPass( $user->getName() ); |
496 | } |
497 | } |
498 | return AuthenticationResponse::newAbstain(); |
499 | } |
500 | |
501 | /** @inheritDoc */ |
502 | public function finishAccountCreation( $user, $creator, AuthenticationResponse $response ) { |
503 | $centralUser = CentralAuthUser::getPrimaryInstance( $user ); |
504 | // Do the attach in finishAccountCreation instead of begin because now the user has been |
505 | // added to database and local ID exists (which is needed in attach) |
506 | $centralUser->attach( WikiMap::getCurrentWikiId(), 'new' ); |
507 | $this->databaseManager->getCentralPrimaryDB()->onTransactionCommitOrIdle( |
508 | function () use ( $centralUser ) { |
509 | $this->utilityService->scheduleCreationJobs( $centralUser ); |
510 | }, |
511 | __METHOD__ |
512 | ); |
513 | return null; |
514 | } |
515 | |
516 | /** @inheritDoc */ |
517 | public function autoCreatedAccount( $user, $source ) { |
518 | $centralUser = CentralAuthUser::getPrimaryInstance( $user ); |
519 | if ( !$centralUser->exists() ) { |
520 | $this->logger->debug( |
521 | 'Not centralizing auto-created user {username}, central account doesn\'t exist', |
522 | [ |
523 | 'user' => $user->getName(), |
524 | ] |
525 | ); |
526 | } elseif ( $source !== $this->getUniqueId() && $centralUser->listUnattached() ) { |
527 | $this->logger->debug( |
528 | 'Not centralizing auto-created user {username}, unattached accounts exist', |
529 | [ |
530 | 'user' => $user->getName(), |
531 | 'source' => $source, |
532 | ] |
533 | ); |
534 | } else { |
535 | $this->logger->debug( |
536 | 'Centralizing auto-created user {username}', |
537 | [ |
538 | 'user' => $user->getName(), |
539 | ] |
540 | ); |
541 | $centralUser->attach( WikiMap::getCurrentWikiId(), 'login' ); |
542 | $centralUser->addLocalName( WikiMap::getCurrentWikiId() ); |
543 | |
544 | if ( $centralUser->getEmail() != $user->getEmail() ) { |
545 | $user->setEmail( $centralUser->getEmail() ); |
546 | $user->mEmailAuthenticated = $centralUser->getEmailAuthenticationTimestamp(); |
547 | } |
548 | } |
549 | } |
550 | } |