MediaWiki REL1_35
AuthManager.php
Go to the documentation of this file.
1<?php
24namespace MediaWiki\Auth;
25
26use Config;
31use Psr\Log\LoggerAwareInterface;
32use Psr\Log\LoggerInterface;
33use Psr\Log\NullLogger;
34use Status;
35use StatusValue;
36use User;
37use WebRequest;
38use Wikimedia\ObjectFactory;
39
88class AuthManager implements LoggerAwareInterface {
90 public const ACTION_LOGIN = 'login';
94 public const ACTION_LOGIN_CONTINUE = 'login-continue';
96 public const ACTION_CREATE = 'create';
100 public const ACTION_CREATE_CONTINUE = 'create-continue';
102 public const ACTION_LINK = 'link';
106 public const ACTION_LINK_CONTINUE = 'link-continue';
108 public const ACTION_CHANGE = 'change';
110 public const ACTION_REMOVE = 'remove';
112 public const ACTION_UNLINK = 'unlink';
113
115 public const SEC_OK = 'ok';
117 public const SEC_REAUTH = 'reauth';
119 public const SEC_FAIL = 'fail';
120
122 public const AUTOCREATE_SOURCE_SESSION = \MediaWiki\Session\SessionManager::class;
123
125 public const AUTOCREATE_SOURCE_MAINT = '::Maintenance::';
126
128 private static $instance = null;
129
131 private $request;
132
134 private $config;
135
138
140 private $logger;
141
144
147
150
153
156
159
162
164 private $hookRunner;
165
171 public static function singleton() {
172 return MediaWikiServices::getInstance()->getAuthManager();
173 }
174
182 public function __construct(
185 ObjectFactory $objectFactory,
188 ) {
189 $this->request = $request;
190 $this->config = $config;
191 $this->objectFactory = $objectFactory;
192 $this->permManager = $permManager;
193 $this->hookContainer = $hookContainer;
194 $this->hookRunner = new HookRunner( $hookContainer );
195 $this->setLogger( new NullLogger() );
196 }
197
201 public function setLogger( LoggerInterface $logger ) {
202 $this->logger = $logger;
203 }
204
208 public function getRequest() {
209 return $this->request;
210 }
211
218 public function forcePrimaryAuthenticationProviders( array $providers, $why ) {
219 $this->logger->warning( "Overriding AuthManager primary authn because $why" );
220
221 if ( $this->primaryAuthenticationProviders !== null ) {
222 $this->logger->warning(
223 'PrimaryAuthenticationProviders have already been accessed! I hope nothing breaks.'
224 );
225
226 $this->allAuthenticationProviders = array_diff_key(
227 $this->allAuthenticationProviders,
228 $this->primaryAuthenticationProviders
229 );
230 $session = $this->request->getSession();
231 $session->remove( 'AuthManager::authnState' );
232 $session->remove( 'AuthManager::accountCreationState' );
233 $session->remove( 'AuthManager::accountLinkState' );
234 $this->createdAccountAuthenticationRequests = [];
235 }
236
237 $this->primaryAuthenticationProviders = [];
238 foreach ( $providers as $provider ) {
239 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
240 throw new \RuntimeException(
241 'Expected instance of MediaWiki\\Auth\\PrimaryAuthenticationProvider, got ' .
242 get_class( $provider )
243 );
244 }
245 $provider->setLogger( $this->logger );
246 $provider->setManager( $this );
247 $provider->setConfig( $this->config );
248 $provider->setHookContainer( $this->hookContainer );
249 $id = $provider->getUniqueId();
250 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
251 throw new \RuntimeException(
252 "Duplicate specifications for id $id (classes " .
253 get_class( $provider ) . ' and ' .
254 get_class( $this->allAuthenticationProviders[$id] ) . ')'
255 );
256 }
257 $this->allAuthenticationProviders[$id] = $provider;
258 $this->primaryAuthenticationProviders[$id] = $provider;
259 }
260 }
261
275 public function canAuthenticateNow() {
276 return $this->request->getSession()->canSetUser();
277 }
278
297 public function beginAuthentication( array $reqs, $returnToUrl ) {
298 $session = $this->request->getSession();
299 if ( !$session->canSetUser() ) {
300 // Caller should have called canAuthenticateNow()
301 $session->remove( 'AuthManager::authnState' );
302 throw new \LogicException( 'Authentication is not possible now' );
303 }
304
305 $guessUserName = null;
306 foreach ( $reqs as $req ) {
307 $req->returnToUrl = $returnToUrl;
308 // @codeCoverageIgnoreStart
309 if ( $req->username !== null && $req->username !== '' ) {
310 if ( $guessUserName === null ) {
311 $guessUserName = $req->username;
312 } elseif ( $guessUserName !== $req->username ) {
313 $guessUserName = null;
314 break;
315 }
316 }
317 // @codeCoverageIgnoreEnd
318 }
319
320 // Check for special-case login of a just-created account
322 $reqs, CreatedAccountAuthenticationRequest::class
323 );
324 if ( $req ) {
325 if ( !in_array( $req, $this->createdAccountAuthenticationRequests, true ) ) {
326 throw new \LogicException(
327 'CreatedAccountAuthenticationRequests are only valid on ' .
328 'the same AuthManager that created the account'
329 );
330 }
331
332 $user = User::newFromName( $req->username );
333 // @codeCoverageIgnoreStart
334 if ( !$user ) {
335 throw new \UnexpectedValueException(
336 "CreatedAccountAuthenticationRequest had invalid username \"{$req->username}\""
337 );
338 } elseif ( $user->getId() != $req->id ) {
339 throw new \UnexpectedValueException(
340 "ID for \"{$req->username}\" was {$user->getId()}, expected {$req->id}"
341 );
342 }
343 // @codeCoverageIgnoreEnd
344
345 $this->logger->info( 'Logging in {user} after account creation', [
346 'user' => $user->getName(),
347 ] );
348 $ret = AuthenticationResponse::newPass( $user->getName() );
349 $this->setSessionDataForUser( $user );
350 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
351 $session->remove( 'AuthManager::authnState' );
352 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
353 $ret, $user, $user->getName(), [] );
354 return $ret;
355 }
356
357 $this->removeAuthenticationSessionData( null );
358
359 foreach ( $this->getPreAuthenticationProviders() as $provider ) {
360 $status = $provider->testForAuthentication( $reqs );
361 if ( !$status->isGood() ) {
362 $this->logger->debug( 'Login failed in pre-authentication by ' . $provider->getUniqueId() );
364 Status::wrap( $status )->getMessage()
365 );
366 $this->callMethodOnProviders( 7, 'postAuthentication',
367 [ User::newFromName( $guessUserName ) ?: null, $ret ]
368 );
369 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit( $ret, null, $guessUserName, [] );
370 return $ret;
371 }
372 }
373
374 $state = [
375 'reqs' => $reqs,
376 'returnToUrl' => $returnToUrl,
377 'guessUserName' => $guessUserName,
378 'primary' => null,
379 'primaryResponse' => null,
380 'secondary' => [],
381 'maybeLink' => [],
382 'continueRequests' => [],
383 ];
384
385 // Preserve state from a previous failed login
387 $reqs, CreateFromLoginAuthenticationRequest::class
388 );
389 if ( $req ) {
390 $state['maybeLink'] = $req->maybeLink;
391 }
392
393 $session = $this->request->getSession();
394 $session->setSecret( 'AuthManager::authnState', $state );
395 $session->persist();
396
397 return $this->continueAuthentication( $reqs );
398 }
399
422 public function continueAuthentication( array $reqs ) {
423 $session = $this->request->getSession();
424 try {
425 if ( !$session->canSetUser() ) {
426 // Caller should have called canAuthenticateNow()
427 // @codeCoverageIgnoreStart
428 throw new \LogicException( 'Authentication is not possible now' );
429 // @codeCoverageIgnoreEnd
430 }
431
432 $state = $session->getSecret( 'AuthManager::authnState' );
433 if ( !is_array( $state ) ) {
435 wfMessage( 'authmanager-authn-not-in-progress' )
436 );
437 }
438 $state['continueRequests'] = [];
439
440 $guessUserName = $state['guessUserName'];
441
442 foreach ( $reqs as $req ) {
443 $req->returnToUrl = $state['returnToUrl'];
444 }
445
446 // Step 1: Choose an primary authentication provider, and call it until it succeeds.
447
448 if ( $state['primary'] === null ) {
449 // We haven't picked a PrimaryAuthenticationProvider yet
450 // @codeCoverageIgnoreStart
451 $guessUserName = null;
452 foreach ( $reqs as $req ) {
453 if ( $req->username !== null && $req->username !== '' ) {
454 if ( $guessUserName === null ) {
455 $guessUserName = $req->username;
456 } elseif ( $guessUserName !== $req->username ) {
457 $guessUserName = null;
458 break;
459 }
460 }
461 }
462 $state['guessUserName'] = $guessUserName;
463 // @codeCoverageIgnoreEnd
464 $state['reqs'] = $reqs;
465
466 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
467 $res = $provider->beginPrimaryAuthentication( $reqs );
468 switch ( $res->status ) {
470 $state['primary'] = $id;
471 $state['primaryResponse'] = $res;
472 $this->logger->debug( "Primary login with $id succeeded" );
473 break 2;
475 $this->logger->debug( "Login failed in primary authentication by $id" );
476 if ( $res->createRequest || $state['maybeLink'] ) {
477 $res->createRequest = new CreateFromLoginAuthenticationRequest(
478 $res->createRequest, $state['maybeLink']
479 );
480 }
481 $this->callMethodOnProviders( 7, 'postAuthentication',
482 [ User::newFromName( $guessUserName ) ?: null, $res ]
483 );
484 $session->remove( 'AuthManager::authnState' );
485 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
486 $res, null, $guessUserName, [] );
487 return $res;
489 // Continue loop
490 break;
493 $this->logger->debug( "Primary login with $id returned $res->status" );
494 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
495 $state['primary'] = $id;
496 $state['continueRequests'] = $res->neededRequests;
497 $session->setSecret( 'AuthManager::authnState', $state );
498 return $res;
499
500 // @codeCoverageIgnoreStart
501 default:
502 throw new \DomainException(
503 get_class( $provider ) . "::beginPrimaryAuthentication() returned $res->status"
504 );
505 // @codeCoverageIgnoreEnd
506 }
507 }
508 if ( $state['primary'] === null ) {
509 $this->logger->debug( 'Login failed in primary authentication because no provider accepted' );
511 wfMessage( 'authmanager-authn-no-primary' )
512 );
513 $this->callMethodOnProviders( 7, 'postAuthentication',
514 [ User::newFromName( $guessUserName ) ?: null, $ret ]
515 );
516 $session->remove( 'AuthManager::authnState' );
517 return $ret;
518 }
519 } elseif ( $state['primaryResponse'] === null ) {
520 $provider = $this->getAuthenticationProvider( $state['primary'] );
521 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
522 // Configuration changed? Force them to start over.
523 // @codeCoverageIgnoreStart
525 wfMessage( 'authmanager-authn-not-in-progress' )
526 );
527 $this->callMethodOnProviders( 7, 'postAuthentication',
528 [ User::newFromName( $guessUserName ) ?: null, $ret ]
529 );
530 $session->remove( 'AuthManager::authnState' );
531 return $ret;
532 // @codeCoverageIgnoreEnd
533 }
534 $id = $provider->getUniqueId();
535 $res = $provider->continuePrimaryAuthentication( $reqs );
536 switch ( $res->status ) {
538 $state['primaryResponse'] = $res;
539 $this->logger->debug( "Primary login with $id succeeded" );
540 break;
542 $this->logger->debug( "Login failed in primary authentication by $id" );
543 if ( $res->createRequest || $state['maybeLink'] ) {
544 $res->createRequest = new CreateFromLoginAuthenticationRequest(
545 $res->createRequest, $state['maybeLink']
546 );
547 }
548 $this->callMethodOnProviders( 7, 'postAuthentication',
549 [ User::newFromName( $guessUserName ) ?: null, $res ]
550 );
551 $session->remove( 'AuthManager::authnState' );
552 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
553 $res, null, $guessUserName, [] );
554 return $res;
557 $this->logger->debug( "Primary login with $id returned $res->status" );
558 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $guessUserName );
559 $state['continueRequests'] = $res->neededRequests;
560 $session->setSecret( 'AuthManager::authnState', $state );
561 return $res;
562 default:
563 throw new \DomainException(
564 get_class( $provider ) . "::continuePrimaryAuthentication() returned $res->status"
565 );
566 }
567 }
568
569 $res = $state['primaryResponse'];
570 if ( $res->username === null ) {
571 $provider = $this->getAuthenticationProvider( $state['primary'] );
572 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
573 // Configuration changed? Force them to start over.
574 // @codeCoverageIgnoreStart
576 wfMessage( 'authmanager-authn-not-in-progress' )
577 );
578 $this->callMethodOnProviders( 7, 'postAuthentication',
579 [ User::newFromName( $guessUserName ) ?: null, $ret ]
580 );
581 $session->remove( 'AuthManager::authnState' );
582 return $ret;
583 // @codeCoverageIgnoreEnd
584 }
585
586 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK &&
587 $res->linkRequest &&
588 // don't confuse the user with an incorrect message if linking is disabled
589 $this->getAuthenticationProvider( ConfirmLinkSecondaryAuthenticationProvider::class )
590 ) {
591 $state['maybeLink'][$res->linkRequest->getUniqueId()] = $res->linkRequest;
592 $msg = 'authmanager-authn-no-local-user-link';
593 } else {
594 $msg = 'authmanager-authn-no-local-user';
595 }
596 $this->logger->debug(
597 "Primary login with {$provider->getUniqueId()} succeeded, but returned no user"
598 );
600 $ret->neededRequests = $this->getAuthenticationRequestsInternal(
601 self::ACTION_LOGIN,
602 [],
604 );
605 if ( $res->createRequest || $state['maybeLink'] ) {
606 $ret->createRequest = new CreateFromLoginAuthenticationRequest(
607 $res->createRequest, $state['maybeLink']
608 );
609 $ret->neededRequests[] = $ret->createRequest;
610 }
611 $this->fillRequests( $ret->neededRequests, self::ACTION_LOGIN, null, true );
612 $session->setSecret( 'AuthManager::authnState', [
613 'reqs' => [], // Will be filled in later
614 'primary' => null,
615 'primaryResponse' => null,
616 'secondary' => [],
617 'continueRequests' => $ret->neededRequests,
618 ] + $state );
619 return $ret;
620 }
621
622 // Step 2: Primary authentication succeeded, create the User object
623 // (and add the user locally if necessary)
624
625 $user = User::newFromName( $res->username, 'usable' );
626 if ( !$user ) {
627 $provider = $this->getAuthenticationProvider( $state['primary'] );
628 throw new \DomainException(
629 get_class( $provider ) . " returned an invalid username: {$res->username}"
630 );
631 }
632 if ( $user->getId() === 0 ) {
633 // User doesn't exist locally. Create it.
634 $this->logger->info( 'Auto-creating {user} on login', [
635 'user' => $user->getName(),
636 ] );
637 $status = $this->autoCreateUser( $user, $state['primary'], false );
638 if ( !$status->isGood() ) {
640 Status::wrap( $status )->getMessage( 'authmanager-authn-autocreate-failed' )
641 );
642 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
643 $session->remove( 'AuthManager::authnState' );
644 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
645 $ret, $user, $user->getName(), [] );
646 return $ret;
647 }
648 }
649
650 // Step 3: Iterate over all the secondary authentication providers.
651
652 $beginReqs = $state['reqs'];
653
654 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
655 if ( !isset( $state['secondary'][$id] ) ) {
656 // This provider isn't started yet, so we pass it the set
657 // of reqs from beginAuthentication instead of whatever
658 // might have been used by a previous provider in line.
659 $func = 'beginSecondaryAuthentication';
660 $res = $provider->beginSecondaryAuthentication( $user, $beginReqs );
661 } elseif ( !$state['secondary'][$id] ) {
662 $func = 'continueSecondaryAuthentication';
663 $res = $provider->continueSecondaryAuthentication( $user, $reqs );
664 } else {
665 continue;
666 }
667 switch ( $res->status ) {
669 $this->logger->debug( "Secondary login with $id succeeded" );
670 // fall through
672 $state['secondary'][$id] = true;
673 break;
675 $this->logger->debug( "Login failed in secondary authentication by $id" );
676 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $res ] );
677 $session->remove( 'AuthManager::authnState' );
678 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
679 $res, $user, $user->getName(), [] );
680 return $res;
683 $this->logger->debug( "Secondary login with $id returned " . $res->status );
684 $this->fillRequests( $res->neededRequests, self::ACTION_LOGIN, $user->getName() );
685 $state['secondary'][$id] = false;
686 $state['continueRequests'] = $res->neededRequests;
687 $session->setSecret( 'AuthManager::authnState', $state );
688 return $res;
689
690 // @codeCoverageIgnoreStart
691 default:
692 throw new \DomainException(
693 get_class( $provider ) . "::{$func}() returned $res->status"
694 );
695 // @codeCoverageIgnoreEnd
696 }
697 }
698
699 // Step 4: Authentication complete! Set the user in the session and
700 // clean up.
701
702 $this->logger->info( 'Login for {user} succeeded from {clientip}', [
703 'user' => $user->getName(),
704 'clientip' => $this->request->getIP(),
705 ] );
708 $beginReqs, RememberMeAuthenticationRequest::class
709 );
710 $this->setSessionDataForUser( $user, $req && $req->rememberMe );
711 $ret = AuthenticationResponse::newPass( $user->getName() );
712 $this->callMethodOnProviders( 7, 'postAuthentication', [ $user, $ret ] );
713 $session->remove( 'AuthManager::authnState' );
714 $this->removeAuthenticationSessionData( null );
715 $this->getHookRunner()->onAuthManagerLoginAuthenticateAudit(
716 $ret, $user, $user->getName(), [] );
717 return $ret;
718 } catch ( \Exception $ex ) {
719 $session->remove( 'AuthManager::authnState' );
720 throw $ex;
721 }
722 }
723
735 public function securitySensitiveOperationStatus( $operation ) {
736 $status = self::SEC_OK;
737
738 $this->logger->debug( __METHOD__ . ": Checking $operation" );
739
740 $session = $this->request->getSession();
741 $aId = $session->getUser()->getId();
742 if ( $aId === 0 ) {
743 // User isn't authenticated. DWIM?
745 $this->logger->info( __METHOD__ . ": Not logged in! $operation is $status" );
746 return $status;
747 }
748
749 if ( $session->canSetUser() ) {
750 $id = $session->get( 'AuthManager:lastAuthId' );
751 $last = $session->get( 'AuthManager:lastAuthTimestamp' );
752 if ( $id !== $aId || $last === null ) {
753 $timeSinceLogin = PHP_INT_MAX; // Forever ago
754 } else {
755 $timeSinceLogin = max( 0, time() - $last );
756 }
757
758 $thresholds = $this->config->get( 'ReauthenticateTime' );
759 if ( isset( $thresholds[$operation] ) ) {
760 $threshold = $thresholds[$operation];
761 } elseif ( isset( $thresholds['default'] ) ) {
762 $threshold = $thresholds['default'];
763 } else {
764 throw new \UnexpectedValueException( '$wgReauthenticateTime lacks a default' );
765 }
766
767 if ( $threshold >= 0 && $timeSinceLogin > $threshold ) {
768 $status = self::SEC_REAUTH;
769 }
770 } else {
771 $timeSinceLogin = -1;
772
773 $pass = $this->config->get( 'AllowSecuritySensitiveOperationIfCannotReauthenticate' );
774 if ( isset( $pass[$operation] ) ) {
775 $status = $pass[$operation] ? self::SEC_OK : self::SEC_FAIL;
776 } elseif ( isset( $pass['default'] ) ) {
777 $status = $pass['default'] ? self::SEC_OK : self::SEC_FAIL;
778 } else {
779 throw new \UnexpectedValueException(
780 '$wgAllowSecuritySensitiveOperationIfCannotReauthenticate lacks a default'
781 );
782 }
783 }
784
785 $this->getHookRunner()->onSecuritySensitiveOperationStatus(
786 $status, $operation, $session, $timeSinceLogin );
787
788 // If authentication is not possible, downgrade from "REAUTH" to "FAIL".
789 if ( !$this->canAuthenticateNow() && $status === self::SEC_REAUTH ) {
790 $status = self::SEC_FAIL;
791 }
792
793 $this->logger->info( __METHOD__ . ": $operation is $status for '{user}'",
794 [
795 'user' => $session->getUser()->getName(),
796 'clientip' => $this->getRequest()->getIP(),
797 ]
798 );
799
800 return $status;
801 }
802
812 public function userCanAuthenticate( $username ) {
813 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
814 if ( $provider->testUserCanAuthenticate( $username ) ) {
815 return true;
816 }
817 }
818 return false;
819 }
820
835 public function normalizeUsername( $username ) {
836 $ret = [];
837 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
838 $normalized = $provider->providerNormalizeUsername( $username );
839 if ( $normalized !== null ) {
840 $ret[$normalized] = true;
841 }
842 }
843 return array_keys( $ret );
844 }
845
860 public function revokeAccessForUser( $username ) {
861 $this->logger->info( 'Revoking access for {user}', [
862 'user' => $username,
863 ] );
864 $this->callMethodOnProviders( 6, 'providerRevokeAccessForUser', [ $username ] );
865 }
866
876 public function allowsAuthenticationDataChange( AuthenticationRequest $req, $checkData = true ) {
877 $any = false;
878 $providers = $this->getPrimaryAuthenticationProviders() +
880
881 foreach ( $providers as $provider ) {
882 $status = $provider->providerAllowsAuthenticationDataChange( $req, $checkData );
883 if ( !$status->isGood() ) {
884 // If status is not good because reset email password last attempt was within
885 // $wgPasswordReminderResendTime then return good status with throttled-mailpassword value;
886 // otherwise, return the $status wrapped.
887 return $status->hasMessage( 'throttled-mailpassword' )
888 ? Status::newGood( 'throttled-mailpassword' )
889 : Status::wrap( $status );
890 }
891 $any = $any || $status->value !== 'ignored';
892 }
893 if ( !$any ) {
894 $status = Status::newGood( 'ignored' );
895 $status->warning( 'authmanager-change-not-supported' );
896 return $status;
897 }
898 return Status::newGood();
899 }
900
918 public function changeAuthenticationData( AuthenticationRequest $req, $isAddition = false ) {
919 $this->logger->info( 'Changing authentication data for {user} class {what}', [
920 'user' => is_string( $req->username ) ? $req->username : '<no name>',
921 'what' => get_class( $req ),
922 ] );
923
924 $this->callMethodOnProviders( 6, 'providerChangeAuthenticationData', [ $req ] );
925
926 // When the main account's authentication data is changed, invalidate
927 // all BotPasswords too.
928 if ( !$isAddition ) {
929 \BotPassword::invalidateAllPasswordsForUser( $req->username );
930 }
931 }
932
944 public function canCreateAccounts() {
945 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
946 switch ( $provider->accountCreationType() ) {
949 return true;
950 }
951 }
952 return false;
953 }
954
963 public function canCreateAccount( $username, $options = [] ) {
964 // Back compat
965 if ( is_int( $options ) ) {
966 $options = [ 'flags' => $options ];
967 }
968 $options += [
969 'flags' => User::READ_NORMAL,
970 'creating' => false,
971 ];
972 $flags = $options['flags'];
973
974 if ( !$this->canCreateAccounts() ) {
975 return Status::newFatal( 'authmanager-create-disabled' );
976 }
977
978 if ( $this->userExists( $username, $flags ) ) {
979 return Status::newFatal( 'userexists' );
980 }
981
982 $user = User::newFromName( $username, 'creatable' );
983 if ( !is_object( $user ) ) {
984 return Status::newFatal( 'noname' );
985 } else {
986 $user->load( $flags ); // Explicitly load with $flags, auto-loading always uses READ_NORMAL
987 if ( $user->getId() !== 0 ) {
988 return Status::newFatal( 'userexists' );
989 }
990 }
991
992 // Denied by providers?
993 $providers = $this->getPreAuthenticationProviders() +
996 foreach ( $providers as $provider ) {
997 $status = $provider->testUserForCreation( $user, false, $options );
998 if ( !$status->isGood() ) {
999 return Status::wrap( $status );
1000 }
1001 }
1002
1003 return Status::newGood();
1004 }
1005
1011 public function checkAccountCreatePermissions( User $creator ) {
1012 // Wiki is read-only?
1013 if ( wfReadOnly() ) {
1014 return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
1015 }
1016
1017 $permErrors = $this->permManager->getPermissionErrors(
1018 'createaccount',
1019 $creator,
1020 \SpecialPage::getTitleFor( 'CreateAccount' )
1021 );
1022 if ( $permErrors ) {
1023 $status = Status::newGood();
1024 foreach ( $permErrors as $args ) {
1025 $status->fatal( ...$args );
1026 }
1027 return $status;
1028 }
1029
1030 $ip = $this->getRequest()->getIP();
1031
1032 $block = $creator->isBlockedFromCreateAccount();
1033 if ( $block ) {
1034 $language = \RequestContext::getMain()->getLanguage();
1035 $formatter = MediaWikiServices::getInstance()->getBlockErrorFormatter();
1036 return Status::newFatal(
1037 $formatter->getMessage( $block, $creator, $language, $ip )
1038 );
1039 }
1040
1041 if (
1042 MediaWikiServices::getInstance()->getBlockManager()
1043 ->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ )
1044 ) {
1045 return Status::newFatal( 'sorbs_create_account_reason' );
1046 }
1047
1048 return Status::newGood();
1049 }
1050
1070 public function beginAccountCreation( User $creator, array $reqs, $returnToUrl ) {
1071 $session = $this->request->getSession();
1072 if ( !$this->canCreateAccounts() ) {
1073 // Caller should have called canCreateAccounts()
1074 $session->remove( 'AuthManager::accountCreationState' );
1075 throw new \LogicException( 'Account creation is not possible' );
1076 }
1077
1078 try {
1080 } catch ( \UnexpectedValueException $ex ) {
1081 $username = null;
1082 }
1083 if ( $username === null ) {
1084 $this->logger->debug( __METHOD__ . ': No username provided' );
1085 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1086 }
1087
1088 // Permissions check
1089 $status = $this->checkAccountCreatePermissions( $creator );
1090 if ( !$status->isGood() ) {
1091 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1092 'user' => $username,
1093 'creator' => $creator->getName(),
1094 'reason' => $status->getWikiText( null, null, 'en' )
1095 ] );
1096 return AuthenticationResponse::newFail( $status->getMessage() );
1097 }
1098
1099 $status = $this->canCreateAccount(
1100 $username, [ 'flags' => User::READ_LOCKING, 'creating' => true ]
1101 );
1102 if ( !$status->isGood() ) {
1103 $this->logger->debug( __METHOD__ . ': {user} cannot be created: {reason}', [
1104 'user' => $username,
1105 'creator' => $creator->getName(),
1106 'reason' => $status->getWikiText( null, null, 'en' )
1107 ] );
1108 return AuthenticationResponse::newFail( $status->getMessage() );
1109 }
1110
1111 $user = User::newFromName( $username, 'creatable' );
1112 foreach ( $reqs as $req ) {
1113 $req->username = $username;
1114 $req->returnToUrl = $returnToUrl;
1115 if ( $req instanceof UserDataAuthenticationRequest ) {
1116 $status = $req->populateUser( $user );
1117 if ( !$status->isGood() ) {
1118 $status = Status::wrap( $status );
1119 $session->remove( 'AuthManager::accountCreationState' );
1120 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1121 'user' => $user->getName(),
1122 'creator' => $creator->getName(),
1123 'reason' => $status->getWikiText( null, null, 'en' ),
1124 ] );
1125 return AuthenticationResponse::newFail( $status->getMessage() );
1126 }
1127 }
1128 }
1129
1130 $this->removeAuthenticationSessionData( null );
1131
1132 $state = [
1133 'username' => $username,
1134 'userid' => 0,
1135 'creatorid' => $creator->getId(),
1136 'creatorname' => $creator->getName(),
1137 'reqs' => $reqs,
1138 'returnToUrl' => $returnToUrl,
1139 'primary' => null,
1140 'primaryResponse' => null,
1141 'secondary' => [],
1142 'continueRequests' => [],
1143 'maybeLink' => [],
1144 'ranPreTests' => false,
1145 ];
1146
1147 // Special case: converting a login to an account creation
1149 $reqs, CreateFromLoginAuthenticationRequest::class
1150 );
1151 if ( $req ) {
1152 $state['maybeLink'] = $req->maybeLink;
1153
1154 if ( $req->createRequest ) {
1155 $reqs[] = $req->createRequest;
1156 $state['reqs'][] = $req->createRequest;
1157 }
1158 }
1159
1160 $session->setSecret( 'AuthManager::accountCreationState', $state );
1161 $session->persist();
1162
1163 return $this->continueAccountCreation( $reqs );
1164 }
1165
1171 public function continueAccountCreation( array $reqs ) {
1172 $session = $this->request->getSession();
1173 try {
1174 if ( !$this->canCreateAccounts() ) {
1175 // Caller should have called canCreateAccounts()
1176 $session->remove( 'AuthManager::accountCreationState' );
1177 throw new \LogicException( 'Account creation is not possible' );
1178 }
1179
1180 $state = $session->getSecret( 'AuthManager::accountCreationState' );
1181 if ( !is_array( $state ) ) {
1183 wfMessage( 'authmanager-create-not-in-progress' )
1184 );
1185 }
1186 $state['continueRequests'] = [];
1187
1188 // Step 0: Prepare and validate the input
1189
1190 $user = User::newFromName( $state['username'], 'creatable' );
1191 if ( !is_object( $user ) ) {
1192 $session->remove( 'AuthManager::accountCreationState' );
1193 $this->logger->debug( __METHOD__ . ': Invalid username', [
1194 'user' => $state['username'],
1195 ] );
1196 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1197 }
1198
1199 if ( $state['creatorid'] ) {
1200 $creator = User::newFromId( $state['creatorid'] );
1201 } else {
1202 $creator = new User;
1203 $creator->setName( $state['creatorname'] );
1204 }
1205
1206 // Avoid account creation races on double submissions
1207 $cache = \ObjectCache::getLocalClusterInstance();
1208 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $user->getName() ) ) );
1209 if ( !$lock ) {
1210 // Don't clear AuthManager::accountCreationState for this code
1211 // path because the process that won the race owns it.
1212 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1213 'user' => $user->getName(),
1214 'creator' => $creator->getName(),
1215 ] );
1216 return AuthenticationResponse::newFail( wfMessage( 'usernameinprogress' ) );
1217 }
1218
1219 // Permissions check
1220 $status = $this->checkAccountCreatePermissions( $creator );
1221 if ( !$status->isGood() ) {
1222 $this->logger->debug( __METHOD__ . ': {creator} cannot create users: {reason}', [
1223 'user' => $user->getName(),
1224 'creator' => $creator->getName(),
1225 'reason' => $status->getWikiText( null, null, 'en' )
1226 ] );
1227 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1228 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1229 $session->remove( 'AuthManager::accountCreationState' );
1230 return $ret;
1231 }
1232
1233 // Load from master for existence check
1234 $user->load( User::READ_LOCKING );
1235
1236 if ( $state['userid'] === 0 ) {
1237 if ( $user->getId() !== 0 ) {
1238 $this->logger->debug( __METHOD__ . ': User exists locally', [
1239 'user' => $user->getName(),
1240 'creator' => $creator->getName(),
1241 ] );
1242 $ret = AuthenticationResponse::newFail( wfMessage( 'userexists' ) );
1243 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1244 $session->remove( 'AuthManager::accountCreationState' );
1245 return $ret;
1246 }
1247 } else {
1248 if ( $user->getId() === 0 ) {
1249 $this->logger->debug( __METHOD__ . ': User does not exist locally when it should', [
1250 'user' => $user->getName(),
1251 'creator' => $creator->getName(),
1252 'expected_id' => $state['userid'],
1253 ] );
1254 throw new \UnexpectedValueException(
1255 "User \"{$state['username']}\" should exist now, but doesn't!"
1256 );
1257 }
1258 if ( $user->getId() !== $state['userid'] ) {
1259 $this->logger->debug( __METHOD__ . ': User ID/name mismatch', [
1260 'user' => $user->getName(),
1261 'creator' => $creator->getName(),
1262 'expected_id' => $state['userid'],
1263 'actual_id' => $user->getId(),
1264 ] );
1265 throw new \UnexpectedValueException(
1266 "User \"{$state['username']}\" exists, but " .
1267 "ID {$user->getId()} !== {$state['userid']}!"
1268 );
1269 }
1270 }
1271 foreach ( $state['reqs'] as $req ) {
1272 if ( $req instanceof UserDataAuthenticationRequest ) {
1273 $status = $req->populateUser( $user );
1274 if ( !$status->isGood() ) {
1275 // This should never happen...
1276 $status = Status::wrap( $status );
1277 $this->logger->debug( __METHOD__ . ': UserData is invalid: {reason}', [
1278 'user' => $user->getName(),
1279 'creator' => $creator->getName(),
1280 'reason' => $status->getWikiText( null, null, 'en' ),
1281 ] );
1282 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1283 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1284 $session->remove( 'AuthManager::accountCreationState' );
1285 return $ret;
1286 }
1287 }
1288 }
1289
1290 foreach ( $reqs as $req ) {
1291 $req->returnToUrl = $state['returnToUrl'];
1292 $req->username = $state['username'];
1293 }
1294
1295 // Run pre-creation tests, if we haven't already
1296 if ( !$state['ranPreTests'] ) {
1297 $providers = $this->getPreAuthenticationProviders() +
1300 foreach ( $providers as $id => $provider ) {
1301 $status = $provider->testForAccountCreation( $user, $creator, $reqs );
1302 if ( !$status->isGood() ) {
1303 $this->logger->debug( __METHOD__ . ": Fail in pre-authentication by $id", [
1304 'user' => $user->getName(),
1305 'creator' => $creator->getName(),
1306 ] );
1308 Status::wrap( $status )->getMessage()
1309 );
1310 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1311 $session->remove( 'AuthManager::accountCreationState' );
1312 return $ret;
1313 }
1314 }
1315
1316 $state['ranPreTests'] = true;
1317 }
1318
1319 // Step 1: Choose a primary authentication provider and call it until it succeeds.
1320
1321 if ( $state['primary'] === null ) {
1322 // We haven't picked a PrimaryAuthenticationProvider yet
1323 foreach ( $this->getPrimaryAuthenticationProviders() as $id => $provider ) {
1324 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_NONE ) {
1325 continue;
1326 }
1327 $res = $provider->beginPrimaryAccountCreation( $user, $creator, $reqs );
1328 switch ( $res->status ) {
1330 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1331 'user' => $user->getName(),
1332 'creator' => $creator->getName(),
1333 ] );
1334 $state['primary'] = $id;
1335 $state['primaryResponse'] = $res;
1336 break 2;
1338 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1339 'user' => $user->getName(),
1340 'creator' => $creator->getName(),
1341 ] );
1342 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1343 $session->remove( 'AuthManager::accountCreationState' );
1344 return $res;
1346 // Continue loop
1347 break;
1350 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1351 'user' => $user->getName(),
1352 'creator' => $creator->getName(),
1353 ] );
1354 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1355 $state['primary'] = $id;
1356 $state['continueRequests'] = $res->neededRequests;
1357 $session->setSecret( 'AuthManager::accountCreationState', $state );
1358 return $res;
1359
1360 // @codeCoverageIgnoreStart
1361 default:
1362 throw new \DomainException(
1363 get_class( $provider ) . "::beginPrimaryAccountCreation() returned $res->status"
1364 );
1365 // @codeCoverageIgnoreEnd
1366 }
1367 }
1368 if ( $state['primary'] === null ) {
1369 $this->logger->debug( __METHOD__ . ': Primary creation failed because no provider accepted', [
1370 'user' => $user->getName(),
1371 'creator' => $creator->getName(),
1372 ] );
1374 wfMessage( 'authmanager-create-no-primary' )
1375 );
1376 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1377 $session->remove( 'AuthManager::accountCreationState' );
1378 return $ret;
1379 }
1380 } elseif ( $state['primaryResponse'] === null ) {
1381 $provider = $this->getAuthenticationProvider( $state['primary'] );
1382 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1383 // Configuration changed? Force them to start over.
1384 // @codeCoverageIgnoreStart
1386 wfMessage( 'authmanager-create-not-in-progress' )
1387 );
1388 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1389 $session->remove( 'AuthManager::accountCreationState' );
1390 return $ret;
1391 // @codeCoverageIgnoreEnd
1392 }
1393 $id = $provider->getUniqueId();
1394 $res = $provider->continuePrimaryAccountCreation( $user, $creator, $reqs );
1395 switch ( $res->status ) {
1397 $this->logger->debug( __METHOD__ . ": Primary creation passed by $id", [
1398 'user' => $user->getName(),
1399 'creator' => $creator->getName(),
1400 ] );
1401 $state['primaryResponse'] = $res;
1402 break;
1404 $this->logger->debug( __METHOD__ . ": Primary creation failed by $id", [
1405 'user' => $user->getName(),
1406 'creator' => $creator->getName(),
1407 ] );
1408 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $res ] );
1409 $session->remove( 'AuthManager::accountCreationState' );
1410 return $res;
1413 $this->logger->debug( __METHOD__ . ": Primary creation $res->status by $id", [
1414 'user' => $user->getName(),
1415 'creator' => $creator->getName(),
1416 ] );
1417 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1418 $state['continueRequests'] = $res->neededRequests;
1419 $session->setSecret( 'AuthManager::accountCreationState', $state );
1420 return $res;
1421 default:
1422 throw new \DomainException(
1423 get_class( $provider ) . "::continuePrimaryAccountCreation() returned $res->status"
1424 );
1425 }
1426 }
1427
1428 // Step 2: Primary authentication succeeded, create the User object
1429 // and add the user locally.
1430
1431 if ( $state['userid'] === 0 ) {
1432 $this->logger->info( 'Creating user {user} during account creation', [
1433 'user' => $user->getName(),
1434 'creator' => $creator->getName(),
1435 ] );
1436 $status = $user->addToDatabase();
1437 if ( !$status->isOK() ) {
1438 // @codeCoverageIgnoreStart
1439 $ret = AuthenticationResponse::newFail( $status->getMessage() );
1440 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1441 $session->remove( 'AuthManager::accountCreationState' );
1442 return $ret;
1443 // @codeCoverageIgnoreEnd
1444 }
1445 $this->setDefaultUserOptions( $user, $creator->isAnon() );
1446 $this->getHookRunner()->onLocalUserCreated( $user, false );
1447 $user->saveSettings();
1448 $state['userid'] = $user->getId();
1449
1450 // Update user count
1451 \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1452
1453 // Watch user's userpage and talk page
1454 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1455
1456 // Inform the provider
1457 $logSubtype = $provider->finishAccountCreation( $user, $creator, $state['primaryResponse'] );
1458
1459 // Log the creation
1460 if ( $this->config->get( 'NewUserLog' ) ) {
1461 $isAnon = $creator->isAnon();
1462 $logEntry = new \ManualLogEntry(
1463 'newusers',
1464 $logSubtype ?: ( $isAnon ? 'create' : 'create2' )
1465 );
1466 $logEntry->setPerformer( $isAnon ? $user : $creator );
1467 $logEntry->setTarget( $user->getUserPage() );
1470 $state['reqs'], CreationReasonAuthenticationRequest::class
1471 );
1472 $logEntry->setComment( $req ? $req->reason : '' );
1473 $logEntry->setParameters( [
1474 '4::userid' => $user->getId(),
1475 ] );
1476 $logid = $logEntry->insert();
1477 $logEntry->publish( $logid );
1478 }
1479 }
1480
1481 // Step 3: Iterate over all the secondary authentication providers.
1482
1483 $beginReqs = $state['reqs'];
1484
1485 foreach ( $this->getSecondaryAuthenticationProviders() as $id => $provider ) {
1486 if ( !isset( $state['secondary'][$id] ) ) {
1487 // This provider isn't started yet, so we pass it the set
1488 // of reqs from beginAuthentication instead of whatever
1489 // might have been used by a previous provider in line.
1490 $func = 'beginSecondaryAccountCreation';
1491 $res = $provider->beginSecondaryAccountCreation( $user, $creator, $beginReqs );
1492 } elseif ( !$state['secondary'][$id] ) {
1493 $func = 'continueSecondaryAccountCreation';
1494 $res = $provider->continueSecondaryAccountCreation( $user, $creator, $reqs );
1495 } else {
1496 continue;
1497 }
1498 switch ( $res->status ) {
1500 $this->logger->debug( __METHOD__ . ": Secondary creation passed by $id", [
1501 'user' => $user->getName(),
1502 'creator' => $creator->getName(),
1503 ] );
1504 // fall through
1506 $state['secondary'][$id] = true;
1507 break;
1510 $this->logger->debug( __METHOD__ . ": Secondary creation $res->status by $id", [
1511 'user' => $user->getName(),
1512 'creator' => $creator->getName(),
1513 ] );
1514 $this->fillRequests( $res->neededRequests, self::ACTION_CREATE, null );
1515 $state['secondary'][$id] = false;
1516 $state['continueRequests'] = $res->neededRequests;
1517 $session->setSecret( 'AuthManager::accountCreationState', $state );
1518 return $res;
1520 throw new \DomainException(
1521 get_class( $provider ) . "::{$func}() returned $res->status." .
1522 ' Secondary providers are not allowed to fail account creation, that' .
1523 ' should have been done via testForAccountCreation().'
1524 );
1525 // @codeCoverageIgnoreStart
1526 default:
1527 throw new \DomainException(
1528 get_class( $provider ) . "::{$func}() returned $res->status"
1529 );
1530 // @codeCoverageIgnoreEnd
1531 }
1532 }
1533
1534 $id = $user->getId();
1535 $name = $user->getName();
1536 $req = new CreatedAccountAuthenticationRequest( $id, $name );
1537 $ret = AuthenticationResponse::newPass( $name );
1538 $ret->loginRequest = $req;
1539 $this->createdAccountAuthenticationRequests[] = $req;
1540
1541 $this->logger->info( __METHOD__ . ': Account creation succeeded for {user}', [
1542 'user' => $user->getName(),
1543 'creator' => $creator->getName(),
1544 ] );
1545
1546 $this->callMethodOnProviders( 7, 'postAccountCreation', [ $user, $creator, $ret ] );
1547 $session->remove( 'AuthManager::accountCreationState' );
1548 $this->removeAuthenticationSessionData( null );
1549 return $ret;
1550 } catch ( \Exception $ex ) {
1551 $session->remove( 'AuthManager::accountCreationState' );
1552 throw $ex;
1553 }
1554 }
1555
1573 public function autoCreateUser( User $user, $source, $login = true ) {
1574 if ( $source !== self::AUTOCREATE_SOURCE_SESSION &&
1575 $source !== self::AUTOCREATE_SOURCE_MAINT &&
1577 ) {
1578 throw new \InvalidArgumentException( "Unknown auto-creation source: $source" );
1579 }
1580
1581 $username = $user->getName();
1582
1583 // Try the local user from the replica DB
1584 $localId = User::idFromName( $username );
1585 $flags = User::READ_NORMAL;
1586
1587 // Fetch the user ID from the master, so that we don't try to create the user
1588 // when they already exist, due to replication lag
1589 // @codeCoverageIgnoreStart
1590 if (
1591 !$localId &&
1592 MediaWikiServices::getInstance()->getDBLoadBalancer()->getReaderIndex() !== 0
1593 ) {
1594 $localId = User::idFromName( $username, User::READ_LATEST );
1595 $flags = User::READ_LATEST;
1596 }
1597 // @codeCoverageIgnoreEnd
1598
1599 if ( $localId ) {
1600 $this->logger->debug( __METHOD__ . ': {username} already exists locally', [
1601 'username' => $username,
1602 ] );
1603 $user->setId( $localId );
1604 $user->loadFromId( $flags );
1605 if ( $login ) {
1606 $this->setSessionDataForUser( $user );
1607 }
1608 $status = Status::newGood();
1609 $status->warning( 'userexists' );
1610 return $status;
1611 }
1612
1613 // Wiki is read-only?
1614 if ( wfReadOnly() ) {
1615 $this->logger->debug( __METHOD__ . ': denied by wfReadOnly(): {reason}', [
1616 'username' => $username,
1617 'reason' => wfReadOnlyReason(),
1618 ] );
1619 $user->setId( 0 );
1620 $user->loadFromId();
1621 return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
1622 }
1623
1624 // Check the session, if we tried to create this user already there's
1625 // no point in retrying.
1626 $session = $this->request->getSession();
1627 if ( $session->get( 'AuthManager::AutoCreateBlacklist' ) ) {
1628 $this->logger->debug( __METHOD__ . ': blacklisted in session {sessionid}', [
1629 'username' => $username,
1630 'sessionid' => $session->getId(),
1631 ] );
1632 $user->setId( 0 );
1633 $user->loadFromId();
1634 $reason = $session->get( 'AuthManager::AutoCreateBlacklist' );
1635 if ( $reason instanceof StatusValue ) {
1636 return Status::wrap( $reason );
1637 } else {
1638 return Status::newFatal( $reason );
1639 }
1640 }
1641
1642 // Is the username creatable?
1643 if ( !User::isCreatableName( $username ) ) {
1644 $this->logger->debug( __METHOD__ . ': name "{username}" is not creatable', [
1645 'username' => $username,
1646 ] );
1647 $session->set( 'AuthManager::AutoCreateBlacklist', 'noname' );
1648 $user->setId( 0 );
1649 $user->loadFromId();
1650 return Status::newFatal( 'noname' );
1651 }
1652
1653 // Is the IP user able to create accounts?
1654 $anon = new User;
1655 if ( $source !== self::AUTOCREATE_SOURCE_MAINT && !MediaWikiServices::getInstance()
1657 ->userHasAnyRight( $anon, 'createaccount', 'autocreateaccount' )
1658 ) {
1659 $this->logger->debug( __METHOD__ . ': IP lacks the ability to create or autocreate accounts', [
1660 'username' => $username,
1661 'clientip' => $anon->getName(),
1662 ] );
1663 $session->set( 'AuthManager::AutoCreateBlacklist', 'authmanager-autocreate-noperm' );
1664 $session->persist();
1665 $user->setId( 0 );
1666 $user->loadFromId();
1667 return Status::newFatal( 'authmanager-autocreate-noperm' );
1668 }
1669
1670 // Avoid account creation races on double submissions
1671 $cache = \ObjectCache::getLocalClusterInstance();
1672 $lock = $cache->getScopedLock( $cache->makeGlobalKey( 'account', md5( $username ) ) );
1673 if ( !$lock ) {
1674 $this->logger->debug( __METHOD__ . ': Could not acquire account creation lock', [
1675 'user' => $username,
1676 ] );
1677 $user->setId( 0 );
1678 $user->loadFromId();
1679 return Status::newFatal( 'usernameinprogress' );
1680 }
1681
1682 // Denied by providers?
1683 $options = [
1684 'flags' => User::READ_LATEST,
1685 'creating' => true,
1686 ];
1687 $providers = $this->getPreAuthenticationProviders() +
1690 foreach ( $providers as $provider ) {
1691 $status = $provider->testUserForCreation( $user, $source, $options );
1692 if ( !$status->isGood() ) {
1693 $ret = Status::wrap( $status );
1694 $this->logger->debug( __METHOD__ . ': Provider denied creation of {username}: {reason}', [
1695 'username' => $username,
1696 'reason' => $ret->getWikiText( null, null, 'en' ),
1697 ] );
1698 $session->set( 'AuthManager::AutoCreateBlacklist', $status );
1699 $user->setId( 0 );
1700 $user->loadFromId();
1701 return $ret;
1702 }
1703 }
1704
1705 $backoffKey = $cache->makeKey( 'AuthManager', 'autocreate-failed', md5( $username ) );
1706 if ( $cache->get( $backoffKey ) ) {
1707 $this->logger->debug( __METHOD__ . ': {username} denied by prior creation attempt failures', [
1708 'username' => $username,
1709 ] );
1710 $user->setId( 0 );
1711 $user->loadFromId();
1712 return Status::newFatal( 'authmanager-autocreate-exception' );
1713 }
1714
1715 // Checks passed, create the user...
1716 $from = $_SERVER['REQUEST_URI'] ?? 'CLI';
1717 $this->logger->info( __METHOD__ . ': creating new user ({username}) - from: {from}', [
1718 'username' => $username,
1719 'from' => $from,
1720 ] );
1721
1722 // Ignore warnings about master connections/writes...hard to avoid here
1723 $trxProfiler = \Profiler::instance()->getTransactionProfiler();
1724 $old = $trxProfiler->setSilenced( true );
1725 try {
1726 $status = $user->addToDatabase();
1727 if ( !$status->isOK() ) {
1728 // Double-check for a race condition (T70012). We make use of the fact that when
1729 // addToDatabase fails due to the user already existing, the user object gets loaded.
1730 if ( $user->getId() ) {
1731 $this->logger->info( __METHOD__ . ': {username} already exists locally (race)', [
1732 'username' => $username,
1733 ] );
1734 if ( $login ) {
1735 $this->setSessionDataForUser( $user );
1736 }
1737 $status = Status::newGood();
1738 $status->warning( 'userexists' );
1739 } else {
1740 $this->logger->error( __METHOD__ . ': {username} failed with message {msg}', [
1741 'username' => $username,
1742 'msg' => $status->getWikiText( null, null, 'en' )
1743 ] );
1744 $user->setId( 0 );
1745 $user->loadFromId();
1746 }
1747 return $status;
1748 }
1749 } catch ( \Exception $ex ) {
1750 $trxProfiler->setSilenced( $old );
1751 $this->logger->error( __METHOD__ . ': {username} failed with exception {exception}', [
1752 'username' => $username,
1753 'exception' => $ex,
1754 ] );
1755 // Do not keep throwing errors for a while
1756 $cache->set( $backoffKey, 1, 600 );
1757 // Bubble up error; which should normally trigger DB rollbacks
1758 throw $ex;
1759 }
1760
1761 $this->setDefaultUserOptions( $user, false );
1762
1763 // Inform the providers
1764 $this->callMethodOnProviders( 6, 'autoCreatedAccount', [ $user, $source ] );
1765
1766 $this->getHookRunner()->onLocalUserCreated( $user, true );
1767 $user->saveSettings();
1768
1769 // Update user count
1770 \DeferredUpdates::addUpdate( \SiteStatsUpdate::factory( [ 'users' => 1 ] ) );
1771 // Watch user's userpage and talk page
1772 \DeferredUpdates::addCallableUpdate( function () use ( $user ) {
1773 $user->addWatch( $user->getUserPage(), User::IGNORE_USER_RIGHTS );
1774 } );
1775
1776 // Log the creation
1777 if ( $this->config->get( 'NewUserLog' ) ) {
1778 $logEntry = new \ManualLogEntry( 'newusers', 'autocreate' );
1779 $logEntry->setPerformer( $user );
1780 $logEntry->setTarget( $user->getUserPage() );
1781 $logEntry->setComment( '' );
1782 $logEntry->setParameters( [
1783 '4::userid' => $user->getId(),
1784 ] );
1785 $logEntry->insert();
1786 }
1787
1788 $trxProfiler->setSilenced( $old );
1789
1790 if ( $login ) {
1791 $this->setSessionDataForUser( $user );
1792 }
1793
1794 return Status::newGood();
1795 }
1796
1808 public function canLinkAccounts() {
1809 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
1810 if ( $provider->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
1811 return true;
1812 }
1813 }
1814 return false;
1815 }
1816
1826 public function beginAccountLink( User $user, array $reqs, $returnToUrl ) {
1827 $session = $this->request->getSession();
1828 $session->remove( 'AuthManager::accountLinkState' );
1829
1830 if ( !$this->canLinkAccounts() ) {
1831 // Caller should have called canLinkAccounts()
1832 throw new \LogicException( 'Account linking is not possible' );
1833 }
1834
1835 if ( $user->getId() === 0 ) {
1836 if ( !User::isUsableName( $user->getName() ) ) {
1837 $msg = wfMessage( 'noname' );
1838 } else {
1839 $msg = wfMessage( 'authmanager-userdoesnotexist', $user->getName() );
1840 }
1841 return AuthenticationResponse::newFail( $msg );
1842 }
1843 foreach ( $reqs as $req ) {
1844 $req->username = $user->getName();
1845 $req->returnToUrl = $returnToUrl;
1846 }
1847
1848 $this->removeAuthenticationSessionData( null );
1849
1850 $providers = $this->getPreAuthenticationProviders();
1851 foreach ( $providers as $id => $provider ) {
1852 $status = $provider->testForAccountLink( $user );
1853 if ( !$status->isGood() ) {
1854 $this->logger->debug( __METHOD__ . ": Account linking pre-check failed by $id", [
1855 'user' => $user->getName(),
1856 ] );
1858 Status::wrap( $status )->getMessage()
1859 );
1860 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1861 return $ret;
1862 }
1863 }
1864
1865 $state = [
1866 'username' => $user->getName(),
1867 'userid' => $user->getId(),
1868 'returnToUrl' => $returnToUrl,
1869 'primary' => null,
1870 'continueRequests' => [],
1871 ];
1872
1873 $providers = $this->getPrimaryAuthenticationProviders();
1874 foreach ( $providers as $id => $provider ) {
1875 if ( $provider->accountCreationType() !== PrimaryAuthenticationProvider::TYPE_LINK ) {
1876 continue;
1877 }
1878
1879 $res = $provider->beginPrimaryAccountLink( $user, $reqs );
1880 switch ( $res->status ) {
1882 $this->logger->info( "Account linked to {user} by $id", [
1883 'user' => $user->getName(),
1884 ] );
1885 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1886 return $res;
1887
1889 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1890 'user' => $user->getName(),
1891 ] );
1892 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1893 return $res;
1894
1896 // Continue loop
1897 break;
1898
1901 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
1902 'user' => $user->getName(),
1903 ] );
1904 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
1905 $state['primary'] = $id;
1906 $state['continueRequests'] = $res->neededRequests;
1907 $session->setSecret( 'AuthManager::accountLinkState', $state );
1908 $session->persist();
1909 return $res;
1910
1911 // @codeCoverageIgnoreStart
1912 default:
1913 throw new \DomainException(
1914 get_class( $provider ) . "::beginPrimaryAccountLink() returned $res->status"
1915 );
1916 // @codeCoverageIgnoreEnd
1917 }
1918 }
1919
1920 $this->logger->debug( __METHOD__ . ': Account linking failed because no provider accepted', [
1921 'user' => $user->getName(),
1922 ] );
1924 wfMessage( 'authmanager-link-no-primary' )
1925 );
1926 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1927 return $ret;
1928 }
1929
1935 public function continueAccountLink( array $reqs ) {
1936 $session = $this->request->getSession();
1937 try {
1938 if ( !$this->canLinkAccounts() ) {
1939 // Caller should have called canLinkAccounts()
1940 $session->remove( 'AuthManager::accountLinkState' );
1941 throw new \LogicException( 'Account linking is not possible' );
1942 }
1943
1944 $state = $session->getSecret( 'AuthManager::accountLinkState' );
1945 if ( !is_array( $state ) ) {
1947 wfMessage( 'authmanager-link-not-in-progress' )
1948 );
1949 }
1950 $state['continueRequests'] = [];
1951
1952 // Step 0: Prepare and validate the input
1953
1954 $user = User::newFromName( $state['username'], 'usable' );
1955 if ( !is_object( $user ) ) {
1956 $session->remove( 'AuthManager::accountLinkState' );
1957 return AuthenticationResponse::newFail( wfMessage( 'noname' ) );
1958 }
1959 if ( $user->getId() !== $state['userid'] ) {
1960 throw new \UnexpectedValueException(
1961 "User \"{$state['username']}\" is valid, but " .
1962 "ID {$user->getId()} !== {$state['userid']}!"
1963 );
1964 }
1965
1966 foreach ( $reqs as $req ) {
1967 $req->username = $state['username'];
1968 $req->returnToUrl = $state['returnToUrl'];
1969 }
1970
1971 // Step 1: Call the primary again until it succeeds
1972
1973 $provider = $this->getAuthenticationProvider( $state['primary'] );
1974 if ( !$provider instanceof PrimaryAuthenticationProvider ) {
1975 // Configuration changed? Force them to start over.
1976 // @codeCoverageIgnoreStart
1978 wfMessage( 'authmanager-link-not-in-progress' )
1979 );
1980 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $ret ] );
1981 $session->remove( 'AuthManager::accountLinkState' );
1982 return $ret;
1983 // @codeCoverageIgnoreEnd
1984 }
1985 $id = $provider->getUniqueId();
1986 $res = $provider->continuePrimaryAccountLink( $user, $reqs );
1987 switch ( $res->status ) {
1989 $this->logger->info( "Account linked to {user} by $id", [
1990 'user' => $user->getName(),
1991 ] );
1992 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
1993 $session->remove( 'AuthManager::accountLinkState' );
1994 return $res;
1996 $this->logger->debug( __METHOD__ . ": Account linking failed by $id", [
1997 'user' => $user->getName(),
1998 ] );
1999 $this->callMethodOnProviders( 3, 'postAccountLink', [ $user, $res ] );
2000 $session->remove( 'AuthManager::accountLinkState' );
2001 return $res;
2004 $this->logger->debug( __METHOD__ . ": Account linking $res->status by $id", [
2005 'user' => $user->getName(),
2006 ] );
2007 $this->fillRequests( $res->neededRequests, self::ACTION_LINK, $user->getName() );
2008 $state['continueRequests'] = $res->neededRequests;
2009 $session->setSecret( 'AuthManager::accountLinkState', $state );
2010 return $res;
2011 default:
2012 throw new \DomainException(
2013 get_class( $provider ) . "::continuePrimaryAccountLink() returned $res->status"
2014 );
2015 }
2016 } catch ( \Exception $ex ) {
2017 $session->remove( 'AuthManager::accountLinkState' );
2018 throw $ex;
2019 }
2020 }
2021
2047 public function getAuthenticationRequests( $action, User $user = null ) {
2048 $options = [];
2049 $providerAction = $action;
2050
2051 // Figure out which providers to query
2052 switch ( $action ) {
2053 case self::ACTION_LOGIN:
2055 $providers = $this->getPreAuthenticationProviders() +
2058 break;
2059
2061 $state = $this->request->getSession()->getSecret( 'AuthManager::authnState' );
2062 return is_array( $state ) ? $state['continueRequests'] : [];
2063
2065 $state = $this->request->getSession()->getSecret( 'AuthManager::accountCreationState' );
2066 return is_array( $state ) ? $state['continueRequests'] : [];
2067
2068 case self::ACTION_LINK:
2069 $providers = [];
2070 foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2071 if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2072 $providers[] = $p;
2073 }
2074 }
2075 break;
2076
2078 $providers = [];
2079 foreach ( $this->getPrimaryAuthenticationProviders() as $p ) {
2080 if ( $p->accountCreationType() === PrimaryAuthenticationProvider::TYPE_LINK ) {
2081 $providers[] = $p;
2082 }
2083 }
2084
2085 // To providers, unlink and remove are identical.
2086 $providerAction = self::ACTION_REMOVE;
2087 break;
2088
2090 $state = $this->request->getSession()->getSecret( 'AuthManager::accountLinkState' );
2091 return is_array( $state ) ? $state['continueRequests'] : [];
2092
2095 $providers = $this->getPrimaryAuthenticationProviders() +
2097 break;
2098
2099 // @codeCoverageIgnoreStart
2100 default:
2101 throw new \DomainException( __METHOD__ . ": Invalid action \"$action\"" );
2102 }
2103 // @codeCoverageIgnoreEnd
2104
2105 return $this->getAuthenticationRequestsInternal( $providerAction, $options, $providers, $user );
2106 }
2107
2118 $providerAction, array $options, array $providers, User $user = null
2119 ) {
2120 $user = $user ?: \RequestContext::getMain()->getUser();
2121 $options['username'] = $user->isAnon() ? null : $user->getName();
2122
2123 // Query them and merge results
2124 $reqs = [];
2125 foreach ( $providers as $provider ) {
2126 $isPrimary = $provider instanceof PrimaryAuthenticationProvider;
2127 foreach ( $provider->getAuthenticationRequests( $providerAction, $options ) as $req ) {
2128 $id = $req->getUniqueId();
2129
2130 // If a required request if from a Primary, mark it as "primary-required" instead
2131 if ( $isPrimary && $req->required ) {
2133 }
2134
2135 if (
2136 !isset( $reqs[$id] )
2137 || $req->required === AuthenticationRequest::REQUIRED
2138 || $reqs[$id] === AuthenticationRequest::OPTIONAL
2139 ) {
2140 $reqs[$id] = $req;
2141 }
2142 }
2143 }
2144
2145 // AuthManager has its own req for some actions
2146 switch ( $providerAction ) {
2147 case self::ACTION_LOGIN:
2148 $reqs[] = new RememberMeAuthenticationRequest;
2149 break;
2150
2152 $reqs[] = new UsernameAuthenticationRequest;
2153 $reqs[] = new UserDataAuthenticationRequest;
2154 if ( $options['username'] !== null ) {
2156 $options['username'] = null; // Don't fill in the username below
2157 }
2158 break;
2159 }
2160
2161 // Fill in reqs data
2162 $this->fillRequests( $reqs, $providerAction, $options['username'], true );
2163
2164 // For self::ACTION_CHANGE, filter out any that something else *doesn't* allow changing
2165 if ( $providerAction === self::ACTION_CHANGE || $providerAction === self::ACTION_REMOVE ) {
2166 $reqs = array_filter( $reqs, function ( $req ) {
2167 return $this->allowsAuthenticationDataChange( $req, false )->isGood();
2168 } );
2169 }
2170
2171 return array_values( $reqs );
2172 }
2173
2181 private function fillRequests( array &$reqs, $action, $username, $forceAction = false ) {
2182 foreach ( $reqs as $req ) {
2183 if ( !$req->action || $forceAction ) {
2184 $req->action = $action;
2185 }
2186 if ( $req->username === null ) {
2187 $req->username = $username;
2188 }
2189 }
2190 }
2191
2198 public function userExists( $username, $flags = User::READ_NORMAL ) {
2199 foreach ( $this->getPrimaryAuthenticationProviders() as $provider ) {
2200 if ( $provider->testUserExists( $username, $flags ) ) {
2201 return true;
2202 }
2203 }
2204
2205 return false;
2206 }
2207
2219 public function allowsPropertyChange( $property ) {
2220 $providers = $this->getPrimaryAuthenticationProviders() +
2222 foreach ( $providers as $provider ) {
2223 if ( !$provider->providerAllowsPropertyChange( $property ) ) {
2224 return false;
2225 }
2226 }
2227 return true;
2228 }
2229
2238 public function getAuthenticationProvider( $id ) {
2239 // Fast version
2240 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2241 return $this->allAuthenticationProviders[$id];
2242 }
2243
2244 // Slow version: instantiate each kind and check
2245 $providers = $this->getPrimaryAuthenticationProviders();
2246 if ( isset( $providers[$id] ) ) {
2247 return $providers[$id];
2248 }
2249 $providers = $this->getSecondaryAuthenticationProviders();
2250 if ( isset( $providers[$id] ) ) {
2251 return $providers[$id];
2252 }
2253 $providers = $this->getPreAuthenticationProviders();
2254 if ( isset( $providers[$id] ) ) {
2255 return $providers[$id];
2256 }
2257
2258 return null;
2259 }
2260
2274 public function setAuthenticationSessionData( $key, $data ) {
2275 $session = $this->request->getSession();
2276 $arr = $session->getSecret( 'authData' );
2277 if ( !is_array( $arr ) ) {
2278 $arr = [];
2279 }
2280 $arr[$key] = $data;
2281 $session->setSecret( 'authData', $arr );
2282 }
2283
2291 public function getAuthenticationSessionData( $key, $default = null ) {
2292 $arr = $this->request->getSession()->getSecret( 'authData' );
2293 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2294 return $arr[$key];
2295 } else {
2296 return $default;
2297 }
2298 }
2299
2305 public function removeAuthenticationSessionData( $key ) {
2306 $session = $this->request->getSession();
2307 if ( $key === null ) {
2308 $session->remove( 'authData' );
2309 } else {
2310 $arr = $session->getSecret( 'authData' );
2311 if ( is_array( $arr ) && array_key_exists( $key, $arr ) ) {
2312 unset( $arr[$key] );
2313 $session->setSecret( 'authData', $arr );
2314 }
2315 }
2316 }
2317
2324 protected function providerArrayFromSpecs( $class, array $specs ) {
2325 $i = 0;
2326 foreach ( $specs as &$spec ) {
2327 $spec = [ 'sort2' => $i++ ] + $spec + [ 'sort' => 0 ];
2328 }
2329 unset( $spec );
2330 // Sort according to the 'sort' field, and if they are equal, according to 'sort2'
2331 usort( $specs, function ( $a, $b ) {
2332 return $a['sort'] <=> $b['sort']
2333 ?: $a['sort2'] <=> $b['sort2'];
2334 } );
2335
2336 $ret = [];
2337 foreach ( $specs as $spec ) {
2339 $provider = $this->objectFactory->createObject( $spec, [ 'assertClass' => $class ] );
2340 $provider->setLogger( $this->logger );
2341 $provider->setManager( $this );
2342 $provider->setConfig( $this->config );
2343 $provider->setHookContainer( $this->getHookContainer() );
2344 $id = $provider->getUniqueId();
2345 if ( isset( $this->allAuthenticationProviders[$id] ) ) {
2346 throw new \RuntimeException(
2347 "Duplicate specifications for id $id (classes " .
2348 get_class( $provider ) . ' and ' .
2349 get_class( $this->allAuthenticationProviders[$id] ) . ')'
2350 );
2351 }
2352 $this->allAuthenticationProviders[$id] = $provider;
2353 $ret[$id] = $provider;
2354 }
2355 return $ret;
2356 }
2357
2362 private function getConfiguration() {
2363 return $this->config->get( 'AuthManagerConfig' ) ?: $this->config->get( 'AuthManagerAutoConfig' );
2364 }
2365
2370 protected function getPreAuthenticationProviders() {
2371 if ( $this->preAuthenticationProviders === null ) {
2372 $conf = $this->getConfiguration();
2373 $this->preAuthenticationProviders = $this->providerArrayFromSpecs(
2374 PreAuthenticationProvider::class, $conf['preauth']
2375 );
2376 }
2378 }
2379
2385 if ( $this->primaryAuthenticationProviders === null ) {
2386 $conf = $this->getConfiguration();
2387 $this->primaryAuthenticationProviders = $this->providerArrayFromSpecs(
2388 PrimaryAuthenticationProvider::class, $conf['primaryauth']
2389 );
2390 }
2392 }
2393
2399 if ( $this->secondaryAuthenticationProviders === null ) {
2400 $conf = $this->getConfiguration();
2401 $this->secondaryAuthenticationProviders = $this->providerArrayFromSpecs(
2402 SecondaryAuthenticationProvider::class, $conf['secondaryauth']
2403 );
2404 }
2406 }
2407
2413 private function setSessionDataForUser( $user, $remember = null ) {
2414 $session = $this->request->getSession();
2415 $delay = $session->delaySave();
2416
2417 $session->resetId();
2418 $session->resetAllTokens();
2419 if ( $session->canSetUser() ) {
2420 $session->setUser( $user );
2421 }
2422 if ( $remember !== null ) {
2423 $session->setRememberUser( $remember );
2424 }
2425 $session->set( 'AuthManager:lastAuthId', $user->getId() );
2426 $session->set( 'AuthManager:lastAuthTimestamp', time() );
2427 $session->persist();
2428
2429 \Wikimedia\ScopedCallback::consume( $delay );
2430
2431 $this->getHookRunner()->onUserLoggedIn( $user );
2432 }
2433
2438 private function setDefaultUserOptions( User $user, $useContextLang ) {
2439 $user->setToken();
2440
2441 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2442
2443 $lang = $useContextLang ? \RequestContext::getMain()->getLanguage() : $contLang;
2444 $user->setOption( 'language', $lang->getPreferredVariant() );
2445
2446 $contLangConverter = MediaWikiServices::getInstance()->getLanguageConverterFactory()
2447 ->getLanguageConverter();
2448 if ( $contLangConverter->hasVariants() ) {
2449 $user->setOption( 'variant', $contLangConverter->getPreferredVariant() );
2450 }
2451 }
2452
2458 private function callMethodOnProviders( $which, $method, array $args ) {
2459 $providers = [];
2460 if ( $which & 1 ) {
2461 $providers += $this->getPreAuthenticationProviders();
2462 }
2463 if ( $which & 2 ) {
2464 $providers += $this->getPrimaryAuthenticationProviders();
2465 }
2466 if ( $which & 4 ) {
2467 $providers += $this->getSecondaryAuthenticationProviders();
2468 }
2469 foreach ( $providers as $provider ) {
2470 $provider->$method( ...$args );
2471 }
2472 }
2473
2479 public static function resetCache() {
2480 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
2481 // @codeCoverageIgnoreStart
2482 throw new \MWException( __METHOD__ . ' may only be called from unit tests!' );
2483 // @codeCoverageIgnoreEnd
2484 }
2485
2486 self::$instance = null;
2487 }
2488
2492 private function getHookContainer() {
2493 return $this->hookContainer;
2494 }
2495
2499 private function getHookRunner() {
2500 return $this->hookRunner;
2501 }
2502
2505}
2506
getPermissionManager()
wfReadOnly()
Check whether the wiki is in read-only mode.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
This serves as the entry point to the authentication system.
__construct(WebRequest $request, Config $config, ObjectFactory $objectFactory, PermissionManager $permManager, HookContainer $hookContainer)
static resetCache()
Reset the internal caching for unit testing.
canLinkAccounts()
Determine whether accounts can be linked.
const ACTION_UNLINK
Like ACTION_REMOVE but for linking providers only.
PermissionManager $permManager
const SEC_FAIL
Security-sensitive should not be performed.
getPrimaryAuthenticationProviders()
Get the list of PrimaryAuthenticationProviders.
const ACTION_LOGIN_CONTINUE
Continue a login process that was interrupted by the need for user input or communication with an ext...
setAuthenticationSessionData( $key, $data)
Store authentication in the current session.
beginAccountCreation(User $creator, array $reqs, $returnToUrl)
Start an account creation flow.
callMethodOnProviders( $which, $method, array $args)
getAuthenticationProvider( $id)
Get a provider by ID.
setSessionDataForUser( $user, $remember=null)
Log the user in.
securitySensitiveOperationStatus( $operation)
Whether security-sensitive operations should proceed.
SecondaryAuthenticationProvider[] $secondaryAuthenticationProviders
revokeAccessForUser( $username)
Revoke any authentication credentials for a user.
beginAccountLink(User $user, array $reqs, $returnToUrl)
Start an account linking flow.
const SEC_REAUTH
Security-sensitive operations should re-authenticate.
getSecondaryAuthenticationProviders()
Get the list of SecondaryAuthenticationProviders.
allowsPropertyChange( $property)
Determine whether a user property should be allowed to be changed.
const ACTION_CREATE_CONTINUE
Continue a user creation process that was interrupted by the need for user input or communication wit...
continueAccountLink(array $reqs)
Continue an account linking flow.
setLogger(LoggerInterface $logger)
allowsAuthenticationDataChange(AuthenticationRequest $req, $checkData=true)
Validate a change of authentication data (e.g.
beginAuthentication(array $reqs, $returnToUrl)
Start an authentication flow.
getAuthenticationSessionData( $key, $default=null)
Fetch authentication data from the current session.
canCreateAccounts()
Determine whether accounts can be created.
canCreateAccount( $username, $options=[])
Determine whether a particular account can be created.
changeAuthenticationData(AuthenticationRequest $req, $isAddition=false)
Change authentication data (e.g.
userCanAuthenticate( $username)
Determine whether a username can authenticate.
removeAuthenticationSessionData( $key)
Remove authentication data.
providerArrayFromSpecs( $class, array $specs)
Create an array of AuthenticationProviders from an array of ObjectFactory specs.
forcePrimaryAuthenticationProviders(array $providers, $why)
Force certain PrimaryAuthenticationProviders.
const AUTOCREATE_SOURCE_MAINT
Auto-creation is due to a Maintenance script.
setDefaultUserOptions(User $user, $useContextLang)
checkAccountCreatePermissions(User $creator)
Basic permissions checks on whether a user can create accounts.
static singleton()
Get the global AuthManager.
CreatedAccountAuthenticationRequest[] $createdAccountAuthenticationRequests
AuthenticationProvider[] $allAuthenticationProviders
continueAuthentication(array $reqs)
Continue an authentication flow.
const SEC_OK
Security-sensitive operations are ok.
getAuthenticationRequestsInternal( $providerAction, array $options, array $providers, User $user=null)
Internal request lookup for self::getAuthenticationRequests.
autoCreateUser(User $user, $source, $login=true)
Auto-create an account, and log into that account.
const ACTION_LINK_CONTINUE
Continue a user linking process that was interrupted by the need for user input or communication with...
const ACTION_CHANGE
Change a user's credentials.
canAuthenticateNow()
Indicate whether user authentication is possible.
const ACTION_REMOVE
Remove a user's credentials.
const ACTION_LINK
Link an existing user to a third-party account.
PreAuthenticationProvider[] $preAuthenticationProviders
PrimaryAuthenticationProvider[] $primaryAuthenticationProviders
getConfiguration()
Get the configuration.
const AUTOCREATE_SOURCE_SESSION
Auto-creation is due to SessionManager.
getPreAuthenticationProviders()
Get the list of PreAuthenticationProviders.
getAuthenticationRequests( $action, User $user=null)
Return the applicable list of AuthenticationRequests.
fillRequests(array &$reqs, $action, $username, $forceAction=false)
Set values in an array of requests.
userExists( $username, $flags=User::READ_NORMAL)
Determine whether a username exists.
const ACTION_LOGIN
Log in with an existing (not necessarily local) user.
normalizeUsername( $username)
Provide normalized versions of the username for security checks.
static AuthManager null $instance
continueAccountCreation(array $reqs)
Continue an account creation flow.
const ACTION_CREATE
Create a new user.
This is a value object for authentication requests.
const OPTIONAL
Indicates that the request is not required for authentication to proceed.
const PRIMARY_REQUIRED
Indicates that the request is required by a primary authentication provider.
const REQUIRED
Indicates that the request is required for authentication to proceed.
static getUsernameFromRequests(array $reqs)
Get the username from the set of requests.
static getRequestByClass(array $reqs, $class, $allowSubclasses=false)
Select a request by class name.
const FAIL
Indicates that the authentication failed.
const PASS
Indicates that the authentication succeeded.
const UI
Indicates that the authentication needs further user input of some sort.
const REDIRECT
Indicates that the authentication needs to be redirected to a third party to proceed.
const ABSTAIN
Indicates that the authentication provider does not handle this request.
This transfers state between the login and account creation flows.
Returned from account creation to allow for logging into the created account Stable to extend.
Authentication request for the reason given for account creation.
This is an authentication request added by AuthManager to show a "remember me" checkbox.
This represents additional user data requested on the account creation form.
AuthenticationRequest to ensure something with a username is present Stable to extend.
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
MediaWikiServices is the service locator for the application scope of MediaWiki.
static getInstance()
Returns the global default instance of the top level service locator.
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
getName()
Get the user name, or the IP of an anonymous user.
Definition User.php:2150
addToDatabase()
Add this existing user object to the database.
Definition User.php:3633
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
static isCreatableName( $name)
Usernames which fail to pass this function will be blocked from new account registrations,...
Definition User.php:1050
loadFromId( $flags=self::READ_NORMAL)
Load user table data, given mId has already been set.
Definition User.php:406
setName( $str)
Set the user name.
Definition User.php:2178
getId()
Get the user's ID.
Definition User.php:2121
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition User.php:565
setId( $v)
Set the user and reload all fields according to a given ID.
Definition User.php:2141
static isUsableName( $name)
Usernames which fail to pass this function will be blocked from user login and new account registrati...
Definition User.php:995
const IGNORE_USER_RIGHTS
Definition User.php:94
setOption( $oname, $val)
Set the given option for a user.
Definition User.php:2730
isBlockedFromCreateAccount()
Get whether the user is explicitly blocked from account creation.
Definition User.php:3749
getUserPage()
Get this user's personal page title.
Definition User.php:3802
addWatch( $title, $checkRights=self::CHECK_USER_RIGHTS, ?string $expiry=null)
Watch an article.
Definition User.php:3256
static idFromName( $name, $flags=self::READ_NORMAL)
Get database id given a user name.
Definition User.php:887
saveSettings()
Save this user's settings into the database.
Definition User.php:3445
setToken( $token=false)
Set the random token (used for persistent authentication) Called from loadDefaults() among other plac...
Definition User.php:2529
The WebRequest class encapsulates getting at data passed in the URL or via a POSTed form stripping il...
Interface for configuration instances.
Definition Config.php:30
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
An AuthenticationProvider is used by AuthManager when authenticating users.
getUniqueId()
Return a unique identifier for this instance.
A pre-authentication provider can prevent authentication early on.
A primary authentication provider is responsible for associating the submitted authentication data wi...
const TYPE_LINK
Provider can link to existing accounts elsewhere.
const TYPE_NONE
Provider cannot create or link to accounts.
A secondary provider mostly acts when the submitted authentication data has already been associated t...
$cache
Definition mcc.php:33
if( $line===false) $args
Definition mcc.php:124
$source
if(!isset( $args[0])) $lang